@open-mercato/core 0.6.8-develop.6925.1.cefa200fa4 → 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.ids.generated.js +9 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +120 -0
- package/dist/generated/entity-fields-registry.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/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.ids.generated.ts +9 -0
- package/generated/entity-fields-registry.ts +120 -0
- package/package.json +7 -7
- package/src/modules/attachments/fields/attachment.tsx +5 -0
- package/src/modules/attachments/lib/crud.ts +2 -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,13 @@
|
|
|
1
|
+
import type { ModuleInfo } from '@open-mercato/shared/modules/registry'
|
|
2
|
+
|
|
3
|
+
export const metadata: ModuleInfo = {
|
|
4
|
+
name: 'eudr',
|
|
5
|
+
title: 'EUDR Compliance',
|
|
6
|
+
version: '0.1.0',
|
|
7
|
+
description: 'EU Deforestation Regulation compliance: product commodity mappings, supplier origin evidence, due diligence statements.',
|
|
8
|
+
author: 'Open Mercato Team',
|
|
9
|
+
license: 'MIT',
|
|
10
|
+
ejectable: true,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { features } from './acl'
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import {
|
|
3
|
+
serializeExport,
|
|
4
|
+
type CrudExportColumn,
|
|
5
|
+
type PreparedExport,
|
|
6
|
+
} from '@open-mercato/shared/lib/crud/exporters'
|
|
7
|
+
|
|
8
|
+
const nonNegativeIntegerSchema = z.number().int().nonnegative()
|
|
9
|
+
|
|
10
|
+
const supplementaryQuantitySchema = z.object({
|
|
11
|
+
unit: z.string(),
|
|
12
|
+
quantity: z.string(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const commodityReportSchema = z.object({
|
|
16
|
+
commodity: z.string(),
|
|
17
|
+
count: nonNegativeIntegerSchema,
|
|
18
|
+
quantityKg: z.string(),
|
|
19
|
+
supplementaryQuantities: z.array(supplementaryQuantitySchema),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const annualReportSchema = z.object({
|
|
23
|
+
year: z.number().int(),
|
|
24
|
+
generatedAt: z.string().datetime(),
|
|
25
|
+
statements: z.object({
|
|
26
|
+
total: nonNegativeIntegerSchema,
|
|
27
|
+
byStatus: z.record(z.string(), nonNegativeIntegerSchema),
|
|
28
|
+
byCommodity: z.array(commodityReportSchema),
|
|
29
|
+
}),
|
|
30
|
+
countries: z.array(z.object({
|
|
31
|
+
country: z.string(),
|
|
32
|
+
tier: z.string(),
|
|
33
|
+
submissionCount: nonNegativeIntegerSchema,
|
|
34
|
+
})).optional(),
|
|
35
|
+
risk: z.object({
|
|
36
|
+
assessments: nonNegativeIntegerSchema,
|
|
37
|
+
negligible: nonNegativeIntegerSchema,
|
|
38
|
+
nonNegligible: nonNegativeIntegerSchema,
|
|
39
|
+
simplified: nonNegativeIntegerSchema,
|
|
40
|
+
}).optional(),
|
|
41
|
+
mitigation: z.object({
|
|
42
|
+
total: nonNegativeIntegerSchema,
|
|
43
|
+
completed: nonNegativeIntegerSchema,
|
|
44
|
+
}).optional(),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export type AnnualReport = z.infer<typeof annualReportSchema>
|
|
48
|
+
|
|
49
|
+
function parseThousandths(value: string | number | null): bigint | null {
|
|
50
|
+
if (value === null) return 0n
|
|
51
|
+
const normalized = typeof value === 'number' ? value.toFixed(3) : value.trim()
|
|
52
|
+
const match = /^([+-]?)(\d+)(?:\.(\d{1,3}))?$/.exec(normalized)
|
|
53
|
+
if (match === null) return null
|
|
54
|
+
|
|
55
|
+
const sign = match[1] === '-' ? -1n : 1n
|
|
56
|
+
const whole = BigInt(match[2]) * 1000n
|
|
57
|
+
const fraction = BigInt((match[3] ?? '').padEnd(3, '0') || '0')
|
|
58
|
+
return sign * (whole + fraction)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function formatThousandths(value: bigint): string {
|
|
62
|
+
const sign = value < 0n ? '-' : ''
|
|
63
|
+
const absolute = value < 0n ? -value : value
|
|
64
|
+
const whole = absolute / 1000n
|
|
65
|
+
const fraction = (absolute % 1000n).toString().padStart(3, '0')
|
|
66
|
+
return `${sign}${whole}.${fraction}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function accumulateSupplementaryQuantities(
|
|
70
|
+
rows: Array<{ unit: string | null; quantity: string | number | null }>,
|
|
71
|
+
): Array<{ unit: string; quantity: string }> {
|
|
72
|
+
const quantities = new Map<string, bigint>()
|
|
73
|
+
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
const unit = row.unit?.trim().toUpperCase() ?? ''
|
|
76
|
+
if (unit.length === 0) continue
|
|
77
|
+
const quantity = parseThousandths(row.quantity)
|
|
78
|
+
if (quantity === null) continue
|
|
79
|
+
quantities.set(unit, (quantities.get(unit) ?? 0n) + quantity)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return Array.from(quantities, ([unit, quantity]) => ({
|
|
83
|
+
unit,
|
|
84
|
+
quantity: formatThousandths(quantity),
|
|
85
|
+
}))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const annualReportCsvColumns: CrudExportColumn[] = [
|
|
89
|
+
{ field: 'commodity', header: 'commodity' },
|
|
90
|
+
{ field: 'count', header: 'count' },
|
|
91
|
+
{ field: 'quantityKg', header: 'quantityKg' },
|
|
92
|
+
{ field: 'supplementaryUnit', header: 'supplementaryUnit' },
|
|
93
|
+
{ field: 'supplementaryQuantity', header: 'supplementaryQuantity' },
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
export function buildAnnualReportCsv(report: AnnualReport): { content: string; contentType: string } {
|
|
97
|
+
const rows: PreparedExport['rows'] = report.statements.byCommodity.flatMap((commodity) => {
|
|
98
|
+
const supplementaryQuantities = commodity.supplementaryQuantities.length > 0
|
|
99
|
+
? commodity.supplementaryQuantities
|
|
100
|
+
: [null]
|
|
101
|
+
|
|
102
|
+
return supplementaryQuantities.map((supplementaryQuantity) => ({
|
|
103
|
+
commodity: commodity.commodity,
|
|
104
|
+
count: commodity.count,
|
|
105
|
+
quantityKg: commodity.quantityKg,
|
|
106
|
+
supplementaryUnit: supplementaryQuantity?.unit ?? '',
|
|
107
|
+
supplementaryQuantity: supplementaryQuantity?.quantity ?? '',
|
|
108
|
+
}))
|
|
109
|
+
})
|
|
110
|
+
const prepared: PreparedExport = {
|
|
111
|
+
columns: annualReportCsvColumns,
|
|
112
|
+
rows,
|
|
113
|
+
}
|
|
114
|
+
const serialized = serializeExport(prepared, 'csv')
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
content: serialized.body,
|
|
118
|
+
contentType: serialized.contentType,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { GEOJSON_TYPES } from '../data/validators'
|
|
2
|
+
|
|
3
|
+
export const COMPLETENESS_DIMENSIONS = ['origin_country', 'geolocation', 'quantity', 'harvest_period', 'producer', 'documents'] as const
|
|
4
|
+
export const REQUIRED_DIMENSIONS = COMPLETENESS_DIMENSIONS
|
|
5
|
+
export type CompletenessDimension = (typeof COMPLETENESS_DIMENSIONS)[number]
|
|
6
|
+
|
|
7
|
+
export type CompletenessInput = {
|
|
8
|
+
originCountry?: string | null
|
|
9
|
+
geolocation?: unknown
|
|
10
|
+
quantityKg?: string | number | null
|
|
11
|
+
harvestFrom?: Date | string | null
|
|
12
|
+
harvestTo?: Date | string | null
|
|
13
|
+
producerName?: string | null
|
|
14
|
+
attachmentIds?: string[] | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type CompletenessContext = {
|
|
18
|
+
linkedAttachmentCount?: number
|
|
19
|
+
activePlotCount?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type CompletenessResult = { score: number; missingFields: CompletenessDimension[] }
|
|
23
|
+
|
|
24
|
+
function isOriginCountryComplete(value: string | null | undefined): boolean {
|
|
25
|
+
return typeof value === 'string' && /^[A-Za-z]{2}$/.test(value.trim())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isGeolocationComplete(value: unknown): boolean {
|
|
29
|
+
if (value === null || typeof value !== 'object') return false
|
|
30
|
+
const candidate = value as { type?: unknown }
|
|
31
|
+
return typeof candidate.type === 'string' && GEOJSON_TYPES.includes(candidate.type as (typeof GEOJSON_TYPES)[number])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isQuantityComplete(value: string | number | null | undefined): boolean {
|
|
35
|
+
if (typeof value !== 'string' && typeof value !== 'number') return false
|
|
36
|
+
const quantity = Number(value)
|
|
37
|
+
return Number.isFinite(quantity) && quantity > 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseDate(value: Date | string | null | undefined): Date | null {
|
|
41
|
+
if (value == null) return null
|
|
42
|
+
const date = value instanceof Date ? value : new Date(value)
|
|
43
|
+
return Number.isNaN(date.getTime()) ? null : date
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isHarvestPeriodComplete(harvestFrom: Date | string | null | undefined, harvestTo: Date | string | null | undefined): boolean {
|
|
47
|
+
const parsedHarvestFrom = parseDate(harvestFrom)
|
|
48
|
+
const parsedHarvestTo = parseDate(harvestTo)
|
|
49
|
+
return parsedHarvestFrom !== null && parsedHarvestTo !== null && parsedHarvestFrom <= parsedHarvestTo
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isProducerComplete(value: string | null | undefined): boolean {
|
|
53
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isDocumentsComplete(value: string[] | null | undefined): boolean {
|
|
57
|
+
return Array.isArray(value) && value.length >= 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function computeCompleteness(input: CompletenessInput, context: CompletenessContext = {}): CompletenessResult {
|
|
61
|
+
const completionByDimension: Record<CompletenessDimension, boolean> = {
|
|
62
|
+
origin_country: isOriginCountryComplete(input.originCountry),
|
|
63
|
+
geolocation: isGeolocationComplete(input.geolocation) || (context.activePlotCount ?? 0) > 0,
|
|
64
|
+
quantity: isQuantityComplete(input.quantityKg),
|
|
65
|
+
harvest_period: isHarvestPeriodComplete(input.harvestFrom, input.harvestTo),
|
|
66
|
+
producer: isProducerComplete(input.producerName),
|
|
67
|
+
documents: isDocumentsComplete(input.attachmentIds) || (context.linkedAttachmentCount ?? 0) > 0,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const missingFields = COMPLETENESS_DIMENSIONS.filter((dimension) => !completionByDimension[dimension])
|
|
71
|
+
const metCount = COMPLETENESS_DIMENSIONS.length - missingFields.length
|
|
72
|
+
const score = Math.round((metCount / COMPLETENESS_DIMENSIONS.length) * 100)
|
|
73
|
+
|
|
74
|
+
return { score, missingFields }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function computeSubmissionCompleteness(input: CompletenessInput, context?: CompletenessContext): CompletenessResult {
|
|
78
|
+
return computeCompleteness(input, context)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const HARVEST_CUTOFF_DATE = new Date('2020-12-31T23:59:59.999Z')
|
|
82
|
+
|
|
83
|
+
export function computeHarvestCutoffWarning(
|
|
84
|
+
harvestFrom: Date | string | null | undefined,
|
|
85
|
+
harvestTo: Date | string | null | undefined,
|
|
86
|
+
): 'harvest_before_cutoff' | null {
|
|
87
|
+
const windowEnd = parseDate(harvestTo) ?? parseDate(harvestFrom)
|
|
88
|
+
if (!windowEnd) return null
|
|
89
|
+
return windowEnd.getTime() <= HARVEST_CUTOFF_DATE.getTime() ? 'harvest_before_cutoff' : null
|
|
90
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type DetailReadScope = {
|
|
2
|
+
tenantId: string
|
|
3
|
+
/** Matches the organization scope the list query itself used. */
|
|
4
|
+
organizationFilter: string | { $in: string[] }
|
|
5
|
+
/**
|
|
6
|
+
* Fallback organization for decryption only. `decryptEntitiesWithFallbackScope`
|
|
7
|
+
* prefers each record's own tenant/organization and consults this only when the
|
|
8
|
+
* record carries none, so a single value stays correct for multi-org reads.
|
|
9
|
+
*/
|
|
10
|
+
decryptionOrganizationId: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type DetailReadScopeContext = {
|
|
14
|
+
auth?: { tenantId?: string | null; orgId?: string | null } | null
|
|
15
|
+
organizationIds?: string[] | null
|
|
16
|
+
selectedOrganizationId?: string | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the scope for an `afterList` detail re-fetch (the decryption pass).
|
|
21
|
+
*
|
|
22
|
+
* The list query is scoped by the full `organizationIds` array, so re-fetching by
|
|
23
|
+
* a single organization silently drops decrypted fields for a multi-org user who
|
|
24
|
+
* has not selected one. Returns null when no scope can be established, so callers
|
|
25
|
+
* fail closed rather than reading across the tenant.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveDetailReadScope(ctx: DetailReadScopeContext): DetailReadScope | null {
|
|
28
|
+
const tenantId = ctx.auth?.tenantId ?? null
|
|
29
|
+
if (!tenantId) return null
|
|
30
|
+
|
|
31
|
+
const scopedOrganizationIds = Array.isArray(ctx.organizationIds) && ctx.organizationIds.length
|
|
32
|
+
? Array.from(new Set(ctx.organizationIds))
|
|
33
|
+
: null
|
|
34
|
+
const singleOrganizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
|
|
35
|
+
if (!scopedOrganizationIds && !singleOrganizationId) return null
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
tenantId,
|
|
39
|
+
organizationFilter: scopedOrganizationIds
|
|
40
|
+
? { $in: scopedOrganizationIds }
|
|
41
|
+
: (singleOrganizationId as string),
|
|
42
|
+
decryptionOrganizationId: singleOrganizationId,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export type EudrPlotGeometryType = 'point' | 'polygon'
|
|
2
|
+
|
|
3
|
+
type GeoJsonPosition = [number, number]
|
|
4
|
+
type GeoJsonPoint = { type: 'Point'; coordinates: GeoJsonPosition }
|
|
5
|
+
type GeoJsonPolygon = { type: 'Polygon'; coordinates: GeoJsonPosition[][] }
|
|
6
|
+
type GeoJsonMultiPolygon = { type: 'MultiPolygon'; coordinates: GeoJsonPosition[][][] }
|
|
7
|
+
type GeoJsonSupportedGeometry = GeoJsonPoint | GeoJsonPolygon | GeoJsonMultiPolygon
|
|
8
|
+
|
|
9
|
+
export type GeoJsonFeature = {
|
|
10
|
+
type: 'Feature'
|
|
11
|
+
properties: Record<string, unknown>
|
|
12
|
+
geometry: GeoJsonSupportedGeometry
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type GeometryValidationResult =
|
|
16
|
+
| { ok: true; feature: GeoJsonFeature; plotType: EudrPlotGeometryType; computedAreaHa: number | null; warnings: string[] }
|
|
17
|
+
| { ok: false; errorKey: string }
|
|
18
|
+
|
|
19
|
+
type PositionValidationResult =
|
|
20
|
+
| { ok: true; position: GeoJsonPosition; lowPrecision: boolean }
|
|
21
|
+
| { ok: false; errorKey: 'geometryOutOfRange' }
|
|
22
|
+
|
|
23
|
+
const MAX_GEOMETRY_SIZE = 262_144
|
|
24
|
+
const MAX_IMPORT_SIZE = 1_048_576
|
|
25
|
+
const MAX_FEATURE_COLLECTION_FEATURES = 500
|
|
26
|
+
const EARTH_RADIUS_METERS = 6_371_008.8
|
|
27
|
+
|
|
28
|
+
export const POINT_MAX_AREA_HA = 4
|
|
29
|
+
|
|
30
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
31
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function serializedLength(value: unknown): number | null {
|
|
35
|
+
try {
|
|
36
|
+
const serialized = JSON.stringify(value)
|
|
37
|
+
return typeof serialized === 'string' ? serialized.length : null
|
|
38
|
+
} catch {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function decimalPlaces(value: number): number {
|
|
44
|
+
const decimal = String(value).split(/[eE]/)[0]?.split('.')[1]
|
|
45
|
+
return decimal?.length ?? 0
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validatePosition(input: unknown): PositionValidationResult {
|
|
49
|
+
if (!Array.isArray(input) || input.length < 2) return { ok: false, errorKey: 'geometryOutOfRange' }
|
|
50
|
+
const [longitude, latitude] = input
|
|
51
|
+
if (typeof longitude !== 'number' || typeof latitude !== 'number') return { ok: false, errorKey: 'geometryOutOfRange' }
|
|
52
|
+
if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return { ok: false, errorKey: 'geometryOutOfRange' }
|
|
53
|
+
if (longitude < -180 || longitude > 180 || latitude < -90 || latitude > 90) return { ok: false, errorKey: 'geometryOutOfRange' }
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
position: [longitude, latitude],
|
|
58
|
+
lowPrecision: decimalPlaces(longitude) < 6 || decimalPlaces(latitude) < 6,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function positionsEqual(left: GeoJsonPosition, right: GeoJsonPosition): boolean {
|
|
63
|
+
return left[0] === right[0] && left[1] === right[1]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validateRing(input: unknown): { ok: true; ring: GeoJsonPosition[]; lowPrecision: boolean } | { ok: false; errorKey: string } {
|
|
67
|
+
if (!Array.isArray(input)) return { ok: false, errorKey: 'geometryInvalid' }
|
|
68
|
+
if (input.length < 4) return { ok: false, errorKey: 'geometryRingNotClosed' }
|
|
69
|
+
|
|
70
|
+
const ring: GeoJsonPosition[] = []
|
|
71
|
+
let lowPrecision = false
|
|
72
|
+
for (const candidate of input) {
|
|
73
|
+
const result = validatePosition(candidate)
|
|
74
|
+
if (!result.ok) return result
|
|
75
|
+
ring.push(result.position)
|
|
76
|
+
lowPrecision = lowPrecision || result.lowPrecision
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!positionsEqual(ring[0], ring[ring.length - 1])) return { ok: false, errorKey: 'geometryRingNotClosed' }
|
|
80
|
+
return { ok: true, ring, lowPrecision }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validatePolygonCoordinates(input: unknown): { ok: true; rings: GeoJsonPosition[][]; lowPrecision: boolean } | { ok: false; errorKey: string } {
|
|
84
|
+
if (!Array.isArray(input) || input.length === 0) return { ok: false, errorKey: 'geometryInvalid' }
|
|
85
|
+
|
|
86
|
+
const rings: GeoJsonPosition[][] = []
|
|
87
|
+
let lowPrecision = false
|
|
88
|
+
for (const candidate of input) {
|
|
89
|
+
const result = validateRing(candidate)
|
|
90
|
+
if (!result.ok) return result
|
|
91
|
+
rings.push(result.ring)
|
|
92
|
+
lowPrecision = lowPrecision || result.lowPrecision
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { ok: true, rings, lowPrecision }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function validateGeometry(input: unknown): { ok: true; geometry: GeoJsonSupportedGeometry; lowPrecision: boolean } | { ok: false; errorKey: string } {
|
|
99
|
+
if (!isRecord(input) || typeof input.type !== 'string') return { ok: false, errorKey: 'geometryInvalid' }
|
|
100
|
+
|
|
101
|
+
if (input.type === 'Point') {
|
|
102
|
+
const result = validatePosition(input.coordinates)
|
|
103
|
+
if (!result.ok) return result
|
|
104
|
+
return { ok: true, geometry: { type: 'Point', coordinates: result.position }, lowPrecision: result.lowPrecision }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (input.type === 'Polygon') {
|
|
108
|
+
const result = validatePolygonCoordinates(input.coordinates)
|
|
109
|
+
if (!result.ok) return result
|
|
110
|
+
return { ok: true, geometry: { type: 'Polygon', coordinates: result.rings }, lowPrecision: result.lowPrecision }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (input.type === 'MultiPolygon') {
|
|
114
|
+
if (!Array.isArray(input.coordinates) || input.coordinates.length === 0) return { ok: false, errorKey: 'geometryInvalid' }
|
|
115
|
+
const polygons: GeoJsonPosition[][][] = []
|
|
116
|
+
let lowPrecision = false
|
|
117
|
+
for (const candidate of input.coordinates) {
|
|
118
|
+
const result = validatePolygonCoordinates(candidate)
|
|
119
|
+
if (!result.ok) return result
|
|
120
|
+
polygons.push(result.rings)
|
|
121
|
+
lowPrecision = lowPrecision || result.lowPrecision
|
|
122
|
+
}
|
|
123
|
+
return { ok: true, geometry: { type: 'MultiPolygon', coordinates: polygons }, lowPrecision }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { ok: false, errorKey: 'geometryInvalid' }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function toRadians(value: number): number {
|
|
130
|
+
return (value * Math.PI) / 180
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function ringAreaSquareMeters(ring: GeoJsonPosition[]): number {
|
|
134
|
+
let total = 0
|
|
135
|
+
for (let index = 0; index < ring.length - 1; index += 1) {
|
|
136
|
+
const [longitude1, latitude1] = ring[index]
|
|
137
|
+
const [longitude2, latitude2] = ring[index + 1]
|
|
138
|
+
total += (toRadians(longitude2) - toRadians(longitude1)) * (2 + Math.sin(toRadians(latitude1)) + Math.sin(toRadians(latitude2)))
|
|
139
|
+
}
|
|
140
|
+
return Math.abs((total * EARTH_RADIUS_METERS * EARTH_RADIUS_METERS) / 2)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function polygonAreaSquareMeters(rings: GeoJsonPosition[][]): number {
|
|
144
|
+
const [exterior, ...holes] = rings
|
|
145
|
+
const holesArea = holes.reduce((sum, ring) => sum + ringAreaSquareMeters(ring), 0)
|
|
146
|
+
return Math.max(0, ringAreaSquareMeters(exterior) - holesArea)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function areaHa(geometry: GeoJsonPolygon | GeoJsonMultiPolygon): number {
|
|
150
|
+
// Spherical-excess area is approximate, but is within about 0.5% at normal
|
|
151
|
+
// plot scale and sufficient for the 4-ha EUDR threshold hinting flow.
|
|
152
|
+
const squareMeters = geometry.type === 'Polygon'
|
|
153
|
+
? polygonAreaSquareMeters(geometry.coordinates)
|
|
154
|
+
: geometry.coordinates.reduce((sum, polygon) => sum + polygonAreaSquareMeters(polygon), 0)
|
|
155
|
+
|
|
156
|
+
return Math.round((squareMeters / 10_000) * 10_000) / 10_000
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function validatePlotGeometry(input: unknown): GeometryValidationResult {
|
|
160
|
+
const length = serializedLength(input)
|
|
161
|
+
if (length === null) return { ok: false, errorKey: 'geometryInvalid' }
|
|
162
|
+
if (length > MAX_GEOMETRY_SIZE) return { ok: false, errorKey: 'geometryTooLarge' }
|
|
163
|
+
|
|
164
|
+
const isFeature = isRecord(input) && input.type === 'Feature'
|
|
165
|
+
const geometryInput = isFeature ? input.geometry : input
|
|
166
|
+
const properties = isFeature && isRecord(input.properties) ? input.properties : {}
|
|
167
|
+
const result = validateGeometry(geometryInput)
|
|
168
|
+
if (!result.ok) return result
|
|
169
|
+
|
|
170
|
+
const plotType: EudrPlotGeometryType = result.geometry.type === 'Point' ? 'point' : 'polygon'
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
feature: { type: 'Feature', properties, geometry: result.geometry },
|
|
174
|
+
plotType,
|
|
175
|
+
computedAreaHa: result.geometry.type === 'Point' ? null : areaHa(result.geometry),
|
|
176
|
+
warnings: result.lowPrecision ? ['low_precision'] : [],
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function collectFeatures(featureCollection: unknown): { ok: true; features: unknown[] } | { ok: false; errorKey: string } {
|
|
181
|
+
const length = serializedLength(featureCollection)
|
|
182
|
+
if (length === null) return { ok: false, errorKey: 'geometryInvalid' }
|
|
183
|
+
if (length > MAX_IMPORT_SIZE) return { ok: false, errorKey: 'importTooLarge' }
|
|
184
|
+
|
|
185
|
+
if (!isRecord(featureCollection) || featureCollection.type !== 'FeatureCollection' || !Array.isArray(featureCollection.features)) {
|
|
186
|
+
return { ok: false, errorKey: 'geometryInvalid' }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (featureCollection.features.length > MAX_FEATURE_COLLECTION_FEATURES) return { ok: false, errorKey: 'importTooManyFeatures' }
|
|
190
|
+
return { ok: true, features: featureCollection.features }
|
|
191
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
2
|
+
import { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'
|
|
3
|
+
import { resolveNotificationService } from '../../notifications/lib/notificationService'
|
|
4
|
+
import { notificationTypes } from '../notifications'
|
|
5
|
+
|
|
6
|
+
const logger = createLogger('eudr').child({ component: 'notifications' })
|
|
7
|
+
|
|
8
|
+
export type EudrNotificationTypeId =
|
|
9
|
+
| 'eudr.statement.submitted'
|
|
10
|
+
| 'eudr.statement.reference_issued'
|
|
11
|
+
| 'eudr.statement.withdrawn'
|
|
12
|
+
| 'eudr.risk.non_negligible'
|
|
13
|
+
| 'eudr.mitigation.completed'
|
|
14
|
+
|
|
15
|
+
export type EudrNotificationContainer = {
|
|
16
|
+
resolve: (name: string) => unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type EudrNotificationPayload = {
|
|
20
|
+
tenantId: string
|
|
21
|
+
organizationId: string | null
|
|
22
|
+
entityId: string
|
|
23
|
+
occurredAt: string
|
|
24
|
+
bodyVariables: Record<string, string>
|
|
25
|
+
linkHref: string
|
|
26
|
+
sourceEntityType: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type DeliverEudrNotificationInput = {
|
|
30
|
+
container: EudrNotificationContainer
|
|
31
|
+
typeId: EudrNotificationTypeId
|
|
32
|
+
payload: EudrNotificationPayload
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const REQUIRED_FEATURE_BY_TYPE: Record<EudrNotificationTypeId, string> = {
|
|
36
|
+
'eudr.statement.submitted': 'eudr.statements.manage',
|
|
37
|
+
'eudr.statement.reference_issued': 'eudr.statements.manage',
|
|
38
|
+
'eudr.statement.withdrawn': 'eudr.statements.manage',
|
|
39
|
+
'eudr.risk.non_negligible': 'eudr.risk.manage',
|
|
40
|
+
'eudr.mitigation.completed': 'eudr.risk.manage',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function deliverEudrNotification({
|
|
44
|
+
container,
|
|
45
|
+
typeId,
|
|
46
|
+
payload,
|
|
47
|
+
}: DeliverEudrNotificationInput): Promise<void> {
|
|
48
|
+
let notificationService: ReturnType<typeof resolveNotificationService>
|
|
49
|
+
try {
|
|
50
|
+
notificationService = resolveNotificationService(container)
|
|
51
|
+
} catch (err) {
|
|
52
|
+
logger.debug('Notification service unavailable; skipping EUDR notification', {
|
|
53
|
+
typeId,
|
|
54
|
+
entityId: payload.entityId,
|
|
55
|
+
err,
|
|
56
|
+
})
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const typeDef = notificationTypes.find((candidate) => candidate.type === typeId)
|
|
61
|
+
if (!typeDef) {
|
|
62
|
+
throw new Error(`[internal] EUDR notification type is not registered: ${typeId}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const requiredFeature = REQUIRED_FEATURE_BY_TYPE[typeId]
|
|
66
|
+
const content = buildFeatureNotificationFromType(typeDef, {
|
|
67
|
+
requiredFeature,
|
|
68
|
+
bodyVariables: payload.bodyVariables,
|
|
69
|
+
sourceEntityType: payload.sourceEntityType,
|
|
70
|
+
sourceEntityId: payload.entityId,
|
|
71
|
+
linkHref: payload.linkHref,
|
|
72
|
+
groupKey: `${typeId}:${payload.entityId}:${payload.occurredAt}`,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
await notificationService.createForFeature(
|
|
76
|
+
{
|
|
77
|
+
...content,
|
|
78
|
+
restrictRecipientsToOrganization: true,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
tenantId: payload.tenantId,
|
|
82
|
+
organizationId: payload.organizationId,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
type Translator = (
|
|
2
|
+
key: string,
|
|
3
|
+
fallbackOrParams?: string | Record<string, string | number>,
|
|
4
|
+
params?: Record<string, string | number>,
|
|
5
|
+
) => string
|
|
6
|
+
|
|
7
|
+
const pluralRulesCache = new Map<string, Intl.PluralRules>()
|
|
8
|
+
|
|
9
|
+
function pluralRulesFor(locale: string): Intl.PluralRules {
|
|
10
|
+
const cached = pluralRulesCache.get(locale)
|
|
11
|
+
if (cached) return cached
|
|
12
|
+
let rules: Intl.PluralRules
|
|
13
|
+
try {
|
|
14
|
+
rules = new Intl.PluralRules(locale)
|
|
15
|
+
} catch {
|
|
16
|
+
rules = new Intl.PluralRules('en')
|
|
17
|
+
}
|
|
18
|
+
pluralRulesCache.set(locale, rules)
|
|
19
|
+
return rules
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function translatePlural(
|
|
23
|
+
translate: Translator,
|
|
24
|
+
locale: string,
|
|
25
|
+
baseKey: string,
|
|
26
|
+
count: number,
|
|
27
|
+
params?: Record<string, string | number>,
|
|
28
|
+
): string {
|
|
29
|
+
const category = pluralRulesFor(locale).select(count)
|
|
30
|
+
const mergedParams = { count, ...(params ?? {}) }
|
|
31
|
+
for (const key of [`${baseKey}.${category}`, `${baseKey}.other`, baseKey]) {
|
|
32
|
+
const result = translate(key, mergedParams)
|
|
33
|
+
if (result !== key) return result
|
|
34
|
+
}
|
|
35
|
+
return translate(baseKey, mergedParams)
|
|
36
|
+
}
|