@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.
Files changed (402) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/eudr_due_diligence_statement/index.js +47 -0
  3. package/dist/generated/entities/eudr_due_diligence_statement/index.js.map +7 -0
  4. package/dist/generated/entities/eudr_evidence_submission/index.js +51 -0
  5. package/dist/generated/entities/eudr_evidence_submission/index.js.map +7 -0
  6. package/dist/generated/entities/eudr_mitigation_action/index.js +31 -0
  7. package/dist/generated/entities/eudr_mitigation_action/index.js.map +7 -0
  8. package/dist/generated/entities/eudr_plot/index.js +39 -0
  9. package/dist/generated/entities/eudr_plot/index.js.map +7 -0
  10. package/dist/generated/entities/eudr_product_mapping/index.js +31 -0
  11. package/dist/generated/entities/eudr_product_mapping/index.js.map +7 -0
  12. package/dist/generated/entities/eudr_risk_assessment/index.js +35 -0
  13. package/dist/generated/entities/eudr_risk_assessment/index.js.map +7 -0
  14. package/dist/generated/entities.ids.generated.js +9 -0
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +120 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/modules/attachments/fields/attachment.js +5 -1
  19. package/dist/modules/attachments/fields/attachment.js.map +2 -2
  20. package/dist/modules/attachments/lib/crud.js +3 -1
  21. package/dist/modules/attachments/lib/crud.js.map +2 -2
  22. package/dist/modules/eudr/acl.js +43 -0
  23. package/dist/modules/eudr/acl.js.map +7 -0
  24. package/dist/modules/eudr/ai-agents.js +205 -0
  25. package/dist/modules/eudr/ai-agents.js.map +7 -0
  26. package/dist/modules/eudr/ai-tools/compliance-pack.js +495 -0
  27. package/dist/modules/eudr/ai-tools/compliance-pack.js.map +7 -0
  28. package/dist/modules/eudr/ai-tools/types.js +10 -0
  29. package/dist/modules/eudr/ai-tools/types.js.map +7 -0
  30. package/dist/modules/eudr/ai-tools.js +10 -0
  31. package/dist/modules/eudr/ai-tools.js.map +7 -0
  32. package/dist/modules/eudr/api/dashboard/widgets/compliance-overview/route.js +386 -0
  33. package/dist/modules/eudr/api/dashboard/widgets/compliance-overview/route.js.map +7 -0
  34. package/dist/modules/eudr/api/evidence-submissions/route.js +318 -0
  35. package/dist/modules/eudr/api/evidence-submissions/route.js.map +7 -0
  36. package/dist/modules/eudr/api/mitigation-actions/route.js +249 -0
  37. package/dist/modules/eudr/api/mitigation-actions/route.js.map +7 -0
  38. package/dist/modules/eudr/api/openapi.js +27 -0
  39. package/dist/modules/eudr/api/openapi.js.map +7 -0
  40. package/dist/modules/eudr/api/plots/import/route.js +294 -0
  41. package/dist/modules/eudr/api/plots/import/route.js.map +7 -0
  42. package/dist/modules/eudr/api/plots/route.js +279 -0
  43. package/dist/modules/eudr/api/plots/route.js.map +7 -0
  44. package/dist/modules/eudr/api/product-mappings/route.js +257 -0
  45. package/dist/modules/eudr/api/product-mappings/route.js.map +7 -0
  46. package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js +298 -0
  47. package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +7 -0
  48. package/dist/modules/eudr/api/product-mappings/suggestions/route.js +132 -0
  49. package/dist/modules/eudr/api/product-mappings/suggestions/route.js.map +7 -0
  50. package/dist/modules/eudr/api/reports/annual/route.js +266 -0
  51. package/dist/modules/eudr/api/reports/annual/route.js.map +7 -0
  52. package/dist/modules/eudr/api/risk-assessments/route.js +300 -0
  53. package/dist/modules/eudr/api/risk-assessments/route.js.map +7 -0
  54. package/dist/modules/eudr/api/statements/[id]/export/route.js +631 -0
  55. package/dist/modules/eudr/api/statements/[id]/export/route.js.map +7 -0
  56. package/dist/modules/eudr/api/statements/[id]/readiness/route.js +111 -0
  57. package/dist/modules/eudr/api/statements/[id]/readiness/route.js.map +7 -0
  58. package/dist/modules/eudr/api/statements/route.js +314 -0
  59. package/dist/modules/eudr/api/statements/route.js.map +7 -0
  60. package/dist/modules/eudr/api/suppliers/compliance/route.js +173 -0
  61. package/dist/modules/eudr/api/suppliers/compliance/route.js.map +7 -0
  62. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.js +468 -0
  63. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.js.map +7 -0
  64. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js +16 -0
  65. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js.map +7 -0
  66. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.js +351 -0
  67. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.js.map +7 -0
  68. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js +17 -0
  69. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js.map +7 -0
  70. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js +313 -0
  71. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js.map +7 -0
  72. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js +16 -0
  73. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js.map +7 -0
  74. package/dist/modules/eudr/backend/eudr/page.js +360 -0
  75. package/dist/modules/eudr/backend/eudr/page.js.map +7 -0
  76. package/dist/modules/eudr/backend/eudr/page.meta.js +16 -0
  77. package/dist/modules/eudr/backend/eudr/page.meta.js.map +7 -0
  78. package/dist/modules/eudr/backend/eudr/plots/[id]/page.js +346 -0
  79. package/dist/modules/eudr/backend/eudr/plots/[id]/page.js.map +7 -0
  80. package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js +16 -0
  81. package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js.map +7 -0
  82. package/dist/modules/eudr/backend/eudr/plots/create/page.js +253 -0
  83. package/dist/modules/eudr/backend/eudr/plots/create/page.js.map +7 -0
  84. package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js +17 -0
  85. package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js.map +7 -0
  86. package/dist/modules/eudr/backend/eudr/plots/page.js +386 -0
  87. package/dist/modules/eudr/backend/eudr/plots/page.js.map +7 -0
  88. package/dist/modules/eudr/backend/eudr/plots/page.meta.js +16 -0
  89. package/dist/modules/eudr/backend/eudr/plots/page.meta.js.map +7 -0
  90. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.js +253 -0
  91. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.js.map +7 -0
  92. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js +16 -0
  93. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js.map +7 -0
  94. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.js +174 -0
  95. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.js.map +7 -0
  96. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js +17 -0
  97. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js.map +7 -0
  98. package/dist/modules/eudr/backend/eudr/product-mappings/page.js +306 -0
  99. package/dist/modules/eudr/backend/eudr/product-mappings/page.js.map +7 -0
  100. package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js +16 -0
  101. package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js.map +7 -0
  102. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.js +335 -0
  103. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.js.map +7 -0
  104. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js +16 -0
  105. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js.map +7 -0
  106. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.js +220 -0
  107. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.js.map +7 -0
  108. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js +17 -0
  109. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js.map +7 -0
  110. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js +324 -0
  111. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js.map +7 -0
  112. package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js +16 -0
  113. package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js.map +7 -0
  114. package/dist/modules/eudr/backend/eudr/statements/[id]/page.js +648 -0
  115. package/dist/modules/eudr/backend/eudr/statements/[id]/page.js.map +7 -0
  116. package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js +16 -0
  117. package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js.map +7 -0
  118. package/dist/modules/eudr/backend/eudr/statements/create/page.js +432 -0
  119. package/dist/modules/eudr/backend/eudr/statements/create/page.js.map +7 -0
  120. package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js +17 -0
  121. package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js.map +7 -0
  122. package/dist/modules/eudr/backend/eudr/statements/page.js +309 -0
  123. package/dist/modules/eudr/backend/eudr/statements/page.js.map +7 -0
  124. package/dist/modules/eudr/backend/eudr/statements/page.meta.js +16 -0
  125. package/dist/modules/eudr/backend/eudr/statements/page.meta.js.map +7 -0
  126. package/dist/modules/eudr/ce.js +56 -0
  127. package/dist/modules/eudr/ce.js.map +7 -0
  128. package/dist/modules/eudr/commands/evidence-submissions.js +571 -0
  129. package/dist/modules/eudr/commands/evidence-submissions.js.map +7 -0
  130. package/dist/modules/eudr/commands/index.js +7 -0
  131. package/dist/modules/eudr/commands/index.js.map +7 -0
  132. package/dist/modules/eudr/commands/lifecycle-events.js +19 -0
  133. package/dist/modules/eudr/commands/lifecycle-events.js.map +7 -0
  134. package/dist/modules/eudr/commands/mitigation-actions.js +488 -0
  135. package/dist/modules/eudr/commands/mitigation-actions.js.map +7 -0
  136. package/dist/modules/eudr/commands/plots.js +517 -0
  137. package/dist/modules/eudr/commands/plots.js.map +7 -0
  138. package/dist/modules/eudr/commands/product-mappings.js +468 -0
  139. package/dist/modules/eudr/commands/product-mappings.js.map +7 -0
  140. package/dist/modules/eudr/commands/risk-assessments.js +599 -0
  141. package/dist/modules/eudr/commands/risk-assessments.js.map +7 -0
  142. package/dist/modules/eudr/commands/statements.js +747 -0
  143. package/dist/modules/eudr/commands/statements.js.map +7 -0
  144. package/dist/modules/eudr/components/CountrySelectField.js +64 -0
  145. package/dist/modules/eudr/components/CountrySelectField.js.map +7 -0
  146. package/dist/modules/eudr/components/GeometryInput.js +133 -0
  147. package/dist/modules/eudr/components/GeometryInput.js.map +7 -0
  148. package/dist/modules/eudr/components/MappingSuggestionsDialog.js +307 -0
  149. package/dist/modules/eudr/components/MappingSuggestionsDialog.js.map +7 -0
  150. package/dist/modules/eudr/components/MitigationActionsSection.js +345 -0
  151. package/dist/modules/eudr/components/MitigationActionsSection.js.map +7 -0
  152. package/dist/modules/eudr/components/OrderSelectField.js +88 -0
  153. package/dist/modules/eudr/components/OrderSelectField.js.map +7 -0
  154. package/dist/modules/eudr/components/PlotImportDialog.js +229 -0
  155. package/dist/modules/eudr/components/PlotImportDialog.js.map +7 -0
  156. package/dist/modules/eudr/components/PlotMapPreview.js +35 -0
  157. package/dist/modules/eudr/components/PlotMapPreview.js.map +7 -0
  158. package/dist/modules/eudr/components/PlotMapPreviewImpl.js +97 -0
  159. package/dist/modules/eudr/components/PlotMapPreviewImpl.js.map +7 -0
  160. package/dist/modules/eudr/components/PlotMultiSelectField.js +198 -0
  161. package/dist/modules/eudr/components/PlotMultiSelectField.js.map +7 -0
  162. package/dist/modules/eudr/components/RiskCriteriaField.js +128 -0
  163. package/dist/modules/eudr/components/RiskCriteriaField.js.map +7 -0
  164. package/dist/modules/eudr/components/StatementLifecycleBar.js +274 -0
  165. package/dist/modules/eudr/components/StatementLifecycleBar.js.map +7 -0
  166. package/dist/modules/eudr/components/StatementReadinessChecklist.js +56 -0
  167. package/dist/modules/eudr/components/StatementReadinessChecklist.js.map +7 -0
  168. package/dist/modules/eudr/components/StatementRiskSection.js +179 -0
  169. package/dist/modules/eudr/components/StatementRiskSection.js.map +7 -0
  170. package/dist/modules/eudr/components/crudErrorI18n.js +31 -0
  171. package/dist/modules/eudr/components/crudErrorI18n.js.map +7 -0
  172. package/dist/modules/eudr/components/formConfig.js +593 -0
  173. package/dist/modules/eudr/components/formConfig.js.map +7 -0
  174. package/dist/modules/eudr/components/plotForm.js +68 -0
  175. package/dist/modules/eudr/components/plotForm.js.map +7 -0
  176. package/dist/modules/eudr/data/enrichers.js +62 -0
  177. package/dist/modules/eudr/data/enrichers.js.map +7 -0
  178. package/dist/modules/eudr/data/entities.js +437 -0
  179. package/dist/modules/eudr/data/entities.js.map +7 -0
  180. package/dist/modules/eudr/data/validators.js +271 -0
  181. package/dist/modules/eudr/data/validators.js.map +7 -0
  182. package/dist/modules/eudr/encryption.js +33 -0
  183. package/dist/modules/eudr/encryption.js.map +7 -0
  184. package/dist/modules/eudr/events.js +38 -0
  185. package/dist/modules/eudr/events.js.map +7 -0
  186. package/dist/modules/eudr/index.js +15 -0
  187. package/dist/modules/eudr/index.js.map +7 -0
  188. package/dist/modules/eudr/lib/annual-report.js +104 -0
  189. package/dist/modules/eudr/lib/annual-report.js.map +7 -0
  190. package/dist/modules/eudr/lib/completeness.js +64 -0
  191. package/dist/modules/eudr/lib/completeness.js.map +7 -0
  192. package/dist/modules/eudr/lib/detail-read-scope.js +16 -0
  193. package/dist/modules/eudr/lib/detail-read-scope.js.map +7 -0
  194. package/dist/modules/eudr/lib/geometry.js +142 -0
  195. package/dist/modules/eudr/lib/geometry.js.map +7 -0
  196. package/dist/modules/eudr/lib/notifications.js +56 -0
  197. package/dist/modules/eudr/lib/notifications.js.map +7 -0
  198. package/dist/modules/eudr/lib/plural.js +26 -0
  199. package/dist/modules/eudr/lib/plural.js.map +7 -0
  200. package/dist/modules/eudr/lib/reference-data.js +240 -0
  201. package/dist/modules/eudr/lib/reference-data.js.map +7 -0
  202. package/dist/modules/eudr/lib/species.js +10 -0
  203. package/dist/modules/eudr/lib/species.js.map +7 -0
  204. package/dist/modules/eudr/lib/statement-lifecycle.js +84 -0
  205. package/dist/modules/eudr/lib/statement-lifecycle.js.map +7 -0
  206. package/dist/modules/eudr/lib/statement-seeding.js +49 -0
  207. package/dist/modules/eudr/lib/statement-seeding.js.map +7 -0
  208. package/dist/modules/eudr/migrations/Migration20260706102002_eudr.js +15 -0
  209. package/dist/modules/eudr/migrations/Migration20260706102002_eudr.js.map +7 -0
  210. package/dist/modules/eudr/migrations/Migration20260706152612_eudr.js +24 -0
  211. package/dist/modules/eudr/migrations/Migration20260706152612_eudr.js.map +7 -0
  212. package/dist/modules/eudr/migrations/Migration20260722100000_eudr.js +15 -0
  213. package/dist/modules/eudr/migrations/Migration20260722100000_eudr.js.map +7 -0
  214. package/dist/modules/eudr/notifications.js +58 -0
  215. package/dist/modules/eudr/notifications.js.map +7 -0
  216. package/dist/modules/eudr/search.js +186 -0
  217. package/dist/modules/eudr/search.js.map +7 -0
  218. package/dist/modules/eudr/setup.js +12 -0
  219. package/dist/modules/eudr/setup.js.map +7 -0
  220. package/dist/modules/eudr/subscribers/notify-mitigation-completed.js +34 -0
  221. package/dist/modules/eudr/subscribers/notify-mitigation-completed.js.map +7 -0
  222. package/dist/modules/eudr/subscribers/notify-risk-concluded.js +35 -0
  223. package/dist/modules/eudr/subscribers/notify-risk-concluded.js.map +7 -0
  224. package/dist/modules/eudr/subscribers/notify-statement-reference-issued.js +37 -0
  225. package/dist/modules/eudr/subscribers/notify-statement-reference-issued.js.map +7 -0
  226. package/dist/modules/eudr/subscribers/notify-statement-submitted.js +34 -0
  227. package/dist/modules/eudr/subscribers/notify-statement-submitted.js.map +7 -0
  228. package/dist/modules/eudr/subscribers/notify-statement-withdrawn.js +34 -0
  229. package/dist/modules/eudr/subscribers/notify-statement-withdrawn.js.map +7 -0
  230. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-created.js +124 -0
  231. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-created.js.map +7 -0
  232. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.js +22 -0
  233. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.js.map +7 -0
  234. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.client.js +203 -0
  235. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.client.js.map +7 -0
  236. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.js +23 -0
  237. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.js.map +7 -0
  238. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.client.js +19 -0
  239. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.client.js.map +7 -0
  240. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.js +31 -0
  241. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.js.map +7 -0
  242. package/dist/modules/eudr/widgets/injection/order-compliance/widget.client.js +100 -0
  243. package/dist/modules/eudr/widgets/injection/order-compliance/widget.client.js.map +7 -0
  244. package/dist/modules/eudr/widgets/injection/order-compliance/widget.js +17 -0
  245. package/dist/modules/eudr/widgets/injection/order-compliance/widget.js.map +7 -0
  246. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.client.js +116 -0
  247. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.client.js.map +7 -0
  248. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.js +17 -0
  249. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.js.map +7 -0
  250. package/dist/modules/eudr/widgets/injection-table.js +32 -0
  251. package/dist/modules/eudr/widgets/injection-table.js.map +7 -0
  252. package/dist/modules/notifications/lib/notificationService.js +67 -3
  253. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  254. package/dist/modules/query_index/lib/engine.js +121 -48
  255. package/dist/modules/query_index/lib/engine.js.map +2 -2
  256. package/dist/modules/sales/api/documents/factory.js +17 -0
  257. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  258. package/dist/modules/sales/commands/documents.js +32 -18
  259. package/dist/modules/sales/commands/documents.js.map +2 -2
  260. package/dist/modules/sales/data/entities.js +1 -0
  261. package/dist/modules/sales/data/entities.js.map +2 -2
  262. package/dist/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.js +17 -0
  263. package/dist/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.js.map +7 -0
  264. package/generated/entities/eudr_due_diligence_statement/index.ts +22 -0
  265. package/generated/entities/eudr_evidence_submission/index.ts +24 -0
  266. package/generated/entities/eudr_mitigation_action/index.ts +14 -0
  267. package/generated/entities/eudr_plot/index.ts +18 -0
  268. package/generated/entities/eudr_product_mapping/index.ts +14 -0
  269. package/generated/entities/eudr_risk_assessment/index.ts +16 -0
  270. package/generated/entities.ids.generated.ts +9 -0
  271. package/generated/entity-fields-registry.ts +120 -0
  272. package/package.json +7 -7
  273. package/src/modules/attachments/fields/attachment.tsx +5 -0
  274. package/src/modules/attachments/lib/crud.ts +2 -0
  275. package/src/modules/eudr/acl.ts +39 -0
  276. package/src/modules/eudr/ai-agents.ts +258 -0
  277. package/src/modules/eudr/ai-tools/compliance-pack.ts +618 -0
  278. package/src/modules/eudr/ai-tools/types.ts +44 -0
  279. package/src/modules/eudr/ai-tools.ts +15 -0
  280. package/src/modules/eudr/api/dashboard/widgets/compliance-overview/route.ts +481 -0
  281. package/src/modules/eudr/api/evidence-submissions/route.ts +335 -0
  282. package/src/modules/eudr/api/mitigation-actions/route.ts +263 -0
  283. package/src/modules/eudr/api/openapi.ts +28 -0
  284. package/src/modules/eudr/api/plots/import/route.ts +369 -0
  285. package/src/modules/eudr/api/plots/route.ts +296 -0
  286. package/src/modules/eudr/api/product-mappings/route.ts +269 -0
  287. package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +382 -0
  288. package/src/modules/eudr/api/product-mappings/suggestions/route.ts +169 -0
  289. package/src/modules/eudr/api/reports/annual/route.ts +457 -0
  290. package/src/modules/eudr/api/risk-assessments/route.ts +323 -0
  291. package/src/modules/eudr/api/statements/[id]/export/route.ts +713 -0
  292. package/src/modules/eudr/api/statements/[id]/readiness/route.ts +129 -0
  293. package/src/modules/eudr/api/statements/route.ts +329 -0
  294. package/src/modules/eudr/api/suppliers/compliance/route.ts +241 -0
  295. package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.ts +12 -0
  296. package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.tsx +545 -0
  297. package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.ts +13 -0
  298. package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.tsx +383 -0
  299. package/src/modules/eudr/backend/eudr/evidence-submissions/page.meta.ts +12 -0
  300. package/src/modules/eudr/backend/eudr/evidence-submissions/page.tsx +379 -0
  301. package/src/modules/eudr/backend/eudr/page.meta.ts +12 -0
  302. package/src/modules/eudr/backend/eudr/page.tsx +477 -0
  303. package/src/modules/eudr/backend/eudr/plots/[id]/page.meta.ts +12 -0
  304. package/src/modules/eudr/backend/eudr/plots/[id]/page.tsx +404 -0
  305. package/src/modules/eudr/backend/eudr/plots/create/page.meta.ts +13 -0
  306. package/src/modules/eudr/backend/eudr/plots/create/page.tsx +267 -0
  307. package/src/modules/eudr/backend/eudr/plots/page.meta.ts +12 -0
  308. package/src/modules/eudr/backend/eudr/plots/page.tsx +442 -0
  309. package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.ts +12 -0
  310. package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.tsx +311 -0
  311. package/src/modules/eudr/backend/eudr/product-mappings/create/page.meta.ts +13 -0
  312. package/src/modules/eudr/backend/eudr/product-mappings/create/page.tsx +190 -0
  313. package/src/modules/eudr/backend/eudr/product-mappings/page.meta.ts +12 -0
  314. package/src/modules/eudr/backend/eudr/product-mappings/page.tsx +344 -0
  315. package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.ts +12 -0
  316. package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.tsx +421 -0
  317. package/src/modules/eudr/backend/eudr/risk-assessments/create/page.meta.ts +13 -0
  318. package/src/modules/eudr/backend/eudr/risk-assessments/create/page.tsx +247 -0
  319. package/src/modules/eudr/backend/eudr/risk-assessments/page.meta.ts +12 -0
  320. package/src/modules/eudr/backend/eudr/risk-assessments/page.tsx +383 -0
  321. package/src/modules/eudr/backend/eudr/statements/[id]/page.meta.ts +12 -0
  322. package/src/modules/eudr/backend/eudr/statements/[id]/page.tsx +777 -0
  323. package/src/modules/eudr/backend/eudr/statements/create/page.meta.ts +13 -0
  324. package/src/modules/eudr/backend/eudr/statements/create/page.tsx +511 -0
  325. package/src/modules/eudr/backend/eudr/statements/page.meta.ts +12 -0
  326. package/src/modules/eudr/backend/eudr/statements/page.tsx +372 -0
  327. package/src/modules/eudr/ce.ts +52 -0
  328. package/src/modules/eudr/commands/evidence-submissions.ts +699 -0
  329. package/src/modules/eudr/commands/index.ts +6 -0
  330. package/src/modules/eudr/commands/lifecycle-events.ts +28 -0
  331. package/src/modules/eudr/commands/mitigation-actions.ts +574 -0
  332. package/src/modules/eudr/commands/plots.ts +627 -0
  333. package/src/modules/eudr/commands/product-mappings.ts +556 -0
  334. package/src/modules/eudr/commands/risk-assessments.ts +728 -0
  335. package/src/modules/eudr/commands/statements.ts +933 -0
  336. package/src/modules/eudr/components/CountrySelectField.tsx +77 -0
  337. package/src/modules/eudr/components/GeometryInput.tsx +170 -0
  338. package/src/modules/eudr/components/MappingSuggestionsDialog.tsx +385 -0
  339. package/src/modules/eudr/components/MitigationActionsSection.tsx +417 -0
  340. package/src/modules/eudr/components/OrderSelectField.tsx +116 -0
  341. package/src/modules/eudr/components/PlotImportDialog.tsx +277 -0
  342. package/src/modules/eudr/components/PlotMapPreview.tsx +46 -0
  343. package/src/modules/eudr/components/PlotMapPreviewImpl.tsx +111 -0
  344. package/src/modules/eudr/components/PlotMultiSelectField.tsx +238 -0
  345. package/src/modules/eudr/components/RiskCriteriaField.tsx +173 -0
  346. package/src/modules/eudr/components/StatementLifecycleBar.tsx +363 -0
  347. package/src/modules/eudr/components/StatementReadinessChecklist.tsx +82 -0
  348. package/src/modules/eudr/components/StatementRiskSection.tsx +242 -0
  349. package/src/modules/eudr/components/crudErrorI18n.ts +36 -0
  350. package/src/modules/eudr/components/formConfig.tsx +717 -0
  351. package/src/modules/eudr/components/plotForm.ts +75 -0
  352. package/src/modules/eudr/data/enrichers.ts +82 -0
  353. package/src/modules/eudr/data/entities.ts +369 -0
  354. package/src/modules/eudr/data/validators.ts +357 -0
  355. package/src/modules/eudr/encryption.ts +31 -0
  356. package/src/modules/eudr/events.ts +38 -0
  357. package/src/modules/eudr/i18n/de.json +747 -0
  358. package/src/modules/eudr/i18n/en.json +747 -0
  359. package/src/modules/eudr/i18n/es.json +747 -0
  360. package/src/modules/eudr/i18n/ko.json +747 -0
  361. package/src/modules/eudr/i18n/pl.json +747 -0
  362. package/src/modules/eudr/index.ts +13 -0
  363. package/src/modules/eudr/lib/annual-report.ts +120 -0
  364. package/src/modules/eudr/lib/completeness.ts +90 -0
  365. package/src/modules/eudr/lib/detail-read-scope.ts +44 -0
  366. package/src/modules/eudr/lib/geometry.ts +191 -0
  367. package/src/modules/eudr/lib/notifications.ts +85 -0
  368. package/src/modules/eudr/lib/plural.ts +36 -0
  369. package/src/modules/eudr/lib/reference-data.ts +128 -0
  370. package/src/modules/eudr/lib/species.ts +12 -0
  371. package/src/modules/eudr/lib/statement-lifecycle.ts +109 -0
  372. package/src/modules/eudr/lib/statement-seeding.ts +86 -0
  373. package/src/modules/eudr/migrations/.snapshot-open-mercato.json +2046 -0
  374. package/src/modules/eudr/migrations/Migration20260706102002_eudr.ts +16 -0
  375. package/src/modules/eudr/migrations/Migration20260706152612_eudr.ts +30 -0
  376. package/src/modules/eudr/migrations/Migration20260722100000_eudr.ts +17 -0
  377. package/src/modules/eudr/notifications.ts +56 -0
  378. package/src/modules/eudr/search.ts +212 -0
  379. package/src/modules/eudr/setup.ts +10 -0
  380. package/src/modules/eudr/subscribers/notify-mitigation-completed.ts +56 -0
  381. package/src/modules/eudr/subscribers/notify-risk-concluded.ts +58 -0
  382. package/src/modules/eudr/subscribers/notify-statement-reference-issued.ts +59 -0
  383. package/src/modules/eudr/subscribers/notify-statement-submitted.ts +49 -0
  384. package/src/modules/eudr/subscribers/notify-statement-withdrawn.ts +49 -0
  385. package/src/modules/eudr/subscribers/recompute-completeness-on-attachment-created.ts +189 -0
  386. package/src/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.ts +31 -0
  387. package/src/modules/eudr/widgets/dashboard/compliance-overview/widget.client.tsx +271 -0
  388. package/src/modules/eudr/widgets/dashboard/compliance-overview/widget.ts +24 -0
  389. package/src/modules/eudr/widgets/injection/eudr-product-column/widget.client.tsx +37 -0
  390. package/src/modules/eudr/widgets/injection/eudr-product-column/widget.ts +30 -0
  391. package/src/modules/eudr/widgets/injection/order-compliance/widget.client.tsx +131 -0
  392. package/src/modules/eudr/widgets/injection/order-compliance/widget.ts +16 -0
  393. package/src/modules/eudr/widgets/injection/supplier-compliance/widget.client.tsx +168 -0
  394. package/src/modules/eudr/widgets/injection/supplier-compliance/widget.ts +16 -0
  395. package/src/modules/eudr/widgets/injection-table.ts +31 -0
  396. package/src/modules/notifications/lib/notificationService.ts +83 -4
  397. package/src/modules/query_index/lib/engine.ts +139 -51
  398. package/src/modules/sales/api/documents/factory.ts +29 -0
  399. package/src/modules/sales/commands/documents.ts +65 -18
  400. package/src/modules/sales/data/entities.ts +1 -0
  401. package/src/modules/sales/migrations/.snapshot-open-mercato.json +10 -0
  402. package/src/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.ts +59 -0
