@open-mercato/core 0.6.8-develop.6924.1.a8d208fcdc → 0.6.8-develop.6930.1.1e5976efc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (453) 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/role/index.js +2 -0
  15. package/dist/generated/entities/role/index.js.map +2 -2
  16. package/dist/generated/entities.ids.generated.js +9 -0
  17. package/dist/generated/entities.ids.generated.js.map +2 -2
  18. package/dist/generated/entity-fields-registry.js +121 -0
  19. package/dist/generated/entity-fields-registry.js.map +2 -2
  20. package/dist/helpers/integration/api.js +4 -0
  21. package/dist/helpers/integration/api.js.map +2 -2
  22. package/dist/modules/attachments/fields/attachment.js +5 -1
  23. package/dist/modules/attachments/fields/attachment.js.map +2 -2
  24. package/dist/modules/attachments/lib/crud.js +3 -1
  25. package/dist/modules/attachments/lib/crud.js.map +2 -2
  26. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +5 -0
  27. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
  28. package/dist/modules/audit_logs/api/audit-logs/actions/undo/route.js +5 -0
  29. package/dist/modules/audit_logs/api/audit-logs/actions/undo/route.js.map +2 -2
  30. package/dist/modules/audit_logs/backend/audit-logs/page.js +4 -4
  31. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  32. package/dist/modules/audit_logs/components/AuditLogsActions.js +9 -2
  33. package/dist/modules/audit_logs/components/AuditLogsActions.js.map +2 -2
  34. package/dist/modules/auth/api/login.js +5 -2
  35. package/dist/modules/auth/api/login.js.map +2 -2
  36. package/dist/modules/auth/api/users/route.js +8 -5
  37. package/dist/modules/auth/api/users/route.js.map +2 -2
  38. package/dist/modules/auth/backend/users/[id]/edit/page.js +15 -1
  39. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  40. package/dist/modules/auth/backend/users/page.js +7 -1
  41. package/dist/modules/auth/backend/users/page.js.map +2 -2
  42. package/dist/modules/auth/commands/users.js +175 -76
  43. package/dist/modules/auth/commands/users.js.map +2 -2
  44. package/dist/modules/auth/data/entities.js +4 -0
  45. package/dist/modules/auth/data/entities.js.map +2 -2
  46. package/dist/modules/auth/lib/sessionIntegrity.js +1 -1
  47. package/dist/modules/auth/lib/sessionIntegrity.js.map +2 -2
  48. package/dist/modules/auth/lib/setup-app.js +1 -1
  49. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  50. package/dist/modules/auth/migrations/Migration20260728134212_auth.js +14 -0
  51. package/dist/modules/auth/migrations/Migration20260728134212_auth.js.map +7 -0
  52. package/dist/modules/eudr/acl.js +43 -0
  53. package/dist/modules/eudr/acl.js.map +7 -0
  54. package/dist/modules/eudr/ai-agents.js +205 -0
  55. package/dist/modules/eudr/ai-agents.js.map +7 -0
  56. package/dist/modules/eudr/ai-tools/compliance-pack.js +495 -0
  57. package/dist/modules/eudr/ai-tools/compliance-pack.js.map +7 -0
  58. package/dist/modules/eudr/ai-tools/types.js +10 -0
  59. package/dist/modules/eudr/ai-tools/types.js.map +7 -0
  60. package/dist/modules/eudr/ai-tools.js +10 -0
  61. package/dist/modules/eudr/ai-tools.js.map +7 -0
  62. package/dist/modules/eudr/api/dashboard/widgets/compliance-overview/route.js +386 -0
  63. package/dist/modules/eudr/api/dashboard/widgets/compliance-overview/route.js.map +7 -0
  64. package/dist/modules/eudr/api/evidence-submissions/route.js +318 -0
  65. package/dist/modules/eudr/api/evidence-submissions/route.js.map +7 -0
  66. package/dist/modules/eudr/api/mitigation-actions/route.js +249 -0
  67. package/dist/modules/eudr/api/mitigation-actions/route.js.map +7 -0
  68. package/dist/modules/eudr/api/openapi.js +27 -0
  69. package/dist/modules/eudr/api/openapi.js.map +7 -0
  70. package/dist/modules/eudr/api/plots/import/route.js +294 -0
  71. package/dist/modules/eudr/api/plots/import/route.js.map +7 -0
  72. package/dist/modules/eudr/api/plots/route.js +279 -0
  73. package/dist/modules/eudr/api/plots/route.js.map +7 -0
  74. package/dist/modules/eudr/api/product-mappings/route.js +257 -0
  75. package/dist/modules/eudr/api/product-mappings/route.js.map +7 -0
  76. package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js +298 -0
  77. package/dist/modules/eudr/api/product-mappings/suggestions/apply/route.js.map +7 -0
  78. package/dist/modules/eudr/api/product-mappings/suggestions/route.js +132 -0
  79. package/dist/modules/eudr/api/product-mappings/suggestions/route.js.map +7 -0
  80. package/dist/modules/eudr/api/reports/annual/route.js +266 -0
  81. package/dist/modules/eudr/api/reports/annual/route.js.map +7 -0
  82. package/dist/modules/eudr/api/risk-assessments/route.js +300 -0
  83. package/dist/modules/eudr/api/risk-assessments/route.js.map +7 -0
  84. package/dist/modules/eudr/api/statements/[id]/export/route.js +631 -0
  85. package/dist/modules/eudr/api/statements/[id]/export/route.js.map +7 -0
  86. package/dist/modules/eudr/api/statements/[id]/readiness/route.js +111 -0
  87. package/dist/modules/eudr/api/statements/[id]/readiness/route.js.map +7 -0
  88. package/dist/modules/eudr/api/statements/route.js +314 -0
  89. package/dist/modules/eudr/api/statements/route.js.map +7 -0
  90. package/dist/modules/eudr/api/suppliers/compliance/route.js +173 -0
  91. package/dist/modules/eudr/api/suppliers/compliance/route.js.map +7 -0
  92. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.js +468 -0
  93. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.js.map +7 -0
  94. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js +16 -0
  95. package/dist/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.js.map +7 -0
  96. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.js +351 -0
  97. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.js.map +7 -0
  98. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js +17 -0
  99. package/dist/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.js.map +7 -0
  100. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js +313 -0
  101. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js.map +7 -0
  102. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js +16 -0
  103. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.meta.js.map +7 -0
  104. package/dist/modules/eudr/backend/eudr/page.js +360 -0
  105. package/dist/modules/eudr/backend/eudr/page.js.map +7 -0
  106. package/dist/modules/eudr/backend/eudr/page.meta.js +16 -0
  107. package/dist/modules/eudr/backend/eudr/page.meta.js.map +7 -0
  108. package/dist/modules/eudr/backend/eudr/plots/[id]/page.js +346 -0
  109. package/dist/modules/eudr/backend/eudr/plots/[id]/page.js.map +7 -0
  110. package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js +16 -0
  111. package/dist/modules/eudr/backend/eudr/plots/[id]/page.meta.js.map +7 -0
  112. package/dist/modules/eudr/backend/eudr/plots/create/page.js +253 -0
  113. package/dist/modules/eudr/backend/eudr/plots/create/page.js.map +7 -0
  114. package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js +17 -0
  115. package/dist/modules/eudr/backend/eudr/plots/create/page.meta.js.map +7 -0
  116. package/dist/modules/eudr/backend/eudr/plots/page.js +386 -0
  117. package/dist/modules/eudr/backend/eudr/plots/page.js.map +7 -0
  118. package/dist/modules/eudr/backend/eudr/plots/page.meta.js +16 -0
  119. package/dist/modules/eudr/backend/eudr/plots/page.meta.js.map +7 -0
  120. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.js +253 -0
  121. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.js.map +7 -0
  122. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js +16 -0
  123. package/dist/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.js.map +7 -0
  124. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.js +174 -0
  125. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.js.map +7 -0
  126. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js +17 -0
  127. package/dist/modules/eudr/backend/eudr/product-mappings/create/page.meta.js.map +7 -0
  128. package/dist/modules/eudr/backend/eudr/product-mappings/page.js +306 -0
  129. package/dist/modules/eudr/backend/eudr/product-mappings/page.js.map +7 -0
  130. package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js +16 -0
  131. package/dist/modules/eudr/backend/eudr/product-mappings/page.meta.js.map +7 -0
  132. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.js +335 -0
  133. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.js.map +7 -0
  134. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js +16 -0
  135. package/dist/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.js.map +7 -0
  136. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.js +220 -0
  137. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.js.map +7 -0
  138. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js +17 -0
  139. package/dist/modules/eudr/backend/eudr/risk-assessments/create/page.meta.js.map +7 -0
  140. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js +324 -0
  141. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js.map +7 -0
  142. package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js +16 -0
  143. package/dist/modules/eudr/backend/eudr/risk-assessments/page.meta.js.map +7 -0
  144. package/dist/modules/eudr/backend/eudr/statements/[id]/page.js +648 -0
  145. package/dist/modules/eudr/backend/eudr/statements/[id]/page.js.map +7 -0
  146. package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js +16 -0
  147. package/dist/modules/eudr/backend/eudr/statements/[id]/page.meta.js.map +7 -0
  148. package/dist/modules/eudr/backend/eudr/statements/create/page.js +432 -0
  149. package/dist/modules/eudr/backend/eudr/statements/create/page.js.map +7 -0
  150. package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js +17 -0
  151. package/dist/modules/eudr/backend/eudr/statements/create/page.meta.js.map +7 -0
  152. package/dist/modules/eudr/backend/eudr/statements/page.js +309 -0
  153. package/dist/modules/eudr/backend/eudr/statements/page.js.map +7 -0
  154. package/dist/modules/eudr/backend/eudr/statements/page.meta.js +16 -0
  155. package/dist/modules/eudr/backend/eudr/statements/page.meta.js.map +7 -0
  156. package/dist/modules/eudr/ce.js +56 -0
  157. package/dist/modules/eudr/ce.js.map +7 -0
  158. package/dist/modules/eudr/commands/evidence-submissions.js +571 -0
  159. package/dist/modules/eudr/commands/evidence-submissions.js.map +7 -0
  160. package/dist/modules/eudr/commands/index.js +7 -0
  161. package/dist/modules/eudr/commands/index.js.map +7 -0
  162. package/dist/modules/eudr/commands/lifecycle-events.js +19 -0
  163. package/dist/modules/eudr/commands/lifecycle-events.js.map +7 -0
  164. package/dist/modules/eudr/commands/mitigation-actions.js +488 -0
  165. package/dist/modules/eudr/commands/mitigation-actions.js.map +7 -0
  166. package/dist/modules/eudr/commands/plots.js +517 -0
  167. package/dist/modules/eudr/commands/plots.js.map +7 -0
  168. package/dist/modules/eudr/commands/product-mappings.js +468 -0
  169. package/dist/modules/eudr/commands/product-mappings.js.map +7 -0
  170. package/dist/modules/eudr/commands/risk-assessments.js +599 -0
  171. package/dist/modules/eudr/commands/risk-assessments.js.map +7 -0
  172. package/dist/modules/eudr/commands/statements.js +747 -0
  173. package/dist/modules/eudr/commands/statements.js.map +7 -0
  174. package/dist/modules/eudr/components/CountrySelectField.js +64 -0
  175. package/dist/modules/eudr/components/CountrySelectField.js.map +7 -0
  176. package/dist/modules/eudr/components/GeometryInput.js +133 -0
  177. package/dist/modules/eudr/components/GeometryInput.js.map +7 -0
  178. package/dist/modules/eudr/components/MappingSuggestionsDialog.js +307 -0
  179. package/dist/modules/eudr/components/MappingSuggestionsDialog.js.map +7 -0
  180. package/dist/modules/eudr/components/MitigationActionsSection.js +345 -0
  181. package/dist/modules/eudr/components/MitigationActionsSection.js.map +7 -0
  182. package/dist/modules/eudr/components/OrderSelectField.js +88 -0
  183. package/dist/modules/eudr/components/OrderSelectField.js.map +7 -0
  184. package/dist/modules/eudr/components/PlotImportDialog.js +229 -0
  185. package/dist/modules/eudr/components/PlotImportDialog.js.map +7 -0
  186. package/dist/modules/eudr/components/PlotMapPreview.js +35 -0
  187. package/dist/modules/eudr/components/PlotMapPreview.js.map +7 -0
  188. package/dist/modules/eudr/components/PlotMapPreviewImpl.js +97 -0
  189. package/dist/modules/eudr/components/PlotMapPreviewImpl.js.map +7 -0
  190. package/dist/modules/eudr/components/PlotMultiSelectField.js +198 -0
  191. package/dist/modules/eudr/components/PlotMultiSelectField.js.map +7 -0
  192. package/dist/modules/eudr/components/RiskCriteriaField.js +128 -0
  193. package/dist/modules/eudr/components/RiskCriteriaField.js.map +7 -0
  194. package/dist/modules/eudr/components/StatementLifecycleBar.js +274 -0
  195. package/dist/modules/eudr/components/StatementLifecycleBar.js.map +7 -0
  196. package/dist/modules/eudr/components/StatementReadinessChecklist.js +56 -0
  197. package/dist/modules/eudr/components/StatementReadinessChecklist.js.map +7 -0
  198. package/dist/modules/eudr/components/StatementRiskSection.js +179 -0
  199. package/dist/modules/eudr/components/StatementRiskSection.js.map +7 -0
  200. package/dist/modules/eudr/components/crudErrorI18n.js +31 -0
  201. package/dist/modules/eudr/components/crudErrorI18n.js.map +7 -0
  202. package/dist/modules/eudr/components/formConfig.js +593 -0
  203. package/dist/modules/eudr/components/formConfig.js.map +7 -0
  204. package/dist/modules/eudr/components/plotForm.js +68 -0
  205. package/dist/modules/eudr/components/plotForm.js.map +7 -0
  206. package/dist/modules/eudr/data/enrichers.js +62 -0
  207. package/dist/modules/eudr/data/enrichers.js.map +7 -0
  208. package/dist/modules/eudr/data/entities.js +437 -0
  209. package/dist/modules/eudr/data/entities.js.map +7 -0
  210. package/dist/modules/eudr/data/validators.js +271 -0
  211. package/dist/modules/eudr/data/validators.js.map +7 -0
  212. package/dist/modules/eudr/encryption.js +33 -0
  213. package/dist/modules/eudr/encryption.js.map +7 -0
  214. package/dist/modules/eudr/events.js +38 -0
  215. package/dist/modules/eudr/events.js.map +7 -0
  216. package/dist/modules/eudr/index.js +15 -0
  217. package/dist/modules/eudr/index.js.map +7 -0
  218. package/dist/modules/eudr/lib/annual-report.js +104 -0
  219. package/dist/modules/eudr/lib/annual-report.js.map +7 -0
  220. package/dist/modules/eudr/lib/completeness.js +64 -0
  221. package/dist/modules/eudr/lib/completeness.js.map +7 -0
  222. package/dist/modules/eudr/lib/detail-read-scope.js +16 -0
  223. package/dist/modules/eudr/lib/detail-read-scope.js.map +7 -0
  224. package/dist/modules/eudr/lib/geometry.js +142 -0
  225. package/dist/modules/eudr/lib/geometry.js.map +7 -0
  226. package/dist/modules/eudr/lib/notifications.js +56 -0
  227. package/dist/modules/eudr/lib/notifications.js.map +7 -0
  228. package/dist/modules/eudr/lib/plural.js +26 -0
  229. package/dist/modules/eudr/lib/plural.js.map +7 -0
  230. package/dist/modules/eudr/lib/reference-data.js +240 -0
  231. package/dist/modules/eudr/lib/reference-data.js.map +7 -0
  232. package/dist/modules/eudr/lib/species.js +10 -0
  233. package/dist/modules/eudr/lib/species.js.map +7 -0
  234. package/dist/modules/eudr/lib/statement-lifecycle.js +84 -0
  235. package/dist/modules/eudr/lib/statement-lifecycle.js.map +7 -0
  236. package/dist/modules/eudr/lib/statement-seeding.js +49 -0
  237. package/dist/modules/eudr/lib/statement-seeding.js.map +7 -0
  238. package/dist/modules/eudr/migrations/Migration20260706102002_eudr.js +15 -0
  239. package/dist/modules/eudr/migrations/Migration20260706102002_eudr.js.map +7 -0
  240. package/dist/modules/eudr/migrations/Migration20260706152612_eudr.js +24 -0
  241. package/dist/modules/eudr/migrations/Migration20260706152612_eudr.js.map +7 -0
  242. package/dist/modules/eudr/migrations/Migration20260722100000_eudr.js +15 -0
  243. package/dist/modules/eudr/migrations/Migration20260722100000_eudr.js.map +7 -0
  244. package/dist/modules/eudr/notifications.js +58 -0
  245. package/dist/modules/eudr/notifications.js.map +7 -0
  246. package/dist/modules/eudr/search.js +186 -0
  247. package/dist/modules/eudr/search.js.map +7 -0
  248. package/dist/modules/eudr/setup.js +12 -0
  249. package/dist/modules/eudr/setup.js.map +7 -0
  250. package/dist/modules/eudr/subscribers/notify-mitigation-completed.js +34 -0
  251. package/dist/modules/eudr/subscribers/notify-mitigation-completed.js.map +7 -0
  252. package/dist/modules/eudr/subscribers/notify-risk-concluded.js +35 -0
  253. package/dist/modules/eudr/subscribers/notify-risk-concluded.js.map +7 -0
  254. package/dist/modules/eudr/subscribers/notify-statement-reference-issued.js +37 -0
  255. package/dist/modules/eudr/subscribers/notify-statement-reference-issued.js.map +7 -0
  256. package/dist/modules/eudr/subscribers/notify-statement-submitted.js +34 -0
  257. package/dist/modules/eudr/subscribers/notify-statement-submitted.js.map +7 -0
  258. package/dist/modules/eudr/subscribers/notify-statement-withdrawn.js +34 -0
  259. package/dist/modules/eudr/subscribers/notify-statement-withdrawn.js.map +7 -0
  260. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-created.js +124 -0
  261. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-created.js.map +7 -0
  262. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.js +22 -0
  263. package/dist/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.js.map +7 -0
  264. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.client.js +203 -0
  265. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.client.js.map +7 -0
  266. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.js +23 -0
  267. package/dist/modules/eudr/widgets/dashboard/compliance-overview/widget.js.map +7 -0
  268. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.client.js +19 -0
  269. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.client.js.map +7 -0
  270. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.js +31 -0
  271. package/dist/modules/eudr/widgets/injection/eudr-product-column/widget.js.map +7 -0
  272. package/dist/modules/eudr/widgets/injection/order-compliance/widget.client.js +100 -0
  273. package/dist/modules/eudr/widgets/injection/order-compliance/widget.client.js.map +7 -0
  274. package/dist/modules/eudr/widgets/injection/order-compliance/widget.js +17 -0
  275. package/dist/modules/eudr/widgets/injection/order-compliance/widget.js.map +7 -0
  276. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.client.js +116 -0
  277. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.client.js.map +7 -0
  278. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.js +17 -0
  279. package/dist/modules/eudr/widgets/injection/supplier-compliance/widget.js.map +7 -0
  280. package/dist/modules/eudr/widgets/injection-table.js +32 -0
  281. package/dist/modules/eudr/widgets/injection-table.js.map +7 -0
  282. package/dist/modules/notifications/lib/notificationService.js +67 -3
  283. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  284. package/dist/modules/query_index/lib/engine.js +121 -48
  285. package/dist/modules/query_index/lib/engine.js.map +2 -2
  286. package/dist/modules/sales/api/documents/factory.js +17 -0
  287. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  288. package/dist/modules/sales/commands/documents.js +32 -18
  289. package/dist/modules/sales/commands/documents.js.map +2 -2
  290. package/dist/modules/sales/data/entities.js +1 -0
  291. package/dist/modules/sales/data/entities.js.map +2 -2
  292. package/dist/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.js +17 -0
  293. package/dist/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.js.map +7 -0
  294. package/generated/entities/eudr_due_diligence_statement/index.ts +22 -0
  295. package/generated/entities/eudr_evidence_submission/index.ts +24 -0
  296. package/generated/entities/eudr_mitigation_action/index.ts +14 -0
  297. package/generated/entities/eudr_plot/index.ts +18 -0
  298. package/generated/entities/eudr_product_mapping/index.ts +14 -0
  299. package/generated/entities/eudr_risk_assessment/index.ts +16 -0
  300. package/generated/entities/role/index.ts +1 -0
  301. package/generated/entities.ids.generated.ts +9 -0
  302. package/generated/entity-fields-registry.ts +121 -0
  303. package/package.json +7 -7
  304. package/src/helpers/integration/api.ts +13 -0
  305. package/src/modules/attachments/fields/attachment.tsx +5 -0
  306. package/src/modules/attachments/lib/crud.ts +2 -0
  307. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +7 -0
  308. package/src/modules/audit_logs/api/audit-logs/actions/undo/route.ts +9 -0
  309. package/src/modules/audit_logs/backend/audit-logs/page.tsx +4 -4
  310. package/src/modules/audit_logs/components/AuditLogsActions.tsx +20 -4
  311. package/src/modules/auth/api/login.ts +9 -2
  312. package/src/modules/auth/api/users/route.ts +8 -4
  313. package/src/modules/auth/backend/users/[id]/edit/page.tsx +17 -1
  314. package/src/modules/auth/backend/users/page.tsx +11 -0
  315. package/src/modules/auth/commands/users.ts +251 -85
  316. package/src/modules/auth/data/entities.ts +3 -0
  317. package/src/modules/auth/i18n/de.json +6 -0
  318. package/src/modules/auth/i18n/en.json +6 -0
  319. package/src/modules/auth/i18n/es.json +6 -0
  320. package/src/modules/auth/i18n/ko.json +6 -0
  321. package/src/modules/auth/i18n/pl.json +6 -0
  322. package/src/modules/auth/lib/sessionIntegrity.ts +1 -1
  323. package/src/modules/auth/lib/setup-app.ts +1 -1
  324. package/src/modules/auth/migrations/.snapshot-open-mercato.json +16 -0
  325. package/src/modules/auth/migrations/Migration20260728134212_auth.ts +14 -0
  326. package/src/modules/eudr/acl.ts +39 -0
  327. package/src/modules/eudr/ai-agents.ts +258 -0
  328. package/src/modules/eudr/ai-tools/compliance-pack.ts +618 -0
  329. package/src/modules/eudr/ai-tools/types.ts +44 -0
  330. package/src/modules/eudr/ai-tools.ts +15 -0
  331. package/src/modules/eudr/api/dashboard/widgets/compliance-overview/route.ts +481 -0
  332. package/src/modules/eudr/api/evidence-submissions/route.ts +335 -0
  333. package/src/modules/eudr/api/mitigation-actions/route.ts +263 -0
  334. package/src/modules/eudr/api/openapi.ts +28 -0
  335. package/src/modules/eudr/api/plots/import/route.ts +369 -0
  336. package/src/modules/eudr/api/plots/route.ts +296 -0
  337. package/src/modules/eudr/api/product-mappings/route.ts +269 -0
  338. package/src/modules/eudr/api/product-mappings/suggestions/apply/route.ts +382 -0
  339. package/src/modules/eudr/api/product-mappings/suggestions/route.ts +169 -0
  340. package/src/modules/eudr/api/reports/annual/route.ts +457 -0
  341. package/src/modules/eudr/api/risk-assessments/route.ts +323 -0
  342. package/src/modules/eudr/api/statements/[id]/export/route.ts +713 -0
  343. package/src/modules/eudr/api/statements/[id]/readiness/route.ts +129 -0
  344. package/src/modules/eudr/api/statements/route.ts +329 -0
  345. package/src/modules/eudr/api/suppliers/compliance/route.ts +241 -0
  346. package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.meta.ts +12 -0
  347. package/src/modules/eudr/backend/eudr/evidence-submissions/[id]/page.tsx +545 -0
  348. package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.meta.ts +13 -0
  349. package/src/modules/eudr/backend/eudr/evidence-submissions/create/page.tsx +383 -0
  350. package/src/modules/eudr/backend/eudr/evidence-submissions/page.meta.ts +12 -0
  351. package/src/modules/eudr/backend/eudr/evidence-submissions/page.tsx +379 -0
  352. package/src/modules/eudr/backend/eudr/page.meta.ts +12 -0
  353. package/src/modules/eudr/backend/eudr/page.tsx +477 -0
  354. package/src/modules/eudr/backend/eudr/plots/[id]/page.meta.ts +12 -0
  355. package/src/modules/eudr/backend/eudr/plots/[id]/page.tsx +404 -0
  356. package/src/modules/eudr/backend/eudr/plots/create/page.meta.ts +13 -0
  357. package/src/modules/eudr/backend/eudr/plots/create/page.tsx +267 -0
  358. package/src/modules/eudr/backend/eudr/plots/page.meta.ts +12 -0
  359. package/src/modules/eudr/backend/eudr/plots/page.tsx +442 -0
  360. package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.meta.ts +12 -0
  361. package/src/modules/eudr/backend/eudr/product-mappings/[id]/page.tsx +311 -0
  362. package/src/modules/eudr/backend/eudr/product-mappings/create/page.meta.ts +13 -0
  363. package/src/modules/eudr/backend/eudr/product-mappings/create/page.tsx +190 -0
  364. package/src/modules/eudr/backend/eudr/product-mappings/page.meta.ts +12 -0
  365. package/src/modules/eudr/backend/eudr/product-mappings/page.tsx +344 -0
  366. package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.meta.ts +12 -0
  367. package/src/modules/eudr/backend/eudr/risk-assessments/[id]/page.tsx +421 -0
  368. package/src/modules/eudr/backend/eudr/risk-assessments/create/page.meta.ts +13 -0
  369. package/src/modules/eudr/backend/eudr/risk-assessments/create/page.tsx +247 -0
  370. package/src/modules/eudr/backend/eudr/risk-assessments/page.meta.ts +12 -0
  371. package/src/modules/eudr/backend/eudr/risk-assessments/page.tsx +383 -0
  372. package/src/modules/eudr/backend/eudr/statements/[id]/page.meta.ts +12 -0
  373. package/src/modules/eudr/backend/eudr/statements/[id]/page.tsx +777 -0
  374. package/src/modules/eudr/backend/eudr/statements/create/page.meta.ts +13 -0
  375. package/src/modules/eudr/backend/eudr/statements/create/page.tsx +511 -0
  376. package/src/modules/eudr/backend/eudr/statements/page.meta.ts +12 -0
  377. package/src/modules/eudr/backend/eudr/statements/page.tsx +372 -0
  378. package/src/modules/eudr/ce.ts +52 -0
  379. package/src/modules/eudr/commands/evidence-submissions.ts +699 -0
  380. package/src/modules/eudr/commands/index.ts +6 -0
  381. package/src/modules/eudr/commands/lifecycle-events.ts +28 -0
  382. package/src/modules/eudr/commands/mitigation-actions.ts +574 -0
  383. package/src/modules/eudr/commands/plots.ts +627 -0
  384. package/src/modules/eudr/commands/product-mappings.ts +556 -0
  385. package/src/modules/eudr/commands/risk-assessments.ts +728 -0
  386. package/src/modules/eudr/commands/statements.ts +933 -0
  387. package/src/modules/eudr/components/CountrySelectField.tsx +77 -0
  388. package/src/modules/eudr/components/GeometryInput.tsx +170 -0
  389. package/src/modules/eudr/components/MappingSuggestionsDialog.tsx +385 -0
  390. package/src/modules/eudr/components/MitigationActionsSection.tsx +417 -0
  391. package/src/modules/eudr/components/OrderSelectField.tsx +116 -0
  392. package/src/modules/eudr/components/PlotImportDialog.tsx +277 -0
  393. package/src/modules/eudr/components/PlotMapPreview.tsx +46 -0
  394. package/src/modules/eudr/components/PlotMapPreviewImpl.tsx +111 -0
  395. package/src/modules/eudr/components/PlotMultiSelectField.tsx +238 -0
  396. package/src/modules/eudr/components/RiskCriteriaField.tsx +173 -0
  397. package/src/modules/eudr/components/StatementLifecycleBar.tsx +363 -0
  398. package/src/modules/eudr/components/StatementReadinessChecklist.tsx +82 -0
  399. package/src/modules/eudr/components/StatementRiskSection.tsx +242 -0
  400. package/src/modules/eudr/components/crudErrorI18n.ts +36 -0
  401. package/src/modules/eudr/components/formConfig.tsx +717 -0
  402. package/src/modules/eudr/components/plotForm.ts +75 -0
  403. package/src/modules/eudr/data/enrichers.ts +82 -0
  404. package/src/modules/eudr/data/entities.ts +369 -0
  405. package/src/modules/eudr/data/validators.ts +357 -0
  406. package/src/modules/eudr/encryption.ts +31 -0
  407. package/src/modules/eudr/events.ts +38 -0
  408. package/src/modules/eudr/i18n/de.json +747 -0
  409. package/src/modules/eudr/i18n/en.json +747 -0
  410. package/src/modules/eudr/i18n/es.json +747 -0
  411. package/src/modules/eudr/i18n/ko.json +747 -0
  412. package/src/modules/eudr/i18n/pl.json +747 -0
  413. package/src/modules/eudr/index.ts +13 -0
  414. package/src/modules/eudr/lib/annual-report.ts +120 -0
  415. package/src/modules/eudr/lib/completeness.ts +90 -0
  416. package/src/modules/eudr/lib/detail-read-scope.ts +44 -0
  417. package/src/modules/eudr/lib/geometry.ts +191 -0
  418. package/src/modules/eudr/lib/notifications.ts +85 -0
  419. package/src/modules/eudr/lib/plural.ts +36 -0
  420. package/src/modules/eudr/lib/reference-data.ts +128 -0
  421. package/src/modules/eudr/lib/species.ts +12 -0
  422. package/src/modules/eudr/lib/statement-lifecycle.ts +109 -0
  423. package/src/modules/eudr/lib/statement-seeding.ts +86 -0
  424. package/src/modules/eudr/migrations/.snapshot-open-mercato.json +2046 -0
  425. package/src/modules/eudr/migrations/Migration20260706102002_eudr.ts +16 -0
  426. package/src/modules/eudr/migrations/Migration20260706152612_eudr.ts +30 -0
  427. package/src/modules/eudr/migrations/Migration20260722100000_eudr.ts +17 -0
  428. package/src/modules/eudr/notifications.ts +56 -0
  429. package/src/modules/eudr/search.ts +212 -0
  430. package/src/modules/eudr/setup.ts +10 -0
  431. package/src/modules/eudr/subscribers/notify-mitigation-completed.ts +56 -0
  432. package/src/modules/eudr/subscribers/notify-risk-concluded.ts +58 -0
  433. package/src/modules/eudr/subscribers/notify-statement-reference-issued.ts +59 -0
  434. package/src/modules/eudr/subscribers/notify-statement-submitted.ts +49 -0
  435. package/src/modules/eudr/subscribers/notify-statement-withdrawn.ts +49 -0
  436. package/src/modules/eudr/subscribers/recompute-completeness-on-attachment-created.ts +189 -0
  437. package/src/modules/eudr/subscribers/recompute-completeness-on-attachment-deleted.ts +31 -0
  438. package/src/modules/eudr/widgets/dashboard/compliance-overview/widget.client.tsx +271 -0
  439. package/src/modules/eudr/widgets/dashboard/compliance-overview/widget.ts +24 -0
  440. package/src/modules/eudr/widgets/injection/eudr-product-column/widget.client.tsx +37 -0
  441. package/src/modules/eudr/widgets/injection/eudr-product-column/widget.ts +30 -0
  442. package/src/modules/eudr/widgets/injection/order-compliance/widget.client.tsx +131 -0
  443. package/src/modules/eudr/widgets/injection/order-compliance/widget.ts +16 -0
  444. package/src/modules/eudr/widgets/injection/supplier-compliance/widget.client.tsx +168 -0
  445. package/src/modules/eudr/widgets/injection/supplier-compliance/widget.ts +16 -0
  446. package/src/modules/eudr/widgets/injection-table.ts +31 -0
  447. package/src/modules/notifications/lib/notificationService.ts +83 -4
  448. package/src/modules/query_index/lib/engine.ts +139 -51
  449. package/src/modules/sales/api/documents/factory.ts +29 -0
  450. package/src/modules/sales/commands/documents.ts +65 -18
  451. package/src/modules/sales/data/entities.ts +1 -0
  452. package/src/modules/sales/migrations/.snapshot-open-mercato.json +10 -0
  453. package/src/modules/sales/migrations/Migration20260806120000_sales_tag_assignment_document_idx.ts +59 -0
