@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
|
@@ -38,6 +38,11 @@ import { parseNumberWithDefault } from '@open-mercato/shared/lib/number'
|
|
|
38
38
|
|
|
39
39
|
const logger = createLogger('query_index').child({ component: 'engine' })
|
|
40
40
|
|
|
41
|
+
/** Operators `buildCfFilterExpression` compiles; anything else yields no predicate. */
|
|
42
|
+
const CF_FILTER_SUPPORTED_OPS = new Set<FilterOp>([
|
|
43
|
+
'eq', 'ne', 'in', 'nin', 'like', 'ilike', 'exists', 'gt', 'gte', 'lt', 'lte',
|
|
44
|
+
])
|
|
45
|
+
|
|
41
46
|
const DECRYPT_CONCURRENCY = 8
|
|
42
47
|
const AUTO_REINDEX_DEBOUNCE_DEFAULT_MS = 30_000
|
|
43
48
|
const AUTO_REINDEX_DEBOUNCE_MAX_SCOPES = 10_000
|
|
@@ -742,9 +747,14 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
742
747
|
return next
|
|
743
748
|
}
|
|
744
749
|
|
|
750
|
+
// Custom-field leaves that belong to an OR disjunct are handled together with
|
|
751
|
+
// the base ones below; applying them here would AND them onto every disjunct.
|
|
752
|
+
const regularCfFilters = cfFilters.filter((filter) => !filter.orGroup)
|
|
753
|
+
const orGroupCfFilters = cfFilters.filter((filter) => filter.orGroup)
|
|
754
|
+
|
|
745
755
|
const applyCfFilters = (q: AnyBuilder): AnyBuilder => {
|
|
746
756
|
let next = q
|
|
747
|
-
for (const filter of
|
|
757
|
+
for (const filter of regularCfFilters) {
|
|
748
758
|
next = this.applyCfFilterAcrossSources(
|
|
749
759
|
next, filter.field, filter.op, filter.value, indexSources, searchRuntime,
|
|
750
760
|
)
|
|
@@ -775,28 +785,60 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
775
785
|
return next
|
|
776
786
|
}
|
|
777
787
|
|
|
788
|
+
// Also used by the optimized count path, which builds from a bare base table with
|
|
789
|
+
// no index/custom-field joins. Emitting a cf predicate there would reference an
|
|
790
|
+
// alias that query has never joined, so this stays safe only while
|
|
791
|
+
// `canOptimizeCount` is false whenever any cf filter exists (see `hasCustomFieldFilters`).
|
|
778
792
|
const applyOrGroupedBaseFilters = (q: AnyBuilder): AnyBuilder => {
|
|
779
|
-
if (orGroupFilters.length === 0) return q
|
|
780
|
-
|
|
793
|
+
if (orGroupFilters.length === 0 && orGroupCfFilters.length === 0) return q
|
|
794
|
+
// `BaseFilter` here is just the normalized-leaf shape; the `cf` bucket holds
|
|
795
|
+
// custom-field leaves, which never resolve to a base column.
|
|
796
|
+
const groups = new Map<string, { base: BaseFilter[]; cf: BaseFilter[] }>()
|
|
797
|
+
const bucketFor = (orGroup: string) => {
|
|
798
|
+
const existing = groups.get(orGroup) ?? { base: [], cf: [] }
|
|
799
|
+
groups.set(orGroup, existing)
|
|
800
|
+
return existing
|
|
801
|
+
}
|
|
781
802
|
for (const filter of orGroupFilters) {
|
|
782
803
|
if (!filter.orGroup) continue
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
804
|
+
bucketFor(filter.orGroup).base.push(filter)
|
|
805
|
+
}
|
|
806
|
+
// Custom-field leaves belong to the same disjunct as the base ones sharing
|
|
807
|
+
// their orGroup, so a mixed `base OR cf` filter unites rather than intersects.
|
|
808
|
+
for (const filter of orGroupCfFilters) {
|
|
809
|
+
if (!filter.orGroup) continue
|
|
810
|
+
bucketFor(filter.orGroup).cf.push(filter)
|
|
786
811
|
}
|
|
787
|
-
|
|
788
|
-
|
|
812
|
+
// A cf leaf that compiles to no predicate is dropped, and a disjunct left with
|
|
813
|
+
// no leaves is dropped too — an empty AND would read as TRUE and widen the result.
|
|
814
|
+
const applicableGroups = Array.from(groups.values())
|
|
815
|
+
.map((group) => ({
|
|
816
|
+
base: group.base,
|
|
817
|
+
cf: group.cf.filter((filter) =>
|
|
818
|
+
this.cfFilterHasPredicate(filter.op, filter.value, indexSources, searchRuntime),
|
|
819
|
+
),
|
|
820
|
+
}))
|
|
821
|
+
.filter((group) => group.base.length > 0 || group.cf.length > 0)
|
|
822
|
+
if (applicableGroups.length === 0) return q
|
|
789
823
|
// Combine all groups in a single WHERE: disjuncts are OR'd together; within
|
|
790
824
|
// each disjunct, fields are AND'd. Building this as separate `.where()` calls
|
|
791
825
|
// would AND the disjuncts (wrong semantics).
|
|
792
|
-
return q.where((eb: any) =>
|
|
793
|
-
|
|
794
|
-
const parts =
|
|
795
|
-
|
|
796
|
-
|
|
826
|
+
return q.where((eb: any) => {
|
|
827
|
+
const disjuncts = applicableGroups.map((groupFilters) => {
|
|
828
|
+
const parts = [
|
|
829
|
+
...groupFilters.base.map((filter) =>
|
|
830
|
+
this.buildBaseFilterExpression(eb, filter, resolveBaseColumn, qualify, entity, searchRuntime),
|
|
831
|
+
),
|
|
832
|
+
...groupFilters.cf.map((filter) =>
|
|
833
|
+
this.buildCfFilterExpression(
|
|
834
|
+
eb, filter.field, filter.op, filter.value, indexSources, searchRuntime,
|
|
835
|
+
),
|
|
836
|
+
),
|
|
837
|
+
]
|
|
797
838
|
return parts.length === 1 ? parts[0] : eb.and(parts)
|
|
798
|
-
})
|
|
799
|
-
|
|
839
|
+
})
|
|
840
|
+
return disjuncts.length === 1 ? disjuncts[0] : eb.or(disjuncts)
|
|
841
|
+
})
|
|
800
842
|
}
|
|
801
843
|
|
|
802
844
|
const applyAliasScopes = async (target: AnyBuilder, aliasName: string): Promise<AnyBuilder> => {
|
|
@@ -1350,37 +1392,44 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1350
1392
|
return sql<string | null>`coalesce(${sql.join(parts, sql`, `)})`
|
|
1351
1393
|
}
|
|
1352
1394
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1395
|
+
/**
|
|
1396
|
+
* Build a custom-field predicate as a standalone expression.
|
|
1397
|
+
*
|
|
1398
|
+
* Returning an expression rather than mutating a builder is what lets OR-grouped
|
|
1399
|
+
* cf leaves be combined with the base-column ones inside a single `WHERE`
|
|
1400
|
+
* (#5039). `null` means "no predicate" — an empty search-token set or an
|
|
1401
|
+
* unsupported operator — and callers must treat that as the leaf being absent.
|
|
1402
|
+
*/
|
|
1403
|
+
private buildCfFilterExpression(
|
|
1404
|
+
eb: any,
|
|
1355
1405
|
key: string,
|
|
1356
1406
|
op: FilterOp,
|
|
1357
1407
|
value: unknown,
|
|
1358
1408
|
sources: IndexDocSource[],
|
|
1359
1409
|
search?: SearchRuntime
|
|
1360
|
-
):
|
|
1361
|
-
if (!sources.length) return
|
|
1410
|
+
): any | null {
|
|
1411
|
+
if (!sources.length) return null
|
|
1362
1412
|
if ((op === 'like' || op === 'ilike') && search?.enabled && typeof value === 'string') {
|
|
1363
1413
|
const tokens = tokenizeText(String(value), search.config)
|
|
1364
1414
|
const hashes = tokens.hashes
|
|
1365
|
-
if (hashes.length) {
|
|
1366
|
-
const applied = this.applyMultiSourceSearchExists(builder, sources, key, hashes, search)
|
|
1367
|
-
this.logSearchDebug('search:cf-filter-across', {
|
|
1368
|
-
entity: sources.map((src) => src.entityId),
|
|
1369
|
-
field: key, tokens: tokens.tokens, hashes, applied,
|
|
1370
|
-
tenantId: search.tenantId ?? null, organizationScope: search.organizationScope,
|
|
1371
|
-
})
|
|
1372
|
-
if (applied.builder !== builder) return applied.builder
|
|
1373
|
-
} else {
|
|
1415
|
+
if (!hashes.length) {
|
|
1374
1416
|
this.logSearchDebug('search:cf-skip-empty-hashes', {
|
|
1375
1417
|
entity: sources.map((src) => src.entityId), field: key, value,
|
|
1376
1418
|
})
|
|
1419
|
+
return null
|
|
1377
1420
|
}
|
|
1378
|
-
|
|
1421
|
+
const expression = this.buildMultiSourceSearchExists(eb, sources, key, hashes, search)
|
|
1422
|
+
this.logSearchDebug('search:cf-filter-across', {
|
|
1423
|
+
entity: sources.map((src) => src.entityId),
|
|
1424
|
+
field: key, tokens: tokens.tokens, hashes, applied: expression !== null,
|
|
1425
|
+
tenantId: search.tenantId ?? null, organizationScope: search.organizationScope,
|
|
1426
|
+
})
|
|
1427
|
+
return expression
|
|
1379
1428
|
}
|
|
1380
1429
|
|
|
1381
1430
|
const textExpr = this.buildCfTextExprSql(key, sources)
|
|
1382
1431
|
const jsonExpr = this.buildCfJsonExprSql(key, sources)
|
|
1383
|
-
if (!textExpr || !jsonExpr) return
|
|
1432
|
+
if (!textExpr || !jsonExpr) return null
|
|
1384
1433
|
|
|
1385
1434
|
const arrContains = (val: unknown) => sql<boolean>`${jsonExpr} @> ${JSON.stringify([val])}::jsonb`
|
|
1386
1435
|
|
|
@@ -1389,58 +1438,98 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1389
1438
|
// An unset custom field has no array element to contain, so the
|
|
1390
1439
|
// arrContains branch cannot match it — compare the text value only.
|
|
1391
1440
|
return value === null
|
|
1392
|
-
?
|
|
1393
|
-
:
|
|
1441
|
+
? sql<boolean>`${textExpr} is null`
|
|
1442
|
+
: eb.or([
|
|
1394
1443
|
sql<boolean>`${textExpr} = ${value}`,
|
|
1395
1444
|
arrContains(value),
|
|
1396
|
-
])
|
|
1445
|
+
])
|
|
1397
1446
|
case 'ne':
|
|
1398
1447
|
return value === null
|
|
1399
|
-
?
|
|
1400
|
-
:
|
|
1448
|
+
? sql<boolean>`${textExpr} is not null`
|
|
1449
|
+
: sql<boolean>`${textExpr} <> ${value}`
|
|
1401
1450
|
case 'in': {
|
|
1402
1451
|
const values = this.toArray(value)
|
|
1403
|
-
return
|
|
1452
|
+
return eb.or(
|
|
1404
1453
|
values.flatMap((val) => [
|
|
1405
1454
|
sql<boolean>`${textExpr} = ${val}`,
|
|
1406
1455
|
arrContains(val),
|
|
1407
1456
|
])
|
|
1408
|
-
)
|
|
1457
|
+
)
|
|
1409
1458
|
}
|
|
1410
1459
|
case 'nin': {
|
|
1411
1460
|
const values = this.toArray(value)
|
|
1412
|
-
return
|
|
1461
|
+
return sql<boolean>`${textExpr} not in (${sql.join(values.map((v) => sql`${v}`), sql`, `)})`
|
|
1413
1462
|
}
|
|
1414
1463
|
case 'like':
|
|
1415
|
-
return
|
|
1464
|
+
return sql<boolean>`${textExpr} like ${value}`
|
|
1416
1465
|
case 'ilike':
|
|
1417
|
-
return
|
|
1466
|
+
return sql<boolean>`${textExpr} ilike ${value}`
|
|
1418
1467
|
case 'exists':
|
|
1419
1468
|
return value
|
|
1420
|
-
?
|
|
1421
|
-
:
|
|
1469
|
+
? sql<boolean>`${textExpr} is not null`
|
|
1470
|
+
: sql<boolean>`${textExpr} is null`
|
|
1422
1471
|
case 'gt':
|
|
1423
1472
|
case 'gte':
|
|
1424
1473
|
case 'lt':
|
|
1425
1474
|
case 'lte': {
|
|
1426
1475
|
const operator = sql.raw(op === 'gt' ? '>' : op === 'gte' ? '>=' : op === 'lt' ? '<' : '<=')
|
|
1427
|
-
return
|
|
1476
|
+
return sql<boolean>`${textExpr} ${operator} ${value}`
|
|
1428
1477
|
}
|
|
1429
1478
|
default:
|
|
1430
|
-
return
|
|
1479
|
+
return null
|
|
1431
1480
|
}
|
|
1432
1481
|
}
|
|
1433
1482
|
|
|
1434
|
-
/**
|
|
1435
|
-
|
|
1483
|
+
/**
|
|
1484
|
+
* Whether `buildCfFilterExpression` will produce a predicate for this leaf.
|
|
1485
|
+
*
|
|
1486
|
+
* Decidable without an ExpressionBuilder, which lets callers skip the `.where()`
|
|
1487
|
+
* entirely (rather than emitting a tautology) and lets the OR-group builder drop
|
|
1488
|
+
* leaves before it has to decide whether a disjunct survives.
|
|
1489
|
+
*/
|
|
1490
|
+
private cfFilterHasPredicate(
|
|
1491
|
+
op: FilterOp,
|
|
1492
|
+
value: unknown,
|
|
1493
|
+
sources: IndexDocSource[],
|
|
1494
|
+
search?: SearchRuntime,
|
|
1495
|
+
): boolean {
|
|
1496
|
+
if (!sources.length) return false
|
|
1497
|
+
if ((op === 'like' || op === 'ilike') && search?.enabled && typeof value === 'string') {
|
|
1498
|
+
return tokenizeText(String(value), search.config).hashes.length > 0
|
|
1499
|
+
}
|
|
1500
|
+
return CF_FILTER_SUPPORTED_OPS.has(op)
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
private applyCfFilterAcrossSources(
|
|
1436
1504
|
builder: AnyBuilder,
|
|
1505
|
+
key: string,
|
|
1506
|
+
op: FilterOp,
|
|
1507
|
+
value: unknown,
|
|
1508
|
+
sources: IndexDocSource[],
|
|
1509
|
+
search?: SearchRuntime
|
|
1510
|
+
): AnyBuilder {
|
|
1511
|
+
if (!this.cfFilterHasPredicate(op, value, sources, search)) {
|
|
1512
|
+
// Preserve the pre-existing behaviour of dropping a leaf we cannot compile.
|
|
1513
|
+
if (sources.length && (op === 'like' || op === 'ilike') && search?.enabled && typeof value === 'string') {
|
|
1514
|
+
this.logSearchDebug('search:cf-skip-empty-hashes', {
|
|
1515
|
+
entity: sources.map((src) => src.entityId), field: key, value,
|
|
1516
|
+
})
|
|
1517
|
+
}
|
|
1518
|
+
return builder
|
|
1519
|
+
}
|
|
1520
|
+
return builder.where((eb: any) => this.buildCfFilterExpression(eb, key, op, value, sources, search))
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/** Build a search-token EXISTS predicate across multiple sources (OR-joined). */
|
|
1524
|
+
private buildMultiSourceSearchExists(
|
|
1525
|
+
eb: any,
|
|
1437
1526
|
sources: IndexDocSource[],
|
|
1438
1527
|
key: string,
|
|
1439
1528
|
hashes: string[],
|
|
1440
1529
|
search: SearchRuntime,
|
|
1441
|
-
):
|
|
1442
|
-
if (!sources.length || !hashes.length) return
|
|
1443
|
-
|
|
1530
|
+
): any | null {
|
|
1531
|
+
if (!sources.length || !hashes.length) return null
|
|
1532
|
+
return eb.or(
|
|
1444
1533
|
sources.map((source) =>
|
|
1445
1534
|
eb.exists(this.buildSearchTokensSub(eb, {
|
|
1446
1535
|
entity: String(source.entityId),
|
|
@@ -1451,8 +1540,7 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1451
1540
|
mintAlias: search.mintAlias,
|
|
1452
1541
|
}))
|
|
1453
1542
|
)
|
|
1454
|
-
)
|
|
1455
|
-
return { builder: next, applied: true }
|
|
1543
|
+
)
|
|
1456
1544
|
}
|
|
1457
1545
|
|
|
1458
1546
|
/** Construct a search-token EXISTS subquery using the given ExpressionBuilder. */
|
|
@@ -22,6 +22,7 @@ import { parseScopedCommandInput, resolveCrudRecordId } from '../utils'
|
|
|
22
22
|
import { documentUpdateSchema } from '../../commands/documents'
|
|
23
23
|
import { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'
|
|
24
24
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
25
|
+
import { parseIdsParam } from '@open-mercato/shared/lib/crud/ids'
|
|
25
26
|
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
26
27
|
import { recalculateOrderTotalsForDisplay } from '../../commands/returns'
|
|
27
28
|
import { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'
|
|
@@ -89,6 +90,18 @@ const listSchema = z
|
|
|
89
90
|
id: z.string().uuid().optional(),
|
|
90
91
|
customerId: z.string().uuid().optional(),
|
|
91
92
|
channelId: z.string().uuid().optional(),
|
|
93
|
+
channelIds: z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe(
|
|
97
|
+
'Comma-separated sales channel uuids; matches documents on any of them. Capped at 200 ids, malformed entries are dropped. Ignored when channelId is supplied; combines with channelIdsEmpty.',
|
|
98
|
+
),
|
|
99
|
+
channelIdsEmpty: z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe(
|
|
103
|
+
'Boolean token; matches documents with no sales channel. Ignored when channelId is supplied; combines with channelIds.',
|
|
104
|
+
),
|
|
92
105
|
lineItemCountMin: z.coerce.number().min(0).optional(),
|
|
93
106
|
lineItemCountMax: z.coerce.number().min(0).optional(),
|
|
94
107
|
totalNetMin: z.coerce.number().optional(),
|
|
@@ -117,8 +130,24 @@ function buildFilters(query: ListQuery, numberColumn: string, kind: DocumentKind
|
|
|
117
130
|
if (query.customerId) {
|
|
118
131
|
filters.customer_entity_id = { $eq: query.customerId }
|
|
119
132
|
}
|
|
133
|
+
// Singular wins over plural, mirroring how `api/channels/route.ts` resolves `id` before `ids`.
|
|
134
|
+
// An all-malformed `channelIds` narrows to no channel filter rather than an empty-set filter, so
|
|
135
|
+
// a typo returns the unfiltered page instead of silently returning zero rows.
|
|
120
136
|
if (query.channelId) {
|
|
121
137
|
filters.channel_id = { $eq: query.channelId }
|
|
138
|
+
} else {
|
|
139
|
+
const channelIds = parseIdsParam(query.channelIds)
|
|
140
|
+
const wantsUnassigned = parseBooleanToken(query.channelIdsEmpty) === true
|
|
141
|
+
if (channelIds.length && wantsUnassigned) {
|
|
142
|
+
// A channel multi-select with an "(No channel)" entry produces both at once, so they combine
|
|
143
|
+
// rather than one silently dropping the other. `filters.$or` is a single key — a future filter
|
|
144
|
+
// that also needs `$or` would clobber this one; nothing else in this factory writes it today.
|
|
145
|
+
filters.$or = [{ channel_id: { $in: channelIds } }, { channel_id: { $exists: false } }]
|
|
146
|
+
} else if (wantsUnassigned) {
|
|
147
|
+
filters.channel_id = { $exists: false }
|
|
148
|
+
} else if (channelIds.length) {
|
|
149
|
+
filters.channel_id = { $in: channelIds }
|
|
150
|
+
}
|
|
122
151
|
}
|
|
123
152
|
const lineRange: Record<string, number> = {}
|
|
124
153
|
if (typeof query.lineItemCountMin === 'number') lineRange.$gte = query.lineItemCountMin
|
|
@@ -3632,6 +3632,22 @@ function normalizeTagIds(tags?: Array<string | null | undefined>): string[] {
|
|
|
3632
3632
|
return Array.from(set);
|
|
3633
3633
|
}
|
|
3634
3634
|
|
|
3635
|
+
// A loaded assignment exposes its tag as an entity, an unwrapped id or the raw
|
|
3636
|
+
// column depending on how it was fetched; the snapshot loaders read it the same
|
|
3637
|
+
// defensive way. Returns null when the id cannot be determined, which callers
|
|
3638
|
+
// must treat as "unrecognised" rather than as a match.
|
|
3639
|
+
function readAssignmentTagId(assignment: {
|
|
3640
|
+
tag?: unknown;
|
|
3641
|
+
}): string | null {
|
|
3642
|
+
const tag = (assignment as { tag?: unknown }).tag;
|
|
3643
|
+
if (typeof tag === "string") return tag;
|
|
3644
|
+
return (
|
|
3645
|
+
(tag as { id?: string } | null | undefined)?.id ??
|
|
3646
|
+
(assignment as { tag_id?: string }).tag_id ??
|
|
3647
|
+
null
|
|
3648
|
+
);
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3635
3651
|
function buildTagChange(
|
|
3636
3652
|
beforeTags: TagAssignmentSnapshot[] | undefined,
|
|
3637
3653
|
afterTags: TagAssignmentSnapshot[] | undefined,
|
|
@@ -3719,6 +3735,28 @@ function buildDocumentUpdateChangeKeys(kind: SalesDocumentKind, input: DocumentU
|
|
|
3719
3735
|
return Array.from(keys);
|
|
3720
3736
|
}
|
|
3721
3737
|
|
|
3738
|
+
// Writes only the difference between the stored and the incoming tag set, and
|
|
3739
|
+
// no-ops when they are equal. The previous delete-all-then-reinsert rewrote an
|
|
3740
|
+
// unchanged set on every save that carried `tags`: on a large
|
|
3741
|
+
// sales_document_tag_assignments that delete was the dominant cost of a bulk
|
|
3742
|
+
// import, and it also reset each assignment's id/created_at, churned the unique
|
|
3743
|
+
// index and left two dead tuples per document per save.
|
|
3744
|
+
//
|
|
3745
|
+
// Semantics are unchanged — a tag removed out of band still differs from the
|
|
3746
|
+
// incoming set and is still restored. Removals go through the UnitOfWork rather
|
|
3747
|
+
// than nativeDelete because the assignments are loaded (managed) here; a native
|
|
3748
|
+
// delete would leave stale identities behind. The add and remove sets are
|
|
3749
|
+
// disjoint by construction, so MikroORM's insert-before-delete commit order can
|
|
3750
|
+
// never transiently violate the unique (tag_id, document_id, document_kind).
|
|
3751
|
+
//
|
|
3752
|
+
// The assignment read below is deliberately scoped by document only, with no
|
|
3753
|
+
// organization/tenant predicate — unlike the display reads in
|
|
3754
|
+
// api/documents/factory.ts, and unlike the tag-scope check above. It has to
|
|
3755
|
+
// match the scope of the unique constraint it reconciles against, which spans
|
|
3756
|
+
// (tag_id, document_id, document_kind) and no scope columns. Narrowing the read
|
|
3757
|
+
// would hide a mis-scoped row from the diff, put its tag in the add set, and
|
|
3758
|
+
// turn the insert into a unique-constraint violation. The document itself is
|
|
3759
|
+
// already scope-checked by the caller before this runs.
|
|
3722
3760
|
async function syncSalesDocumentTags(
|
|
3723
3761
|
em: EntityManager,
|
|
3724
3762
|
params: {
|
|
@@ -3731,29 +3769,38 @@ async function syncSalesDocumentTags(
|
|
|
3731
3769
|
) {
|
|
3732
3770
|
if (params.tagIds === undefined) return;
|
|
3733
3771
|
const tagIds = normalizeTagIds(params.tagIds);
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
}
|
|
3741
|
-
const tagsInScope = await em.find(SalesDocumentTag, {
|
|
3742
|
-
id: { $in: tagIds },
|
|
3743
|
-
organizationId: params.organizationId,
|
|
3744
|
-
tenantId: params.tenantId,
|
|
3745
|
-
});
|
|
3746
|
-
if (tagsInScope.length !== tagIds.length) {
|
|
3747
|
-
throw new CrudHttpError(400, {
|
|
3748
|
-
error: "One or more tags not found for this scope",
|
|
3772
|
+
const byId = new Map<string, SalesDocumentTag>();
|
|
3773
|
+
if (tagIds.length > 0) {
|
|
3774
|
+
const tagsInScope = await em.find(SalesDocumentTag, {
|
|
3775
|
+
id: { $in: tagIds },
|
|
3776
|
+
organizationId: params.organizationId,
|
|
3777
|
+
tenantId: params.tenantId,
|
|
3749
3778
|
});
|
|
3779
|
+
if (tagsInScope.length !== tagIds.length) {
|
|
3780
|
+
throw new CrudHttpError(400, {
|
|
3781
|
+
error: "One or more tags not found for this scope",
|
|
3782
|
+
});
|
|
3783
|
+
}
|
|
3784
|
+
tagsInScope.forEach((tag) => byId.set(tag.id, tag));
|
|
3750
3785
|
}
|
|
3751
|
-
const
|
|
3752
|
-
await em.nativeDelete(SalesDocumentTagAssignment, {
|
|
3786
|
+
const existing = await em.find(SalesDocumentTagAssignment, {
|
|
3753
3787
|
documentId: params.documentId,
|
|
3754
3788
|
documentKind: params.kind,
|
|
3755
3789
|
});
|
|
3756
|
-
|
|
3790
|
+
const existingByTagId = new Map<string, (typeof existing)[number]>();
|
|
3791
|
+
existing.forEach((assignment) => {
|
|
3792
|
+
const tagId = readAssignmentTagId(assignment);
|
|
3793
|
+
if (tagId) existingByTagId.set(tagId, assignment);
|
|
3794
|
+
});
|
|
3795
|
+
const wanted = new Set(tagIds);
|
|
3796
|
+
const toRemove = existing.filter((assignment) => {
|
|
3797
|
+
const tagId = readAssignmentTagId(assignment);
|
|
3798
|
+
return tagId === null || !wanted.has(tagId);
|
|
3799
|
+
});
|
|
3800
|
+
const toAdd = tagIds.filter((tagId) => !existingByTagId.has(tagId));
|
|
3801
|
+
if (toRemove.length === 0 && toAdd.length === 0) return;
|
|
3802
|
+
toRemove.forEach((assignment) => em.remove(assignment));
|
|
3803
|
+
for (const tagId of toAdd) {
|
|
3757
3804
|
const tag = byId.get(tagId);
|
|
3758
3805
|
if (!tag) continue;
|
|
3759
3806
|
const assignment = em.create(SalesDocumentTagAssignment, {
|
|
@@ -1947,6 +1947,7 @@ export class SalesDocumentTag {
|
|
|
1947
1947
|
|
|
1948
1948
|
@Entity({ tableName: 'sales_document_tag_assignments' })
|
|
1949
1949
|
@Index({ name: 'sales_document_tag_assignments_scope_idx', properties: ['organizationId', 'tenantId'] })
|
|
1950
|
+
@Index({ name: 'sales_document_tag_assignments_document_idx', properties: ['documentId'] })
|
|
1950
1951
|
@Unique({
|
|
1951
1952
|
name: 'sales_document_tag_assignments_unique',
|
|
1952
1953
|
properties: ['tag', 'documentId', 'documentKind'],
|
|
@@ -2341,6 +2341,16 @@
|
|
|
2341
2341
|
"primary": false,
|
|
2342
2342
|
"unique": false
|
|
2343
2343
|
},
|
|
2344
|
+
{
|
|
2345
|
+
"columnNames": [
|
|
2346
|
+
"document_id"
|
|
2347
|
+
],
|
|
2348
|
+
"composite": false,
|
|
2349
|
+
"constraint": false,
|
|
2350
|
+
"keyName": "sales_document_tag_assignments_document_idx",
|
|
2351
|
+
"primary": false,
|
|
2352
|
+
"unique": false
|
|
2353
|
+
},
|
|
2344
2354
|
{
|
|
2345
2355
|
"columnNames": [
|
|
2346
2356
|
"tag_id",
|
package/src/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
// sales_document_tag_assignments had no index whose leading column is
|
|
4
|
+
// document_id: the table carries PK(id), (organization_id, tenant_id) and the
|
|
5
|
+
// unique (tag_id, document_id, document_kind) — and the unique index leads with
|
|
6
|
+
// tag_id, which a document-scoped predicate never supplies. Every read and
|
|
7
|
+
// write that resolves a document's tags therefore degraded to a sequential scan
|
|
8
|
+
// as the table grew: syncSalesDocumentTags' per-save delete
|
|
9
|
+
// (commands/documents.ts) and the order/quote grid's
|
|
10
|
+
// `left join ... on id = document_id` (api/documents/factory.ts) alike. On a
|
|
11
|
+
// 2.85M-row / 868MB production table the delete measured 139.9 ms — a full
|
|
12
|
+
// table scan to remove the two rows belonging to one order.
|
|
13
|
+
//
|
|
14
|
+
// document_kind is deliberately omitted. sales_orders.id and sales_quotes.id
|
|
15
|
+
// are both gen_random_uuid() from their own tables, so a document_id already
|
|
16
|
+
// determines its kind; the second column adds no selectivity for ~30% more
|
|
17
|
+
// index and document_kind is simply rechecked over the rows the seek returns.
|
|
18
|
+
// The unique (tag_id, document_id, document_kind) index stays as-is — its
|
|
19
|
+
// leading tag_id is load-bearing for sales.tags.delete (commands/tags.ts), for
|
|
20
|
+
// the grid's tag filter and for the foreign-key check. Both column orders are
|
|
21
|
+
// needed; two indexes is correct.
|
|
22
|
+
//
|
|
23
|
+
// The table is high-churn, so the index is built CONCURRENTLY to avoid blocking
|
|
24
|
+
// writes during the build. CREATE INDEX CONCURRENTLY cannot run inside a
|
|
25
|
+
// transaction, hence isTransactional() => false; the migration runner applies
|
|
26
|
+
// migrations one-by-one, so this opt-out is safe. Drop first so retrying a
|
|
27
|
+
// failed concurrent build removes PostgreSQL's invalid index stub instead of
|
|
28
|
+
// letting IF NOT EXISTS silently accept it: a half-built concurrent index is
|
|
29
|
+
// left INVALID, the planner ignores it, and IF NOT EXISTS would report success
|
|
30
|
+
// while the table stays unindexed.
|
|
31
|
+
//
|
|
32
|
+
// Both halves follow Migration20260731105052_query_index, which builds
|
|
33
|
+
// concurrently and drops first for the same reason. The earlier
|
|
34
|
+
// Migration20260611103000_query_index builds concurrently but uses IF NOT
|
|
35
|
+
// EXISTS without the drop — cited here only so a reader comparing the two knows
|
|
36
|
+
// the divergence is deliberate rather than an oversight.
|
|
37
|
+
//
|
|
38
|
+
// On a database that does not have the index yet — every fresh install — the
|
|
39
|
+
// drop is a no-op and there is no window without it. An operator who already
|
|
40
|
+
// created this index out of band should expect it to be rebuilt here, and the
|
|
41
|
+
// document-scoped scans to fall back to their pre-index cost until the
|
|
42
|
+
// concurrent build finishes; on a large table, run this while the writers that
|
|
43
|
+
// depend on it are paused.
|
|
44
|
+
export class Migration20260806120000_sales_tag_assignment_document_idx extends Migration {
|
|
45
|
+
|
|
46
|
+
override isTransactional(): boolean {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override up(): void | Promise<void> {
|
|
51
|
+
this.addSql(`drop index concurrently if exists "sales_document_tag_assignments_document_idx";`);
|
|
52
|
+
this.addSql(`create index concurrently "sales_document_tag_assignments_document_idx" on "sales_document_tag_assignments" ("document_id");`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override down(): void | Promise<void> {
|
|
56
|
+
this.addSql(`drop index concurrently if exists "sales_document_tag_assignments_document_idx";`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|