@open-mercato/core 0.6.8-develop.6993.1.3fc50f3409 → 0.6.8-develop.7000.1.0ae682bad1
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 +2 -2
- package/dist/generated/entities/sync_run/index.js +2 -0
- package/dist/generated/entities/sync_run/index.js.map +2 -2
- package/dist/generated/entities/warranty_claim/index.js +105 -0
- package/dist/generated/entities/warranty_claim/index.js.map +7 -0
- package/dist/generated/entities/warranty_claim_event/index.js +25 -0
- package/dist/generated/entities/warranty_claim_event/index.js.map +7 -0
- package/dist/generated/entities/warranty_claim_line/index.js +77 -0
- package/dist/generated/entities/warranty_claim_line/index.js.map +7 -0
- package/dist/generated/entities/warranty_claim_registration/index.js +43 -0
- package/dist/generated/entities/warranty_claim_registration/index.js.map +7 -0
- package/dist/generated/entities/warranty_claim_sequence/index.js +17 -0
- package/dist/generated/entities/warranty_claim_sequence/index.js.map +7 -0
- package/dist/generated/entities/warranty_claim_settings/index.js +43 -0
- package/dist/generated/entities/warranty_claim_settings/index.js.map +7 -0
- package/dist/generated/entities/warranty_claim_sla_signal/index.js +25 -0
- package/dist/generated/entities/warranty_claim_sla_signal/index.js.map +7 -0
- package/dist/generated/entities/warranty_troubleshooting_guide/index.js +25 -0
- package/dist/generated/entities/warranty_troubleshooting_guide/index.js.map +7 -0
- package/dist/generated/entities/warranty_vendor_policy/index.js +31 -0
- package/dist/generated/entities/warranty_vendor_policy/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +12 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +201 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/api.js +3 -2
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/salesFixtures.js +13 -6
- package/dist/helpers/integration/salesFixtures.js.map +2 -2
- package/dist/modules/attachments/di.js +18 -0
- package/dist/modules/attachments/di.js.map +2 -2
- package/dist/modules/attachments/fields/attachment.js +186 -26
- package/dist/modules/attachments/fields/attachment.js.map +2 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js +202 -0
- package/dist/modules/attachments/lib/scoped-upload-service.js.map +7 -0
- package/dist/modules/attachments/lib/target-access-service.js +39 -0
- package/dist/modules/attachments/lib/target-access-service.js.map +7 -0
- package/dist/modules/auth/api/sidebar/preferences/route.js +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +1 -1
- package/dist/modules/auth/lib/backendChrome.js +3 -3
- package/dist/modules/auth/lib/backendChrome.js.map +1 -1
- package/dist/modules/auth/services/sidebarPreferencesService.js +7 -2
- package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
- package/dist/modules/customers/api/companies/route.js +1 -0
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/assignableStaff.js +6 -69
- package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
- package/dist/modules/customers/lib/assignableStaff.js +76 -0
- package/dist/modules/customers/lib/assignableStaff.js.map +7 -0
- package/dist/modules/data_sync/api/options.js +1 -0
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +15 -1
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +23 -1
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/route.js +1 -0
- package/dist/modules/data_sync/api/runs/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +60 -17
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +34 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +21 -15
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/components/RunParameterFields.js +135 -0
- package/dist/modules/data_sync/components/RunParameterFields.js.map +7 -0
- package/dist/modules/data_sync/data/entities.js +3 -0
- package/dist/modules/data_sync/data/entities.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +4 -1
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/batch-stream.js +56 -0
- package/dist/modules/data_sync/lib/batch-stream.js.map +7 -0
- package/dist/modules/data_sync/lib/run-parameters.js +108 -0
- package/dist/modules/data_sync/lib/run-parameters.js.map +7 -0
- package/dist/modules/data_sync/lib/start-run.js +1 -0
- package/dist/modules/data_sync/lib/start-run.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +165 -101
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +1 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/data_sync/migrations/Migration20260810120000.js +13 -0
- package/dist/modules/data_sync/migrations/Migration20260810120000.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js +23 -2
- package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
- package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +4 -0
- package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/extract/route.js +4 -0
- package/dist/modules/inbox_ops/api/extract/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/webhook/inbound.js +4 -0
- package/dist/modules/inbox_ops/api/webhook/inbound.js.map +2 -2
- package/dist/modules/query_index/di.js +125 -48
- package/dist/modules/query_index/di.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +34 -13
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/warranty_claims/acl.js +75 -0
- package/dist/modules/warranty_claims/acl.js.map +7 -0
- package/dist/modules/warranty_claims/ai-agents.js +161 -0
- package/dist/modules/warranty_claims/ai-agents.js.map +7 -0
- package/dist/modules/warranty_claims/ai-tools.js +591 -0
- package/dist/modules/warranty_claims/ai-tools.js.map +7 -0
- package/dist/modules/warranty_claims/api/ai/assess/route.js +304 -0
- package/dist/modules/warranty_claims/api/ai/assess/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +127 -0
- package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/ai/suggest/route.js +177 -0
- package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/assign/route.js +124 -0
- package/dist/modules/warranty_claims/api/assign/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/assignees/route.js +97 -0
- package/dist/modules/warranty_claims/api/assignees/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/credit-memo/route.js +152 -0
- package/dist/modules/warranty_claims/api/credit-memo/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/entitlement/route.js +145 -0
- package/dist/modules/warranty_claims/api/entitlement/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/events/route.js +199 -0
- package/dist/modules/warranty_claims/api/events/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/external/claims/route.js +446 -0
- package/dist/modules/warranty_claims/api/external/claims/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/lines/route.js +258 -0
- package/dist/modules/warranty_claims/api/lines/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/openapi.js +27 -0
- package/dist/modules/warranty_claims/api/openapi.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/attachments/route.js +490 -0
- package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js +178 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js +101 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/shared.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +78 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +81 -0
- package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/claims/route.js +462 -0
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/events/route.js +228 -0
- package/dist/modules/warranty_claims/api/portal/events/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/options/route.js +91 -0
- package/dist/modules/warranty_claims/api/portal/options/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/orders/lines/route.js +172 -0
- package/dist/modules/warranty_claims/api/portal/orders/lines/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/orders/route.js +151 -0
- package/dist/modules/warranty_claims/api/portal/orders/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/portal/troubleshooting/route.js +121 -0
- package/dist/modules/warranty_claims/api/portal/troubleshooting/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/receiving/route.js +176 -0
- package/dist/modules/warranty_claims/api/receiving/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/registrations/route.js +382 -0
- package/dist/modules/warranty_claims/api/registrations/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/replacement-order/route.js +150 -0
- package/dist/modules/warranty_claims/api/replacement-order/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/return-label/route.js +235 -0
- package/dist/modules/warranty_claims/api/return-label/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/risk/route.js +101 -0
- package/dist/modules/warranty_claims/api/risk/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/route.js +482 -0
- package/dist/modules/warranty_claims/api/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/sales-return/route.js +151 -0
- package/dist/modules/warranty_claims/api/sales-return/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/settings-general/route.js +195 -0
- package/dist/modules/warranty_claims/api/settings-general/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/stats/route.js +199 -0
- package/dist/modules/warranty_claims/api/stats/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/submit/route.js +120 -0
- package/dist/modules/warranty_claims/api/submit/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/transition/route.js +126 -0
- package/dist/modules/warranty_claims/api/transition/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js +261 -0
- package/dist/modules/warranty_claims/api/troubleshooting-guides/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/vendor-policies/route.js +311 -0
- package/dist/modules/warranty_claims/api/vendor-policies/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js +130 -0
- package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +7 -0
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +153 -0
- package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/AiAssessButtons.js +268 -0
- package/dist/modules/warranty_claims/backend/components/AiAssessButtons.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/ClaimStageProgress.js +54 -0
- package/dist/modules/warranty_claims/backend/components/ClaimStageProgress.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/ClaimStatusBadge.js +87 -0
- package/dist/modules/warranty_claims/backend/components/ClaimStatusBadge.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/ClaimsKpiStrip.js +120 -0
- package/dist/modules/warranty_claims/backend/components/ClaimsKpiStrip.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/EntitlementLookupBadge.js +135 -0
- package/dist/modules/warranty_claims/backend/components/EntitlementLookupBadge.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/ReceivingPanel.js +218 -0
- package/dist/modules/warranty_claims/backend/components/ReceivingPanel.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/ReturnLabelPanel.js +231 -0
- package/dist/modules/warranty_claims/backend/components/ReturnLabelPanel.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/TroubleshootingWalker.js +82 -0
- package/dist/modules/warranty_claims/backend/components/TroubleshootingWalker.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/VendorRecoverySuggestionsPanel.js +136 -0
- package/dist/modules/warranty_claims/backend/components/VendorRecoverySuggestionsPanel.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js +41 -0
- package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/claimReasonOptions.js +45 -0
- package/dist/modules/warranty_claims/backend/components/claimReasonOptions.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/claimSla.js +92 -0
- package/dist/modules/warranty_claims/backend/components/claimSla.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/orderLookup.js +81 -0
- package/dist/modules/warranty_claims/backend/components/orderLookup.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/productLookup.js +317 -0
- package/dist/modules/warranty_claims/backend/components/productLookup.js.map +7 -0
- package/dist/modules/warranty_claims/backend/components/useUserDisplayNames.js +61 -0
- package/dist/modules/warranty_claims/backend/components/useUserDisplayNames.js.map +7 -0
- package/dist/modules/warranty_claims/backend/page.js +1088 -0
- package/dist/modules/warranty_claims/backend/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/page.meta.js +16 -0
- package/dist/modules/warranty_claims/backend/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.js +427 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.meta.js +16 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +2101 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.meta.js +16 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/create/page.js +1249 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/create/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/create/page.meta.js +16 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/create/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.js +110 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.meta.js +17 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.js +49 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.meta.js +17 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/create/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js +485 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.meta.js +19 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/registrationForm.js +253 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/registrations/registrationForm.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +1565 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.meta.js +19 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/TroubleshootingTreeBuilder.js +472 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/TroubleshootingTreeBuilder.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.js +115 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.meta.js +19 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.js +54 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.meta.js +19 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js +414 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.meta.js +21 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/troubleshootingGuideForm.js +171 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/troubleshootingGuideForm.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.js +111 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.meta.js +19 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.js +48 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.meta.js +19 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js +429 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.meta.js +21 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/vendorPolicyForm.js +182 -0
- package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/vendorPolicyForm.js.map +7 -0
- package/dist/modules/warranty_claims/ce.js +48 -0
- package/dist/modules/warranty_claims/ce.js.map +7 -0
- package/dist/modules/warranty_claims/commands/claim-lines.js +911 -0
- package/dist/modules/warranty_claims/commands/claim-lines.js.map +7 -0
- package/dist/modules/warranty_claims/commands/claims.js +2577 -0
- package/dist/modules/warranty_claims/commands/claims.js.map +7 -0
- package/dist/modules/warranty_claims/commands/index.js +4 -0
- package/dist/modules/warranty_claims/commands/index.js.map +7 -0
- package/dist/modules/warranty_claims/commands/settings.js +169 -0
- package/dist/modules/warranty_claims/commands/settings.js.map +7 -0
- package/dist/modules/warranty_claims/commands/shared.js +162 -0
- package/dist/modules/warranty_claims/commands/shared.js.map +7 -0
- package/dist/modules/warranty_claims/data/constants.js +35 -0
- package/dist/modules/warranty_claims/data/constants.js.map +7 -0
- package/dist/modules/warranty_claims/data/enrichers.js +97 -0
- package/dist/modules/warranty_claims/data/enrichers.js.map +7 -0
- package/dist/modules/warranty_claims/data/entities.js +731 -0
- package/dist/modules/warranty_claims/data/entities.js.map +7 -0
- package/dist/modules/warranty_claims/data/validators.js +488 -0
- package/dist/modules/warranty_claims/data/validators.js.map +7 -0
- package/dist/modules/warranty_claims/di.js +29 -0
- package/dist/modules/warranty_claims/di.js.map +7 -0
- package/dist/modules/warranty_claims/encryption.js +28 -0
- package/dist/modules/warranty_claims/encryption.js.map +7 -0
- package/dist/modules/warranty_claims/events.js +81 -0
- package/dist/modules/warranty_claims/events.js.map +7 -0
- package/dist/modules/warranty_claims/extension-points.js +19 -0
- package/dist/modules/warranty_claims/extension-points.js.map +7 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +825 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +7 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.meta.js +11 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +1139 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +7 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.meta.js +11 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +253 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +7 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.meta.js +18 -0
- package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.meta.js.map +7 -0
- package/dist/modules/warranty_claims/index.js +15 -0
- package/dist/modules/warranty_claims/index.js.map +7 -0
- package/dist/modules/warranty_claims/lib/aiAssist.js +496 -0
- package/dist/modules/warranty_claims/lib/aiAssist.js.map +7 -0
- package/dist/modules/warranty_claims/lib/apiIdsFilter.js +11 -0
- package/dist/modules/warranty_claims/lib/apiIdsFilter.js.map +7 -0
- package/dist/modules/warranty_claims/lib/assigneeNames.js +153 -0
- package/dist/modules/warranty_claims/lib/assigneeNames.js.map +7 -0
- package/dist/modules/warranty_claims/lib/attachmentVisibility.js +9 -0
- package/dist/modules/warranty_claims/lib/attachmentVisibility.js.map +7 -0
- package/dist/modules/warranty_claims/lib/bulkFeedback.js +8 -0
- package/dist/modules/warranty_claims/lib/bulkFeedback.js.map +7 -0
- package/dist/modules/warranty_claims/lib/businessHours.js +250 -0
- package/dist/modules/warranty_claims/lib/businessHours.js.map +7 -0
- package/dist/modules/warranty_claims/lib/businessHoursForm.js +146 -0
- package/dist/modules/warranty_claims/lib/businessHoursForm.js.map +7 -0
- package/dist/modules/warranty_claims/lib/claimTypeConfig.js +39 -0
- package/dist/modules/warranty_claims/lib/claimTypeConfig.js.map +7 -0
- package/dist/modules/warranty_claims/lib/decryptionSafety.js +17 -0
- package/dist/modules/warranty_claims/lib/decryptionSafety.js.map +7 -0
- package/dist/modules/warranty_claims/lib/deskFilters.js +88 -0
- package/dist/modules/warranty_claims/lib/deskFilters.js.map +7 -0
- package/dist/modules/warranty_claims/lib/dictionaries.js +157 -0
- package/dist/modules/warranty_claims/lib/dictionaries.js.map +7 -0
- package/dist/modules/warranty_claims/lib/dictionaryLabels.js +12 -0
- package/dist/modules/warranty_claims/lib/dictionaryLabels.js.map +7 -0
- package/dist/modules/warranty_claims/lib/emailIntake.js +87 -0
- package/dist/modules/warranty_claims/lib/emailIntake.js.map +7 -0
- package/dist/modules/warranty_claims/lib/escalation.js +58 -0
- package/dist/modules/warranty_claims/lib/escalation.js.map +7 -0
- package/dist/modules/warranty_claims/lib/externalIntake.js +233 -0
- package/dist/modules/warranty_claims/lib/externalIntake.js.map +7 -0
- package/dist/modules/warranty_claims/lib/grading.js +21 -0
- package/dist/modules/warranty_claims/lib/grading.js.map +7 -0
- package/dist/modules/warranty_claims/lib/listSegments.js +18 -0
- package/dist/modules/warranty_claims/lib/listSegments.js.map +7 -0
- package/dist/modules/warranty_claims/lib/orderOwnership.js +25 -0
- package/dist/modules/warranty_claims/lib/orderOwnership.js.map +7 -0
- package/dist/modules/warranty_claims/lib/portalAttachmentUpload.js +11 -0
- package/dist/modules/warranty_claims/lib/portalAttachmentUpload.js.map +7 -0
- package/dist/modules/warranty_claims/lib/portalAttachmentValidation.js +108 -0
- package/dist/modules/warranty_claims/lib/portalAttachmentValidation.js.map +7 -0
- package/dist/modules/warranty_claims/lib/portalClaimAccess.js +26 -0
- package/dist/modules/warranty_claims/lib/portalClaimAccess.js.map +7 -0
- package/dist/modules/warranty_claims/lib/quantity.js +19 -0
- package/dist/modules/warranty_claims/lib/quantity.js.map +7 -0
- package/dist/modules/warranty_claims/lib/risk.js +204 -0
- package/dist/modules/warranty_claims/lib/risk.js.map +7 -0
- package/dist/modules/warranty_claims/lib/settings.js +55 -0
- package/dist/modules/warranty_claims/lib/settings.js.map +7 -0
- package/dist/modules/warranty_claims/lib/stateMachine.js +88 -0
- package/dist/modules/warranty_claims/lib/stateMachine.js.map +7 -0
- package/dist/modules/warranty_claims/lib/timelineFilters.js +16 -0
- package/dist/modules/warranty_claims/lib/timelineFilters.js.map +7 -0
- package/dist/modules/warranty_claims/lib/triage.js +241 -0
- package/dist/modules/warranty_claims/lib/triage.js.map +7 -0
- package/dist/modules/warranty_claims/lib/troubleshooting.js +111 -0
- package/dist/modules/warranty_claims/lib/troubleshooting.js.map +7 -0
- package/dist/modules/warranty_claims/lib/tryResolve.js +11 -0
- package/dist/modules/warranty_claims/lib/tryResolve.js.map +7 -0
- package/dist/modules/warranty_claims/lib/vendorPolicyRecovery.js +101 -0
- package/dist/modules/warranty_claims/lib/vendorPolicyRecovery.js.map +7 -0
- package/dist/modules/warranty_claims/lib/warrantyPreview.js +18 -0
- package/dist/modules/warranty_claims/lib/warrantyPreview.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260703191537_warranty_claims.js +32 -0
- package/dist/modules/warranty_claims/migrations/Migration20260703191537_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260704115605_warranty_claims.js +16 -0
- package/dist/modules/warranty_claims/migrations/Migration20260704115605_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260704230000_warranty_claims.js +17 -0
- package/dist/modules/warranty_claims/migrations/Migration20260704230000_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260705121601_warranty_claims.js +29 -0
- package/dist/modules/warranty_claims/migrations/Migration20260705121601_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260709120000_warranty_claims.js +25 -0
- package/dist/modules/warranty_claims/migrations/Migration20260709120000_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260710023607_warranty_claims.js +13 -0
- package/dist/modules/warranty_claims/migrations/Migration20260710023607_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260716203936_warranty_claims.js +15 -0
- package/dist/modules/warranty_claims/migrations/Migration20260716203936_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260717150000_warranty_claims.js +13 -0
- package/dist/modules/warranty_claims/migrations/Migration20260717150000_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260719120000_warranty_claims.js +13 -0
- package/dist/modules/warranty_claims/migrations/Migration20260719120000_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/migrations/Migration20260813103053_warranty_claims.js +12 -0
- package/dist/modules/warranty_claims/migrations/Migration20260813103053_warranty_claims.js.map +7 -0
- package/dist/modules/warranty_claims/notifications.client.js +67 -0
- package/dist/modules/warranty_claims/notifications.client.js.map +7 -0
- package/dist/modules/warranty_claims/notifications.js +103 -0
- package/dist/modules/warranty_claims/notifications.js.map +7 -0
- package/dist/modules/warranty_claims/search.js +174 -0
- package/dist/modules/warranty_claims/search.js.map +7 -0
- package/dist/modules/warranty_claims/services/adjudicationEvaluator.js +202 -0
- package/dist/modules/warranty_claims/services/adjudicationEvaluator.js.map +7 -0
- package/dist/modules/warranty_claims/services/claimNumberGenerator.js +58 -0
- package/dist/modules/warranty_claims/services/claimNumberGenerator.js.map +7 -0
- package/dist/modules/warranty_claims/services/entitlementResolver.js +69 -0
- package/dist/modules/warranty_claims/services/entitlementResolver.js.map +7 -0
- package/dist/modules/warranty_claims/services/returnLabelProvider.js +14 -0
- package/dist/modules/warranty_claims/services/returnLabelProvider.js.map +7 -0
- package/dist/modules/warranty_claims/setup.js +121 -0
- package/dist/modules/warranty_claims/setup.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/auto-vendor-recovery.js +119 -0
- package/dist/modules/warranty_claims/subscribers/auto-vendor-recovery.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/claim-assigned-notification.js +47 -0
- package/dist/modules/warranty_claims/subscribers/claim-assigned-notification.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/claim-customer-reply-notification.js +60 -0
- package/dist/modules/warranty_claims/subscribers/claim-customer-reply-notification.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/claim-status-notification.js +83 -0
- package/dist/modules/warranty_claims/subscribers/claim-status-notification.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/claim-submitted-notification.js +46 -0
- package/dist/modules/warranty_claims/subscribers/claim-submitted-notification.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/email-to-claim.js +135 -0
- package/dist/modules/warranty_claims/subscribers/email-to-claim.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js +164 -0
- package/dist/modules/warranty_claims/subscribers/return-shipment-tracking.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation-undo.js +24 -0
- package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation-undo.js.map +7 -0
- package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation.js +26 -0
- package/dist/modules/warranty_claims/subscribers/vendor-recovery-reconciliation.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/config.js +15 -0
- package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/config.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.client.js +197 -0
- package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.client.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.js +28 -0
- package/dist/modules/warranty_claims/widgets/dashboard/claims-queue/widget.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.client.js +418 -0
- package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.client.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.js +18 -0
- package/dist/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab/widget.js +17 -0
- package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab/widget.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab.js +146 -0
- package/dist/modules/warranty_claims/widgets/injection/customer-claims-tab.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/injection/order-claims-tab/widget.js +17 -0
- package/dist/modules/warranty_claims/widgets/injection/order-claims-tab/widget.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/injection/order-claims-tab.js +147 -0
- package/dist/modules/warranty_claims/widgets/injection/order-claims-tab.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/injection-table.js +38 -0
- package/dist/modules/warranty_claims/widgets/injection-table.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/notifications/WarrantyClaimNotificationRenderer.js +223 -0
- package/dist/modules/warranty_claims/widgets/notifications/WarrantyClaimNotificationRenderer.js.map +7 -0
- package/dist/modules/warranty_claims/widgets/notifications/index.js +5 -0
- package/dist/modules/warranty_claims/widgets/notifications/index.js.map +7 -0
- package/dist/modules/warranty_claims/workers/sla-escalation-sweep.js +334 -0
- package/dist/modules/warranty_claims/workers/sla-escalation-sweep.js.map +7 -0
- package/generated/entities/sync_run/index.ts +1 -0
- package/generated/entities/warranty_claim/index.ts +51 -0
- package/generated/entities/warranty_claim_event/index.ts +11 -0
- package/generated/entities/warranty_claim_line/index.ts +37 -0
- package/generated/entities/warranty_claim_registration/index.ts +20 -0
- package/generated/entities/warranty_claim_sequence/index.ts +7 -0
- package/generated/entities/warranty_claim_settings/index.ts +20 -0
- package/generated/entities/warranty_claim_sla_signal/index.ts +11 -0
- package/generated/entities/warranty_troubleshooting_guide/index.ts +11 -0
- package/generated/entities/warranty_vendor_policy/index.ts +14 -0
- package/generated/entities.ids.generated.ts +12 -0
- package/generated/entity-fields-registry.ts +201 -0
- package/package.json +7 -7
- package/src/helpers/integration/api.ts +10 -3
- package/src/helpers/integration/salesFixtures.ts +17 -7
- package/src/modules/attachments/di.ts +30 -1
- package/src/modules/attachments/fields/attachment.tsx +215 -23
- package/src/modules/attachments/i18n/de.json +5 -0
- package/src/modules/attachments/i18n/en.json +5 -0
- package/src/modules/attachments/i18n/es.json +5 -0
- package/src/modules/attachments/i18n/ko.json +5 -0
- package/src/modules/attachments/i18n/pl.json +5 -0
- package/src/modules/attachments/lib/scoped-upload-service.ts +257 -0
- package/src/modules/attachments/lib/target-access-service.ts +57 -0
- package/src/modules/auth/api/sidebar/preferences/route.ts +2 -2
- package/src/modules/auth/lib/backendChrome.tsx +2 -2
- package/src/modules/auth/services/sidebarPreferencesService.ts +22 -3
- package/src/modules/customers/api/companies/route.ts +1 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +4 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +4 -2
- package/src/modules/customers/components/detail/assignableStaff.ts +14 -177
- package/src/modules/customers/lib/assignableStaff.ts +177 -0
- package/src/modules/data_sync/AGENTS.md +86 -8
- package/src/modules/data_sync/api/options.ts +1 -0
- package/src/modules/data_sync/api/run.ts +17 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +32 -1
- package/src/modules/data_sync/api/runs/[id]/route.ts +1 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +73 -17
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +62 -2
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +28 -15
- package/src/modules/data_sync/components/RunParameterFields.tsx +229 -0
- package/src/modules/data_sync/data/entities.ts +4 -1
- package/src/modules/data_sync/data/validators.ts +3 -0
- package/src/modules/data_sync/i18n/de.json +10 -0
- package/src/modules/data_sync/i18n/en.json +10 -0
- package/src/modules/data_sync/i18n/es.json +10 -0
- package/src/modules/data_sync/i18n/ko.json +10 -0
- package/src/modules/data_sync/i18n/pl.json +10 -0
- package/src/modules/data_sync/lib/adapter.ts +88 -0
- package/src/modules/data_sync/lib/batch-stream.ts +117 -0
- package/src/modules/data_sync/lib/run-parameters.ts +196 -0
- package/src/modules/data_sync/lib/start-run.ts +2 -0
- package/src/modules/data_sync/lib/sync-engine.ts +193 -114
- package/src/modules/data_sync/lib/sync-run-service.ts +2 -0
- package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +17 -1
- package/src/modules/data_sync/migrations/Migration20260810120000.ts +13 -0
- package/src/modules/data_sync/workers/sync-scheduled.ts +32 -1
- package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +4 -0
- package/src/modules/inbox_ops/api/extract/route.ts +4 -0
- package/src/modules/inbox_ops/api/webhook/inbound.ts +4 -0
- package/src/modules/query_index/di.ts +153 -56
- package/src/modules/sales/commands/documents.ts +56 -12
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/ko.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
- package/src/modules/warranty_claims/AGENTS.md +49 -0
- package/src/modules/warranty_claims/acl.ts +71 -0
- package/src/modules/warranty_claims/ai-agents.ts +189 -0
- package/src/modules/warranty_claims/ai-tools.ts +711 -0
- package/src/modules/warranty_claims/api/ai/assess/route.ts +371 -0
- package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +149 -0
- package/src/modules/warranty_claims/api/ai/suggest/route.ts +190 -0
- package/src/modules/warranty_claims/api/assign/route.ts +142 -0
- package/src/modules/warranty_claims/api/assignees/route.ts +102 -0
- package/src/modules/warranty_claims/api/credit-memo/route.ts +191 -0
- package/src/modules/warranty_claims/api/entitlement/route.ts +224 -0
- package/src/modules/warranty_claims/api/events/route.ts +226 -0
- package/src/modules/warranty_claims/api/external/claims/route.ts +528 -0
- package/src/modules/warranty_claims/api/lines/route.ts +275 -0
- package/src/modules/warranty_claims/api/openapi.ts +28 -0
- package/src/modules/warranty_claims/api/portal/attachments/route.ts +547 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/route.ts +195 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/shared.ts +125 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +82 -0
- package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +87 -0
- package/src/modules/warranty_claims/api/portal/claims/route.ts +579 -0
- package/src/modules/warranty_claims/api/portal/events/route.ts +260 -0
- package/src/modules/warranty_claims/api/portal/options/route.ts +113 -0
- package/src/modules/warranty_claims/api/portal/orders/lines/route.ts +258 -0
- package/src/modules/warranty_claims/api/portal/orders/route.ts +206 -0
- package/src/modules/warranty_claims/api/portal/troubleshooting/route.ts +151 -0
- package/src/modules/warranty_claims/api/receiving/route.ts +215 -0
- package/src/modules/warranty_claims/api/registrations/route.ts +417 -0
- package/src/modules/warranty_claims/api/replacement-order/route.ts +188 -0
- package/src/modules/warranty_claims/api/return-label/route.ts +291 -0
- package/src/modules/warranty_claims/api/risk/route.ts +112 -0
- package/src/modules/warranty_claims/api/route.ts +543 -0
- package/src/modules/warranty_claims/api/sales-return/route.ts +183 -0
- package/src/modules/warranty_claims/api/settings-general/route.ts +230 -0
- package/src/modules/warranty_claims/api/stats/route.ts +306 -0
- package/src/modules/warranty_claims/api/submit/route.ts +146 -0
- package/src/modules/warranty_claims/api/transition/route.ts +144 -0
- package/src/modules/warranty_claims/api/troubleshooting-guides/route.ts +295 -0
- package/src/modules/warranty_claims/api/vendor-policies/route.ts +351 -0
- package/src/modules/warranty_claims/api/vendor-recovery/route.ts +148 -0
- package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +167 -0
- package/src/modules/warranty_claims/backend/components/AiAssessButtons.tsx +389 -0
- package/src/modules/warranty_claims/backend/components/ClaimStageProgress.tsx +65 -0
- package/src/modules/warranty_claims/backend/components/ClaimStatusBadge.tsx +137 -0
- package/src/modules/warranty_claims/backend/components/ClaimsKpiStrip.tsx +169 -0
- package/src/modules/warranty_claims/backend/components/EntitlementLookupBadge.tsx +203 -0
- package/src/modules/warranty_claims/backend/components/ReceivingPanel.tsx +300 -0
- package/src/modules/warranty_claims/backend/components/ReturnLabelPanel.tsx +292 -0
- package/src/modules/warranty_claims/backend/components/TroubleshootingWalker.tsx +125 -0
- package/src/modules/warranty_claims/backend/components/VendorRecoverySuggestionsPanel.tsx +217 -0
- package/src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx +66 -0
- package/src/modules/warranty_claims/backend/components/claimReasonOptions.ts +57 -0
- package/src/modules/warranty_claims/backend/components/claimSla.tsx +127 -0
- package/src/modules/warranty_claims/backend/components/orderLookup.ts +109 -0
- package/src/modules/warranty_claims/backend/components/productLookup.tsx +433 -0
- package/src/modules/warranty_claims/backend/components/useUserDisplayNames.ts +66 -0
- package/src/modules/warranty_claims/backend/page.meta.ts +12 -0
- package/src/modules/warranty_claims/backend/page.tsx +1302 -0
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.meta.ts +12 -0
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/edit/page.tsx +531 -0
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.meta.ts +12 -0
- package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +2599 -0
- package/src/modules/warranty_claims/backend/warranty_claims/create/page.meta.ts +12 -0
- package/src/modules/warranty_claims/backend/warranty_claims/create/page.tsx +1533 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.meta.ts +13 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/[id]/edit/page.tsx +136 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/create/page.meta.ts +13 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/create/page.tsx +52 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.meta.ts +15 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.tsx +555 -0
- package/src/modules/warranty_claims/backend/warranty_claims/registrations/registrationForm.tsx +295 -0
- package/src/modules/warranty_claims/backend/warranty_claims/settings/page.meta.ts +15 -0
- package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +1903 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/TroubleshootingTreeBuilder.tsx +550 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.meta.ts +15 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/[id]/edit/page.tsx +141 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.meta.ts +15 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/create/page.tsx +57 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.meta.ts +17 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.tsx +477 -0
- package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/troubleshootingGuideForm.tsx +214 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.meta.ts +15 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/[id]/edit/page.tsx +137 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.meta.ts +15 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/create/page.tsx +51 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.meta.ts +17 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.tsx +506 -0
- package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/vendorPolicyForm.tsx +220 -0
- package/src/modules/warranty_claims/ce.ts +44 -0
- package/src/modules/warranty_claims/commands/claim-lines.ts +1082 -0
- package/src/modules/warranty_claims/commands/claims.ts +3364 -0
- package/src/modules/warranty_claims/commands/index.ts +3 -0
- package/src/modules/warranty_claims/commands/settings.ts +198 -0
- package/src/modules/warranty_claims/commands/shared.ts +212 -0
- package/src/modules/warranty_claims/data/constants.ts +37 -0
- package/src/modules/warranty_claims/data/enrichers.ts +165 -0
- package/src/modules/warranty_claims/data/entities.ts +663 -0
- package/src/modules/warranty_claims/data/validators.ts +634 -0
- package/src/modules/warranty_claims/di.ts +34 -0
- package/src/modules/warranty_claims/encryption.ts +26 -0
- package/src/modules/warranty_claims/events.ts +81 -0
- package/src/modules/warranty_claims/extension-points.ts +16 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.meta.ts +9 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +1037 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.meta.ts +9 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +1409 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.meta.ts +16 -0
- package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +304 -0
- package/src/modules/warranty_claims/i18n/de.json +1270 -0
- package/src/modules/warranty_claims/i18n/en.json +1270 -0
- package/src/modules/warranty_claims/i18n/es.json +1270 -0
- package/src/modules/warranty_claims/i18n/ko.json +1270 -0
- package/src/modules/warranty_claims/i18n/pl.json +1270 -0
- package/src/modules/warranty_claims/index.ts +13 -0
- package/src/modules/warranty_claims/lib/aiAssist.ts +677 -0
- package/src/modules/warranty_claims/lib/apiIdsFilter.ts +12 -0
- package/src/modules/warranty_claims/lib/assigneeNames.ts +184 -0
- package/src/modules/warranty_claims/lib/attachmentVisibility.ts +5 -0
- package/src/modules/warranty_claims/lib/bulkFeedback.ts +6 -0
- package/src/modules/warranty_claims/lib/businessHours.ts +333 -0
- package/src/modules/warranty_claims/lib/businessHoursForm.ts +206 -0
- package/src/modules/warranty_claims/lib/claimTypeConfig.ts +49 -0
- package/src/modules/warranty_claims/lib/decryptionSafety.ts +19 -0
- package/src/modules/warranty_claims/lib/deskFilters.ts +131 -0
- package/src/modules/warranty_claims/lib/dictionaries.ts +212 -0
- package/src/modules/warranty_claims/lib/dictionaryLabels.ts +16 -0
- package/src/modules/warranty_claims/lib/emailIntake.ts +123 -0
- package/src/modules/warranty_claims/lib/escalation.ts +83 -0
- package/src/modules/warranty_claims/lib/externalIntake.ts +362 -0
- package/src/modules/warranty_claims/lib/grading.ts +23 -0
- package/src/modules/warranty_claims/lib/listSegments.ts +19 -0
- package/src/modules/warranty_claims/lib/orderOwnership.ts +58 -0
- package/src/modules/warranty_claims/lib/portalAttachmentUpload.ts +11 -0
- package/src/modules/warranty_claims/lib/portalAttachmentValidation.ts +115 -0
- package/src/modules/warranty_claims/lib/portalClaimAccess.ts +37 -0
- package/src/modules/warranty_claims/lib/quantity.ts +15 -0
- package/src/modules/warranty_claims/lib/risk.ts +371 -0
- package/src/modules/warranty_claims/lib/settings.ts +76 -0
- package/src/modules/warranty_claims/lib/stateMachine.ts +130 -0
- package/src/modules/warranty_claims/lib/timelineFilters.ts +22 -0
- package/src/modules/warranty_claims/lib/triage.ts +366 -0
- package/src/modules/warranty_claims/lib/troubleshooting.ts +157 -0
- package/src/modules/warranty_claims/lib/tryResolve.ts +7 -0
- package/src/modules/warranty_claims/lib/vendorPolicyRecovery.ts +182 -0
- package/src/modules/warranty_claims/lib/warrantyPreview.ts +20 -0
- package/src/modules/warranty_claims/migrations/.snapshot-open-mercato.json +3525 -0
- package/src/modules/warranty_claims/migrations/Migration20260703191537_warranty_claims.ts +38 -0
- package/src/modules/warranty_claims/migrations/Migration20260704115605_warranty_claims.ts +17 -0
- package/src/modules/warranty_claims/migrations/Migration20260704230000_warranty_claims.ts +17 -0
- package/src/modules/warranty_claims/migrations/Migration20260705121601_warranty_claims.ts +37 -0
- package/src/modules/warranty_claims/migrations/Migration20260709120000_warranty_claims.ts +25 -0
- package/src/modules/warranty_claims/migrations/Migration20260710023607_warranty_claims.ts +13 -0
- package/src/modules/warranty_claims/migrations/Migration20260716203936_warranty_claims.ts +15 -0
- package/src/modules/warranty_claims/migrations/Migration20260717150000_warranty_claims.ts +13 -0
- package/src/modules/warranty_claims/migrations/Migration20260719120000_warranty_claims.ts +13 -0
- package/src/modules/warranty_claims/migrations/Migration20260813103053_warranty_claims.ts +11 -0
- package/src/modules/warranty_claims/notifications.client.ts +78 -0
- package/src/modules/warranty_claims/notifications.ts +101 -0
- package/src/modules/warranty_claims/search.ts +199 -0
- package/src/modules/warranty_claims/services/adjudicationEvaluator.ts +344 -0
- package/src/modules/warranty_claims/services/claimNumberGenerator.ts +69 -0
- package/src/modules/warranty_claims/services/entitlementResolver.ts +102 -0
- package/src/modules/warranty_claims/services/returnLabelProvider.ts +30 -0
- package/src/modules/warranty_claims/setup.ts +154 -0
- package/src/modules/warranty_claims/subscribers/auto-vendor-recovery.ts +140 -0
- package/src/modules/warranty_claims/subscribers/claim-assigned-notification.ts +54 -0
- package/src/modules/warranty_claims/subscribers/claim-customer-reply-notification.ts +68 -0
- package/src/modules/warranty_claims/subscribers/claim-status-notification.ts +100 -0
- package/src/modules/warranty_claims/subscribers/claim-submitted-notification.ts +53 -0
- package/src/modules/warranty_claims/subscribers/email-to-claim.ts +175 -0
- package/src/modules/warranty_claims/subscribers/return-shipment-tracking.ts +226 -0
- package/src/modules/warranty_claims/subscribers/vendor-recovery-reconciliation-undo.ts +28 -0
- package/src/modules/warranty_claims/subscribers/vendor-recovery-reconciliation.ts +31 -0
- package/src/modules/warranty_claims/widgets/dashboard/claims-queue/config.ts +15 -0
- package/src/modules/warranty_claims/widgets/dashboard/claims-queue/widget.client.tsx +237 -0
- package/src/modules/warranty_claims/widgets/dashboard/claims-queue/widget.ts +27 -0
- package/src/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.client.tsx +527 -0
- package/src/modules/warranty_claims/widgets/injection/claims-ai-trigger/widget.ts +37 -0
- package/src/modules/warranty_claims/widgets/injection/customer-claims-tab/widget.ts +21 -0
- package/src/modules/warranty_claims/widgets/injection/customer-claims-tab.tsx +210 -0
- package/src/modules/warranty_claims/widgets/injection/order-claims-tab/widget.ts +21 -0
- package/src/modules/warranty_claims/widgets/injection/order-claims-tab.tsx +210 -0
- package/src/modules/warranty_claims/widgets/injection-table.ts +36 -0
- package/src/modules/warranty_claims/widgets/notifications/WarrantyClaimNotificationRenderer.tsx +253 -0
- package/src/modules/warranty_claims/widgets/notifications/index.ts +1 -0
- package/src/modules/warranty_claims/workers/sla-escalation-sweep.ts +450 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/inbox_ops/api/webhook/inbound.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createHmac, timingSafeEqual } from 'node:crypto'\nimport { z } from 'zod'\nimport { Webhook } from 'svix'\nimport { Resend } from 'resend'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxSettings, InboxEmail } from '../../data/entities'\nimport { parseInboundEmail } from '../../lib/emailParser'\nimport { checkRateLimit } from '../../lib/rateLimiter'\nimport { emitInboxOpsEvent } from '../../events'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n readBoundedRequestBody,\n resolveWebhookBodyLimitBytes,\n WebhookBodyTooLargeError,\n} from '@open-mercato/shared/lib/webhooks'\n\nconst logger = createLogger('inbox_ops').child({ component: 'webhook' })\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst MAX_PAYLOAD_SIZE = resolveWebhookBodyLimitBytes(\n process.env.INBOX_OPS_WEBHOOK_MAX_BODY_BYTES ?? process.env.OM_WEBHOOK_MAX_BODY_BYTES,\n 2 * 1024 * 1024,\n)\nconst REPLAY_WINDOW_MS = 5 * 60 * 1000 // 5 minutes\n\nfunction isTimestampFresh(timestamp: string): boolean {\n if (!timestamp) return false\n const timestampMs = parseInt(timestamp, 10) * 1000\n if (isNaN(timestampMs)) return false\n return Math.abs(Date.now() - timestampMs) <= REPLAY_WINDOW_MS\n}\n\nfunction verifyHmacSignature(\n payload: string,\n signature: string,\n timestamp: string,\n secret: string,\n): boolean {\n if (!signature || !timestamp || !secret) return false\n\n if (!isTimestampFresh(timestamp)) return false\n\n const expected = createHmac('sha256', secret)\n .update(`${timestamp}.${payload}`)\n .digest('hex')\n const received = signature.startsWith('sha256=')\n ? signature.slice('sha256='.length)\n : signature\n\n try {\n if (received.length !== expected.length) return false\n return timingSafeEqual(\n Buffer.from(received, 'hex'),\n Buffer.from(expected, 'hex'),\n )\n } catch {\n return false\n }\n}\n\n// True when the signature verifies against ANY of the candidate secrets. Used\n// to bind the inbound signature to the resolved tenant: once the target inbox\n// declares its own per-tenant secret, the global INBOX_OPS_WEBHOOK_SECRET is no\n// longer accepted for that inbox, so a holder of the global key alone cannot\n// forge mail into a tenant that opted into a dedicated secret.\nfunction verifyHmacAgainstAny(\n payload: string,\n signature: string,\n timestamp: string,\n secrets: string[],\n): boolean {\n for (const secret of secrets) {\n if (secret && verifyHmacSignature(payload, signature, timestamp, secret)) return true\n }\n return false\n}\n\n// Stable, non-reversible identifier for the pre-DB global rate-limit bucket.\n// Keying on the configured signing secret (rather than the attacker-set `to` or\n// the per-request signature) means the bucket is request-invariant for a given\n// secret, so rotating recipients or mutating the body cannot dilute the limit.\nfunction secretFingerprint(secret: string): string {\n return createHmac('sha256', 'inbox_ops:rate_limit:salt').update(secret).digest('hex').slice(0, 32)\n}\n\nfunction verifySvixSignature(\n rawBody: string,\n headers: Record<string, string>,\n secret: string,\n): boolean {\n try {\n const wh = new Webhook(secret)\n wh.verify(rawBody, {\n 'svix-id': headers['svix-id'],\n 'svix-timestamp': headers['svix-timestamp'],\n 'svix-signature': headers['svix-signature'],\n })\n return true\n } catch {\n return false\n }\n}\n\nasync function fetchResendEmail(emailId: string): Promise<{\n from?: string\n to?: string[]\n subject?: string\n text?: string\n html?: string\n messageId?: string\n replyTo?: string\n inReplyTo?: string\n references?: string[]\n}> {\n const apiKey = process.env.RESEND_API_KEY\n if (!apiKey) throw new Error('RESEND_API_KEY is required to fetch Resend inbound emails')\n\n const resend = new Resend(apiKey)\n const { data, error } = await resend.emails.receiving.get(emailId)\n if (error || !data) {\n throw new Error(`Failed to fetch Resend email ${emailId}: ${error?.message || 'unknown error'}`)\n }\n\n return {\n from: data.from,\n to: data.to,\n subject: data.subject,\n text: data.text ?? undefined,\n html: data.html ?? undefined,\n messageId: data.message_id,\n replyTo: data.reply_to?.[0] ?? undefined,\n inReplyTo: data.headers?.['in-reply-to'] ?? undefined,\n references: data.headers?.['references']?.split(/\\s+/).filter(Boolean),\n }\n}\n\ntype VerifiedPayload =\n | { kind: 'custom'; payload: Record<string, unknown>; signature: string; timestamp: string }\n | { kind: 'resend'; emailFields: Awaited<ReturnType<typeof fetchResendEmail>>; to: string[] }\n\nasync function verifyAndParse(req: Request, rawBody: string): Promise<\n | { ok: true; data: VerifiedPayload }\n | { ok: false; response: NextResponse }\n> {\n const customSig = req.headers.get('x-webhook-signature')\n const svixId = req.headers.get('svix-id')\n\n if (customSig) {\n const webhookSecret = process.env.INBOX_OPS_WEBHOOK_SECRET\n if (!webhookSecret) {\n logger.error('INBOX_OPS_WEBHOOK_SECRET not configured')\n return { ok: false, response: NextResponse.json({ error: 'Service unavailable' }, { status: 503 }) }\n }\n const timestamp = req.headers.get('x-webhook-timestamp') || ''\n // Cheap, secret-independent gate first: reject replayed/stale requests\n // before parsing. The HMAC itself is verified later, against the secret\n // bound to the resolved inbox (see the POST handler), so a holder of the\n // global secret cannot inject into a tenant that owns its own secret.\n if (!isTimestampFresh(timestamp)) {\n return { ok: false, response: NextResponse.json({ error: 'Invalid signature' }, { status: 400 }) }\n }\n let payload: Record<string, unknown>\n try {\n payload = JSON.parse(rawBody)\n } catch {\n return { ok: false, response: NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n }\n return { ok: true, data: { kind: 'custom', payload, signature: customSig, timestamp } }\n }\n\n if (svixId) {\n const signingSecret = process.env.RESEND_WEBHOOK_SIGNING_SECRET\n if (!signingSecret) {\n logger.error('RESEND_WEBHOOK_SIGNING_SECRET not configured')\n return { ok: false, response: NextResponse.json({ error: 'Service unavailable' }, { status: 503 }) }\n }\n const headers: Record<string, string> = {\n 'svix-id': svixId,\n 'svix-timestamp': req.headers.get('svix-timestamp') || '',\n 'svix-signature': req.headers.get('svix-signature') || '',\n }\n if (!verifySvixSignature(rawBody, headers, signingSecret)) {\n return { ok: false, response: NextResponse.json({ error: 'Invalid signature' }, { status: 400 }) }\n }\n let envelope: { type?: string; data?: { email_id?: string; from?: string; to?: string[] } }\n try {\n envelope = JSON.parse(rawBody)\n } catch {\n return { ok: false, response: NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n }\n if (envelope.type !== 'email.received') {\n return { ok: false, response: NextResponse.json({ ok: true }) }\n }\n const emailId = envelope.data?.email_id\n if (!emailId) {\n return { ok: false, response: NextResponse.json({ error: 'Missing email_id in Resend payload' }, { status: 400 }) }\n }\n const emailFields = await fetchResendEmail(emailId)\n const to = emailFields.to || envelope.data?.to || []\n return { ok: true, data: { kind: 'resend', emailFields, to } }\n }\n\n return { ok: false, response: NextResponse.json({ error: 'Missing signature headers' }, { status: 400 }) }\n}\n\nexport async function POST(req: Request) {\n const hasCustomSecret = Boolean(process.env.INBOX_OPS_WEBHOOK_SECRET)\n const hasResendSecret = Boolean(process.env.RESEND_WEBHOOK_SIGNING_SECRET)\n if (!hasCustomSecret && !hasResendSecret) {\n logger.error('Neither INBOX_OPS_WEBHOOK_SECRET nor RESEND_WEBHOOK_SIGNING_SECRET is configured')\n return NextResponse.json({ error: 'Service unavailable' }, { status: 503 })\n }\n\n let rawBody: string\n try {\n rawBody = await readBoundedRequestBody(req, { maxBytes: MAX_PAYLOAD_SIZE })\n } catch (error) {\n if (error instanceof WebhookBodyTooLargeError) {\n return NextResponse.json({ error: 'Payload too large' }, { status: 413 })\n }\n return NextResponse.json({ error: 'Bad request' }, { status: 400 })\n }\n\n const verified = await verifyAndParse(req, rawBody)\n if (!verified.ok) {\n return verified.response\n }\n\n let toAddress: string | null\n let emailInput: Parameters<typeof parseInboundEmail>[0]\n\n if (verified.data.kind === 'custom') {\n const payload = verified.data.payload\n toAddress = extractToAddress(payload)\n emailInput = {\n from: payload.from as string | undefined,\n to: payload.to as string | string[] | undefined,\n subject: payload.subject as string | undefined,\n text: payload.text as string | undefined,\n html: payload.html as string | undefined,\n messageId: payload.messageId as string | undefined,\n replyTo: payload.replyTo as string | undefined,\n inReplyTo: payload.inReplyTo as string | undefined,\n references: payload.references as string | string[] | undefined,\n }\n } else {\n const { emailFields, to } = verified.data\n toAddress = to.length > 0 ? to[0].toLowerCase() : null\n emailInput = {\n from: emailFields.from,\n to: emailFields.to,\n subject: emailFields.subject,\n text: emailFields.text,\n html: emailFields.html,\n messageId: emailFields.messageId,\n replyTo: emailFields.replyTo,\n inReplyTo: emailFields.inReplyTo,\n references: emailFields.references,\n }\n }\n\n if (!toAddress) {\n return NextResponse.json({ error: 'Missing recipient address' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n\n let cache: CacheStrategy | null = null\n try {\n cache = container.resolve('cache') as CacheStrategy\n } catch {\n // Cache not available \u2014 the limiter now fails closed with a process-local\n // bucket instead of waving the request through.\n }\n\n // Pre-DB global bucket keyed on the configured signing secret (custom) or the\n // static Resend path, NOT on the attacker-controllable `to` and NOT on the\n // per-request signature (which mutates with timestamp/body, so it would mint a\n // fresh bucket every request and never throttle). The per-tenant secret lives\n // behind the encrypted-column lookup, so the only secret available here without\n // a DB hit is the global one \u2014 keying on it caps total inbound volume for the\n // custom path and runs BEFORE the lookup so a flood cannot amplify DB work.\n const globalBucketKey = verified.data.kind === 'custom'\n ? `webhook:secret:${secretFingerprint(process.env.INBOX_OPS_WEBHOOK_SECRET || '')}`\n : 'webhook:resend'\n const globalRateCheck = await checkRateLimit(cache, globalBucketKey)\n if (!globalRateCheck.allowed) {\n return rateLimitedResponse(globalRateCheck.retryAfterSeconds)\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n // No explicit scope: decryptEntitiesWithFallbackScope reads tenant/org off the\n // resolved row, which is exactly what binds webhook_secret to its own tenant.\n const settings = await findOneWithDecryption(\n em,\n InboxSettings,\n {\n inboxAddress: toAddress.toLowerCase(),\n isActive: true,\n deletedAt: null,\n },\n )\n\n // Final HMAC verification for the custom provider path: bind the signature to\n // the resolved tenant. When the inbox declares its own secret, ONLY that\n // secret is accepted; otherwise the global secret is accepted (back-compat).\n // For an unknown `to` we still require the global secret so the endpoint never\n // behaves as an unauthenticated probe oracle.\n if (verified.data.kind === 'custom') {\n const globalSecret = process.env.INBOX_OPS_WEBHOOK_SECRET || ''\n const inboxSecret = settings?.webhookSecret || ''\n const acceptableSecrets = inboxSecret ? [inboxSecret] : [globalSecret]\n const signatureValid = verifyHmacAgainstAny(\n rawBody,\n verified.data.signature,\n verified.data.timestamp,\n acceptableSecrets,\n )\n if (!signatureValid) {\n return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })\n }\n }\n\n if (!settings) {\n return NextResponse.json({ ok: true })\n }\n\n // Per-tenant bucket keyed on the RESOLVED tenant (not the raw `to`), so it\n // cannot be diluted by alias addresses that map to the same inbox.\n const tenantRateCheck = await checkRateLimit(\n cache,\n `webhook:tenant:${settings.tenantId}`,\n settings.tenantId,\n )\n if (!tenantRateCheck.allowed) {\n return rateLimitedResponse(tenantRateCheck.retryAfterSeconds)\n }\n\n const parsed = parseInboundEmail(emailInput)\n\n const isDuplicate = await checkDuplicate(em, settings, parsed.messageId, parsed.contentHash)\n if (isDuplicate) {\n try {\n await emitInboxOpsEvent('inbox_ops.email.deduplicated', {\n tenantId: settings.tenantId,\n organizationId: settings.organizationId,\n toAddress,\n })\n } catch (eventError) {\n logger.error('Failed to emit deduplicated event', { err: eventError })\n }\n return NextResponse.json({ ok: true })\n }\n\n const maxTextSize = parseInt(process.env.INBOX_OPS_MAX_TEXT_SIZE || '204800', 10)\n const cleanedText = parsed.cleanedText.slice(0, maxTextSize)\n\n const email = em.create(InboxEmail, {\n messageId: parsed.messageId,\n contentHash: parsed.contentHash,\n forwardedByAddress: parsed.from.email,\n forwardedByName: parsed.from.name || null,\n toAddress,\n subject: parsed.subject,\n replyTo: parsed.replyTo,\n inReplyTo: parsed.inReplyTo,\n emailReferences: parsed.references,\n rawText: parsed.rawText,\n rawHtml: parsed.rawHtml,\n cleanedText,\n threadMessages: parsed.threadMessages,\n detectedLanguage: parsed.detectedLanguage,\n receivedAt: new Date(),\n status: 'received' as const,\n isActive: true,\n organizationId: settings.organizationId,\n tenantId: settings.tenantId,\n })\n\n em.persist(email)\n await em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.email.received', {\n emailId: email.id,\n tenantId: settings.tenantId,\n organizationId: settings.organizationId,\n forwardedByAddress: parsed.from.email,\n subject: parsed.subject,\n })\n } catch (eventError) {\n logger.error('Failed to emit email.received event', { err: eventError })\n }\n\n return NextResponse.json({ ok: true })\n}\n\nfunction rateLimitedResponse(retryAfterSeconds?: number): NextResponse {\n return NextResponse.json(\n { error: 'Rate limit exceeded' },\n {\n status: 429,\n headers: retryAfterSeconds ? { 'Retry-After': String(retryAfterSeconds) } : undefined,\n },\n )\n}\n\nfunction extractToAddress(payload: Record<string, unknown>): string | null {\n const to = payload.to\n if (typeof to === 'string') {\n const match = to.match(/<([^>]+)>/)\n return match ? match[1].toLowerCase() : to.trim().toLowerCase()\n }\n if (Array.isArray(to) && to.length > 0) {\n const first = to[0]\n if (typeof first === 'string') {\n const match = first.match(/<([^>]+)>/)\n return match ? match[1].toLowerCase() : first.trim().toLowerCase()\n }\n }\n return null\n}\n\nasync function checkDuplicate(\n em: EntityManager,\n settings: InboxSettings,\n messageId: string | null | undefined,\n contentHash: string | null | undefined,\n): Promise<boolean> {\n const encScope = {\n tenantId: settings.tenantId,\n organizationId: settings.organizationId,\n }\n const whereBase = {\n organizationId: settings.organizationId,\n tenantId: settings.tenantId,\n deletedAt: null,\n }\n\n if (messageId) {\n const byMessageId = await findOneWithDecryption(em, InboxEmail, { ...whereBase, messageId }, undefined, encScope)\n if (byMessageId) return true\n }\n\n if (contentHash) {\n const byHash = await findOneWithDecryption(em, InboxEmail, { ...whereBase, contentHash }, undefined, encScope)\n if (byHash) return true\n }\n\n return false\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Inbound email webhook',\n methods: {\n POST: {\n summary: 'Receive forwarded email from provider webhook',\n description: 'Public endpoint \u2014 validated by provider HMAC signature or Resend/Svix signature. Rate limited per tenant.',\n requestBody: {\n contentType: 'application/json',\n schema: z.object({\n from: z.string().optional(),\n to: z.union([z.string(), z.array(z.string())]).optional(),\n subject: z.string().optional(),\n text: z.string().optional(),\n html: z.string().optional(),\n messageId: z.string().optional(),\n replyTo: z.string().optional(),\n inReplyTo: z.string().optional(),\n references: z.union([z.string(), z.array(z.string())]).optional(),\n }),\n },\n responses: [\n { status: 200, description: 'Email received and queued for processing' },\n { status: 400, description: 'Invalid payload or signature' },\n { status: 413, description: 'Payload too large' },\n { status: 429, description: 'Rate limit exceeded' },\n { status: 503, description: 'Webhook secret not configured' },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createHmac, timingSafeEqual } from 'node:crypto'\nimport { z } from 'zod'\nimport { Webhook } from 'svix'\nimport { Resend } from 'resend'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { InboxSettings, InboxEmail } from '../../data/entities'\nimport { parseInboundEmail } from '../../lib/emailParser'\nimport { checkRateLimit } from '../../lib/rateLimiter'\nimport { emitInboxOpsEvent } from '../../events'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n readBoundedRequestBody,\n resolveWebhookBodyLimitBytes,\n WebhookBodyTooLargeError,\n} from '@open-mercato/shared/lib/webhooks'\n\nconst logger = createLogger('inbox_ops').child({ component: 'webhook' })\n\nexport const metadata = {\n POST: { requireAuth: false },\n}\n\nconst MAX_PAYLOAD_SIZE = resolveWebhookBodyLimitBytes(\n process.env.INBOX_OPS_WEBHOOK_MAX_BODY_BYTES ?? process.env.OM_WEBHOOK_MAX_BODY_BYTES,\n 2 * 1024 * 1024,\n)\nconst REPLAY_WINDOW_MS = 5 * 60 * 1000 // 5 minutes\n\nfunction isTimestampFresh(timestamp: string): boolean {\n if (!timestamp) return false\n const timestampMs = parseInt(timestamp, 10) * 1000\n if (isNaN(timestampMs)) return false\n return Math.abs(Date.now() - timestampMs) <= REPLAY_WINDOW_MS\n}\n\nfunction verifyHmacSignature(\n payload: string,\n signature: string,\n timestamp: string,\n secret: string,\n): boolean {\n if (!signature || !timestamp || !secret) return false\n\n if (!isTimestampFresh(timestamp)) return false\n\n const expected = createHmac('sha256', secret)\n .update(`${timestamp}.${payload}`)\n .digest('hex')\n const received = signature.startsWith('sha256=')\n ? signature.slice('sha256='.length)\n : signature\n\n try {\n if (received.length !== expected.length) return false\n return timingSafeEqual(\n Buffer.from(received, 'hex'),\n Buffer.from(expected, 'hex'),\n )\n } catch {\n return false\n }\n}\n\n// True when the signature verifies against ANY of the candidate secrets. Used\n// to bind the inbound signature to the resolved tenant: once the target inbox\n// declares its own per-tenant secret, the global INBOX_OPS_WEBHOOK_SECRET is no\n// longer accepted for that inbox, so a holder of the global key alone cannot\n// forge mail into a tenant that opted into a dedicated secret.\nfunction verifyHmacAgainstAny(\n payload: string,\n signature: string,\n timestamp: string,\n secrets: string[],\n): boolean {\n for (const secret of secrets) {\n if (secret && verifyHmacSignature(payload, signature, timestamp, secret)) return true\n }\n return false\n}\n\n// Stable, non-reversible identifier for the pre-DB global rate-limit bucket.\n// Keying on the configured signing secret (rather than the attacker-set `to` or\n// the per-request signature) means the bucket is request-invariant for a given\n// secret, so rotating recipients or mutating the body cannot dilute the limit.\nfunction secretFingerprint(secret: string): string {\n return createHmac('sha256', 'inbox_ops:rate_limit:salt').update(secret).digest('hex').slice(0, 32)\n}\n\nfunction verifySvixSignature(\n rawBody: string,\n headers: Record<string, string>,\n secret: string,\n): boolean {\n try {\n const wh = new Webhook(secret)\n wh.verify(rawBody, {\n 'svix-id': headers['svix-id'],\n 'svix-timestamp': headers['svix-timestamp'],\n 'svix-signature': headers['svix-signature'],\n })\n return true\n } catch {\n return false\n }\n}\n\nasync function fetchResendEmail(emailId: string): Promise<{\n from?: string\n to?: string[]\n subject?: string\n text?: string\n html?: string\n messageId?: string\n replyTo?: string\n inReplyTo?: string\n references?: string[]\n}> {\n const apiKey = process.env.RESEND_API_KEY\n if (!apiKey) throw new Error('RESEND_API_KEY is required to fetch Resend inbound emails')\n\n const resend = new Resend(apiKey)\n const { data, error } = await resend.emails.receiving.get(emailId)\n if (error || !data) {\n throw new Error(`Failed to fetch Resend email ${emailId}: ${error?.message || 'unknown error'}`)\n }\n\n return {\n from: data.from,\n to: data.to,\n subject: data.subject,\n text: data.text ?? undefined,\n html: data.html ?? undefined,\n messageId: data.message_id,\n replyTo: data.reply_to?.[0] ?? undefined,\n inReplyTo: data.headers?.['in-reply-to'] ?? undefined,\n references: data.headers?.['references']?.split(/\\s+/).filter(Boolean),\n }\n}\n\ntype VerifiedPayload =\n | { kind: 'custom'; payload: Record<string, unknown>; signature: string; timestamp: string }\n | { kind: 'resend'; emailFields: Awaited<ReturnType<typeof fetchResendEmail>>; to: string[] }\n\nasync function verifyAndParse(req: Request, rawBody: string): Promise<\n | { ok: true; data: VerifiedPayload }\n | { ok: false; response: NextResponse }\n> {\n const customSig = req.headers.get('x-webhook-signature')\n const svixId = req.headers.get('svix-id')\n\n if (customSig) {\n const webhookSecret = process.env.INBOX_OPS_WEBHOOK_SECRET\n if (!webhookSecret) {\n logger.error('INBOX_OPS_WEBHOOK_SECRET not configured')\n return { ok: false, response: NextResponse.json({ error: 'Service unavailable' }, { status: 503 }) }\n }\n const timestamp = req.headers.get('x-webhook-timestamp') || ''\n // Cheap, secret-independent gate first: reject replayed/stale requests\n // before parsing. The HMAC itself is verified later, against the secret\n // bound to the resolved inbox (see the POST handler), so a holder of the\n // global secret cannot inject into a tenant that owns its own secret.\n if (!isTimestampFresh(timestamp)) {\n return { ok: false, response: NextResponse.json({ error: 'Invalid signature' }, { status: 400 }) }\n }\n let payload: Record<string, unknown>\n try {\n payload = JSON.parse(rawBody)\n } catch {\n return { ok: false, response: NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n }\n return { ok: true, data: { kind: 'custom', payload, signature: customSig, timestamp } }\n }\n\n if (svixId) {\n const signingSecret = process.env.RESEND_WEBHOOK_SIGNING_SECRET\n if (!signingSecret) {\n logger.error('RESEND_WEBHOOK_SIGNING_SECRET not configured')\n return { ok: false, response: NextResponse.json({ error: 'Service unavailable' }, { status: 503 }) }\n }\n const headers: Record<string, string> = {\n 'svix-id': svixId,\n 'svix-timestamp': req.headers.get('svix-timestamp') || '',\n 'svix-signature': req.headers.get('svix-signature') || '',\n }\n if (!verifySvixSignature(rawBody, headers, signingSecret)) {\n return { ok: false, response: NextResponse.json({ error: 'Invalid signature' }, { status: 400 }) }\n }\n let envelope: { type?: string; data?: { email_id?: string; from?: string; to?: string[] } }\n try {\n envelope = JSON.parse(rawBody)\n } catch {\n return { ok: false, response: NextResponse.json({ error: 'Invalid JSON' }, { status: 400 }) }\n }\n if (envelope.type !== 'email.received') {\n return { ok: false, response: NextResponse.json({ ok: true }) }\n }\n const emailId = envelope.data?.email_id\n if (!emailId) {\n return { ok: false, response: NextResponse.json({ error: 'Missing email_id in Resend payload' }, { status: 400 }) }\n }\n const emailFields = await fetchResendEmail(emailId)\n const to = emailFields.to || envelope.data?.to || []\n return { ok: true, data: { kind: 'resend', emailFields, to } }\n }\n\n return { ok: false, response: NextResponse.json({ error: 'Missing signature headers' }, { status: 400 }) }\n}\n\nexport async function POST(req: Request) {\n const hasCustomSecret = Boolean(process.env.INBOX_OPS_WEBHOOK_SECRET)\n const hasResendSecret = Boolean(process.env.RESEND_WEBHOOK_SIGNING_SECRET)\n if (!hasCustomSecret && !hasResendSecret) {\n logger.error('Neither INBOX_OPS_WEBHOOK_SECRET nor RESEND_WEBHOOK_SIGNING_SECRET is configured')\n return NextResponse.json({ error: 'Service unavailable' }, { status: 503 })\n }\n\n let rawBody: string\n try {\n rawBody = await readBoundedRequestBody(req, { maxBytes: MAX_PAYLOAD_SIZE })\n } catch (error) {\n if (error instanceof WebhookBodyTooLargeError) {\n return NextResponse.json({ error: 'Payload too large' }, { status: 413 })\n }\n return NextResponse.json({ error: 'Bad request' }, { status: 400 })\n }\n\n const verified = await verifyAndParse(req, rawBody)\n if (!verified.ok) {\n return verified.response\n }\n\n let toAddress: string | null\n let emailInput: Parameters<typeof parseInboundEmail>[0]\n\n if (verified.data.kind === 'custom') {\n const payload = verified.data.payload\n toAddress = extractToAddress(payload)\n emailInput = {\n from: payload.from as string | undefined,\n to: payload.to as string | string[] | undefined,\n subject: payload.subject as string | undefined,\n text: payload.text as string | undefined,\n html: payload.html as string | undefined,\n messageId: payload.messageId as string | undefined,\n replyTo: payload.replyTo as string | undefined,\n inReplyTo: payload.inReplyTo as string | undefined,\n references: payload.references as string | string[] | undefined,\n }\n } else {\n const { emailFields, to } = verified.data\n toAddress = to.length > 0 ? to[0].toLowerCase() : null\n emailInput = {\n from: emailFields.from,\n to: emailFields.to,\n subject: emailFields.subject,\n text: emailFields.text,\n html: emailFields.html,\n messageId: emailFields.messageId,\n replyTo: emailFields.replyTo,\n inReplyTo: emailFields.inReplyTo,\n references: emailFields.references,\n }\n }\n\n if (!toAddress) {\n return NextResponse.json({ error: 'Missing recipient address' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n\n let cache: CacheStrategy | null = null\n try {\n cache = container.resolve('cache') as CacheStrategy\n } catch {\n // Cache not available \u2014 the limiter now fails closed with a process-local\n // bucket instead of waving the request through.\n }\n\n // Pre-DB global bucket keyed on the configured signing secret (custom) or the\n // static Resend path, NOT on the attacker-controllable `to` and NOT on the\n // per-request signature (which mutates with timestamp/body, so it would mint a\n // fresh bucket every request and never throttle). The per-tenant secret lives\n // behind the encrypted-column lookup, so the only secret available here without\n // a DB hit is the global one \u2014 keying on it caps total inbound volume for the\n // custom path and runs BEFORE the lookup so a flood cannot amplify DB work.\n const globalBucketKey = verified.data.kind === 'custom'\n ? `webhook:secret:${secretFingerprint(process.env.INBOX_OPS_WEBHOOK_SECRET || '')}`\n : 'webhook:resend'\n const globalRateCheck = await checkRateLimit(cache, globalBucketKey)\n if (!globalRateCheck.allowed) {\n return rateLimitedResponse(globalRateCheck.retryAfterSeconds)\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n\n // No explicit scope: decryptEntitiesWithFallbackScope reads tenant/org off the\n // resolved row, which is exactly what binds webhook_secret to its own tenant.\n const settings = await findOneWithDecryption(\n em,\n InboxSettings,\n {\n inboxAddress: toAddress.toLowerCase(),\n isActive: true,\n deletedAt: null,\n },\n )\n\n // Final HMAC verification for the custom provider path: bind the signature to\n // the resolved tenant. When the inbox declares its own secret, ONLY that\n // secret is accepted; otherwise the global secret is accepted (back-compat).\n // For an unknown `to` we still require the global secret so the endpoint never\n // behaves as an unauthenticated probe oracle.\n if (verified.data.kind === 'custom') {\n const globalSecret = process.env.INBOX_OPS_WEBHOOK_SECRET || ''\n const inboxSecret = settings?.webhookSecret || ''\n const acceptableSecrets = inboxSecret ? [inboxSecret] : [globalSecret]\n const signatureValid = verifyHmacAgainstAny(\n rawBody,\n verified.data.signature,\n verified.data.timestamp,\n acceptableSecrets,\n )\n if (!signatureValid) {\n return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })\n }\n }\n\n if (!settings) {\n return NextResponse.json({ ok: true })\n }\n\n // Per-tenant bucket keyed on the RESOLVED tenant (not the raw `to`), so it\n // cannot be diluted by alias addresses that map to the same inbox.\n const tenantRateCheck = await checkRateLimit(\n cache,\n `webhook:tenant:${settings.tenantId}`,\n settings.tenantId,\n )\n if (!tenantRateCheck.allowed) {\n return rateLimitedResponse(tenantRateCheck.retryAfterSeconds)\n }\n\n const parsed = parseInboundEmail(emailInput)\n\n const isDuplicate = await checkDuplicate(em, settings, parsed.messageId, parsed.contentHash)\n if (isDuplicate) {\n try {\n await emitInboxOpsEvent('inbox_ops.email.deduplicated', {\n tenantId: settings.tenantId,\n organizationId: settings.organizationId,\n toAddress,\n })\n } catch (eventError) {\n logger.error('Failed to emit deduplicated event', { err: eventError })\n }\n return NextResponse.json({ ok: true })\n }\n\n const maxTextSize = parseInt(process.env.INBOX_OPS_MAX_TEXT_SIZE || '204800', 10)\n const cleanedText = parsed.cleanedText.slice(0, maxTextSize)\n\n const email = em.create(InboxEmail, {\n messageId: parsed.messageId,\n contentHash: parsed.contentHash,\n forwardedByAddress: parsed.from.email,\n forwardedByName: parsed.from.name || null,\n toAddress,\n subject: parsed.subject,\n replyTo: parsed.replyTo,\n inReplyTo: parsed.inReplyTo,\n emailReferences: parsed.references,\n rawText: parsed.rawText,\n rawHtml: parsed.rawHtml,\n cleanedText,\n threadMessages: parsed.threadMessages,\n detectedLanguage: parsed.detectedLanguage,\n receivedAt: new Date(),\n status: 'received' as const,\n isActive: true,\n organizationId: settings.organizationId,\n tenantId: settings.tenantId,\n })\n\n em.persist(email)\n await em.flush()\n\n try {\n await emitInboxOpsEvent('inbox_ops.email.received', {\n emailId: email.id,\n tenantId: settings.tenantId,\n organizationId: settings.organizationId,\n forwardedByAddress: parsed.from.email,\n subject: parsed.subject,\n }, {\n persistent: true,\n tenantId: settings.tenantId,\n organizationId: settings.organizationId,\n })\n } catch (eventError) {\n logger.error('Failed to emit email.received event', { err: eventError })\n }\n\n return NextResponse.json({ ok: true })\n}\n\nfunction rateLimitedResponse(retryAfterSeconds?: number): NextResponse {\n return NextResponse.json(\n { error: 'Rate limit exceeded' },\n {\n status: 429,\n headers: retryAfterSeconds ? { 'Retry-After': String(retryAfterSeconds) } : undefined,\n },\n )\n}\n\nfunction extractToAddress(payload: Record<string, unknown>): string | null {\n const to = payload.to\n if (typeof to === 'string') {\n const match = to.match(/<([^>]+)>/)\n return match ? match[1].toLowerCase() : to.trim().toLowerCase()\n }\n if (Array.isArray(to) && to.length > 0) {\n const first = to[0]\n if (typeof first === 'string') {\n const match = first.match(/<([^>]+)>/)\n return match ? match[1].toLowerCase() : first.trim().toLowerCase()\n }\n }\n return null\n}\n\nasync function checkDuplicate(\n em: EntityManager,\n settings: InboxSettings,\n messageId: string | null | undefined,\n contentHash: string | null | undefined,\n): Promise<boolean> {\n const encScope = {\n tenantId: settings.tenantId,\n organizationId: settings.organizationId,\n }\n const whereBase = {\n organizationId: settings.organizationId,\n tenantId: settings.tenantId,\n deletedAt: null,\n }\n\n if (messageId) {\n const byMessageId = await findOneWithDecryption(em, InboxEmail, { ...whereBase, messageId }, undefined, encScope)\n if (byMessageId) return true\n }\n\n if (contentHash) {\n const byHash = await findOneWithDecryption(em, InboxEmail, { ...whereBase, contentHash }, undefined, encScope)\n if (byHash) return true\n }\n\n return false\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'InboxOps',\n summary: 'Inbound email webhook',\n methods: {\n POST: {\n summary: 'Receive forwarded email from provider webhook',\n description: 'Public endpoint \u2014 validated by provider HMAC signature or Resend/Svix signature. Rate limited per tenant.',\n requestBody: {\n contentType: 'application/json',\n schema: z.object({\n from: z.string().optional(),\n to: z.union([z.string(), z.array(z.string())]).optional(),\n subject: z.string().optional(),\n text: z.string().optional(),\n html: z.string().optional(),\n messageId: z.string().optional(),\n replyTo: z.string().optional(),\n inReplyTo: z.string().optional(),\n references: z.union([z.string(), z.array(z.string())]).optional(),\n }),\n },\n responses: [\n { status: 200, description: 'Email received and queued for processing' },\n { status: 400, description: 'Invalid payload or signature' },\n { status: 413, description: 'Payload too large' },\n { status: 429, description: 'Rate limit exceeded' },\n { status: 503, description: 'Webhook secret not configured' },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,11 +1,85 @@
|
|
|
1
|
-
import { BasicQueryEngine, resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
|
|
2
|
-
import { HybridQueryEngine } from "./lib/engine.js";
|
|
3
1
|
import { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from "@open-mercato/shared/lib/crud/types";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
|
|
3
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
4
|
+
import { BasicQueryEngine } from "@open-mercato/shared/lib/query/engine";
|
|
5
|
+
import { HybridQueryEngine } from "./lib/engine.js";
|
|
6
|
+
import {
|
|
7
|
+
loadQueryIndexRowScope,
|
|
8
|
+
QueryIndexScopeError,
|
|
9
|
+
resolveQueryIndexRecordScope,
|
|
10
|
+
resolveQueryIndexSourceMetadata
|
|
11
|
+
} from "./lib/subscriber-scope.js";
|
|
12
|
+
const logger = createLogger("query_index").child({ component: "crud-bridge" });
|
|
13
|
+
function hasOwn(value, key) {
|
|
14
|
+
return !!value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key);
|
|
15
|
+
}
|
|
16
|
+
function readScopeValue(source, keys) {
|
|
17
|
+
const sourceRecord = source && typeof source === "object" ? source : {};
|
|
18
|
+
const key = keys.find((candidate) => hasOwn(source, candidate));
|
|
19
|
+
if (!key) return { value: void 0, present: false };
|
|
20
|
+
const value = sourceRecord[key];
|
|
21
|
+
if (value === null) return { value: null, present: true };
|
|
22
|
+
if (typeof value === "string" && value.trim().length > 0) return { value, present: true };
|
|
23
|
+
return { value: void 0, present: false };
|
|
24
|
+
}
|
|
25
|
+
async function resolveBridgeRecordScope(em, entityType, recordId, payload, action, options) {
|
|
26
|
+
let organization = readScopeValue(payload, ["organizationId", "orgId"]);
|
|
27
|
+
let tenant = readScopeValue(payload, ["tenantId"]);
|
|
28
|
+
if (organization.present && tenant.present && options?.validateCompletePayload !== true) {
|
|
29
|
+
return {
|
|
30
|
+
organizationId: organization.value ?? null,
|
|
31
|
+
tenantId: tenant.value ?? null,
|
|
32
|
+
sourceValidated: false
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const source = resolveQueryIndexSourceMetadata(em, entityType);
|
|
36
|
+
const sourceScope = await loadQueryIndexRowScope(em, source, recordId);
|
|
37
|
+
if (sourceScope.kind === "global") {
|
|
38
|
+
if (!organization.present) organization = { value: null, present: true };
|
|
39
|
+
if (!tenant.present) tenant = { value: null, present: true };
|
|
40
|
+
} else if (sourceScope.kind === "missing" && action === "upsert") {
|
|
41
|
+
throw new QueryIndexScopeError(
|
|
42
|
+
"Query index upsert event source row scope could not be resolved"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const resolvedScope = resolveQueryIndexRecordScope({
|
|
46
|
+
payloadOrganizationId: organization.value,
|
|
47
|
+
payloadTenantId: tenant.value,
|
|
48
|
+
hasPayloadOrganizationId: organization.present,
|
|
49
|
+
hasPayloadTenantId: tenant.present,
|
|
50
|
+
sourceScope
|
|
51
|
+
});
|
|
52
|
+
return { ...resolvedScope, sourceValidated: true };
|
|
53
|
+
}
|
|
54
|
+
async function recordBridgeError(input) {
|
|
55
|
+
const handler = `event:query_index.crud_bridge.${input.handler}`;
|
|
56
|
+
if (!input.em) {
|
|
57
|
+
logger.error("CRUD bridge event failed before the entity manager was resolved", {
|
|
58
|
+
handler,
|
|
59
|
+
entityType: input.entityType,
|
|
60
|
+
recordId: input.recordId,
|
|
61
|
+
err: input.error
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await recordIndexerError(
|
|
66
|
+
{ em: input.em },
|
|
67
|
+
{
|
|
68
|
+
source: "query_index",
|
|
69
|
+
handler,
|
|
70
|
+
error: input.error,
|
|
71
|
+
entityType: input.entityType,
|
|
72
|
+
recordId: input.recordId,
|
|
73
|
+
payload: input.payload
|
|
74
|
+
}
|
|
75
|
+
).catch((loggingError) => {
|
|
76
|
+
logger.error("Failed to record CRUD bridge error", {
|
|
77
|
+
handler,
|
|
78
|
+
entityType: input.entityType,
|
|
79
|
+
recordId: input.recordId,
|
|
80
|
+
err: loggingError
|
|
81
|
+
});
|
|
82
|
+
});
|
|
9
83
|
}
|
|
10
84
|
function register(container) {
|
|
11
85
|
try {
|
|
@@ -61,23 +135,21 @@ function register(container) {
|
|
|
61
135
|
return;
|
|
62
136
|
}
|
|
63
137
|
const makeUpsertHandler = (entityType) => async (payload, ctx) => {
|
|
138
|
+
let em = null;
|
|
139
|
+
let id = "";
|
|
64
140
|
try {
|
|
65
141
|
if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let tenantId = payload?.tenantId || ctx?.tenantId || null;
|
|
69
|
-
const id = String(payload?.id || payload?.recordId || "");
|
|
142
|
+
em = ctx.resolve("em");
|
|
143
|
+
id = String(payload?.id || payload?.recordId || "");
|
|
70
144
|
if (!id) return;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
}
|
|
145
|
+
const { organizationId: orgId, tenantId, sourceValidated } = await resolveBridgeRecordScope(
|
|
146
|
+
em,
|
|
147
|
+
entityType,
|
|
148
|
+
id,
|
|
149
|
+
payload,
|
|
150
|
+
"upsert"
|
|
151
|
+
);
|
|
152
|
+
let hasCustomFields = null;
|
|
81
153
|
try {
|
|
82
154
|
const db = em.getKysely();
|
|
83
155
|
let cfQuery = db.selectFrom("custom_field_defs").select(["id"]).where("entity_id", "=", entityType).where("is_active", "=", true);
|
|
@@ -97,42 +169,47 @@ function register(container) {
|
|
|
97
169
|
} else {
|
|
98
170
|
cfQuery = cfQuery.where("tenant_id", "is", null);
|
|
99
171
|
}
|
|
100
|
-
|
|
101
|
-
if (!hasCf) return;
|
|
172
|
+
hasCustomFields = !!await cfQuery.executeTakeFirst();
|
|
102
173
|
} catch {
|
|
103
174
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
175
|
+
if (hasCustomFields === false) {
|
|
176
|
+
if (!sourceValidated) {
|
|
177
|
+
await resolveBridgeRecordScope(
|
|
178
|
+
em,
|
|
179
|
+
entityType,
|
|
180
|
+
id,
|
|
181
|
+
payload,
|
|
182
|
+
"upsert",
|
|
183
|
+
{ validateCompletePayload: true }
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
108
187
|
}
|
|
109
|
-
|
|
188
|
+
const bus2 = ctx.resolve("eventBus");
|
|
189
|
+
await bus2.emitEvent("query_index.upsert_one", { entityType, recordId: id, organizationId: orgId, tenantId });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
await recordBridgeError({ em, handler: "upsert", error, entityType, recordId: id, payload });
|
|
110
192
|
}
|
|
111
193
|
};
|
|
112
194
|
const makeDeleteHandler = (entityType) => async (payload, ctx) => {
|
|
195
|
+
let em = null;
|
|
196
|
+
let id = "";
|
|
113
197
|
try {
|
|
114
198
|
if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let tenantId = payload?.tenantId || ctx?.tenantId || null;
|
|
118
|
-
const id = String(payload?.id || payload?.recordId || "");
|
|
199
|
+
em = ctx.resolve("em");
|
|
200
|
+
id = String(payload?.id || payload?.recordId || "");
|
|
119
201
|
if (!id) return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const bus2 = ctx.resolve("eventBus");
|
|
132
|
-
await bus2.emitEvent("query_index.delete_one", { entityType, recordId: id, organizationId: orgId, tenantId });
|
|
133
|
-
} catch {
|
|
134
|
-
}
|
|
135
|
-
} catch {
|
|
202
|
+
const { organizationId: orgId, tenantId } = await resolveBridgeRecordScope(
|
|
203
|
+
em,
|
|
204
|
+
entityType,
|
|
205
|
+
id,
|
|
206
|
+
payload,
|
|
207
|
+
"delete"
|
|
208
|
+
);
|
|
209
|
+
const bus2 = ctx.resolve("eventBus");
|
|
210
|
+
await bus2.emitEvent("query_index.delete_one", { entityType, recordId: id, organizationId: orgId, tenantId });
|
|
211
|
+
} catch (error) {
|
|
212
|
+
await recordBridgeError({ em, handler: "delete", error, entityType, recordId: id, payload });
|
|
136
213
|
}
|
|
137
214
|
};
|
|
138
215
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/query_index/di.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { BasicQueryEngine, resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport { markDeleted } from './lib/indexer'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from '@open-mercato/shared/lib/crud/types'\n\nfunction toEntityTypeFromEvent(event: string): string | null {\n // Expect '<module>.<entity>.<action>'\n const parts = event.split('.')\n if (parts.length !== 3) return null\n const [mod, ent] = parts\n return `${mod}:${ent}`\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n // DataEngine emits the canonical query_index.upsert_one itself. The\n // bridge only covers domain events from write paths that do not own an\n // indexer, otherwise failures and error logs are duplicated.\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n // Optional: only index when custom field definitions exist for this entity (org/global)\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n const hasCf = await cfQuery.executeTakeFirst()\n if (!hasCf) return\n } catch {}\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: 'query_index' })\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from '@open-mercato/shared/lib/crud/types'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { BasicQueryEngine } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport {\n loadQueryIndexRowScope,\n QueryIndexScopeError,\n resolveQueryIndexRecordScope,\n resolveQueryIndexSourceMetadata,\n} from './lib/subscriber-scope'\n\nconst logger = createLogger('query_index').child({ component: 'crud-bridge' })\n\nfunction hasOwn(value: unknown, key: string): boolean {\n return !!value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction readScopeValue(\n source: unknown,\n keys: string[],\n): { value: string | null | undefined; present: boolean } {\n const sourceRecord = source && typeof source === 'object' ? source as Record<string, unknown> : {}\n const key = keys.find((candidate) => hasOwn(source, candidate))\n if (!key) return { value: undefined, present: false }\n const value = sourceRecord[key]\n if (value === null) return { value: null, present: true }\n if (typeof value === 'string' && value.trim().length > 0) return { value, present: true }\n return { value: undefined, present: false }\n}\n\nasync function resolveBridgeRecordScope(\n em: EntityManager,\n entityType: string,\n recordId: string,\n payload: unknown,\n action: 'upsert' | 'delete',\n options?: { validateCompletePayload?: boolean },\n) {\n let organization = readScopeValue(payload, ['organizationId', 'orgId'])\n let tenant = readScopeValue(payload, ['tenantId'])\n if (organization.present && tenant.present && options?.validateCompletePayload !== true) {\n return {\n organizationId: organization.value ?? null,\n tenantId: tenant.value ?? null,\n sourceValidated: false,\n }\n }\n const source = resolveQueryIndexSourceMetadata(em, entityType)\n const sourceScope = await loadQueryIndexRowScope(em, source, recordId)\n\n if (sourceScope.kind === 'global') {\n if (!organization.present) organization = { value: null, present: true }\n if (!tenant.present) tenant = { value: null, present: true }\n } else if (sourceScope.kind === 'missing' && action === 'upsert') {\n throw new QueryIndexScopeError(\n 'Query index upsert event source row scope could not be resolved',\n )\n }\n\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: organization.value,\n payloadTenantId: tenant.value,\n hasPayloadOrganizationId: organization.present,\n hasPayloadTenantId: tenant.present,\n sourceScope,\n })\n return { ...resolvedScope, sourceValidated: true }\n}\n\nasync function recordBridgeError(input: {\n em: EntityManager | null\n handler: 'upsert' | 'delete'\n error: unknown\n entityType: string\n recordId: string\n payload: unknown\n}): Promise<void> {\n const handler = `event:query_index.crud_bridge.${input.handler}`\n if (!input.em) {\n logger.error('CRUD bridge event failed before the entity manager was resolved', {\n handler,\n entityType: input.entityType,\n recordId: input.recordId,\n err: input.error,\n })\n return\n }\n await recordIndexerError(\n { em: input.em },\n {\n source: 'query_index',\n handler,\n error: input.error,\n entityType: input.entityType,\n recordId: input.recordId,\n payload: input.payload,\n },\n ).catch((loggingError) => {\n logger.error('Failed to record CRUD bridge error', {\n handler,\n entityType: input.entityType,\n recordId: input.recordId,\n err: loggingError,\n })\n })\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n let em: EntityManager | null = null\n let id = ''\n try {\n // DataEngine emits the canonical query_index.upsert_one itself. The\n // bridge only covers domain events from write paths that do not own an\n // indexer, otherwise failures and error logs are duplicated.\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n em = ctx.resolve('em') as EntityManager\n id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n const { organizationId: orgId, tenantId, sourceValidated } = await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'upsert',\n )\n // Optional: only index when custom field definitions exist for this entity (org/global)\n let hasCustomFields: boolean | null = null\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n hasCustomFields = !!await cfQuery.executeTakeFirst()\n } catch {}\n if (hasCustomFields === false) {\n if (!sourceValidated) {\n await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'upsert',\n { validateCompletePayload: true },\n )\n }\n return\n }\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch (error) {\n await recordBridgeError({ em, handler: 'upsert', error, entityType, recordId: id, payload })\n }\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n let em: EntityManager | null = null\n let id = ''\n try {\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n em = ctx.resolve('em') as EntityManager\n id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n const { organizationId: orgId, tenantId } = await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'delete',\n )\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch (error) {\n await recordBridgeError({ em, handler: 'delete', error, entityType, recordId: id, payload })\n }\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: 'query_index' })\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["bus"]
|
|
7
7
|
}
|
|
@@ -144,6 +144,11 @@ const documentUpdateSchema = z.object({
|
|
|
144
144
|
customerReference: z.string().nullable().optional(),
|
|
145
145
|
externalReference: z.string().nullable().optional(),
|
|
146
146
|
comment: z.string().nullable().optional(),
|
|
147
|
+
// Same names and limits as the create schema (data/validators.ts), so a
|
|
148
|
+
// caller can send one payload shape to create and update. Nullable here
|
|
149
|
+
// because an update — unlike a create — has an existing value to clear.
|
|
150
|
+
comments: z.string().trim().max(4e3).nullable().optional(),
|
|
151
|
+
internalNotes: z.string().trim().max(4e3).nullable().optional(),
|
|
147
152
|
orderNumber: z.string().trim().min(1).max(191).optional(),
|
|
148
153
|
quoteNumber: z.string().trim().min(1).max(191).optional(),
|
|
149
154
|
currencyCode: currencyCodeSchema.optional(),
|
|
@@ -165,7 +170,7 @@ const documentUpdateSchema = z.object({
|
|
|
165
170
|
customFields: z.record(z.string(), z.unknown()).optional(),
|
|
166
171
|
customFieldSetId: z.string().uuid().nullable().optional()
|
|
167
172
|
}).refine(
|
|
168
|
-
(input) => typeof input.currencyCode === "string" || input.placedAt !== void 0 || input.expectedDeliveryAt !== void 0 || input.channelId !== void 0 || input.statusEntryId !== void 0 || input.shippingAddressId !== void 0 || input.billingAddressId !== void 0 || input.customerEntityId !== void 0 || input.customerContactId !== void 0 || input.customerSnapshot !== void 0 || input.metadata !== void 0 || input.customerReference !== void 0 || input.externalReference !== void 0 || input.comment !== void 0 || input.orderNumber !== void 0 || input.quoteNumber !== void 0 || input.shippingAddressSnapshot !== void 0 || input.billingAddressSnapshot !== void 0 || input.shippingMethodId !== void 0 || input.shippingMethodCode !== void 0 || input.shippingMethodSnapshot !== void 0 || input.paymentMethodId !== void 0 || input.paymentMethodCode !== void 0 || input.paymentMethodSnapshot !== void 0 || input.tags !== void 0 || input.customFields !== void 0 || input.customFieldSetId !== void 0,
|
|
173
|
+
(input) => typeof input.currencyCode === "string" || input.placedAt !== void 0 || input.expectedDeliveryAt !== void 0 || input.channelId !== void 0 || input.statusEntryId !== void 0 || input.shippingAddressId !== void 0 || input.billingAddressId !== void 0 || input.customerEntityId !== void 0 || input.customerContactId !== void 0 || input.customerSnapshot !== void 0 || input.metadata !== void 0 || input.customerReference !== void 0 || input.externalReference !== void 0 || input.comment !== void 0 || input.comments !== void 0 || input.internalNotes !== void 0 || input.orderNumber !== void 0 || input.quoteNumber !== void 0 || input.shippingAddressSnapshot !== void 0 || input.billingAddressSnapshot !== void 0 || input.shippingMethodId !== void 0 || input.shippingMethodCode !== void 0 || input.shippingMethodSnapshot !== void 0 || input.paymentMethodId !== void 0 || input.paymentMethodCode !== void 0 || input.paymentMethodSnapshot !== void 0 || input.tags !== void 0 || input.customFields !== void 0 || input.customFieldSetId !== void 0,
|
|
169
174
|
{ message: "update_payload_empty" }
|
|
170
175
|
);
|
|
171
176
|
function cloneJson(value) {
|
|
@@ -518,6 +523,22 @@ async function applyDocumentUpdate({
|
|
|
518
523
|
const normalized = typeof input.comment === "string" ? input.comment.trim() : "";
|
|
519
524
|
entity.comments = normalized.length ? normalized : null;
|
|
520
525
|
}
|
|
526
|
+
if (input.comments !== void 0) {
|
|
527
|
+
const normalized = typeof input.comments === "string" ? input.comments.trim() : "";
|
|
528
|
+
entity.comments = normalized.length ? normalized : null;
|
|
529
|
+
}
|
|
530
|
+
if (kind === "quote" && input.internalNotes !== void 0) {
|
|
531
|
+
throw new CrudHttpError(400, {
|
|
532
|
+
error: translate(
|
|
533
|
+
"sales.quotes.internal_notes_unsupported",
|
|
534
|
+
"Internal notes are not available on quotes."
|
|
535
|
+
)
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
if (kind === "order" && input.internalNotes !== void 0) {
|
|
539
|
+
const normalized = typeof input.internalNotes === "string" ? input.internalNotes.trim() : "";
|
|
540
|
+
entity.internalNotes = normalized.length ? normalized : null;
|
|
541
|
+
}
|
|
521
542
|
if (typeof input.currencyCode === "string") {
|
|
522
543
|
entity.currencyCode = input.currencyCode;
|
|
523
544
|
}
|
|
@@ -1278,8 +1299,8 @@ async function loadCreditMemoSnapshot(em, id) {
|
|
|
1278
1299
|
organizationId: creditMemo.organizationId,
|
|
1279
1300
|
tenantId: creditMemo.tenantId,
|
|
1280
1301
|
creditMemoNumber: creditMemo.creditMemoNumber,
|
|
1281
|
-
orderId: creditMemo.
|
|
1282
|
-
invoiceId: creditMemo.
|
|
1302
|
+
orderId: creditMemo.order?.id ?? null,
|
|
1303
|
+
invoiceId: creditMemo.invoice?.id ?? null,
|
|
1283
1304
|
statusEntryId: creditMemo.statusEntryId ?? null,
|
|
1284
1305
|
status: creditMemo.status ?? null,
|
|
1285
1306
|
reason: creditMemo.reason ?? null,
|
|
@@ -2436,9 +2457,11 @@ function buildDocumentUpdateChangeKeys(kind, input) {
|
|
|
2436
2457
|
if (input.customerSnapshot !== void 0) keys.add("customerSnapshot");
|
|
2437
2458
|
if (input.metadata !== void 0) keys.add("metadata");
|
|
2438
2459
|
if (input.comment !== void 0) keys.add("comments");
|
|
2460
|
+
if (input.comments !== void 0) keys.add("comments");
|
|
2439
2461
|
if (input.currencyCode !== void 0) keys.add("currencyCode");
|
|
2440
2462
|
if (input.channelId !== void 0) keys.add("channelId");
|
|
2441
2463
|
if (kind === "order") {
|
|
2464
|
+
if (input.internalNotes !== void 0) keys.add("internalNotes");
|
|
2442
2465
|
if (input.placedAt !== void 0) keys.add("placedAt");
|
|
2443
2466
|
if (input.expectedDeliveryAt !== void 0) keys.add("expectedDeliveryAt");
|
|
2444
2467
|
}
|
|
@@ -3646,10 +3669,6 @@ const updateQuoteCommand = {
|
|
|
3646
3669
|
ensureQuoteScope(ctx, quote.organizationId, quote.tenantId);
|
|
3647
3670
|
await enforceSalesDocumentOptimisticLock(ctx, quote, SALES_RESOURCE_KIND_QUOTE);
|
|
3648
3671
|
const shouldInvalidateSentToken = (quote.status ?? null) === "sent";
|
|
3649
|
-
if (shouldInvalidateSentToken) {
|
|
3650
|
-
quote.acceptanceToken = null;
|
|
3651
|
-
quote.sentAt = null;
|
|
3652
|
-
}
|
|
3653
3672
|
const shouldRecalculateTotals = parsed.shippingMethodId !== void 0 || parsed.shippingMethodSnapshot !== void 0 || parsed.shippingMethodCode !== void 0 || parsed.paymentMethodId !== void 0 || parsed.paymentMethodSnapshot !== void 0 || parsed.paymentMethodCode !== void 0 || parsed.currencyCode !== void 0;
|
|
3654
3673
|
await withAtomicFlush(
|
|
3655
3674
|
em,
|
|
@@ -3662,6 +3681,8 @@ const updateQuoteCommand = {
|
|
|
3662
3681
|
em
|
|
3663
3682
|
});
|
|
3664
3683
|
if (shouldInvalidateSentToken) {
|
|
3684
|
+
quote.acceptanceToken = null;
|
|
3685
|
+
quote.sentAt = null;
|
|
3665
3686
|
quote.status = "draft";
|
|
3666
3687
|
quote.statusEntryId = await resolveStatusEntryIdByValue(em, {
|
|
3667
3688
|
tenantId: quote.tenantId,
|
|
@@ -7296,8 +7317,8 @@ const createCreditMemoCommand = {
|
|
|
7296
7317
|
organizationId: parsed.organizationId,
|
|
7297
7318
|
tenantId: parsed.tenantId,
|
|
7298
7319
|
creditMemoNumber: ensuredCreditMemoNumber,
|
|
7299
|
-
|
|
7300
|
-
|
|
7320
|
+
order: parsed.orderId ? em.getReference(SalesOrder, parsed.orderId) : null,
|
|
7321
|
+
invoice: parsed.invoiceId ? em.getReference(SalesInvoice, parsed.invoiceId) : null,
|
|
7301
7322
|
statusEntryId: parsed.statusEntryId ?? null,
|
|
7302
7323
|
status,
|
|
7303
7324
|
reason: parsed.reason ?? null,
|
|
@@ -7528,8 +7549,8 @@ const updateCreditMemoCommand = {
|
|
|
7528
7549
|
ensureOrganizationScope(ctx, creditMemo.organizationId);
|
|
7529
7550
|
ensureTenantScope(ctx, creditMemo.tenantId);
|
|
7530
7551
|
creditMemo.creditMemoNumber = before.creditMemo.creditMemoNumber;
|
|
7531
|
-
creditMemo.
|
|
7532
|
-
creditMemo.
|
|
7552
|
+
creditMemo.order = before.creditMemo.orderId ? em.getReference(SalesOrder, before.creditMemo.orderId) : null;
|
|
7553
|
+
creditMemo.invoice = before.creditMemo.invoiceId ? em.getReference(SalesInvoice, before.creditMemo.invoiceId) : null;
|
|
7533
7554
|
creditMemo.statusEntryId = before.creditMemo.statusEntryId;
|
|
7534
7555
|
creditMemo.status = before.creditMemo.status;
|
|
7535
7556
|
creditMemo.reason = before.creditMemo.reason;
|
|
@@ -7625,8 +7646,8 @@ const deleteCreditMemoCommand = {
|
|
|
7625
7646
|
organizationId: before.creditMemo.organizationId,
|
|
7626
7647
|
tenantId: before.creditMemo.tenantId,
|
|
7627
7648
|
creditMemoNumber: before.creditMemo.creditMemoNumber,
|
|
7628
|
-
|
|
7629
|
-
|
|
7649
|
+
order: before.creditMemo.orderId ? em.getReference(SalesOrder, before.creditMemo.orderId) : null,
|
|
7650
|
+
invoice: before.creditMemo.invoiceId ? em.getReference(SalesInvoice, before.creditMemo.invoiceId) : null,
|
|
7630
7651
|
statusEntryId: before.creditMemo.statusEntryId,
|
|
7631
7652
|
status: before.creditMemo.status,
|
|
7632
7653
|
reason: before.creditMemo.reason,
|