@@ -47,6 +47,7 @@ type LoadedUser = {
47
47
  roles: string[]
48
48
  roleIds: string[]
49
49
  hasPassword: boolean
50
+ isConfirmed: boolean
50
51
  updatedAt: string | null
51
52
  }
52
53
 
@@ -61,6 +62,7 @@ type UserApiItem = {
61
62
  roles?: unknown
62
63
  roleIds?: unknown
63
64
  hasPassword?: boolean
65
+ isConfirmed?: boolean
64
66
  updatedAt?: string | null
65
67
  updated_at?: string | null
66
68
  }
@@ -268,6 +270,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
268
270
  roles: roleNames,
269
271
  roleIds: roleIds.length > 0 ? roleIds : roleNames,
270
272
  hasPassword: item.hasPassword !== false,
273
+ isConfirmed: item.isConfirmed !== false,
271
274
  updatedAt: typeof item.updatedAt === 'string'
272
275
  ? item.updatedAt
273
276
  : typeof item.updated_at === 'string'
@@ -399,11 +402,20 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
399
402
  options: initialRoleOptions,
400
403
  loadOptions: loadRoleOptions,
401
404
  })
405
+ items.push({
406
+ id: 'isConfirmed',
407
+ label: t('auth.users.form.field.active', 'Active'),
408
+ type: 'checkbox',
409
+ description: t(
410
+ 'auth.users.form.field.activeHint',
411
+ 'Clearing this deactivates the account: the user can no longer sign in and every active session is revoked.',
412
+ ),
413
+ })
402
414
  return items
