@open-mercato/core 0.6.8-develop.6924.1.a8d208fcdc → 0.6.8-develop.6930.1.1e5976efc3
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/eudr_due_diligence_statement/index.js +47 -0
- package/dist/generated/entities/eudr_due_diligence_statement/index.js.map +7 -0
- package/dist/generated/entities/eudr_evidence_submission/index.js +51 -0
- package/dist/generated/entities/eudr_evidence_submission/index.js.map +7 -0
- package/dist/generated/entities/eudr_mitigation_action/index.js +31 -0
- package/dist/generated/entities/eudr_mitigation_action/index.js.map +7 -0
- package/dist/generated/entities/eudr_plot/index.js +39 -0
- package/dist/generated/entities/eudr_plot/index.js.map +7 -0
- package/dist/generated/entities/eudr_product_mapping/index.js +31 -0
- package/dist/generated/entities/eudr_product_mapping/index.js.map +7 -0
- package/dist/generated/entities/eudr_risk_assessment/index.js +35 -0
- package/dist/generated/entities/eudr_risk_assessment/index.js.map +7 -0
- package/dist/generated/entities/role/index.js +2 -0
- package/dist/generated/entities/role/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +9 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +121 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/api.js +4 -0
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/modules/attachments/fields/attachment.js +5 -1
- package/dist/modules/attachments/fields/attachment.js.map +2 -2
- package/dist/modules/attachments/lib/crud.js +3 -1
- package/dist/modules/attachments/lib/crud.js.map +2 -2
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
- package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
- package/dist/modules/audit_logs/api/audit-logs/actions/undo/route.js +5 -0
- package/dist/modules/audit_logs/api/audit-logs/actions/undo/route.js.map +2 -2
- package/dist/modules/audit_logs/backend/audit-logs/page.js +4 -4
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/audit_logs/components/AuditLogsActions.js +9 -2
- package/dist/modules/audit_logs/components/AuditLogsActions.js.map +2 -2
- package/dist/modules/auth/api/login.js +5 -2
- package/dist/modules/auth/api/login.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +8 -5
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +15 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +7 -1
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/auth/commands/users.js +175 -76
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/entities.js +4 -0
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/lib/sessionIntegrity.js +1 -1
- package/dist/modules/auth/lib/sessionIntegrity.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -1
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/migrations/Migration20260728134212_auth.js +14 -0
- package/dist/modules/auth/migrations/Migration20260728134212_auth.js.map +7 -0
- package/dist/modules/eudr/acl.js +43 -0
- package/dist/modules/eudr/acl.js.map +7 -0
- package/dist/modules/eudr/ai-agents.js +205 -0
- package/dist/modules/eudr/ai-agents.js.map +7 -0
- package/dist/modules/eudr/ai-tools/compliance-pack.js +495 -0
- package/dist/modules/eudr/ai-tools/compliance-pack.js.map +7 -0
- package/dist/modules/eudr/ai-tools/types.js +10 -0
- package/dist/modules/eudr/ai-tools/types.js.map +7 -0
- package/dist/modules/eudr/ai-tools.js +10 -0
- package/dist/modules/eudr/ai-tools.js.map +7 -0
- package/dist/modules/eudr/api/dashboard/widgets/compliance-overview/route.js +386 -0
- package/dist/modules/eudr/api/dashboard/widgets/compliance-overview/route.js.map +7 -0
- package/dist/modules/eudr/api/evidence-submissions/route.js +318 -0
- package/dist/modules/eudr/api/evidence-submissions/route.js.map +7 -0
- package/dist/modules/eudr/api/mitigation-actions/route.js +249 -0
- package/dist/modules/eudr/api/mitigation-actions/route.js.map +7 -0
- package/dist/modules/eudr/api/openapi.js +27 -0
- package/dist/modules/eudr/api/openapi.js.map +7 -0
- package/dist/modules/eudr/api/plots/import/route.js +294 -0
- package/dist/modules/eudr/api/plots/import/route.js.map +7 -0
- package/dist/modules/eudr/api/plots/route.js +279 -0
- package/dist/modules/eudr/api/plots/route.js.map +7 -0
- package/dist/modules/eudr/api/product-mappings/route.js +257 -0
- package/dist/modules/eudr/api/product-mappings/route.js.map +7 -0
- package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js +298 -0
- package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +7 -0
- package/dist/modules/eudr/api/product-mappings/suggestions/route.js +132 -0
- package/dist/modules/eudr/api/product-mappings/suggestions/route.js.map +7 -0
- package/dist/modules/eudr/api/reports/annual/route.js +266 -0
- package/dist/modules/eudr/api/reports/annual/route.js.map +7 -0
- package/dist/modules/eudr/api/risk-assessments/route.js +300 -0
- package/dist/modules/eudr/api/risk-assessments/route.js.map +7 -0
- package/dist/modules/eudr/api/statements/[id]/export/route.js +631 -0
- package/dist/modules/eudr/api/statements/[id]/export/route.js.map +7 -0
- package/dist/modules/eudr/api/statements/[id]/readiness/route.js +111 -0
- package/dist/modules/eudr/api/statements/[id]/readiness/route.js.map +7 -0
- package/dist/modules/eudr/api/statements/route.js +314 -0
- package/dist/modules/eudr/api/statements/route.js.map +7 -0
- package/dist/modules/eudr/api/suppliers/compliance/route.js +173 -0
- package/dist/modules/eudr/api/suppliers/compliance/route.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.js +468 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.js +351 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js +17 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js +313 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/page.js +360 -0
- package/dist/modules/eudr/backend/eudr/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.js +346 -0
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/plots/create/page.js +253 -0
- package/dist/modules/eudr/backend/eudr/plots/create/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js +17 -0
- package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/plots/page.js +386 -0
- package/dist/modules/eudr/backend/eudr/plots/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/plots/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/plots/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.js +253 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/create/page.js +174 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/create/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js +17 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/page.js +306 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.js +335 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.js +220 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js +17 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.js +324 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.js +648 -0
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/statements/create/page.js +432 -0
- package/dist/modules/eudr/backend/eudr/statements/create/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js +17 -0
- package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/statements/page.js +309 -0
- package/dist/modules/eudr/backend/eudr/statements/page.js.map +7 -0
- package/dist/modules/eudr/backend/eudr/statements/page.meta.js +16 -0
- package/dist/modules/eudr/backend/eudr/statements/page.meta.js.map +7 -0
- package/dist/modules/eudr/ce.js +56 -0
- package/dist/modules/eudr/ce.js.map +7 -0
- package/dist/modules/eudr/commands/evidence-submissions.js +571 -0
- package/dist/modules/eudr/commands/evidence-submissions.js.map +7 -0
- package/dist/modules/eudr/commands/index.js +7 -0
- package/dist/modules/eudr/commands/index.js.map +7 -0
- package/dist/modules/eudr/commands/lifecycle-events.js +19 -0
- package/dist/modules/eudr/commands/lifecycle-events.js.map +7 -0
- package/dist/modules/eudr/commands/mitigation-actions.js +488 -0
- package/dist/modules/eudr/commands/mitigation-actions.js.map +7 -0
- package/dist/modules/eudr/commands/plots.js +517 -0
- package/dist/modules/eudr/commands/plots.js.map +7 -0
- package/dist/modules/eudr/commands/product-mappings.js +468 -0
- package/dist/modules/eudr/commands/product-mappings.js.map +7 -0
- package/dist/modules/eudr/commands/risk-assessments.js +599 -0
- package/dist/modules/eudr/commands/risk-assessments.js.map +7 -0
- package/dist/modules/eudr/commands/statements.js +747 -0
- package/dist/modules/eudr/commands/statements.js.map +7 -0
- package/dist/modules/eudr/components/CountrySelectField.js +64 -0
- package/dist/modules/eudr/components/CountrySelectField.js.map +7 -0
- package/dist/modules/eudr/components/GeometryInput.js +133 -0
- package/dist/modules/eudr/components/GeometryInput.js.map +7 -0
- package/dist/modules/eudr/components/MappingSuggestionsDialog.js +307 -0
- package/dist/modules/eudr/components/MappingSuggestionsDialog.js.map +7 -0
- package/dist/modules/eudr/components/MitigationActionsSection.js +345 -0
- package/dist/modules/eudr/components/MitigationActionsSection.js.map +7 -0
- package/dist/modules/eudr/components/OrderSelectField.js +88 -0
- package/dist/modules/eudr/components/OrderSelectField.js.map +7 -0
- package/dist/modules/eudr/components/PlotImportDialog.js +229 -0
- package/dist/modules/eudr/components/PlotImportDialog.js.map +7 -0
- package/dist/modules/eudr/components/PlotMapPreview.js +35 -0
- package/dist/modules/eudr/components/PlotMapPreview.js.map +7 -0
- package/dist/modules/eudr/components/PlotMapPreviewImpl.js +97 -0
- package/dist/modules/eudr/components/PlotMapPreviewImpl.js.map +7 -0
- package/dist/modules/eudr/components/PlotMultiSelectField.js +198 -0
- package/dist/modules/eudr/components/PlotMultiSelectField.js.map +7 -0
- package/dist/modules/eudr/components/RiskCriteriaField.js +128 -0
- package/dist/modules/eudr/components/RiskCriteriaField.js.map +7 -0
- package/dist/modules/eudr/components/StatementLifecycleBar.js +274 -0
- package/dist/modules/eudr/components/StatementLifecycleBar.js.map +7 -0
- package/dist/modules/eudr/components/StatementReadinessChecklist.js +56 -0
- package/dist/modules/eudr/components/StatementReadinessChecklist.js.map +7 -0
- package/dist/modules/eudr/components/StatementRiskSection.js +179 -0
- package/dist/modules/eudr/components/StatementRiskSection.js.map +7 -0
- package/dist/modules/eudr/components/crudErrorI18n.js +31 -0
- package/dist/modules/eudr/components/crudErrorI18n.js.map +7 -0
- package/dist/modules/eudr/components/formConfig.js +593 -0
- package/dist/modules/eudr/components/formConfig.js.map +7 -0
- package/dist/modules/eudr/components/plotForm.js +68 -0
- package/dist/modules/eudr/components/plotForm.js.map +7 -0
- package/dist/modules/eudr/data/enrichers.js +62 -0
- package/dist/modules/eudr/data/enrichers.js.map +7 -0
- package/dist/modules/eudr/data/entities.js +437 -0
- package/dist/modules/eudr/data/entities.js.map +7 -0
- package/dist/modules/eudr/data/validators.js +271 -0
- package/dist/modules/eudr/data/validators.js.map +7 -0
- package/dist/modules/eudr/encryption.js +33 -0
- package/dist/modules/eudr/encryption.js.map +7 -0
- package/dist/modules/eudr/events.js +38 -0
- package/dist/modules/eudr/events.js.map +7 -0
- package/dist/modules/eudr/index.js +15 -0
- package/dist/modules/eudr/index.js.map +7 -0
- package/dist/modules/eudr/lib/annual-report.js +104 -0
- package/dist/modules/eudr/lib/annual-report.js.map +7 -0
- package/dist/modules/eudr/lib/completeness.js +64 -0
- package/dist/modules/eudr/lib/completeness.js.map +7 -0
- package/dist/modules/eudr/lib/detail-read-scope.js +16 -0
- package/dist/modules/eudr/lib/detail-read-scope.js.map +7 -0
- package/dist/modules/eudr/lib/geometry.js +142 -0
- package/dist/modules/eudr/lib/geometry.js.map +7 -0
- package/dist/modules/eudr/lib/notifications.js +56 -0
- package/dist/modules/eudr/lib/notifications.js.map +7 -0
- package/dist/modules/eudr/lib/plural.js +26 -0
- package/dist/modules/eudr/lib/plural.js.map +7 -0
- package/dist/modules/eudr/lib/reference-data.js +240 -0
- package/dist/modules/eudr/lib/reference-data.js.map +7 -0
- package/dist/modules/eudr/lib/species.js +10 -0
- package/dist/modules/eudr/lib/species.js.map +7 -0
- package/dist/modules/eudr/lib/statement-lifecycle.js +84 -0
- package/dist/modules/eudr/lib/statement-lifecycle.js.map +7 -0
- package/dist/modules/eudr/lib/statement-seeding.js +49 -0
- package/dist/modules/eudr/lib/statement-seeding.js.map +7 -0
- package/dist/modules/eudr/migrations/Migration20260706102002_eudr.js +15 -0
- package/dist/modules/eudr/migrations/Migration20260706102002_eudr.js.map +7 -0
- package/dist/modules/eudr/migrations/Migration20260706152612_eudr.js +24 -0
- package/dist/modules/eudr/migrations/Migration20260706152612_eudr.js.map +7 -0
- package/dist/modules/eudr/migrations/Migration20260722100000_eudr.js +15 -0
- package/dist/modules/eudr/migrations/Migration20260722100000_eudr.js.map +7 -0
- package/dist/modules/eudr/notifications.js +58 -0
- package/dist/modules/eudr/notifications.js.map +7 -0
- package/dist/modules/eudr/search.js +186 -0
- package/dist/modules/eudr/search.js.map +7 -0
- package/dist/modules/eudr/setup.js +12 -0
- package/dist/modules/eudr/setup.js.map +7 -0
- package/dist/modules/eudr/subscribers/notify-mitigation-completed.js +34 -0
- package/dist/modules/eudr/subscribers/notify-mitigation-completed.js.map +7 -0
- package/dist/modules/eudr/subscribers/notify-risk-concluded.js +35 -0
- package/dist/modules/eudr/subscribers/notify-risk-concluded.js.map +7 -0
- package/dist/modules/eudr/subscribers/notify-statement-reference-issued.js +37 -0
- package/dist/modules/eudr/subscribers/notify-statement-reference-issued.js.map +7 -0
- package/dist/modules/eudr/subscribers/notify-statement-submitted.js +34 -0
- package/dist/modules/eudr/subscribers/notify-statement-submitted.js.map +7 -0
- package/dist/modules/eudr/subscribers/notify-statement-withdrawn.js +34 -0
- package/dist/modules/eudr/subscribers/notify-statement-withdrawn.js.map +7 -0
- package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-created.js +124 -0
- package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-created.js.map +7 -0
- package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.js +22 -0
- package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.js.map +7 -0
- package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.client.js +203 -0
- package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.client.js.map +7 -0
- package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.js +23 -0
- package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.js.map +7 -0
- package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.client.js +19 -0
- package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.client.js.map +7 -0
- package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.js +31 -0
- package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.js.map +7 -0
- package/dist/modules/eudr/widgets/injection/order-compliance/widget.client.js +100 -0
- package/dist/modules/eudr/widgets/injection/order-compliance/widget.client.js.map +7 -0
- package/dist/modules/eudr/widgets/injection/order-compliance/widget.js +17 -0
- package/dist/modules/eudr/widgets/injection/order-compliance/widget.js.map +7 -0
- package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.client.js +116 -0
- package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.client.js.map +7 -0
- package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.js +17 -0
- package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.js.map +7 -0
- package/dist/modules/eudr/widgets/injection-table.js +32 -0
- package/dist/modules/eudr/widgets/injection-table.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +67 -3
- package/dist/modules/notifications/lib/notificationService.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +121 -48
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +17 -0
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +32 -18
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/data/entities.js +1 -0
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.js +17 -0
- package/dist/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.js.map +7 -0
- package/generated/entities/eudr_due_diligence_statement/index.ts +22 -0
- package/generated/entities/eudr_evidence_submission/index.ts +24 -0
- package/generated/entities/eudr_mitigation_action/index.ts +14 -0
- package/generated/entities/eudr_plot/index.ts +18 -0
- package/generated/entities/eudr_product_mapping/index.ts +14 -0
- package/generated/entities/eudr_risk_assessment/index.ts +16 -0
- package/generated/entities/role/index.ts +1 -0
- package/generated/entities.ids.generated.ts +9 -0
- package/generated/entity-fields-registry.ts +121 -0
- package/package.json +7 -7
- package/src/helpers/integration/api.ts +13 -0
- package/src/modules/attachments/fields/attachment.tsx +5 -0
- package/src/modules/attachments/lib/crud.ts +2 -0
- package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +7 -0
- package/src/modules/audit_logs/api/audit-logs/actions/undo/route.ts +9 -0
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +4 -4
- package/src/modules/audit_logs/components/AuditLogsActions.tsx +20 -4
- package/src/modules/auth/api/login.ts +9 -2
- package/src/modules/auth/api/users/route.ts +8 -4
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -1
- package/src/modules/auth/backend/users/page.tsx +11 -0
- package/src/modules/auth/commands/users.ts +251 -85
- package/src/modules/auth/data/entities.ts +3 -0
- package/src/modules/auth/i18n/de.json +6 -0
- package/src/modules/auth/i18n/en.json +6 -0
- package/src/modules/auth/i18n/es.json +6 -0
- package/src/modules/auth/i18n/ko.json +6 -0
- package/src/modules/auth/i18n/pl.json +6 -0
- package/src/modules/auth/lib/sessionIntegrity.ts +1 -1
- package/src/modules/auth/lib/setup-app.ts +1 -1
- package/src/modules/auth/migrations/.snapshot-open-mercato.json +16 -0
- package/src/modules/auth/migrations/Migration20260728134212_auth.ts +14 -0
- package/src/modules/eudr/acl.ts +39 -0
- package/src/modules/eudr/ai-agents.ts +258 -0
- package/src/modules/eudr/ai-tools/compliance-pack.ts +618 -0
- package/src/modules/eudr/ai-tools/types.ts +44 -0
- package/src/modules/eudr/ai-tools.ts +15 -0
- package/src/modules/eudr/api/dashboard/widgets/compliance-overview/route.ts +481 -0
- package/src/modules/eudr/api/evidence-submissions/route.ts +335 -0
- package/src/modules/eudr/api/mitigation-actions/route.ts +263 -0
- package/src/modules/eudr/api/openapi.ts +28 -0
- package/src/modules/eudr/api/plots/import/route.ts +369 -0
- package/src/modules/eudr/api/plots/route.ts +296 -0
- package/src/modules/eudr/api/product-mappings/route.ts +269 -0
- package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +382 -0
- package/src/modules/eudr/api/product-mappings/suggestions/route.ts +169 -0
- package/src/modules/eudr/api/reports/annual/route.ts +457 -0
- package/src/modules/eudr/api/risk-assessments/route.ts +323 -0
- package/src/modules/eudr/api/statements/[id]/export/route.ts +713 -0
- package/src/modules/eudr/api/statements/[id]/readiness/route.ts +129 -0
- package/src/modules/eudr/api/statements/route.ts +329 -0
- package/src/modules/eudr/api/suppliers/compliance/route.ts +241 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.tsx +545 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.ts +13 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.tsx +383 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/evidence-submissions/page.tsx +379 -0
- package/src/modules/eudr/backend/eudr/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/page.tsx +477 -0
- package/src/modules/eudr/backend/eudr/plots/[id]/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/plots/[id]/page.tsx +404 -0
- package/src/modules/eudr/backend/eudr/plots/create/page.meta.ts +13 -0
- package/src/modules/eudr/backend/eudr/plots/create/page.tsx +267 -0
- package/src/modules/eudr/backend/eudr/plots/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/plots/page.tsx +442 -0
- package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.tsx +311 -0
- package/src/modules/eudr/backend/eudr/product-mappings/create/page.meta.ts +13 -0
- package/src/modules/eudr/backend/eudr/product-mappings/create/page.tsx +190 -0
- package/src/modules/eudr/backend/eudr/product-mappings/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/product-mappings/page.tsx +344 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.tsx +421 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/create/page.meta.ts +13 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/create/page.tsx +247 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/risk-assessments/page.tsx +383 -0
- package/src/modules/eudr/backend/eudr/statements/[id]/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/statements/[id]/page.tsx +777 -0
- package/src/modules/eudr/backend/eudr/statements/create/page.meta.ts +13 -0
- package/src/modules/eudr/backend/eudr/statements/create/page.tsx +511 -0
- package/src/modules/eudr/backend/eudr/statements/page.meta.ts +12 -0
- package/src/modules/eudr/backend/eudr/statements/page.tsx +372 -0
- package/src/modules/eudr/ce.ts +52 -0
- package/src/modules/eudr/commands/evidence-submissions.ts +699 -0
- package/src/modules/eudr/commands/index.ts +6 -0
- package/src/modules/eudr/commands/lifecycle-events.ts +28 -0
- package/src/modules/eudr/commands/mitigation-actions.ts +574 -0
- package/src/modules/eudr/commands/plots.ts +627 -0
- package/src/modules/eudr/commands/product-mappings.ts +556 -0
- package/src/modules/eudr/commands/risk-assessments.ts +728 -0
- package/src/modules/eudr/commands/statements.ts +933 -0
- package/src/modules/eudr/components/CountrySelectField.tsx +77 -0
- package/src/modules/eudr/components/GeometryInput.tsx +170 -0
- package/src/modules/eudr/components/MappingSuggestionsDialog.tsx +385 -0
- package/src/modules/eudr/components/MitigationActionsSection.tsx +417 -0
- package/src/modules/eudr/components/OrderSelectField.tsx +116 -0
- package/src/modules/eudr/components/PlotImportDialog.tsx +277 -0
- package/src/modules/eudr/components/PlotMapPreview.tsx +46 -0
- package/src/modules/eudr/components/PlotMapPreviewImpl.tsx +111 -0
- package/src/modules/eudr/components/PlotMultiSelectField.tsx +238 -0
- package/src/modules/eudr/components/RiskCriteriaField.tsx +173 -0
- package/src/modules/eudr/components/StatementLifecycleBar.tsx +363 -0
- package/src/modules/eudr/components/StatementReadinessChecklist.tsx +82 -0
- package/src/modules/eudr/components/StatementRiskSection.tsx +242 -0
- package/src/modules/eudr/components/crudErrorI18n.ts +36 -0
- package/src/modules/eudr/components/formConfig.tsx +717 -0
- package/src/modules/eudr/components/plotForm.ts +75 -0
- package/src/modules/eudr/data/enrichers.ts +82 -0
- package/src/modules/eudr/data/entities.ts +369 -0
- package/src/modules/eudr/data/validators.ts +357 -0
- package/src/modules/eudr/encryption.ts +31 -0
- package/src/modules/eudr/events.ts +38 -0
- package/src/modules/eudr/i18n/de.json +747 -0
- package/src/modules/eudr/i18n/en.json +747 -0
- package/src/modules/eudr/i18n/es.json +747 -0
- package/src/modules/eudr/i18n/ko.json +747 -0
- package/src/modules/eudr/i18n/pl.json +747 -0
- package/src/modules/eudr/index.ts +13 -0
- package/src/modules/eudr/lib/annual-report.ts +120 -0
- package/src/modules/eudr/lib/completeness.ts +90 -0
- package/src/modules/eudr/lib/detail-read-scope.ts +44 -0
- package/src/modules/eudr/lib/geometry.ts +191 -0
- package/src/modules/eudr/lib/notifications.ts +85 -0
- package/src/modules/eudr/lib/plural.ts +36 -0
- package/src/modules/eudr/lib/reference-data.ts +128 -0
- package/src/modules/eudr/lib/species.ts +12 -0
- package/src/modules/eudr/lib/statement-lifecycle.ts +109 -0
- package/src/modules/eudr/lib/statement-seeding.ts +86 -0
- package/src/modules/eudr/migrations/.snapshot-open-mercato.json +2046 -0
- package/src/modules/eudr/migrations/Migration20260706102002_eudr.ts +16 -0
- package/src/modules/eudr/migrations/Migration20260706152612_eudr.ts +30 -0
- package/src/modules/eudr/migrations/Migration20260722100000_eudr.ts +17 -0
- package/src/modules/eudr/notifications.ts +56 -0
- package/src/modules/eudr/search.ts +212 -0
- package/src/modules/eudr/setup.ts +10 -0
- package/src/modules/eudr/subscribers/notify-mitigation-completed.ts +56 -0
- package/src/modules/eudr/subscribers/notify-risk-concluded.ts +58 -0
- package/src/modules/eudr/subscribers/notify-statement-reference-issued.ts +59 -0
- package/src/modules/eudr/subscribers/notify-statement-submitted.ts +49 -0
- package/src/modules/eudr/subscribers/notify-statement-withdrawn.ts +49 -0
- package/src/modules/eudr/subscribers/recompute-completeness-on-attachment-created.ts +189 -0
- package/src/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.ts +31 -0
- package/src/modules/eudr/widgets/dashboard/compliance-overview/widget.client.tsx +271 -0
- package/src/modules/eudr/widgets/dashboard/compliance-overview/widget.ts +24 -0
- package/src/modules/eudr/widgets/injection/eudr-product-column/widget.client.tsx +37 -0
- package/src/modules/eudr/widgets/injection/eudr-product-column/widget.ts +30 -0
- package/src/modules/eudr/widgets/injection/order-compliance/widget.client.tsx +131 -0
- package/src/modules/eudr/widgets/injection/order-compliance/widget.ts +16 -0
- package/src/modules/eudr/widgets/injection/supplier-compliance/widget.client.tsx +168 -0
- package/src/modules/eudr/widgets/injection/supplier-compliance/widget.ts +16 -0
- package/src/modules/eudr/widgets/injection-table.ts +31 -0
- package/src/modules/notifications/lib/notificationService.ts +83 -4
- package/src/modules/query_index/lib/engine.ts +139 -51
- package/src/modules/sales/api/documents/factory.ts +29 -0
- package/src/modules/sales/commands/documents.ts +65 -18
- package/src/modules/sales/data/entities.ts +1 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +10 -0
- package/src/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.ts +59 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/eudr/api/product-mappings/suggestions/apply/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'\nimport {\n bridgeLegacyGuard,\n runMutationGuards,\n type MutationGuard,\n type MutationGuardInput,\n} from '@open-mercato/shared/lib/crud/mutation-guard-registry'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n EUDR_COMMODITIES,\n productMappingCreateSchema,\n} from '../../../../data/validators'\nimport { suggestCommodityForHsCode } from '../../../../lib/reference-data'\n\nconst logger = createLogger('eudr').child({ component: 'api/product-mappings/suggestions/apply' })\n\nconst CATALOG_LOOKUP_PAGE_SIZE = 100\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['eudr.mappings.manage'] },\n}\n\nconst applyItemSchema = z.object({\n productId: z.string().uuid(),\n commodity: z.enum(EUDR_COMMODITIES),\n hsCode: z.string().max(20).optional().nullable(),\n})\n\nconst applySchema = z\n .object({\n items: z.array(applyItemSchema).max(100).optional(),\n productIds: z.array(z.string().uuid()).max(100).optional(),\n })\n .superRefine((value, context) => {\n if ((value.items?.length ?? 0) > 0 || (value.productIds?.length ?? 0) > 0) return\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'eudr.errors.mapping_suggestions_apply_failed',\n })\n })\n\ntype ApplyItem = z.infer<typeof applyItemSchema>\ntype ApplyFailure = { productId: string; errorKey: string }\ntype ProductSnapshot = { name?: string | null; sku?: string | null }\ntype CatalogProductInfo = { snapshot: ProductSnapshot | null; hsCode: string | null }\ntype CatalogProductRecord = Record<string, unknown> & {\n id?: unknown\n title?: unknown\n name?: unknown\n sku?: unknown\n hs_code?: unknown\n}\ntype RequestContext = {\n ctx: CommandRuntimeContext\n tenantId: string\n organizationId: string\n}\ntype CommandCreateResult = { entityId?: string; id?: string }\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction resolveUserFeatures(auth: unknown): string[] {\n const features = (auth as { features?: unknown })?.features\n if (!Array.isArray(features)) return []\n return features.filter((value): value is string => typeof value === 'string')\n}\n\nasync function runGuards(\n ctx: CommandRuntimeContext,\n input: MutationGuardInput,\n): Promise<{\n ok: boolean\n errorBody?: Record<string, unknown>\n errorStatus?: number\n modifiedPayload?: Record<string, unknown>\n afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>\n}> {\n const legacyGuard = bridgeLegacyGuard(ctx.container)\n const guards = [...getAllMutationGuardInstances(), ...(legacyGuard ? [legacyGuard] : [])]\n return runMutationGuards(guards, input, {\n userFeatures: resolveUserFeatures(ctx.auth),\n })\n}\n\nasync function runGuardAfterSuccessCallbacks(\n callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,\n input: {\n tenantId: string\n organizationId: string\n userId: string\n resourceKind: string\n resourceId: string\n operation: 'create'\n requestMethod: string\n requestHeaders: Headers\n },\n): Promise<void> {\n for (const callback of callbacks) {\n if (!callback.guard.afterSuccess) continue\n try {\n await callback.guard.afterSuccess({\n ...input,\n metadata: callback.metadata ?? null,\n })\n } catch (err) {\n logger.warn('Mutation guard afterSuccess callback failed', { err })\n }\n }\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('eudr.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('eudr.errors.organization_required', 'Organization context is required'),\n })\n }\n\n return {\n tenantId: auth.tenantId,\n organizationId,\n ctx: {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n },\n }\n}\n\nasync function loadCatalogProductInfo(ctx: RequestContext, productIds: string[]): Promise<Map<string, CatalogProductInfo>> {\n const info = new Map<string, CatalogProductInfo>()\n for (const productId of productIds) info.set(productId, { snapshot: null, hsCode: null })\n if (!productIds.length) return info\n\n try {\n const queryEngine = ctx.ctx.container.resolve('queryEngine') as QueryEngine\n // `items` and `productIds` are capped at 100 each, so their union can reach\n // 200 \u2014 chunk the lookup to keep every page within the pageSize <= 100 rule.\n const chunks: string[][] = []\n for (let index = 0; index < productIds.length; index += CATALOG_LOOKUP_PAGE_SIZE) {\n chunks.push(productIds.slice(index, index + CATALOG_LOOKUP_PAGE_SIZE))\n }\n const products: CatalogProductRecord[] = []\n for (const chunk of chunks) {\n const result = await queryEngine.query<CatalogProductRecord>(E.catalog.catalog_product, {\n fields: ['id', 'title', 'name', 'sku', 'hs_code'],\n filters: { id: { $in: chunk } },\n page: { page: 1, pageSize: chunk.length },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n products.push(...result.items)\n }\n for (const product of products) {\n const id = readString(product.id)\n if (!id) continue\n info.set(id, {\n snapshot: {\n name: readString(product.title) ?? readString(product.name),\n sku: readString(product.sku),\n },\n hsCode: readString(product.hs_code),\n })\n }\n } catch {\n return info\n }\n\n return info\n}\n\nfunction resolveApplyItems(\n input: z.infer<typeof applySchema>,\n catalogInfo: Map<string, CatalogProductInfo>,\n): { items: ApplyItem[]; failed: ApplyFailure[] } {\n const items: ApplyItem[] = [...(input.items ?? [])]\n const failed: ApplyFailure[] = []\n const explicitIds = new Set(items.map((item) => item.productId))\n\n for (const productId of input.productIds ?? []) {\n if (explicitIds.has(productId)) continue\n explicitIds.add(productId)\n const hsCode = catalogInfo.get(productId)?.hsCode ?? null\n const suggestedCommodity = suggestCommodityForHsCode(hsCode)\n if (!hsCode || !suggestedCommodity) {\n failed.push({ productId, errorKey: 'eudr.errors.mapping_suggestions_apply_failed' })\n continue\n }\n items.push({ productId, commodity: suggestedCommodity, hsCode })\n }\n\n return { items, failed }\n}\n\nfunction errorKeyFromError(error: unknown): string {\n if (isCrudHttpError(error)) {\n const bodyError = error.body?.error\n return typeof bodyError === 'string' && bodyError.length > 0 ? bodyError : 'eudr.errors.mapping_suggestions_apply_failed'\n }\n if (error instanceof z.ZodError) {\n const firstMessage = error.issues[0]?.message\n return typeof firstMessage === 'string' && firstMessage.length > 0 ? firstMessage : 'eudr.errors.mapping_suggestions_apply_failed'\n }\n return 'eudr.errors.mapping_suggestions_apply_failed'\n}\n\nasync function createMapping(args: {\n commandBus: CommandBus\n ctx: RequestContext\n item: ApplyItem\n productSnapshot: ProductSnapshot | null\n}): Promise<string | null> {\n const parsed = productMappingCreateSchema.parse({\n productId: args.item.productId,\n commodity: args.item.commodity,\n hsCode: args.item.hsCode ?? null,\n productSnapshot: args.productSnapshot,\n })\n const { result } = await args.commandBus.execute<typeof parsed & { tenantId: string; organizationId: string }, CommandCreateResult>(\n 'eudr.product_mappings.create',\n {\n input: {\n ...parsed,\n tenantId: args.ctx.tenantId,\n organizationId: args.ctx.organizationId,\n },\n ctx: args.ctx.ctx,\n metadata: {\n tenantId: args.ctx.tenantId,\n organizationId: args.ctx.organizationId,\n resourceKind: 'eudr.product_mapping',\n context: { cacheAliases: ['eudr.product_mapping'] },\n },\n },\n )\n return result?.entityId ?? result?.id ?? null\n}\n\nexport async function POST(req: Request) {\n try {\n const requestContext = await resolveRequestContext(req)\n const { translate } = await resolveTranslations()\n const payload = await req.json().catch(() => ({}))\n const input = applySchema.parse(payload)\n const catalogInfo = await loadCatalogProductInfo(\n requestContext,\n Array.from(new Set([\n ...(input.items ?? []).map((item) => item.productId),\n ...(input.productIds ?? []),\n ])),\n )\n const resolved = resolveApplyItems(input, catalogInfo)\n const guardResult = await runGuards(requestContext.ctx, {\n tenantId: requestContext.tenantId,\n organizationId: requestContext.organizationId,\n userId: requestContext.ctx.auth?.sub ?? '',\n resourceKind: 'eudr.product_mapping',\n resourceId: null,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { items: resolved.items },\n })\n if (!guardResult.ok) {\n return Response.json(\n guardResult.errorBody ?? { error: translate('eudr.errors.operation_blocked', 'Operation blocked by guard') },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = requestContext.ctx.container.resolve('commandBus') as CommandBus\n const failed: ApplyFailure[] = [...resolved.failed]\n const createdIds: string[] = []\n\n const guardItems = Array.isArray((guardResult.modifiedPayload as { items?: unknown } | undefined)?.items)\n ? ((guardResult.modifiedPayload as { items: unknown[] }).items.filter((entry): entry is typeof resolved.items[number] => (\n typeof (entry as { productId?: unknown })?.productId === 'string'\n && typeof (entry as { commodity?: unknown })?.commodity === 'string'\n )))\n : resolved.items\n\n for (const item of guardItems) {\n try {\n const id = await createMapping({\n commandBus,\n ctx: requestContext,\n item,\n productSnapshot: catalogInfo.get(item.productId)?.snapshot ?? null,\n })\n if (id) createdIds.push(id)\n } catch (error) {\n failed.push({ productId: item.productId, errorKey: errorKeyFromError(error) })\n }\n }\n\n if (guardResult.afterSuccessCallbacks.length) {\n for (const id of createdIds) {\n await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {\n tenantId: requestContext.tenantId,\n organizationId: requestContext.organizationId,\n userId: requestContext.ctx.auth?.sub ?? '',\n resourceKind: 'eudr.product_mapping',\n resourceId: id,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n }\n\n return Response.json({ created: createdIds.length, failed })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n const { translate } = await resolveTranslations()\n if (error instanceof z.ZodError) {\n return Response.json(\n { error: translate('eudr.errors.mapping_suggestions_apply_failed', 'Failed to apply EUDR mapping suggestions') },\n { status: 400 },\n )\n }\n logger.error('Product mapping suggestions apply failed', { err: error })\n return Response.json(\n { error: translate('eudr.errors.mapping_suggestions_apply_failed', 'Failed to apply EUDR mapping suggestions') },\n { status: 500 },\n )\n }\n}\n\nconst applyResponseSchema = z.object({\n created: z.number(),\n failed: z.array(z.object({\n productId: z.string().uuid(),\n errorKey: z.string(),\n })),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'EUDR',\n summary: 'Apply EUDR product mapping suggestions',\n methods: {\n POST: {\n summary: 'Apply mapping suggestions',\n requestBody: {\n contentType: 'application/json',\n schema: applySchema,\n },\n responses: [\n { status: 200, description: 'Apply result', schema: applyResponseSchema },\n { status: 400, description: 'Invalid apply payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n { status: 500, description: 'Mapping suggestion apply failed', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
|
+
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
7
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
8
|
+
import { E } from "../../../../../generated/entities.ids.generated.js";
|
|
9
|
+
import { EudrProductMapping } from "../../../data/entities.js";
|
|
10
|
+
import { EUDR_COMMODITIES } from "../../../data/validators.js";
|
|
11
|
+
import { suggestCommodityForHsCode } from "../../../lib/reference-data.js";
|
|
12
|
+
const metadata = {
|
|
13
|
+
GET: { requireAuth: true, requireFeatures: ["eudr.mappings.view"] }
|
|
14
|
+
};
|
|
15
|
+
const logger = createLogger("eudr").child({ component: "api/product-mappings/suggestions" });
|
|
16
|
+
async function resolveRequestContext(req) {
|
|
17
|
+
const container = await createRequestContainer();
|
|
18
|
+
const auth = await getAuthFromRequest(req);
|
|
19
|
+
const { translate } = await resolveTranslations();
|
|
20
|
+
if (!auth || !auth.tenantId) {
|
|
21
|
+
throw new CrudHttpError(401, { error: translate("eudr.errors.unauthorized", "Unauthorized") });
|
|
22
|
+
}
|
|
23
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
|
|
24
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null;
|
|
25
|
+
if (!organizationId) {
|
|
26
|
+
throw new CrudHttpError(400, {
|
|
27
|
+
error: translate("eudr.errors.organization_required", "Organization context is required")
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
container,
|
|
32
|
+
em: container.resolve("em"),
|
|
33
|
+
tenantId: auth.tenantId,
|
|
34
|
+
organizationId
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function readString(value) {
|
|
38
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
39
|
+
}
|
|
40
|
+
async function loadCatalogProducts(ctx) {
|
|
41
|
+
try {
|
|
42
|
+
const queryEngine = ctx.container.resolve("queryEngine");
|
|
43
|
+
const products = [];
|
|
44
|
+
for (let page = 1; page <= 5; page += 1) {
|
|
45
|
+
const result = await queryEngine.query(E.catalog.catalog_product, {
|
|
46
|
+
fields: ["id", "title", "name", "sku", "hs_code"],
|
|
47
|
+
filters: [
|
|
48
|
+
{ field: "hs_code", op: "exists", value: true },
|
|
49
|
+
{ field: "hs_code", op: "ne", value: "" }
|
|
50
|
+
],
|
|
51
|
+
page: { page, pageSize: 100 },
|
|
52
|
+
tenantId: ctx.tenantId,
|
|
53
|
+
organizationId: ctx.organizationId
|
|
54
|
+
});
|
|
55
|
+
products.push(...result.items);
|
|
56
|
+
if (result.items.length < 100) break;
|
|
57
|
+
}
|
|
58
|
+
return products;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.debug("Catalog product scan failed; returning no mapping suggestions", { err: error });
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function GET(req) {
|
|
65
|
+
try {
|
|
66
|
+
const ctx = await resolveRequestContext(req);
|
|
67
|
+
const products = await loadCatalogProducts(ctx);
|
|
68
|
+
const candidates = products.map((product) => {
|
|
69
|
+
const productId = readString(product.id);
|
|
70
|
+
const hsCode = readString(product.hs_code);
|
|
71
|
+
const suggestedCommodity = suggestCommodityForHsCode(hsCode);
|
|
72
|
+
if (!productId || !hsCode || !suggestedCommodity) return null;
|
|
73
|
+
return {
|
|
74
|
+
productId,
|
|
75
|
+
name: readString(product.title) ?? readString(product.name) ?? productId,
|
|
76
|
+
sku: readString(product.sku),
|
|
77
|
+
hsCode,
|
|
78
|
+
suggestedCommodity
|
|
79
|
+
};
|
|
80
|
+
}).filter((item) => item !== null);
|
|
81
|
+
const productIds = candidates.map((item) => item.productId);
|
|
82
|
+
const activeMappings = productIds.length ? await ctx.em.find(EudrProductMapping, {
|
|
83
|
+
tenantId: ctx.tenantId,
|
|
84
|
+
organizationId: ctx.organizationId,
|
|
85
|
+
deletedAt: null,
|
|
86
|
+
productId: { $in: productIds }
|
|
87
|
+
}) : [];
|
|
88
|
+
const mappedProductIds = new Set(activeMappings.map((mapping) => mapping.productId));
|
|
89
|
+
return Response.json({
|
|
90
|
+
items: candidates.filter((item) => !mappedProductIds.has(item.productId)).slice(0, 200)
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (isCrudHttpError(error)) {
|
|
94
|
+
return Response.json(error.body, { status: error.status });
|
|
95
|
+
}
|
|
96
|
+
const { translate } = await resolveTranslations();
|
|
97
|
+
logger.error("Product mapping suggestions loading failed", { err: error });
|
|
98
|
+
return Response.json(
|
|
99
|
+
{ error: translate("eudr.errors.mapping_suggestions_failed", "Failed to load EUDR mapping suggestions") },
|
|
100
|
+
{ status: 500 }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const suggestionSchema = z.object({
|
|
105
|
+
productId: z.string().uuid(),
|
|
106
|
+
name: z.string(),
|
|
107
|
+
sku: z.string().nullable(),
|
|
108
|
+
hsCode: z.string(),
|
|
109
|
+
suggestedCommodity: z.enum(EUDR_COMMODITIES)
|
|
110
|
+
});
|
|
111
|
+
const openApi = {
|
|
112
|
+
tag: "EUDR",
|
|
113
|
+
summary: "Suggest EUDR product mappings from catalog HS codes",
|
|
114
|
+
methods: {
|
|
115
|
+
GET: {
|
|
116
|
+
summary: "Product mapping suggestions",
|
|
117
|
+
responses: [
|
|
118
|
+
{ status: 200, description: "Suggested product mappings", schema: z.object({ items: z.array(suggestionSchema) }) },
|
|
119
|
+
{ status: 400, description: "Invalid organization context", schema: z.object({ error: z.string() }) },
|
|
120
|
+
{ status: 401, description: "Unauthorized", schema: z.object({ error: z.string() }) },
|
|
121
|
+
{ status: 403, description: "Forbidden", schema: z.object({ error: z.string() }) },
|
|
122
|
+
{ status: 500, description: "Product mapping suggestions loading failed", schema: z.object({ error: z.string() }) }
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
export {
|
|
128
|
+
GET,
|
|
129
|
+
metadata,
|
|
130
|
+
openApi
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/eudr/api/product-mappings/suggestions/route.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport { E } from '#generated/entities.ids.generated'\nimport { EudrProductMapping } from '../../../data/entities'\nimport { EUDR_COMMODITIES } from '../../../data/validators'\nimport { suggestCommodityForHsCode } from '../../../lib/reference-data'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['eudr.mappings.view'] },\n}\n\nconst logger = createLogger('eudr').child({ component: 'api/product-mappings/suggestions' })\n\ntype RequestContext = {\n container: { resolve: <T = unknown>(name: string) => T }\n em: EntityManager\n tenantId: string\n organizationId: string\n}\n\ntype CatalogProductRecord = Record<string, unknown> & {\n id?: unknown\n title?: unknown\n name?: unknown\n sku?: unknown\n hs_code?: unknown\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('eudr.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('eudr.errors.organization_required', 'Organization context is required'),\n })\n }\n\n return {\n container,\n em: container.resolve('em') as EntityManager,\n tenantId: auth.tenantId,\n organizationId,\n }\n}\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nasync function loadCatalogProducts(ctx: RequestContext): Promise<CatalogProductRecord[]> {\n try {\n const queryEngine = ctx.container.resolve<QueryEngine>('queryEngine')\n const products: CatalogProductRecord[] = []\n for (let page = 1; page <= 5; page += 1) {\n const result = await queryEngine.query<CatalogProductRecord>(E.catalog.catalog_product, {\n fields: ['id', 'title', 'name', 'sku', 'hs_code'],\n filters: [\n { field: 'hs_code', op: 'exists', value: true },\n { field: 'hs_code', op: 'ne', value: '' },\n ],\n page: { page, pageSize: 100 },\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n products.push(...result.items)\n if (result.items.length < 100) break\n }\n return products\n } catch (error) {\n logger.debug('Catalog product scan failed; returning no mapping suggestions', { err: error })\n return []\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveRequestContext(req)\n const products = await loadCatalogProducts(ctx)\n const candidates = products\n .map((product) => {\n const productId = readString(product.id)\n const hsCode = readString(product.hs_code)\n const suggestedCommodity = suggestCommodityForHsCode(hsCode)\n if (!productId || !hsCode || !suggestedCommodity) return null\n return {\n productId,\n name: readString(product.title) ?? readString(product.name) ?? productId,\n sku: readString(product.sku),\n hsCode,\n suggestedCommodity,\n }\n })\n .filter((item): item is {\n productId: string\n name: string\n sku: string | null\n hsCode: string\n suggestedCommodity: NonNullable<ReturnType<typeof suggestCommodityForHsCode>>\n } => item !== null)\n\n const productIds = candidates.map((item) => item.productId)\n const activeMappings = productIds.length\n ? await ctx.em.find(EudrProductMapping, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n deletedAt: null,\n productId: { $in: productIds },\n } as FilterQuery<EudrProductMapping>)\n : []\n const mappedProductIds = new Set(activeMappings.map((mapping) => mapping.productId))\n\n return Response.json({\n items: candidates\n .filter((item) => !mappedProductIds.has(item.productId))\n .slice(0, 200),\n })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('Product mapping suggestions loading failed', { err: error })\n return Response.json(\n { error: translate('eudr.errors.mapping_suggestions_failed', 'Failed to load EUDR mapping suggestions') },\n { status: 500 },\n )\n }\n}\n\nconst suggestionSchema = z.object({\n productId: z.string().uuid(),\n name: z.string(),\n sku: z.string().nullable(),\n hsCode: z.string(),\n suggestedCommodity: z.enum(EUDR_COMMODITIES),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'EUDR',\n summary: 'Suggest EUDR product mappings from catalog HS codes',\n methods: {\n GET: {\n summary: 'Product mapping suggestions',\n responses: [\n { status: 200, description: 'Suggested product mappings', schema: z.object({ items: z.array(suggestionSchema) }) },\n { status: 400, description: 'Invalid organization context', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n { status: 500, description: 'Product mapping suggestions loading failed', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
6
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
7
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
8
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
9
|
+
import {
|
|
10
|
+
accumulateSupplementaryQuantities,
|
|
11
|
+
annualReportSchema,
|
|
12
|
+
buildAnnualReportCsv
|
|
13
|
+
} from "../../../lib/annual-report.js";
|
|
14
|
+
import { getCountryRiskTier } from "../../../lib/reference-data.js";
|
|
15
|
+
const metadata = {
|
|
16
|
+
GET: { requireAuth: true, requireFeatures: ["eudr.statements.view"] }
|
|
17
|
+
};
|
|
18
|
+
const logger = createLogger("eudr").child({ route: "reports/annual" });
|
|
19
|
+
const INCLUDED_STATUSES = ["submitted", "available", "withdrawn", "archived"];
|
|
20
|
+
const querySchema = z.object({
|
|
21
|
+
year: z.coerce.number().int().min(2020).max(2100),
|
|
22
|
+
format: z.enum(["json", "csv"]).default("json")
|
|
23
|
+
});
|
|
24
|
+
async function resolveRequestContext(request) {
|
|
25
|
+
const container = await createRequestContainer();
|
|
26
|
+
const auth = await getAuthFromRequest(request);
|
|
27
|
+
const { translate } = await resolveTranslations();
|
|
28
|
+
if (!auth?.tenantId) {
|
|
29
|
+
throw new CrudHttpError(401, {
|
|
30
|
+
error: translate("eudr.errors.unauthorized", "Unauthorized")
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request });
|
|
34
|
+
const organizationIds = Array.isArray(organizationScope.filterIds) ? organizationScope.filterIds : null;
|
|
35
|
+
const featureOrganizationId = organizationScope.selectedId ?? auth.orgId ?? null;
|
|
36
|
+
const tenantId = auth.tenantId;
|
|
37
|
+
const userId = auth.sub ?? null;
|
|
38
|
+
const em = container.resolve("em");
|
|
39
|
+
let rbacService;
|
|
40
|
+
try {
|
|
41
|
+
rbacService = container.resolve("rbacService");
|
|
42
|
+
} catch {
|
|
43
|
+
rbacService = null;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
db: em.getKysely(),
|
|
47
|
+
tenantId,
|
|
48
|
+
organizationIds,
|
|
49
|
+
hasFeature: async (feature) => {
|
|
50
|
+
if (!rbacService || !userId) return false;
|
|
51
|
+
try {
|
|
52
|
+
return await rbacService.userHasAllFeatures(userId, [feature], {
|
|
53
|
+
tenantId,
|
|
54
|
+
organizationId: featureOrganizationId
|
|
55
|
+
});
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function buildStatementBucket(db, scope, start, end) {
|
|
63
|
+
let query = db.selectFrom("eudr_due_diligence_statements").where("tenant_id", "=", scope.tenantId).where("deleted_at", "is", null).where("submitted_at", ">=", start).where("submitted_at", "<", end).where("status", "in", [...INCLUDED_STATUSES]);
|
|
64
|
+
if (Array.isArray(scope.organizationIds)) {
|
|
65
|
+
query = query.where("organization_id", "in", scope.organizationIds);
|
|
66
|
+
}
|
|
67
|
+
return query;
|
|
68
|
+
}
|
|
69
|
+
function buildLatestAssessments(db, scope, start, end) {
|
|
70
|
+
let query = db.selectFrom("eudr_risk_assessments").distinctOn("statement_id").select(["id", "statement_id", "conclusion", "is_simplified"]).where("tenant_id", "=", scope.tenantId).where("deleted_at", "is", null).where("statement_id", "in", buildStatementBucket(db, scope, start, end).select("id")).orderBy("statement_id", "asc").orderBy("assessed_at", "desc").orderBy("created_at", "desc").orderBy("id", "desc");
|
|
71
|
+
if (Array.isArray(scope.organizationIds)) {
|
|
72
|
+
query = query.where("organization_id", "in", scope.organizationIds);
|
|
73
|
+
}
|
|
74
|
+
return query;
|
|
75
|
+
}
|
|
76
|
+
function normalizeCount(value) {
|
|
77
|
+
const count = Number(value ?? 0);
|
|
78
|
+
return Number.isFinite(count) && count >= 0 ? count : 0;
|
|
79
|
+
}
|
|
80
|
+
function normalizeQuantity(value) {
|
|
81
|
+
const quantity = typeof value === "bigint" ? value.toString() : typeof value === "string" || typeof value === "number" ? value : null;
|
|
82
|
+
return accumulateSupplementaryQuantities([{ unit: "KG", quantity }])[0]?.quantity ?? "0.000";
|
|
83
|
+
}
|
|
84
|
+
async function fetchStatementAggregates(db, scope, start, end) {
|
|
85
|
+
const [rawStatusRows, rawCommodityRows, rawSupplementaryRows] = await Promise.all([
|
|
86
|
+
buildStatementBucket(db, scope, start, end).select(["status", sql`count(*)`.as("count")]).groupBy("status").orderBy("status", "asc").execute(),
|
|
87
|
+
buildStatementBucket(db, scope, start, end).select([
|
|
88
|
+
"commodity",
|
|
89
|
+
sql`count(*)`.as("count"),
|
|
90
|
+
sql`coalesce(sum(quantity_kg), 0)`.as("quantity_kg")
|
|
91
|
+
]).groupBy("commodity").orderBy("commodity", "asc").execute(),
|
|
92
|
+
buildStatementBucket(db, scope, start, end).select([
|
|
93
|
+
"commodity",
|
|
94
|
+
"supplementary_unit as unit",
|
|
95
|
+
sql`coalesce(sum(supplementary_quantity), 0)`.as("quantity")
|
|
96
|
+
]).where("supplementary_unit", "is not", null).where(sql`trim(supplementary_unit) != ''`).groupBy(["commodity", "supplementary_unit"]).orderBy("commodity", "asc").orderBy("supplementary_unit", "asc").execute()
|
|
97
|
+
]);
|
|
98
|
+
const statusRows = rawStatusRows;
|
|
99
|
+
const commodityRows = rawCommodityRows;
|
|
100
|
+
const supplementaryRows = rawSupplementaryRows;
|
|
101
|
+
const byStatus = Object.fromEntries(
|
|
102
|
+
statusRows.map((row) => [row.status, normalizeCount(row.count)])
|
|
103
|
+
);
|
|
104
|
+
const supplementaryByCommodity = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const row of supplementaryRows) {
|
|
106
|
+
const rows = supplementaryByCommodity.get(row.commodity) ?? [];
|
|
107
|
+
rows.push(row);
|
|
108
|
+
supplementaryByCommodity.set(row.commodity, rows);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
total: Object.values(byStatus).reduce((total, count) => total + count, 0),
|
|
112
|
+
byStatus,
|
|
113
|
+
byCommodity: commodityRows.map((row) => ({
|
|
114
|
+
commodity: row.commodity,
|
|
115
|
+
count: normalizeCount(row.count),
|
|
116
|
+
quantityKg: normalizeQuantity(row.quantity_kg),
|
|
117
|
+
supplementaryQuantities: accumulateSupplementaryQuantities(
|
|
118
|
+
(supplementaryByCommodity.get(row.commodity) ?? []).map((supplementary) => ({
|
|
119
|
+
unit: supplementary.unit,
|
|
120
|
+
quantity: typeof supplementary.quantity === "bigint" ? supplementary.quantity.toString() : typeof supplementary.quantity === "string" || typeof supplementary.quantity === "number" ? supplementary.quantity : null
|
|
121
|
+
}))
|
|
122
|
+
)
|
|
123
|
+
}))
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async function fetchCountryAggregates(db, scope, start, end) {
|
|
127
|
+
let query = db.selectFrom("eudr_evidence_submissions").select([
|
|
128
|
+
"origin_country as country",
|
|
129
|
+
sql`count(*)`.as("submission_count")
|
|
130
|
+
]).where("tenant_id", "=", scope.tenantId).where("deleted_at", "is", null).where("origin_country", "is not", null).where(sql`trim(origin_country) != ''`).where("statement_id", "in", buildStatementBucket(db, scope, start, end).select("id")).groupBy("origin_country").orderBy("origin_country", "asc");
|
|
131
|
+
if (Array.isArray(scope.organizationIds)) {
|
|
132
|
+
query = query.where("organization_id", "in", scope.organizationIds);
|
|
133
|
+
}
|
|
134
|
+
const rows = await query.execute();
|
|
135
|
+
return rows.flatMap((row) => {
|
|
136
|
+
const country = row.country?.trim().toUpperCase() ?? "";
|
|
137
|
+
if (country.length === 0) return [];
|
|
138
|
+
return [{
|
|
139
|
+
country,
|
|
140
|
+
tier: getCountryRiskTier(country),
|
|
141
|
+
submissionCount: normalizeCount(row.submission_count)
|
|
142
|
+
}];
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async function fetchRiskAggregates(db, scope, start, end) {
|
|
146
|
+
const latestAssessments = buildLatestAssessments(db, scope, start, end);
|
|
147
|
+
const latestAssessmentIds = db.selectFrom(latestAssessments.as("latest_assessment")).select("latest_assessment.id");
|
|
148
|
+
let mitigationQuery = db.selectFrom("eudr_mitigation_actions").select([
|
|
149
|
+
sql`count(*)`.as("total"),
|
|
150
|
+
sql`count(*) filter (where status = 'completed')`.as("completed")
|
|
151
|
+
]).where("tenant_id", "=", scope.tenantId).where("deleted_at", "is", null).where("risk_assessment_id", "in", latestAssessmentIds);
|
|
152
|
+
if (Array.isArray(scope.organizationIds)) {
|
|
153
|
+
mitigationQuery = mitigationQuery.where("organization_id", "in", scope.organizationIds);
|
|
154
|
+
}
|
|
155
|
+
const [rawRisk, rawMitigation] = await Promise.all([
|
|
156
|
+
db.selectFrom(latestAssessments.as("latest_assessment")).select([
|
|
157
|
+
sql`count(*)`.as("assessments"),
|
|
158
|
+
sql`count(*) filter (where conclusion = 'negligible')`.as("negligible"),
|
|
159
|
+
sql`count(*) filter (where conclusion = 'non_negligible')`.as("non_negligible"),
|
|
160
|
+
sql`count(*) filter (where is_simplified = true)`.as("simplified")
|
|
161
|
+
]).executeTakeFirst(),
|
|
162
|
+
mitigationQuery.executeTakeFirst()
|
|
163
|
+
]);
|
|
164
|
+
const riskRow = rawRisk ?? {};
|
|
165
|
+
const mitigationRow = rawMitigation ?? {};
|
|
166
|
+
return {
|
|
167
|
+
risk: {
|
|
168
|
+
assessments: normalizeCount(riskRow.assessments),
|
|
169
|
+
negligible: normalizeCount(riskRow.negligible),
|
|
170
|
+
nonNegligible: normalizeCount(riskRow.non_negligible),
|
|
171
|
+
simplified: normalizeCount(riskRow.simplified)
|
|
172
|
+
},
|
|
173
|
+
mitigation: {
|
|
174
|
+
total: normalizeCount(mitigationRow.total),
|
|
175
|
+
completed: normalizeCount(mitigationRow.completed)
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async function GET(request) {
|
|
180
|
+
const parsedQuery = querySchema.safeParse(Object.fromEntries(new URL(request.url).searchParams.entries()));
|
|
181
|
+
if (!parsedQuery.success) {
|
|
182
|
+
const { translate } = await resolveTranslations();
|
|
183
|
+
return Response.json({
|
|
184
|
+
error: translate("eudr.errors.invalid_query", "Invalid query"),
|
|
185
|
+
details: parsedQuery.error.flatten()
|
|
186
|
+
}, { status: 400 });
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const context = await resolveRequestContext(request);
|
|
190
|
+
const scope = {
|
|
191
|
+
tenantId: context.tenantId,
|
|
192
|
+
organizationIds: context.organizationIds
|
|
193
|
+
};
|
|
194
|
+
const start = new Date(Date.UTC(parsedQuery.data.year, 0, 1));
|
|
195
|
+
const end = new Date(Date.UTC(parsedQuery.data.year + 1, 0, 1));
|
|
196
|
+
const [canViewCountries, canViewRisk] = await Promise.all([
|
|
197
|
+
context.hasFeature("eudr.submissions.view"),
|
|
198
|
+
context.hasFeature("eudr.risk.view")
|
|
199
|
+
]);
|
|
200
|
+
const [statements, countries, riskBlocks] = await Promise.all([
|
|
201
|
+
fetchStatementAggregates(context.db, scope, start, end),
|
|
202
|
+
canViewCountries ? fetchCountryAggregates(context.db, scope, start, end) : Promise.resolve(null),
|
|
203
|
+
canViewRisk ? fetchRiskAggregates(context.db, scope, start, end) : Promise.resolve(null)
|
|
204
|
+
]);
|
|
205
|
+
const report = annualReportSchema.parse({
|
|
206
|
+
year: parsedQuery.data.year,
|
|
207
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
208
|
+
statements,
|
|
209
|
+
...countries !== null ? { countries } : {},
|
|
210
|
+
...riskBlocks !== null ? riskBlocks : {}
|
|
211
|
+
});
|
|
212
|
+
if (parsedQuery.data.format === "csv") {
|
|
213
|
+
const serialized = buildAnnualReportCsv(report);
|
|
214
|
+
return new Response(serialized.content, {
|
|
215
|
+
status: 200,
|
|
216
|
+
headers: {
|
|
217
|
+
"Content-Type": "text/csv; charset=utf-8",
|
|
218
|
+
"Content-Disposition": `attachment; filename="eudr-annual-report-${report.year}.csv"`
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return Response.json(report);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (isCrudHttpError(error)) {
|
|
225
|
+
return Response.json(error.body, { status: error.status });
|
|
226
|
+
}
|
|
227
|
+
const { translate } = await resolveTranslations();
|
|
228
|
+
logger.error("Annual report generation failed", { err: error });
|
|
229
|
+
return Response.json(
|
|
230
|
+
{ error: translate("eudr.errors.annual_report_failed", "Failed to generate EUDR annual report") },
|
|
231
|
+
{ status: 500 }
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const errorSchema = z.object({
|
|
236
|
+
error: z.string(),
|
|
237
|
+
details: z.unknown().optional()
|
|
238
|
+
});
|
|
239
|
+
const openApi = {
|
|
240
|
+
tag: "EUDR",
|
|
241
|
+
summary: "Generate an annual EUDR due diligence report",
|
|
242
|
+
methods: {
|
|
243
|
+
GET: {
|
|
244
|
+
summary: "Generate an annual EUDR due diligence report",
|
|
245
|
+
description: "Returns aggregate-only annual statement metrics. Country, risk, and mitigation blocks are omitted unless the caller holds their corresponding view features. Use format=csv for a statement-derived commodity export.",
|
|
246
|
+
query: querySchema,
|
|
247
|
+
responses: [
|
|
248
|
+
{
|
|
249
|
+
status: 200,
|
|
250
|
+
description: "Annual EUDR due diligence report (JSON by default; with format=csv the same endpoint streams a statement-derived commodity table as text/csv)",
|
|
251
|
+
schema: annualReportSchema
|
|
252
|
+
},
|
|
253
|
+
{ status: 400, description: "Invalid report query", schema: errorSchema },
|
|
254
|
+
{ status: 401, description: "Unauthorized", schema: errorSchema },
|
|
255
|
+
{ status: 403, description: "Forbidden", schema: errorSchema },
|
|
256
|
+
{ status: 500, description: "Annual report generation failed", schema: errorSchema }
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
export {
|
|
262
|
+
GET,
|
|
263
|
+
metadata,
|
|
264
|
+
openApi
|
|
265
|
+
};
|
|
266
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/eudr/api/reports/annual/route.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport {\n accumulateSupplementaryQuantities,\n annualReportSchema,\n buildAnnualReportCsv,\n type AnnualReport,\n} from '../../../lib/annual-report'\nimport { getCountryRiskTier } from '../../../lib/reference-data'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['eudr.statements.view'] },\n}\n\nconst logger = createLogger('eudr').child({ route: 'reports/annual' })\nconst INCLUDED_STATUSES = ['submitted', 'available', 'withdrawn', 'archived'] as const\n\nconst querySchema = z.object({\n year: z.coerce.number().int().min(2020).max(2100),\n format: z.enum(['json', 'csv']).default('json'),\n})\n\ntype AnnualReportDatabase = {\n eudr_due_diligence_statements: {\n id: string\n tenant_id: string\n organization_id: string\n submitted_at: Date | null\n status: string\n commodity: string\n quantity_kg: string | number | null\n supplementary_unit: string | null\n supplementary_quantity: string | number | null\n deleted_at: Date | null\n }\n eudr_evidence_submissions: {\n statement_id: string | null\n tenant_id: string\n organization_id: string\n origin_country: string | null\n deleted_at: Date | null\n }\n eudr_risk_assessments: {\n id: string\n statement_id: string\n tenant_id: string\n organization_id: string\n conclusion: string\n is_simplified: boolean\n assessed_at: Date\n created_at: Date\n deleted_at: Date | null\n }\n eudr_mitigation_actions: {\n risk_assessment_id: string\n tenant_id: string\n organization_id: string\n status: string\n deleted_at: Date | null\n }\n}\n\ntype ReportScope = {\n tenantId: string\n organizationIds: string[] | null\n}\n\ntype RequestContext = ReportScope & {\n db: Kysely<AnnualReportDatabase>\n hasFeature: (feature: string) => Promise<boolean>\n}\n\ntype StatusCountRow = { status: string; count: unknown }\ntype CommodityAggregateRow = {\n commodity: string\n count: unknown\n quantity_kg: unknown\n}\ntype SupplementaryAggregateRow = {\n commodity: string\n unit: string | null\n quantity: unknown\n}\ntype CountryAggregateRow = { country: string | null; submission_count: unknown }\ntype RiskAggregateRow = {\n assessments: unknown\n negligible: unknown\n non_negligible: unknown\n simplified: unknown\n}\ntype MitigationAggregateRow = { total: unknown; completed: unknown }\n\nasync function resolveRequestContext(request: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(request)\n const { translate } = await resolveTranslations()\n\n if (!auth?.tenantId) {\n throw new CrudHttpError(401, {\n error: translate('eudr.errors.unauthorized', 'Unauthorized'),\n })\n }\n\n const organizationScope = await resolveOrganizationScopeForRequest({ container, auth, request })\n const organizationIds = Array.isArray(organizationScope.filterIds)\n ? organizationScope.filterIds\n : null\n const featureOrganizationId = organizationScope.selectedId ?? auth.orgId ?? null\n const tenantId = auth.tenantId\n const userId = auth.sub ?? null\n const em = container.resolve('em') as EntityManager\n\n let rbacService: RbacService | null\n try {\n rbacService = container.resolve('rbacService') as RbacService\n } catch {\n rbacService = null\n }\n\n return {\n db: em.getKysely<AnnualReportDatabase>(),\n tenantId,\n organizationIds,\n hasFeature: async (feature: string) => {\n if (!rbacService || !userId) return false\n try {\n return await rbacService.userHasAllFeatures(userId, [feature], {\n tenantId,\n organizationId: featureOrganizationId,\n })\n } catch {\n return false\n }\n },\n }\n}\n\nfunction buildStatementBucket(\n db: Kysely<AnnualReportDatabase>,\n scope: ReportScope,\n start: Date,\n end: Date,\n) {\n let query = db\n .selectFrom('eudr_due_diligence_statements')\n .where('tenant_id', '=', scope.tenantId)\n .where('deleted_at', 'is', null)\n .where('submitted_at', '>=', start)\n .where('submitted_at', '<', end)\n .where('status', 'in', [...INCLUDED_STATUSES])\n\n if (Array.isArray(scope.organizationIds)) {\n query = query.where('organization_id', 'in', scope.organizationIds)\n }\n return query\n}\n\nfunction buildLatestAssessments(\n db: Kysely<AnnualReportDatabase>,\n scope: ReportScope,\n start: Date,\n end: Date,\n) {\n let query = db\n .selectFrom('eudr_risk_assessments')\n .distinctOn('statement_id')\n .select(['id', 'statement_id', 'conclusion', 'is_simplified'])\n .where('tenant_id', '=', scope.tenantId)\n .where('deleted_at', 'is', null)\n .where('statement_id', 'in', buildStatementBucket(db, scope, start, end).select('id'))\n .orderBy('statement_id', 'asc')\n .orderBy('assessed_at', 'desc')\n .orderBy('created_at', 'desc')\n .orderBy('id', 'desc')\n\n if (Array.isArray(scope.organizationIds)) {\n query = query.where('organization_id', 'in', scope.organizationIds)\n }\n return query\n}\n\nfunction normalizeCount(value: unknown): number {\n const count = Number(value ?? 0)\n return Number.isFinite(count) && count >= 0 ? count : 0\n}\n\nfunction normalizeQuantity(value: unknown): string {\n const quantity = typeof value === 'bigint'\n ? value.toString()\n : (typeof value === 'string' || typeof value === 'number' ? value : null)\n return accumulateSupplementaryQuantities([{ unit: 'KG', quantity }])[0]?.quantity ?? '0.000'\n}\n\nasync function fetchStatementAggregates(\n db: Kysely<AnnualReportDatabase>,\n scope: ReportScope,\n start: Date,\n end: Date,\n): Promise<AnnualReport['statements']> {\n const [rawStatusRows, rawCommodityRows, rawSupplementaryRows] = await Promise.all([\n buildStatementBucket(db, scope, start, end)\n .select(['status', sql<string>`count(*)`.as('count')])\n .groupBy('status')\n .orderBy('status', 'asc')\n .execute(),\n buildStatementBucket(db, scope, start, end)\n .select([\n 'commodity',\n sql<string>`count(*)`.as('count'),\n sql<string>`coalesce(sum(quantity_kg), 0)`.as('quantity_kg'),\n ])\n .groupBy('commodity')\n .orderBy('commodity', 'asc')\n .execute(),\n buildStatementBucket(db, scope, start, end)\n .select([\n 'commodity',\n 'supplementary_unit as unit',\n sql<string>`coalesce(sum(supplementary_quantity), 0)`.as('quantity'),\n ])\n .where('supplementary_unit', 'is not', null)\n .where(sql<boolean>`trim(supplementary_unit) != ''`)\n .groupBy(['commodity', 'supplementary_unit'])\n .orderBy('commodity', 'asc')\n .orderBy('supplementary_unit', 'asc')\n .execute(),\n ])\n\n const statusRows = rawStatusRows as StatusCountRow[]\n const commodityRows = rawCommodityRows as CommodityAggregateRow[]\n const supplementaryRows = rawSupplementaryRows as SupplementaryAggregateRow[]\n const byStatus = Object.fromEntries(\n statusRows.map((row) => [row.status, normalizeCount(row.count)]),\n )\n const supplementaryByCommodity = new Map<string, SupplementaryAggregateRow[]>()\n for (const row of supplementaryRows) {\n const rows = supplementaryByCommodity.get(row.commodity) ?? []\n rows.push(row)\n supplementaryByCommodity.set(row.commodity, rows)\n }\n\n return {\n total: Object.values(byStatus).reduce((total, count) => total + count, 0),\n byStatus,\n byCommodity: commodityRows.map((row) => ({\n commodity: row.commodity,\n count: normalizeCount(row.count),\n quantityKg: normalizeQuantity(row.quantity_kg),\n supplementaryQuantities: accumulateSupplementaryQuantities(\n (supplementaryByCommodity.get(row.commodity) ?? []).map((supplementary) => ({\n unit: supplementary.unit,\n quantity: typeof supplementary.quantity === 'bigint'\n ? supplementary.quantity.toString()\n : (typeof supplementary.quantity === 'string' || typeof supplementary.quantity === 'number'\n ? supplementary.quantity\n : null),\n })),\n ),\n })),\n }\n}\n\nasync function fetchCountryAggregates(\n db: Kysely<AnnualReportDatabase>,\n scope: ReportScope,\n start: Date,\n end: Date,\n): Promise<NonNullable<AnnualReport['countries']>> {\n let query = db\n .selectFrom('eudr_evidence_submissions')\n .select([\n 'origin_country as country',\n sql<string>`count(*)`.as('submission_count'),\n ])\n .where('tenant_id', '=', scope.tenantId)\n .where('deleted_at', 'is', null)\n .where('origin_country', 'is not', null)\n .where(sql<boolean>`trim(origin_country) != ''`)\n .where('statement_id', 'in', buildStatementBucket(db, scope, start, end).select('id'))\n .groupBy('origin_country')\n .orderBy('origin_country', 'asc')\n\n if (Array.isArray(scope.organizationIds)) {\n query = query.where('organization_id', 'in', scope.organizationIds)\n }\n\n const rows = await query.execute() as CountryAggregateRow[]\n return rows.flatMap((row) => {\n const country = row.country?.trim().toUpperCase() ?? ''\n if (country.length === 0) return []\n return [{\n country,\n tier: getCountryRiskTier(country),\n submissionCount: normalizeCount(row.submission_count),\n }]\n })\n}\n\nasync function fetchRiskAggregates(\n db: Kysely<AnnualReportDatabase>,\n scope: ReportScope,\n start: Date,\n end: Date,\n): Promise<{\n risk: NonNullable<AnnualReport['risk']>\n mitigation: NonNullable<AnnualReport['mitigation']>\n}> {\n const latestAssessments = buildLatestAssessments(db, scope, start, end)\n const latestAssessmentIds = db\n .selectFrom(latestAssessments.as('latest_assessment'))\n .select('latest_assessment.id')\n\n let mitigationQuery = db\n .selectFrom('eudr_mitigation_actions')\n .select([\n sql<string>`count(*)`.as('total'),\n sql<string>`count(*) filter (where status = 'completed')`.as('completed'),\n ])\n .where('tenant_id', '=', scope.tenantId)\n .where('deleted_at', 'is', null)\n .where('risk_assessment_id', 'in', latestAssessmentIds)\n\n if (Array.isArray(scope.organizationIds)) {\n mitigationQuery = mitigationQuery.where('organization_id', 'in', scope.organizationIds)\n }\n\n const [rawRisk, rawMitigation] = await Promise.all([\n db\n .selectFrom(latestAssessments.as('latest_assessment'))\n .select([\n sql<string>`count(*)`.as('assessments'),\n sql<string>`count(*) filter (where conclusion = 'negligible')`.as('negligible'),\n sql<string>`count(*) filter (where conclusion = 'non_negligible')`.as('non_negligible'),\n sql<string>`count(*) filter (where is_simplified = true)`.as('simplified'),\n ])\n .executeTakeFirst(),\n mitigationQuery.executeTakeFirst(),\n ])\n const riskRow = (rawRisk ?? {}) as Partial<RiskAggregateRow>\n const mitigationRow = (rawMitigation ?? {}) as Partial<MitigationAggregateRow>\n\n return {\n risk: {\n assessments: normalizeCount(riskRow.assessments),\n negligible: normalizeCount(riskRow.negligible),\n nonNegligible: normalizeCount(riskRow.non_negligible),\n simplified: normalizeCount(riskRow.simplified),\n },\n mitigation: {\n total: normalizeCount(mitigationRow.total),\n completed: normalizeCount(mitigationRow.completed),\n },\n }\n}\n\nexport async function GET(request: Request) {\n const parsedQuery = querySchema.safeParse(Object.fromEntries(new URL(request.url).searchParams.entries()))\n if (!parsedQuery.success) {\n const { translate } = await resolveTranslations()\n return Response.json({\n error: translate('eudr.errors.invalid_query', 'Invalid query'),\n details: parsedQuery.error.flatten(),\n }, { status: 400 })\n }\n\n try {\n const context = await resolveRequestContext(request)\n const scope: ReportScope = {\n tenantId: context.tenantId,\n organizationIds: context.organizationIds,\n }\n const start = new Date(Date.UTC(parsedQuery.data.year, 0, 1))\n const end = new Date(Date.UTC(parsedQuery.data.year + 1, 0, 1))\n const [canViewCountries, canViewRisk] = await Promise.all([\n context.hasFeature('eudr.submissions.view'),\n context.hasFeature('eudr.risk.view'),\n ])\n\n const [statements, countries, riskBlocks] = await Promise.all([\n fetchStatementAggregates(context.db, scope, start, end),\n canViewCountries\n ? fetchCountryAggregates(context.db, scope, start, end)\n : Promise.resolve(null),\n canViewRisk\n ? fetchRiskAggregates(context.db, scope, start, end)\n : Promise.resolve(null),\n ])\n\n const report = annualReportSchema.parse({\n year: parsedQuery.data.year,\n generatedAt: new Date().toISOString(),\n statements,\n ...(countries !== null ? { countries } : {}),\n ...(riskBlocks !== null ? riskBlocks : {}),\n })\n\n if (parsedQuery.data.format === 'csv') {\n const serialized = buildAnnualReportCsv(report)\n return new Response(serialized.content, {\n status: 200,\n headers: {\n 'Content-Type': 'text/csv; charset=utf-8',\n 'Content-Disposition': `attachment; filename=\"eudr-annual-report-${report.year}.csv\"`,\n },\n })\n }\n\n return Response.json(report)\n } catch (error) {\n if (isCrudHttpError(error)) {\n return Response.json(error.body, { status: error.status })\n }\n const { translate } = await resolveTranslations()\n logger.error('Annual report generation failed', { err: error })\n return Response.json(\n { error: translate('eudr.errors.annual_report_failed', 'Failed to generate EUDR annual report') },\n { status: 500 },\n )\n }\n}\n\nconst errorSchema = z.object({\n error: z.string(),\n details: z.unknown().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'EUDR',\n summary: 'Generate an annual EUDR due diligence report',\n methods: {\n GET: {\n summary: 'Generate an annual EUDR due diligence report',\n description: 'Returns aggregate-only annual statement metrics. Country, risk, and mitigation blocks are omitted unless the caller holds their corresponding view features. Use format=csv for a statement-derived commodity export.',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Annual EUDR due diligence report (JSON by default; with format=csv the same endpoint streams a statement-derived commodity table as text/csv)',\n schema: annualReportSchema,\n },\n { status: 400, description: 'Invalid report query', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Forbidden', schema: errorSchema },\n { status: 500, description: 'Annual report generation failed', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|