@@ -0,0 +1,369 @@
1
+ import { z } from 'zod'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
+ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
6
+ import type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
7
+ import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
8
+ import { createLogger } from '@open-mercato/shared/lib/logger'
9
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
+ import { getAllMutationGuardInstances } from '@open-mercato/shared/lib/crud/mutation-guard-store'
11
+ import {
12
+ bridgeLegacyGuard,
13
+ runMutationGuards,
14
+ type MutationGuard,
15
+ type MutationGuardInput,
16
+ } from '@open-mercato/shared/lib/crud/mutation-guard-registry'
17
+ import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
18
+ import { E } from '#generated/entities.ids.generated'
19
+ import { collectFeatures } from '../../../lib/geometry'
20
+ import { plotCreateSchema } from '../../../data/validators'
21
+
22
+ const logger = createLogger('eudr').child({ component: 'api/plots/import' })
23
+
24
+ export const metadata = {
25
+ POST: { requireAuth: true, requireFeatures: ['eudr.plots.manage'] },
26
+ }
27
+
28
+ const importSchema = z.object({
29
+ supplierEntityId: z.string().uuid(),
30
+ defaultCountry: z.string().regex(/^[A-Za-z]{2}$/).transform((value) => value.toUpperCase()).optional(),
31
+ featureCollection: z.unknown(),
32
+ })
33
+
34
+ type PlotImportInput = z.infer<typeof importSchema>
35
+ type PlotImportFailure = { index: number; name: string; errorKey: string }
36
+ type RequestContext = { ctx: CommandRuntimeContext; organizationId: string; tenantId: string }
37
+ type CommandCreateResult = { entityId?: string; id?: string }
38
+
39
+ function isRecord(value: unknown): value is Record<string, unknown> {
40
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
41
+ }
42
+
43
+ function optionalString(value: unknown): string | null {
44
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null
45
+ }
46
+
47
+ function optionalNumber(value: unknown): number | null {
48
+ return typeof value === 'number' && Number.isFinite(value) ? value : null
49
+ }
50
+
51
+ function resolveUserFeatures(auth: unknown): string[] {
52
+ const features = (auth as { features?: unknown })?.features
53
+ if (!Array.isArray(features)) return []
54
+ return features.filter((value): value is string => typeof value === 'string')
55
+ }
56
+
57
+ async function runGuards(
58
+ ctx: CommandRuntimeContext,
59
+ input: MutationGuardInput,
60
+ ): Promise<{
61
+ ok: boolean
62
+ errorBody?: Record<string, unknown>
63
+ errorStatus?: number
64
+ modifiedPayload?: Record<string, unknown>
65
+ afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
66
+ }> {
67
+ const legacyGuard = bridgeLegacyGuard(ctx.container)
68
+ const guards = [...getAllMutationGuardInstances(), ...(legacyGuard ? [legacyGuard] : [])]
69
+ return runMutationGuards(guards, input, {
70
+ userFeatures: resolveUserFeatures(ctx.auth),
71
+ })
72
+ }
73
+
74
+ async function runGuardAfterSuccessCallbacks(
75
+ callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,
76
+ input: {
77
+ tenantId: string
78
+ organizationId: string
79
+ userId: string
80
+ resourceKind: string
81
+ resourceId: string
82
+ operation: 'create'
83
+ requestMethod: string
84
+ requestHeaders: Headers
85
+ },
86
+ ): Promise<void> {
87
+ for (const callback of callbacks) {
88
+ if (!callback.guard.afterSuccess) continue
89
+ try {
90
+ await callback.guard.afterSuccess({
91
+ ...input,
92
+ metadata: callback.metadata ?? null,
93
+ })
94
+ } catch (err) {
95
+ logger.warn('Mutation guard afterSuccess callback failed', { err })
96
+ }
97
+ }
98
+ }
99
+
100
+ async function resolveRequestContext(req: Request): Promise<RequestContext> {
101
+ const container = await createRequestContainer()
102
+ const auth = await getAuthFromRequest(req)
103
+ const { translate } = await resolveTranslations()
104
+
105
+ if (!auth || !auth.tenantId) {
106
+ throw new CrudHttpError(401, { error: translate('eudr.errors.unauthorized', 'Unauthorized') })
107
+ }
108
+
109
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
110
+ const organizationId = scope?.selectedId ?? auth.orgId ?? null
111
+ if (!organizationId) {
112
+ throw new CrudHttpError(400, {
113
+ error: translate('eudr.errors.organization_required', 'Organization context is required'),
114
+ })
115
+ }
116
+
117
+ return {
118
+ tenantId: auth.tenantId,
119
+ organizationId,
120
+ ctx: {
121
+ container,
122
+ auth,
123
+ organizationScope: scope,
124
+ selectedOrganizationId: organizationId,
125
+ organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
126
+ request: req,
127
+ },
128
+ }
129
+ }
130
+
131
+ function featureProperties(feature: unknown): Record<string, unknown> {
132
+ if (!isRecord(feature)) return {}
133
+ const properties = feature.properties
134
+ return isRecord(properties) ? properties : {}
135
+ }
136
+
137
+ function featureGeometryType(feature: unknown): string | null {
138
+ if (!isRecord(feature)) return null
139
+ if (feature.type === 'Feature' && isRecord(feature.geometry) && typeof feature.geometry.type === 'string') {
140
+ return feature.geometry.type
141
+ }
142
+ return typeof feature.type === 'string' ? feature.type : null
143
+ }
144
+
145
+ function resolveFeatureName(properties: Record<string, unknown>, index: number): string {
146
+ return optionalString(properties.name)
147
+ ?? optionalString(properties.ProductionPlace)
148
+ ?? `Plot ${index + 1}`
149
+ }
150
+
151
+ function errorKeyFromError(error: unknown): string {
152
+ if (isCrudHttpError(error)) {
153
+ const bodyError = error.body?.error
154
+ return typeof bodyError === 'string' && bodyError.length > 0 ? bodyError : 'eudr.errors.plot_import_failed'
155
+ }
156
+ if (error instanceof z.ZodError) {
157
+ const firstMessage = error.issues[0]?.message
158
+ return typeof firstMessage === 'string' && firstMessage.length > 0 ? firstMessage : 'eudr.errors.plot_import_failed'
159
+ }
160
+ return 'eudr.errors.plot_import_failed'
161
+ }
162
+
163
+ async function loadSupplierDisplayName(
164
+ queryEngine: QueryEngine,
165
+ scope: { tenantId: string; organizationId: string },
166
+ supplierEntityId: string,
167
+ ): Promise<string | null> {
168
+ // Soft cross-module read (FK-id convention) via the query engine — no direct
169
+ // customers entity import; degrades to null when the peer is absent so the
170
+ // import still succeeds without a snapshot.
171
+ try {
172
+ const customersEntityId = (E as Record<string, Record<string, string>>).customers?.customer_entity
173
+ if (!customersEntityId) return null
174
+ const result = await queryEngine.query<Record<string, unknown>>(customersEntityId, {
175
+ fields: ['id', 'display_name'],
176
+ filters: { id: { $in: [supplierEntityId] } },
177
+ page: { page: 1, pageSize: 1 },
178
+ tenantId: scope.tenantId,
179
+ organizationId: scope.organizationId,
180
+ })
181
+ const rawName = result.items[0]?.display_name ?? result.items[0]?.displayName
182
+ return typeof rawName === 'string' && rawName.length ? rawName : null
183
+ } catch {
184
+ return null
185
+ }
186
+ }
187
+
188
+ async function createPlotFromFeature(args: {
189
+ commandBus: CommandBus
190
+ ctx: CommandRuntimeContext
191
+ scope: { tenantId: string; organizationId: string }
192
+ input: PlotImportInput
193
+ feature: unknown
194
+ index: number
195
+ supplierDisplayName: string | null
196
+ }): Promise<{ id: string | null }> {
197
+ const properties = featureProperties(args.feature)
198
+ const name = resolveFeatureName(properties, args.index)
199
+ const originCountry = optionalString(properties.ProducerCountry) ?? args.input.defaultCountry ?? null
200
+ if (!originCountry) throw new CrudHttpError(400, { error: 'eudr.errors.importCountryMissing' })
201
+
202
+ const area = optionalNumber(properties.Area)
203
+ const base = {
204
+ supplierEntityId: args.input.supplierEntityId,
205
+ name,
206
+ originCountry,
207
+ geometry: args.feature,
208
+ producerName: optionalString(properties.ProducerName),
209
+ areaHa: featureGeometryType(args.feature) === 'Point' && area !== null ? area : undefined,
210
+ supplierSnapshot: args.supplierDisplayName ? { displayName: args.supplierDisplayName } : undefined,
211
+ }
212
+ const parsed = plotCreateSchema.parse(base)
213
+ const { result } = await args.commandBus.execute<typeof parsed & { tenantId: string; organizationId: string }, CommandCreateResult>(
214
+ 'eudr.plots.create',
215
+ {
216
+ input: {
217
+ ...parsed,
218
+ tenantId: args.scope.tenantId,
219
+ organizationId: args.scope.organizationId,
220
+ },
221
+ ctx: args.ctx,
222
+ metadata: {
223
+ tenantId: args.scope.tenantId,
224
+ organizationId: args.scope.organizationId,
225
+ resourceKind: 'eudr.plot',
226
+ context: { cacheAliases: ['eudr.plot'] },
227
+ },
228
+ },
229
+ )
230
+ return { id: result?.entityId ?? result?.id ?? null }
231
+ }
232
+
233
+ export async function POST(req: Request) {
234
+ try {
235
+ const requestContext = await resolveRequestContext(req)
236
+ const { translate } = await resolveTranslations()
237
+ const payload = await req.json().catch(() => ({}))
238
+ const input = importSchema.parse(payload)
239
+ const guardResult = await runGuards(requestContext.ctx, {
240
+ tenantId: requestContext.tenantId,
241
+ organizationId: requestContext.organizationId,
242
+ userId: requestContext.ctx.auth?.sub ?? '',
243
+ resourceKind: 'eudr.plot',
244
+ resourceId: null,
245
+ operation: 'create',
246
+ requestMethod: req.method,
247
+ requestHeaders: req.headers,
248
+ mutationPayload: {
249
+ supplierEntityId: input.supplierEntityId,
250
+ defaultCountry: input.defaultCountry ?? null,
251
+ },
252
+ })
253
+ if (!guardResult.ok) {
254
+ return Response.json(
255
+ guardResult.errorBody ?? { error: translate('eudr.errors.operation_blocked', 'Operation blocked by guard') },
256
+ { status: guardResult.errorStatus ?? 422 },
257
+ )
258
+ }
259
+ const guardPatch = guardResult.modifiedPayload ?? null
260
+ const effectiveSupplierEntityId = typeof guardPatch?.supplierEntityId === 'string' && guardPatch.supplierEntityId.length
261
+ ? guardPatch.supplierEntityId
262
+ : input.supplierEntityId
263
+ const effectiveDefaultCountry = typeof guardPatch?.defaultCountry === 'string' && guardPatch.defaultCountry.length
264
+ ? guardPatch.defaultCountry
265
+ : input.defaultCountry
266
+
267
+ const featureResult = collectFeatures(input.featureCollection)
268
+ if (!featureResult.ok) {
269
+ return Response.json({ errorKey: featureResult.errorKey }, { status: 400 })
270
+ }
271
+
272
+ const commandBus = requestContext.ctx.container.resolve('commandBus') as CommandBus
273
+ const failed: PlotImportFailure[] = []
274
+ const createdIds: string[] = []
275
+ const effectiveInput: PlotImportInput = {
276
+ ...input,
277
+ supplierEntityId: effectiveSupplierEntityId,
278
+ defaultCountry: effectiveDefaultCountry,
279
+ }
280
+
281
+ const importScope = { tenantId: requestContext.tenantId, organizationId: requestContext.organizationId }
282
+ const supplierDisplayName = await loadSupplierDisplayName(
283
+ requestContext.ctx.container.resolve('queryEngine') as QueryEngine,
284
+ importScope,
285
+ effectiveSupplierEntityId,
286
+ )
287
+
288
+ for (const [index, feature] of featureResult.features.entries()) {
289
+ const name = resolveFeatureName(featureProperties(feature), index)
290
+ try {
291
+ const created = await createPlotFromFeature({
292
+ commandBus,
293
+ ctx: requestContext.ctx,
294
+ scope: importScope,
295
+ input: effectiveInput,
296
+ feature,
297
+ index,
298
+ supplierDisplayName,
299
+ })
300
+ if (created.id) createdIds.push(created.id)
301
+ } catch (error) {
302
+ failed.push({ index, name, errorKey: errorKeyFromError(error) })
303
+ }
304
+ }
305
+
306
+ if (guardResult.afterSuccessCallbacks.length) {
307
+ for (const id of createdIds) {
308
+ await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
309
+ tenantId: requestContext.tenantId,
310
+ organizationId: requestContext.organizationId,
311
+ userId: requestContext.ctx.auth?.sub ?? '',
312
+ resourceKind: 'eudr.plot',
313
+ resourceId: id,
314
+ operation: 'create',
315
+ requestMethod: req.method,
316
+ requestHeaders: req.headers,
317
+ })
318
+ }
319
+ }
320
+
321
+ return Response.json({ created: createdIds.length, failed })
322
+ } catch (error) {
323
+ if (isCrudHttpError(error)) {
324
+ return Response.json(error.body, { status: error.status })
325
+ }
326
+ const { translate } = await resolveTranslations()
327
+ if (error instanceof z.ZodError) {
328
+ return Response.json(
329
+ { error: translate('eudr.errors.plot_import_failed', 'Failed to import plots') },
330
+ { status: 400 },
331
+ )
332
+ }
333
+ logger.error('Plot import failed', { err: error })
334
+ return Response.json(
335
+ { error: translate('eudr.errors.plot_import_failed', 'Failed to import plots') },
336
+ { status: 500 },
337
+ )
338
+ }
339
+ }
340
+
341
+ const importResponseSchema = z.object({
342
+ created: z.number(),
343
+ failed: z.array(z.object({
344
+ index: z.number(),
345
+ name: z.string(),
346
+ errorKey: z.string(),
347
+ })),
348
+ })
349
+
350
+ export const openApi: OpenApiRouteDoc = {
351
+ tag: 'EUDR',
352
+ summary: 'Import EUDR plots from GeoJSON',
353
+ methods: {
354
+ POST: {
355
+ summary: 'Import plots',
356
+ requestBody: {
357
+ contentType: 'application/json',
358
+ schema: importSchema,
359
+ },
360
+ responses: [
361
+ { status: 200, description: 'Import result', schema: importResponseSchema },
362
+ { status: 400, description: 'Invalid import payload', schema: z.object({ error: z.string().optional(), errorKey: z.string().optional() }) },
363
+ { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
364
+ { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },
365
+ { status: 500, description: 'Plot import failed', schema: z.object({ error: z.string() }) },
366
+ ],
367
+ },
368
+ },
369
+ }
@@ -0,0 +1,296 @@
1
+ import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
2
+ import { z } from 'zod'
3
+ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
4
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'
6
+ import { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'
7
+ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
8
+ import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'
9
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
10
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
11
+ import { E } from '#generated/entities.ids.generated'
12
+ import { EudrPlot } from '../../data/entities'
13
+ import { resolveDetailReadScope } from '../../lib/detail-read-scope'
14
+ import {
15
+ EUDR_PLOT_TYPES,
16
+ plotCreateSchema,
17
+ plotUpdateSchema,
18
+ } from '../../data/validators'
19
+ import {
20
+ createEudrCrudOpenApi,
21
+ createPagedListResponseSchema,
22
+ defaultOkResponseSchema,
23
+ } from '../openapi'
24
+
25
+ type TranslateFn = (key: string, fallback?: string) => string
26
+
27
+ const rawBodySchema = z.object({}).passthrough()
28
+
29
+ const listSchema = z.object({
30
+ page: z.coerce.number().min(1).default(1),
31
+ pageSize: z.coerce.number().min(1).max(100).default(50),
32
+ search: z.string().optional(),
33
+ supplierEntityId: z.string().uuid().optional(),
34
+ plotType: z.enum(EUDR_PLOT_TYPES).optional(),
35
+ isActive: z.string().optional(),
36
+ originCountry: z.string().length(2).optional(),
37
+ id: z.string().uuid().optional(),
38
+ ids: z.string().optional(),
39
+ sortField: z.string().optional(),
40
+ sortDir: z.enum(['asc', 'desc']).optional(),
41
+ })
42
+
43
+ type PlotListQuery = z.infer<typeof listSchema>
44
+
45
+ const routeMetadata = {
46
+ GET: { requireAuth: true, requireFeatures: ['eudr.plots.view'] },
47
+ POST: { requireAuth: true, requireFeatures: ['eudr.plots.manage'] },
48
+ PUT: { requireAuth: true, requireFeatures: ['eudr.plots.manage'] },
49
+ DELETE: { requireAuth: true, requireFeatures: ['eudr.plots.manage'] },
50
+ }
51
+
52
+ export const metadata = routeMetadata
53
+
54
+ function toIsoString(value: unknown): string | null {
55
+ if (value instanceof Date) return value.toISOString()
56
+ if (typeof value !== 'string' || value.length === 0) return null
57
+ const date = new Date(value)
58
+ return Number.isNaN(date.getTime()) ? value : date.toISOString()
59
+ }
60
+
61
+ function asStringOrNull(value: unknown): string | null {
62
+ return typeof value === 'string' ? value : null
63
+ }
64
+
65
+ function stringArray(value: unknown): string[] {
66
+ if (!Array.isArray(value)) return []
67
+ return value.filter((entry): entry is string => typeof entry === 'string')
68
+ }
69
+
70
+ function resolveDeleteInput(parsed: unknown, ctx: { request?: Request }, translate: TranslateFn) {
71
+ const record = parsed && typeof parsed === 'object' ? recordFrom(parsed) : {}
72
+ const body = record.body && typeof record.body === 'object' ? recordFrom(record.body) : null
73
+ const query = record.query && typeof record.query === 'object' ? recordFrom(record.query) : null
74
+ const requestId = ctx.request ? new URL(ctx.request.url).searchParams.get('id') : null
75
+ const id = asStringOrNull(body?.id) ?? asStringOrNull(record.id) ?? asStringOrNull(query?.id) ?? requestId
76
+ if (!id) throw new CrudHttpError(400, { error: translate('eudr.errors.plot_required', 'Plot id is required') })
77
+ return { id }
78
+ }
79
+
80
+ function recordFrom(value: object): Record<string, unknown> {
81
+ return value as Record<string, unknown>
82
+ }
83
+
84
+ function buildFilters(query: PlotListQuery): Record<string, unknown> {
85
+ const filters: Record<string, unknown> = {}
86
+ if (query.id) filters.id = { $eq: query.id }
87
+ if (query.supplierEntityId) filters.supplier_entity_id = { $eq: query.supplierEntityId }
88
+ if (query.plotType) filters.plot_type = { $eq: query.plotType }
89
+ if (query.originCountry) filters.origin_country = { $eq: query.originCountry.toUpperCase() }
90
+ const isActive = parseBooleanToken(query.isActive)
91
+ if (isActive !== null) filters.is_active = { $eq: isActive }
92
+ const search = typeof query.search === 'string' ? query.search.trim() : ''
93
+ if (search) {
94
+ const searchPattern = buildIlikeTerm(search)
95
+ filters.$or = [
96
+ { name: { $ilike: searchPattern } },
97
+ { external_id: { $ilike: searchPattern } },
98
+ ]
99
+ }
100
+ return filters
101
+ }
102
+
103
+ function transformPlotItem(item: unknown) {
104
+ if (!item || typeof item !== 'object') return item
105
+ const record = item as Record<string, unknown>
106
+ // Bare list items intentionally omit `geometry`; the afterList hook attaches
107
+ // it (with decryption) for id/ids detail reads only.
108
+ return {
109
+ id: record.id,
110
+ supplierEntityId: record.supplier_entity_id ?? null,
111
+ supplierSnapshot: record.supplier_snapshot ?? null,
112
+ name: record.name ?? null,
113
+ externalId: record.external_id ?? null,
114
+ originCountry: record.origin_country ?? null,
115
+ plotType: record.plot_type ?? null,
116
+ areaHa: record.area_ha ?? null,
117
+ validationWarnings: stringArray(record.validation_warnings),
118
+ producerName: record.producer_name ?? null,
119
+ isActive: typeof record.is_active === 'boolean' ? record.is_active : parseBooleanToken(typeof record.is_active === 'string' ? record.is_active : null),
120
+ createdAt: toIsoString(record.created_at),
121
+ updatedAt: toIsoString(record.updated_at),
122
+ }
123
+ }
124
+
125
+ const listFields = [
126
+ 'id',
127
+ 'supplier_entity_id',
128
+ 'supplier_snapshot',
129
+ 'name',
130
+ 'external_id',
131
+ 'origin_country',
132
+ 'plot_type',
133
+ 'area_ha',
134
+ 'validation_warnings',
135
+ 'is_active',
136
+ 'created_at',
137
+ 'updated_at',
138
+ ]
139
+
140
+ const crud = makeCrudRoute({
141
+ metadata: routeMetadata,
142
+ orm: {
143
+ entity: EudrPlot,
144
+ idField: 'id',
145
+ orgField: 'organizationId',
146
+ tenantField: 'tenantId',
147
+ softDeleteField: 'deletedAt',
148
+ },
149
+ indexer: { entityType: E.eudr.eudr_plot },
150
+ list: {
151
+ schema: listSchema,
152
+ entityId: E.eudr.eudr_plot,
153
+ fields: listFields,
154
+ sortFieldMap: {
155
+ created_at: 'created_at',
156
+ createdAt: 'created_at',
157
+ updated_at: 'updated_at',
158
+ updatedAt: 'updated_at',
159
+ name: 'name',
160
+ area_ha: 'area_ha',
161
+ areaHa: 'area_ha',
162
+ },
163
+ buildFilters,
164
+ transformItem: transformPlotItem,
165
+ },
166
+ actions: {
167
+ create: {
168
+ commandId: 'eudr.plots.create',
169
+ schema: rawBodySchema,
170
+ mapInput: async ({ raw, ctx }) => {
171
+ const { translate } = await resolveTranslations()
172
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate)
173
+ const { base, custom } = splitCustomFieldPayload(scoped)
174
+ const parsed = plotCreateSchema.parse(base)
175
+ const input = { ...parsed, tenantId: scoped.tenantId, organizationId: scoped.organizationId }
176
+ return Object.keys(custom).length ? { ...input, customFields: custom } : input
177
+ },
178
+ response: ({ result }) => ({ id: result?.entityId ?? result?.id ?? null }),
179
+ status: 201,
180
+ },
181
+ update: {
182
+ commandId: 'eudr.plots.update',
183
+ schema: rawBodySchema,
184
+ mapInput: async ({ raw, ctx }) => {
185
+ const { translate } = await resolveTranslations()
186
+ const scoped = withScopedPayload(raw ?? {}, ctx, translate)
187
+ const { base, custom } = splitCustomFieldPayload(scoped)
188
+ const parsed = plotUpdateSchema.parse(base)
189
+ const input = { ...parsed, tenantId: scoped.tenantId, organizationId: scoped.organizationId }
190
+ return Object.keys(custom).length ? { ...input, customFields: custom } : input
191
+ },
192
+ response: ({ result }) => {
193
+ const updatedAt = result?.updatedAt
194
+ return {
195
+ ok: true,
196
+ updatedAt: updatedAt instanceof Date ? updatedAt.toISOString() : (typeof updatedAt === 'string' ? updatedAt : null),
197
+ }
198
+ },
199
+ },
200
+ delete: {
201
+ commandId: 'eudr.plots.delete',
202
+ schema: rawBodySchema,
203
+ mapInput: async ({ parsed, ctx }) => {
204
+ const { translate } = await resolveTranslations()
205
+ return resolveDeleteInput(parsed, ctx, translate)
206
+ },
207
+ response: () => ({ ok: true }),
208
+ },
209
+ },
210
+ hooks: {
211
+ afterList: async (payload, ctx) => {
212
+ const detailRead = (typeof ctx.query.id === 'string' && ctx.query.id.length > 0)
213
+ || (typeof ctx.query.ids === 'string' && ctx.query.ids.length > 0)
214
+ if (!detailRead) return
215
+ const items: unknown[] = Array.isArray(payload?.items) ? payload.items : []
216
+ if (!items.length) return
217
+ const scope = resolveDetailReadScope(ctx)
218
+ if (!scope) return
219
+ const ids = items
220
+ .map((item) => (item && typeof item === 'object' ? asStringOrNull((item as Record<string, unknown>).id) : null))
221
+ .filter((id): id is string => id !== null)
222
+ if (!ids.length) return
223
+ const em = ctx.container.resolve('em') as EntityManager
224
+ const where: FilterQuery<EudrPlot> = {
225
+ id: { $in: ids },
226
+ deletedAt: null,
227
+ tenantId: scope.tenantId,
228
+ organizationId: scope.organizationFilter,
229
+ }
230
+ const decrypted = await findWithDecryption(
231
+ em,
232
+ EudrPlot,
233
+ where,
234
+ {},
235
+ { tenantId: scope.tenantId, organizationId: scope.decryptionOrganizationId },
236
+ )
237
+ const byId = new Map(decrypted.map((plot) => [plot.id, plot]))
238
+ for (const item of items) {
239
+ if (!item || typeof item !== 'object') continue
240
+ const record = item as Record<string, unknown>
241
+ const id = asStringOrNull(record.id)
242
+ const plot = id ? byId.get(id) : null
243
+ if (!plot) continue
244
+ record.geometry = plot.geometry ?? null
245
+ record.producerName = plot.producerName ?? null
246
+ record.description = plot.description ?? null
247
+ }
248
+ },
249
+ },
250
+ })
251
+
252
+ export const GET = crud.GET
253
+ export const POST = crud.POST
254
+ export const PUT = crud.PUT
255
+ export const DELETE = crud.DELETE
256
+
257
+ const plotListItemSchema = z.object({
258
+ id: z.string().uuid(),
259
+ supplierEntityId: z.string().uuid().nullable().optional(),
260
+ supplierSnapshot: z.object({
261
+ displayName: z.string().nullable().optional(),
262
+ }).nullable().optional(),
263
+ name: z.string().nullable().optional(),
264
+ externalId: z.string().nullable().optional(),
265
+ originCountry: z.string().nullable().optional(),
266
+ plotType: z.enum(EUDR_PLOT_TYPES).nullable().optional(),
267
+ areaHa: z.union([z.string(), z.number()]).nullable().optional(),
268
+ geometry: z.unknown().nullable().optional().describe(
269
+ 'Present only on detail reads requested with id or ids; omitted from bare paginated list reads.',
270
+ ),
271
+ validationWarnings: z.array(z.string()),
272
+ producerName: z.string().nullable().optional(),
273
+ isActive: z.boolean().nullable().optional(),
274
+ createdAt: z.string().nullable().optional(),
275
+ updatedAt: z.string().nullable().optional(),
276
+ })
277
+
278
+ export const openApi = createEudrCrudOpenApi({
279
+ resourceName: 'Plot',
280
+ querySchema: listSchema,
281
+ listResponseSchema: createPagedListResponseSchema(plotListItemSchema),
282
+ create: {
283
+ schema: plotCreateSchema,
284
+ description: 'Creates an EUDR supplier plot for the scoped organization.',
285
+ },
286
+ update: {
287
+ schema: plotUpdateSchema,
288
+ responseSchema: defaultOkResponseSchema,
289
+ description: 'Updates an EUDR supplier plot.',
290
+ },
291
+ del: {
292
+ schema: z.object({ id: z.string().uuid() }),
293
+ responseSchema: defaultOkResponseSchema,
294
+ description: 'Deletes an EUDR supplier plot by id. Request body or query may provide the identifier.',
295
+ },
296
+ })