403
415
  }, [actorIsSuperAdmin, initialRoleOptions, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t, userHasPassword])
404
416
 
405
417
  const detailFieldIds = React.useMemo(() => {
406
- const base: string[] = ['email', 'name', 'password', 'organizationId', 'roles']
418
+ const base: string[] = ['email', 'name', 'password', 'organizationId', 'roles', 'isConfirmed']
407
419
  if (actorIsSuperAdmin) base.splice(2, 0, 'tenantId')
408
420
  return base
409
421
  }, [actorIsSuperAdmin])
@@ -464,6 +476,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
464
476
  tenantId: initialUser.tenantId,
465
477
  organizationId: initialUser.organizationId,
466
478
  roles: initialUser.roleIds,
479
+ isConfirmed: initialUser.isConfirmed,
467
480
  updatedAt: initialUser.updatedAt,
468
481
  ...customFieldValues,
469
482
  }
@@ -555,6 +568,9 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
555
568
  password: values.password && values.password.trim() ? values.password : undefined,
556
569
  organizationId: values.organizationId ? values.organizationId : undefined,
557
570
  roles: Array.isArray(values.roles) ? values.roles : [],
571
+ // Only sent when the checkbox actually resolved to a boolean — never
572
+ // default a missing value to `false`, which would silently deactivate.
573
+ ...(typeof values.isConfirmed === 'boolean' ? { isConfirmed: values.isConfirmed } : {}),
558
574
  ...(Object.keys(customFields).length ? { customFields } : {}),
559
575
  }
560
576
  const userOptimisticLockHeader = buildOptimisticLockHeader(initialUser?.updatedAt)
@@ -9,6 +9,7 @@ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
9
9
  import type { SortingState } from '@tanstack/react-table'
10
10
  import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
11
11
  import { Button } from '@open-mercato/ui/primitives/button'
12
+ import { Badge } from '@open-mercato/ui/primitives/badge'
12
13
  import { RowActions } from '@open-mercato/ui/backend/RowActions'
13
14
  import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
14
15
  import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
@@ -30,6 +31,7 @@ type Row = {
30
31
  tenantId: string | null
31
32
  tenantName?: string | null
32
33
  roles: string[]
34
+ isConfirmed?: boolean
33
35
  updatedAt?: string | null
34
36
  }
35
37
 
@@ -378,6 +380,15 @@ export default function UsersListPage() {
378
380
  { accessorKey: 'name', header: t('auth.users.list.columns.name', 'Display name') },
379
381
  { accessorKey: 'organizationName', header: 'Organization' },
380
382
  { accessorKey: 'roles', header: 'Roles', cell: ({ row }) => (row.original.roles || []).join(', ') },
383
+ {
384
+ accessorKey: 'isConfirmed',
385
+ header: t('auth.users.list.columns.status', 'Status'),
386
+ cell: ({ row }) => (
387
+ row.original.isConfirmed === false
388
+ ? <Badge variant="neutral" dot>{t('auth.users.list.status.deactivated', 'Deactivated')}</Badge>
389
+ : <Badge variant="success" dot>{t('auth.users.list.status.active', 'Active')}</Badge>
390
+ ),
391
+ },
381
392
  ]
382
393
  if (showTenantColumn) {
383
394
  base.splice(1, 0, { accessorKey: 'tenantName', header: 'Tenant' })
@@ -13,7 +13,7 @@ import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/l
13
13
  import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
14
14
  import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
15
15
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
16
- import { UniqueConstraintViolationException } from '@mikro-orm/core'
16
+ import { UniqueConstraintViolationException, LockMode } from '@mikro-orm/core'
17
17
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
18
18
  import { User, UserRole, Role, UserAcl, Session, PasswordReset } from '@open-mercato/core/modules/auth/data/entities'
19
19
  import { Organization } from '@open-mercato/core/modules/directory/data/entities'
@@ -122,6 +122,7 @@ const updateSchema = z.object({
122
122
  password: passwordSchema.optional(),
123
123
  organizationId: z.string().uuid().optional(),
124
124
  roles: z.array(z.string()).optional(),
125
+ isConfirmed: z.boolean().optional(),
125
126
  })
126
127
 
127
128
  export const userCrudEvents: CrudEventsConfig = {
@@ -329,9 +330,22 @@ const createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResul
329
330
  const em = (ctx.container.resolve('em') as EntityManager)
330
331
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
331
332
 
333
+ // Evaluate the floor against the user's CURRENT tenant, not the one recorded at
334
+ // creation. A user moved to another tenant after being created would otherwise be
335
+ // checked against the origin tenant — where they no longer hold anything — and the
336
+ // undo would hard-delete the destination tenant's last administrator unchecked.
337
+ // The create snapshot is only a fallback for a row that is already gone.
332
338
  let removed: User | null = null
333
339
  await withAtomicFlush(em, [
334
340
  async () => {
341
+ const current = await findOneWithDecryption(em, User, { id: userId, deletedAt: null }, {}, { tenantId: null, organizationId: null })
342
+ const floorTenantId = current?.tenantId ? String(current.tenantId) : (snapshot?.tenantId ?? null)
343
+
344
+ // Undoing a create hard-deletes the user, so it can strip a tenant's last active
345
+ // admin exactly like `auth.users.delete` can — promote a second admin, delete the
346
+ // first, then undo the promotion's create. Same guard applies.
347
+ await enforceProtectedRoleFloor(em, floorTenantId, userId, { deleting: true }, ctx)
348
+
335
349
  await em.nativeDelete(UserAcl, { user: userId })
336
350
  await em.nativeDelete(UserRole, { user: userId })
337
351
  await em.nativeDelete(Session, { user: userId })
@@ -357,7 +371,7 @@ const createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResul
357
371
  soft: false,
358
372
  })
359
373
  },
360
- ], { transaction: true })
374
+ ], { transaction: true, label: 'auth.users.create.undo' })
361
375
 
362
376
  await emitCrudUndoSideEffects({
363
377
  dataEngine: de,
@@ -522,14 +536,17 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
522
536
  async execute(rawInput, ctx) {
523
537
  const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)
524
538
  const em = (ctx.container.resolve('em') as EntityManager)
539
+ const actorTenantScope = resolveActorTenantScope(ctx)
540
+ const existing = await findOneWithDecryption(em, User, { id: parsed.id, deletedAt: null }, {}, { tenantId: null, organizationId: null })
541
+ if (!existing) throw new CrudHttpError(404, { error: 'User not found' })
542
+ if (actorTenantScope && existing.tenantId && String(existing.tenantId) !== actorTenantScope) {
543
+ throw new CrudHttpError(404, { error: 'User not found' })
544
+ }
545
+
525
546
  const rolesBefore = Array.isArray(parsed.roles)
526
547
  ? await loadUserRoleNames(em, parsed.id)
527
548
  : null
528
549
 
529
- // Resolve the tenant the user will belong to after this update first, so the email
530
- // duplicate check below can be scoped to it. Email is unique per-tenant, not globally
531
- // (see Migration20260610120000: users_tenant_email_hash_uniq) — a matching email in another
532
- // tenant must not block the update or leak cross-tenant account existence (#2934).
533
550
  let tenantId: string | null | undefined
534
551
  if (parsed.organizationId !== undefined) {
535
552
  const organization = await findOneWithDecryption(
@@ -543,25 +560,11 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
543
560
  tenantId = organization.tenant?.id ? String(organization.tenant.id) : null
544
561
  }
545
562
 
546
- if (parsed.email !== undefined) {
547
- const targetTenantId = tenantId !== undefined
548
- ? tenantId
549
- : await resolveUserTenantId(em, parsed.id)
550
- const duplicate = await findOneWithDecryption(
551
- em,
552
- User,
553
- {
554
- $or: [{ email: parsed.email }, { emailHash: { $in: emailHashLookupValues(parsed.email) } }],
555
- deletedAt: null,
556
- tenantId: targetTenantId,
557
- id: { $ne: parsed.id } as any,
558
- } as FilterQuery<User>,
559
- {},
560
- { tenantId: null, organizationId: null },
561
- )
562
- if (duplicate) await throwDuplicateEmailError()
563
- }
563
+ const userTenantId = existing.tenantId ? String(existing.tenantId) : null
564
+ const targetTenantId = tenantId !== undefined ? tenantId : userTenantId
565
+ const isTenantChanging = targetTenantId !== userTenantId
564
566
 
567
+ // Hash password BEFORE transaction begins to avoid holding locks during CPU-heavy tasks
565
568
  let hashed: string | null = null
566
569
  let emailHash: string | null = null
567
570
  if (parsed.password) {
@@ -572,53 +575,87 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
572
575
  emailHash = computeEmailHash(parsed.email)
573
576
  }
574
577
 
575
- const actorTenantScope = resolveActorTenantScope(ctx)
576
578
  const updateWhere: Record<string, unknown> = { id: parsed.id, deletedAt: null }
577
579
  if (actorTenantScope) updateWhere.tenantId = actorTenantScope
578
580
 
579
581
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
580
- let user: User | null
581
- try {
582
- user = await de.updateOrmEntity({
583
- entity: User,
584
- where: updateWhere as FilterQuery<User>,
585
- apply: (entity) => {
586
- if (parsed.email !== undefined) {
587
- entity.email = parsed.email
588
- entity.emailHash = emailHash
589
- }
590
- if (parsed.name !== undefined) {
591
- entity.name = parsed.name
592
- }
593
- if (parsed.organizationId !== undefined) {
594
- entity.organizationId = parsed.organizationId
595
- entity.tenantId = tenantId ?? null
596
- }
597
- if (hashed) entity.passwordHash = hashed
598
- },
599
- })
600
- } catch (error) {
601
- if (isUniqueViolation(error)) await throwDuplicateEmailError()
602
- throw error
603
- }
604
- if (!user) throw new CrudHttpError(404, { error: 'User not found' })
582
+ let user!: User
605
583
 
606
- if (hashed) {
607
- await em.nativeDelete(Session, { user: parsed.id })
608
- }
584
+ await withAtomicFlush(em, [
585
+ async () => {
586
+ // Floor check must run inside the transaction so that LockMode.PESSIMISTIC_WRITE locks Role rows properly
587
+ await enforceProtectedRoleFloor(em, userTenantId, parsed.id, {
588
+ deactivating: parsed.isConfirmed === false || isTenantChanging,
589
+ newRoles: parsed.roles,
590
+ }, ctx)
591
+
592
+ // Email is unique per-tenant, not globally (see Migration20260610120000:
593
+ // users_tenant_email_hash_uniq) — a matching email in another tenant must not block
594
+ // the update or leak cross-tenant account existence (#2934). `targetTenantId` is the
595
+ // tenant the user will belong to after this update, so the check follows a move.
596
+ if (parsed.email !== undefined) {
597
+ const duplicate = await findOneWithDecryption(
598
+ em,
599
+ User,
600
+ {
601
+ $or: [{ email: parsed.email }, { emailHash: { $in: emailHashLookupValues(parsed.email) } }],
602
+ deletedAt: null,
603
+ tenantId: targetTenantId,
604
+ id: { $ne: parsed.id } as any,
605
+ } as FilterQuery<User>,
606
+ {},
607
+ { tenantId: null, organizationId: null },
608
+ )
609
+ if (duplicate) await throwDuplicateEmailError()
610
+ }
609
611
 
610
- if (Array.isArray(parsed.roles)) {
611
- await syncUserRoles(em, user, parsed.roles, user.tenantId ? String(user.tenantId) : tenantId ?? null)
612
- }
612
+ try {
613
+ const updated = await de.updateOrmEntity({
614
+ entity: User,
615
+ where: updateWhere as FilterQuery<User>,
616
+ apply: (entity) => {
617
+ if (parsed.email !== undefined) {
618
+ entity.email = parsed.email
619
+ entity.emailHash = emailHash
620
+ }
621
+ if (parsed.name !== undefined) {
622
+ entity.name = parsed.name
623
+ }
624
+ if (parsed.organizationId !== undefined) {
625
+ entity.organizationId = parsed.organizationId
626
+ entity.tenantId = tenantId ?? null
627
+ }
628
+ if (parsed.isConfirmed !== undefined) {
629
+ entity.isConfirmed = parsed.isConfirmed
630
+ }
631
+ if (hashed) entity.passwordHash = hashed
632
+ },
633
+ })
634
+ if (updated) user = updated
635
+ } catch (error) {
636
+ if (isUniqueViolation(error)) await throwDuplicateEmailError()
637
+ throw error
638
+ }
639
+ if (!user) throw new CrudHttpError(404, { error: 'User not found' })
613
640
 
614
- await setCustomFieldsIfAny({
615
- dataEngine: de,
616
- entityId: E.auth.user,
617
- recordId: String(user.id),
618
- organizationId: user.organizationId ? String(user.organizationId) : null,
619
- tenantId: user.tenantId ? String(user.tenantId) : tenantId ?? null,
620
- values: custom,
621
- })
641
+ if (hashed || parsed.isConfirmed === false) {
642
+ await em.nativeDelete(Session, { user: parsed.id })
643
+ }
644
+
645
+ if (Array.isArray(parsed.roles)) {
646
+ await syncUserRoles(em, user, parsed.roles, user.tenantId ? String(user.tenantId) : tenantId ?? null)
647
+ }
648
+
649
+ await setCustomFieldsIfAny({
650
+ dataEngine: de,
651
+ entityId: E.auth.user,
652
+ recordId: String(user.id),
653
+ organizationId: user.organizationId ? String(user.organizationId) : null,
654
+ tenantId: user.tenantId ? String(user.tenantId) : tenantId ?? null,
655
+ values: custom,
656
+ })
657
+ }
658
+ ], { transaction: true })
622
659
 
623
660
  const identifiers = {
624
661
  id: String(user.id),
@@ -706,23 +743,42 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
706
743
  const userId = before.id
707
744
  const em = (ctx.container.resolve('em') as EntityManager)
708
745
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
709
- const updated = await de.updateOrmEntity({
710
- entity: User,
711
- where: { id: userId, deletedAt: null } as FilterQuery<User>,
712
- apply: (entity) => {
713
- entity.email = before.email
714
- entity.organizationId = before.organizationId ?? null
715
- entity.tenantId = before.tenantId ?? null
716
- entity.passwordHash = before.passwordHash ?? null
717
- entity.name = before.name ?? null
718
- entity.isConfirmed = before.isConfirmed
719
- },
720
- })
721
746
 
722
- if (updated) {
723
- await syncUserRoles(em, updated, before.roles, before.tenantId)
724
- await em.flush()
725
- }
747
+ // Reverting an update can drop the tenant below a protected role's floor just as the
748
+ // forward path can — restoring an empty `before.roles` on what is now the last admin,
749
+ // or restoring `isConfirmed: false`. Undo is reachable from the audit-log UI, so the
750
+ // guard has to run here too, inside a transaction so the row lock is valid.
751
+ const restoredTenantId = before.tenantId ? String(before.tenantId) : null
752
+
753
+ let updated: User | null = null
754
+ await withAtomicFlush(em, [
755
+ async () => {
756
+ const current = await findOneWithDecryption(em, User, { id: userId, deletedAt: null }, {}, { tenantId: null, organizationId: null })
757
+ const currentTenantId = current?.tenantId ? String(current.tenantId) : null
758
+
759
+ await enforceProtectedRoleFloor(em, currentTenantId, userId, {
760
+ deactivating: before.isConfirmed === false || restoredTenantId !== currentTenantId,
761
+ newRoles: before.roles,
762
+ }, ctx)
763
+
764
+ updated = await de.updateOrmEntity({
765
+ entity: User,
766
+ where: { id: userId, deletedAt: null } as FilterQuery<User>,
767
+ apply: (entity) => {
768
+ entity.email = before.email
769
+ entity.organizationId = before.organizationId ?? null
770
+ entity.tenantId = before.tenantId ?? null
771
+ entity.passwordHash = before.passwordHash ?? null
772
+ entity.name = before.name ?? null
773
+ entity.isConfirmed = before.isConfirmed
774
+ },
775
+ })
776
+
777
+ if (updated) {
778
+ await syncUserRoles(em, updated, before.roles, before.tenantId)
779
+ }
780
+ },
781
+ ], { transaction: true, label: 'auth.users.update.undo' })
726
782
 
727
783
  const reset = buildCustomFieldResetMap(before.custom, after?.custom)
728
784
  if (Object.keys(reset).length) {
@@ -781,12 +837,22 @@ const deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?
781
837
  const em = (ctx.container.resolve('em') as EntityManager)
782
838
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
783
839
  const actorTenantScope = resolveActorTenantScope(ctx)
840
+
841
+ const existing = await findOneWithDecryption(em, User, { id, deletedAt: null }, {}, { tenantId: null, organizationId: null })
842
+ if (!existing) throw new CrudHttpError(404, { error: 'User not found' })
843
+ if (actorTenantScope && existing.tenantId && String(existing.tenantId) !== actorTenantScope) {
844
+ throw new CrudHttpError(404, { error: 'User not found' })
845
+ }
846
+
784
847
  const deleteWhere: Record<string, unknown> = { id, deletedAt: null }
785
848
  if (actorTenantScope) deleteWhere.tenantId = actorTenantScope
786
849
 
787
850
  let user!: User
788
851
  await withAtomicFlush(em, [
789
852
  async () => {
853
+ const userTenantId = existing.tenantId ? String(existing.tenantId) : null
854
+ await enforceProtectedRoleFloor(em, userTenantId, id, { deleting: true }, ctx)
855
+
790
856
  await em.nativeDelete(UserAcl, { user: id })
791
857
  await em.nativeDelete(UserRole, { user: id })
792
858
  await em.nativeDelete(Session, { user: id })
@@ -1085,11 +1151,6 @@ function arrayEquals(left: string[] | undefined, right: string[]): boolean {
1085
1151
  return left.every((value, idx) => value === right[idx])
1086
1152
  }
1087
1153
 
1088
- async function resolveUserTenantId(em: EntityManager, id: string): Promise<string | null> {
1089
- const existing = await findOneWithDecryption(em, User, { id, deletedAt: null }, {}, { tenantId: null, organizationId: null })
1090
- return existing?.tenantId ? String(existing.tenantId) : null
1091
- }
1092
-
1093
1154
  async function throwDuplicateEmailError(): Promise<never> {
1094
1155
  const { translate } = await resolveTranslations()
1095
1156
  const message = translate('auth.users.errors.emailExists', 'Email already in use')
@@ -1099,3 +1160,108 @@ async function throwDuplicateEmailError(): Promise<never> {
1099
1160
  details: [{ path: ['email'], message, code: 'duplicate', origin: 'validation' }],
1100
1161
  })
1101
1162
  }
1163
+
1164
+ type ProtectedRoleFloorOptions = {
1165
+ deactivating?: boolean
1166
+ newRoles?: string[]
1167
+ deleting?: boolean
1168
+ }
1169
+
1170
+ /**
1171
+ * True when the requested mutation can lower a role's active holder count. Guards the
1172
+ * floor check so that ordinary edits (display name, password, email) never take the
1173
+ * tenant-wide role lock — `PUT /api/auth/profile` routes every self-service password
1174
+ * change through `auth.users.update`, so an unconditional lock would serialize them.
1175
+ */
1176
+ function couldReduceActiveHolders(options: ProtectedRoleFloorOptions): boolean {
1177
+ return options.deleting === true || options.deactivating === true || options.newRoles !== undefined
1178
+ }
1179
+
1180
+ async function enforceProtectedRoleFloor(
1181
+ em: EntityManager,
1182
+ tenantId: string | null,
1183
+ userId: string,
1184
+ options: ProtectedRoleFloorOptions,
1185
+ ctx?: CommandRuntimeContext,
1186
+ ): Promise<void> {
1187
+ // Internal automation (CLI, migrations, tenant teardown) must never be blocked by the
1188
+ // floor. Superadmins are deliberately NOT exempt — see the spec's Risks section.
1189
+ if (ctx?.systemActor === true) return
1190
+ if (!couldReduceActiveHolders(options)) return
1191
+
1192
+ const normalizedTenantId = normalizeTenantId(tenantId) ?? null
1193
+ if (!normalizedTenantId) return
1194
+
1195
+ // Find all protected roles in this tenant, acquiring a pessimistic write lock in a deterministic primary key order
1196
+ const protectedRoles = await findWithDecryption(em, Role, {
1197
+ tenantId: normalizedTenantId,
1198
+ minActiveHolders: { $gt: 0 },
1199
+ deletedAt: null
1200
+ }, {
1201
+ lockMode: LockMode.PESSIMISTIC_WRITE,
1202
+ orderBy: { id: 'ASC' }
1203
+ }, { tenantId: normalizedTenantId, organizationId: null })
1204
+
1205
+ if (protectedRoles.length === 0) return
1206
+
1207
+ const { translate } = await resolveTranslations()
1208
+
1209
+ for (const role of protectedRoles) {
1210
+ const minFloor = role.minActiveHolders ?? 0
1211
+ if (minFloor <= 0) continue
1212
+
1213
+ // Active links for this role, scoped to the tenant by the nested user filter so the
1214
+ // database — not a post-filter — enforces isolation. Deliberately NOT populated:
1215
+ // `findWithDecryption` runs `decryptEntityGraph` over loaded relations, which would
1216
+ // decrypt every admin's email and name on each check just to read their ids.
1217
+ const activeLinks = await findWithDecryption(em, UserRole, {
1218
+ role: role.id,
1219
+ deletedAt: null,
1220
+ user: {
1221
+ deletedAt: null,
1222
+ isConfirmed: true,
1223
+ tenantId: normalizedTenantId
1224
+ }
1225
+ }, {}, { tenantId: null, organizationId: null })
1226
+
1227
+ // Count distinct user IDs in the tenant holding this role to prevent overcounting due to duplicate links
1228
+ const activeUserIds = Array.from(
1229
+ new Set(
1230
+ activeLinks
1231
+ .map((link) => {
1232
+ const userRef = link.user as unknown as { id?: string } | string | null | undefined
1233
+ const linkedUserId = typeof userRef === 'string' ? userRef : userRef?.id
1234
+ return linkedUserId ? String(linkedUserId) : null
1235
+ })
1236
+ .filter((id): id is string => !!id)
1237
+ )
1238
+ )
1239
+
1240
+ // Is the target user currently one of the active holders?
1241
+ if (activeUserIds.includes(userId)) {
1242
+ // Determine if they will remain an active holder
1243
+ let willStillBeActiveHolder = true
1244
+
1245
+ if (options.deleting || options.deactivating) {
1246
+ willStillBeActiveHolder = false
1247
+ } else if (options.newRoles !== undefined) {
1248
+ // Checking role list
1249
+ const desiredUnique = Array.from(new Set(options.newRoles.map((r) => r.trim().toLowerCase()).filter(Boolean)))
1250
+ const roleNameLower = role.name.toLowerCase()
1251
+ const hasRoleByName = desiredUnique.includes(roleNameLower) || desiredUnique.includes(String(role.id).toLowerCase())
1252
+ if (!hasRoleByName) {
1253
+ willStillBeActiveHolder = false
1254
+ }
1255
+ }
1256
+
1257
+ if (!willStillBeActiveHolder) {
1258
+ const remaining = activeUserIds.length - 1
1259
+ if (remaining < minFloor) {
1260
+ throw new CrudHttpError(400, {
1261
+ error: translate('auth.users.errors.lastHolderOfCriticalRole', 'Cannot remove the last active holder of role "{roleName}"', { roleName: role.name })
1262
+ })
1263
+ }
1264
+ }
1265
+ }
1266
+ }
1267
+ }
@@ -62,6 +62,9 @@ export class Role {
62
62
  @Property({ name: 'tenant_id', type: 'uuid' })
63
63
  tenantId!: string
64
64
 
65
+ @Property({ name: 'min_active_holders', type: 'integer', default: 0 })
66
+ minActiveHolders: number = 0
67
+
65
68
  @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
66
69
  createdAt: Date = new Date()
67
70
 
@@ -162,6 +162,7 @@
162
162
  "auth.users.consents.loading": "Einwilligungen werden geladen...",
163
163
  "auth.users.consents.withdrawn": "Widerrufen",
164
164
  "auth.users.errors.emailExists": "E-Mail-Adresse wird bereits verwendet",
165
+ "auth.users.errors.lastHolderOfCriticalRole": "Der letzte aktive Inhaber der Rolle \"{roleName}\" kann nicht entfernt werden",
165
166
  "auth.users.flash.created": "Benutzer erstellt",
166
167
  "auth.users.flash.createdEmailFailed": "Benutzer erstellt, aber die Einladungs-E-Mail konnte nicht gesendet werden. Sie können sie über die Benutzerseite erneut senden.",
167
168
  "auth.users.flash.createdWithInvite": "Benutzer erstellt und Einladung gesendet",
@@ -182,6 +183,8 @@
182
183
  "auth.users.form.errors.noId": "Keine Benutzer-ID angegeben",
183
184
  "auth.users.form.errors.notFound": "Benutzer nicht gefunden",
184
185
  "auth.users.form.errors.widgetsAssign": "Dashboard-Widgets konnten dem neuen Benutzer nicht zugewiesen werden",
186
+ "auth.users.form.field.active": "Aktiv",
187
+ "auth.users.form.field.activeHint": "Das Entfernen des Hakens deaktiviert das Konto: Die Anmeldung ist nicht mehr möglich und alle aktiven Sitzungen werden beendet.",
185
188
  "auth.users.form.field.email": "E-Mail",
186
189
  "auth.users.form.field.name": "Anzeigename",
187
190
  "auth.users.form.field.newPassword": "Neues Passwort",
@@ -203,10 +206,13 @@
203
206
  "auth.users.form.title.create": "Benutzer erstellen",
204
207
  "auth.users.form.title.edit": "Benutzer bearbeiten",
205
208
  "auth.users.list.columns.name": "Anzeigename",
209
+ "auth.users.list.columns.status": "Status",
206
210
  "auth.users.list.error.delete": "Benutzer konnte nicht gelöscht werden",
207
211
  "auth.users.list.error.load": "Benutzer konnten nicht geladen werden",
208
212
  "auth.users.list.filters.name": "Anzeigename",
209
213
  "auth.users.list.filters.namePlaceholder": "Nach Anzeigename filtern",
214
+ "auth.users.list.status.active": "Aktiv",
215
+ "auth.users.list.status.deactivated": "Deaktiviert",
210
216
  "auth.users.widgets.errors.load": "Dashboard-Widgets können nicht geladen werden. Sie können sie später auf der Benutzerseite konfigurieren.",
211
217
  "auth.users.widgets.loading": "Widgets werden geladen…",
212
218
  "auth.users.widgets.mode.hint": "Neue Benutzer erben Widgets von ihren zugewiesenen Rollen. Überschreiben Sie die Auswahl, um eine eigene Zusammenstellung festzulegen.",
@@ -162,6 +162,7 @@
162
162
  "auth.users.consents.loading": "Loading consents...",
163
163
  "auth.users.consents.withdrawn": "Withdrawn",
164
164
  "auth.users.errors.emailExists": "Email already in use",
165
+ "auth.users.errors.lastHolderOfCriticalRole": "Cannot remove the last active holder of role \"{roleName}\"",
165
166
  "auth.users.flash.created": "User created",
166
167
  "auth.users.flash.createdEmailFailed": "User created but invitation email could not be sent. You can resend it from the user page.",
167
168
  "auth.users.flash.createdWithInvite": "User created and invitation sent",
@@ -182,6 +183,8 @@
182
183
  "auth.users.form.errors.noId": "No user ID provided",
183
184
  "auth.users.form.errors.notFound": "User not found",
184
185
  "auth.users.form.errors.widgetsAssign": "Failed to assign dashboard widgets to the new user",
186
+ "auth.users.form.field.active": "Active",
187
+ "auth.users.form.field.activeHint": "Clearing this deactivates the account: the user can no longer sign in and every active session is revoked.",
185
188
  "auth.users.form.field.email": "Email",
186
189
  "auth.users.form.field.name": "Display name",
187
190
  "auth.users.form.field.newPassword": "New Password",
@@ -203,10 +206,13 @@
203
206
  "auth.users.form.title.create": "Create User",
204
207
  "auth.users.form.title.edit": "Edit User",
205
208
  "auth.users.list.columns.name": "Display name",
209
+ "auth.users.list.columns.status": "Status",
206
210
  "auth.users.list.error.delete": "Failed to delete user",
207
211
  "auth.users.list.error.load": "Failed to load users",
208
212
  "auth.users.list.filters.name": "Display name",
209
213
  "auth.users.list.filters.namePlaceholder": "Filter by display name",
214
+ "auth.users.list.status.active": "Active",
215
+ "auth.users.list.status.deactivated": "Deactivated",
210
216
  "auth.users.widgets.errors.load": "Unable to load dashboard widgets. You can configure them later from the user page.",
211
217
  "auth.users.widgets.loading": "Loading widgets…",
212
218
  "auth.users.widgets.mode.hint": "New users inherit widgets from their assigned roles. Override to pick a custom set.",
@@ -162,6 +162,7 @@
162
162
  "auth.users.consents.loading": "Cargando consentimientos...",
163
163
  "auth.users.consents.withdrawn": "Retirado",
164
164
  "auth.users.errors.emailExists": "El correo ya está en uso",
165
+ "auth.users.errors.lastHolderOfCriticalRole": "No se puede eliminar al último titular activo del rol \"{roleName}\"",
165
166
  "auth.users.flash.created": "Usuario creado",
166
167
  "auth.users.flash.createdEmailFailed": "Usuario creado pero no se pudo enviar el correo de invitación. Puede reenviarlo desde la página del usuario.",
167
168
  "auth.users.flash.createdWithInvite": "Usuario creado e invitación enviada",
@@ -182,6 +183,8 @@
182
183
  "auth.users.form.errors.noId": "No se proporcionó ID de usuario",
183
184
  "auth.users.form.errors.notFound": "Usuario no encontrado",
184
185
  "auth.users.form.errors.widgetsAssign": "No se pudieron asignar los widgets del panel al nuevo usuario",
186
+ "auth.users.form.field.active": "Activo",
187
+ "auth.users.form.field.activeHint": "Al desmarcarlo se desactiva la cuenta: el usuario ya no puede iniciar sesión y se revocan todas las sesiones activas.",
185
188
  "auth.users.form.field.email": "Correo electrónico",
186
189
  "auth.users.form.field.name": "Nombre visible",
187
190
  "auth.users.form.field.newPassword": "Nueva contraseña",
@@ -203,10 +206,13 @@
203
206
  "auth.users.form.title.create": "Crear usuario",
204
207
  "auth.users.form.title.edit": "Editar usuario",
205
208
  "auth.users.list.columns.name": "Nombre visible",
209
+ "auth.users.list.columns.status": "Estado",
206
210
  "auth.users.list.error.delete": "No se pudo eliminar el usuario",
207
211
  "auth.users.list.error.load": "No se pudieron cargar los usuarios",
208
212
  "auth.users.list.filters.name": "Nombre visible",
209
213
  "auth.users.list.filters.namePlaceholder": "Filtrar por nombre visible",
214
+ "auth.users.list.status.active": "Activo",
215
+ "auth.users.list.status.deactivated": "Desactivado",
210
216
  "auth.users.widgets.errors.load": "No se pudieron cargar los widgets del panel. Puedes configurarlos más tarde desde la página del usuario.",
211
217
  "auth.users.widgets.loading": "Cargando widgets…",
212
218
  "auth.users.widgets.mode.hint": "Los nuevos usuarios heredan los widgets de las funciones asignadas. Sobrescribe para elegir un conjunto personalizado.",
@@ -162,6 +162,7 @@
162
162
  "auth.users.consents.loading": "동의 정보를 불러오는 중...",
163
163
  "auth.users.consents.withdrawn": "철회함",
164
164
  "auth.users.errors.emailExists": "이미 사용 중인 이메일입니다",
165
+ "auth.users.errors.lastHolderOfCriticalRole": "\"{roleName}\" 역할의 마지막 활성 보유자는 제거할 수 없습니다",
165
166
  "auth.users.flash.created": "사용자가 생성되었습니다",
166
167
  "auth.users.flash.createdEmailFailed": "사용자가 생성되었지만 초대 이메일을 보낼 수 없었습니다. 사용자 페이지에서 다시 보낼 수 있습니다.",
167
168
  "auth.users.flash.createdWithInvite": "사용자가 생성되고 초대가 전송되었습니다",
@@ -182,6 +183,8 @@
182
183
  "auth.users.form.errors.noId": "제공된 사용자 ID가 없습니다",
183
184
  "auth.users.form.errors.notFound": "사용자를 찾을 수 없습니다",
184
185
  "auth.users.form.errors.widgetsAssign": "새 사용자에게 대시보드 위젯을 할당하지 못했습니다",
186
+ "auth.users.form.field.active": "활성",
187
+ "auth.users.form.field.activeHint": "선택을 해제하면 계정이 비활성화됩니다. 사용자는 로그인할 수 없으며 활성 세션이 모두 취소됩니다.",
185
188
  "auth.users.form.field.email": "이메일",
186
189
  "auth.users.form.field.name": "표시 이름",
187
190
  "auth.users.form.field.newPassword": "새 비밀번호",
@@ -203,10 +206,13 @@
203
206
  "auth.users.form.title.create": "사용자 생성",
204
207
  "auth.users.form.title.edit": "사용자 편집",
205
208
  "auth.users.list.columns.name": "표시 이름",
209
+ "auth.users.list.columns.status": "상태",
206
210
  "auth.users.list.error.delete": "사용자 삭제에 실패했습니다",
207
211
  "auth.users.list.error.load": "사용자를 불러오지 못했습니다",
208
212
  "auth.users.list.filters.name": "표시 이름",
209
213
  "auth.users.list.filters.namePlaceholder": "표시 이름으로 필터링",
214
+ "auth.users.list.status.active": "활성",
215
+ "auth.users.list.status.deactivated": "비활성",
210
216
  "auth.users.widgets.errors.load": "대시보드 위젯을 불러올 수 없습니다. 나중에 사용자 페이지에서 구성할 수 있습니다.",
211
217
  "auth.users.widgets.loading": "위젯을 불러오는 중…",
212
218
  "auth.users.widgets.mode.hint": "새 사용자는 할당된 역할에서 위젯을 상속받습니다. 커스텀 세트를 선택하려면 재정의하세요.",