@open-mercato/core 0.6.4-develop.4217.1.c9aa050183 → 0.6.4-develop.4239.1.4a264a5828

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 (408) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_time_entry/index.js +37 -0
  3. package/dist/generated/entities/staff_time_entry/index.js.map +7 -0
  4. package/dist/generated/entities/staff_time_entry_segment/index.js +23 -0
  5. package/dist/generated/entities/staff_time_entry_segment/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +35 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +7 -0
  8. package/dist/generated/entities/staff_time_project_member/index.js +29 -0
  9. package/dist/generated/entities/staff_time_project_member/index.js.map +7 -0
  10. package/dist/generated/entities.ids.generated.js +5 -1
  11. package/dist/generated/entities.ids.generated.js.map +2 -2
  12. package/dist/generated/entity-fields-registry.js +64 -0
  13. package/dist/generated/entity-fields-registry.js.map +2 -2
  14. package/dist/helpers/integration/timesheetFixtures.js +50 -0
  15. package/dist/helpers/integration/timesheetFixtures.js.map +7 -0
  16. package/dist/modules/attachments/api/library/[id]/route.js +20 -16
  17. package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
  18. package/dist/modules/attachments/api/route.js +18 -14
  19. package/dist/modules/attachments/api/route.js.map +2 -2
  20. package/dist/modules/auth/api/roles/acl/route.js +10 -4
  21. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  22. package/dist/modules/auth/api/sidebar/preferences/route.js +27 -20
  23. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  24. package/dist/modules/auth/api/users/acl/route.js +16 -11
  25. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  26. package/dist/modules/auth/commands/users.js +87 -71
  27. package/dist/modules/auth/commands/users.js.map +2 -2
  28. package/dist/modules/auth/services/sidebarPreferencesService.js +39 -30
  29. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  30. package/dist/modules/business_rules/backend/logs/[id]/page.js +24 -5
  31. package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
  32. package/dist/modules/catalog/api/offers/route.js +15 -5
  33. package/dist/modules/catalog/api/offers/route.js.map +2 -2
  34. package/dist/modules/catalog/commands/categories.js +61 -12
  35. package/dist/modules/catalog/commands/categories.js.map +2 -2
  36. package/dist/modules/catalog/commands/products.js +79 -54
  37. package/dist/modules/catalog/commands/products.js.map +2 -2
  38. package/dist/modules/catalog/commands/variants.js +29 -16
  39. package/dist/modules/catalog/commands/variants.js.map +2 -2
  40. package/dist/modules/currencies/backend/currencies/[id]/page.js +19 -2
  41. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  42. package/dist/modules/currencies/commands/currencies.js +15 -8
  43. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  44. package/dist/modules/customer_accounts/api/admin/users.js +27 -26
  45. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  46. package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
  47. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  48. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
  49. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  50. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +27 -7
  51. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +27 -7
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customers/backend/customers/people/[id]/page.js +29 -8
  55. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  56. package/dist/modules/customers/commands/addresses.js +35 -21
  57. package/dist/modules/customers/commands/addresses.js.map +2 -2
  58. package/dist/modules/customers/commands/companies.js +163 -162
  59. package/dist/modules/customers/commands/companies.js.map +2 -2
  60. package/dist/modules/customers/commands/deals.js +3 -4
  61. package/dist/modules/customers/commands/deals.js.map +2 -2
  62. package/dist/modules/customers/commands/interactions.js +19 -22
  63. package/dist/modules/customers/commands/interactions.js.map +2 -2
  64. package/dist/modules/customers/commands/people.js +18 -15
  65. package/dist/modules/customers/commands/people.js.map +2 -2
  66. package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
  67. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  68. package/dist/modules/customers/commands/pipeline-stages.js +30 -23
  69. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  70. package/dist/modules/customers/commands/pipelines.js +27 -20
  71. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  72. package/dist/modules/customers/commands/tags.js +13 -5
  73. package/dist/modules/customers/commands/tags.js.map +2 -2
  74. package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
  75. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  76. package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
  77. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  78. package/dist/modules/data_sync/lib/sync-engine.js +4 -4
  79. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  80. package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
  81. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  82. package/dist/modules/directory/commands/organizations.js +192 -158
  83. package/dist/modules/directory/commands/organizations.js.map +3 -3
  84. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
  85. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  86. package/dist/modules/messages/commands/messages.js +77 -75
  87. package/dist/modules/messages/commands/messages.js.map +2 -2
  88. package/dist/modules/messages/commands/shared.js +132 -132
  89. package/dist/modules/messages/commands/shared.js.map +2 -2
  90. package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
  91. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  92. package/dist/modules/progress/acl.js +8 -4
  93. package/dist/modules/progress/acl.js.map +2 -2
  94. package/dist/modules/resources/commands/resources.js +125 -117
  95. package/dist/modules/resources/commands/resources.js.map +2 -2
  96. package/dist/modules/resources/commands/tags.js +7 -3
  97. package/dist/modules/resources/commands/tags.js.map +2 -2
  98. package/dist/modules/sales/api/quotes/send/route.js +12 -11
  99. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  100. package/dist/modules/sales/commands/documents.js +629 -478
  101. package/dist/modules/sales/commands/documents.js.map +2 -2
  102. package/dist/modules/sales/commands/payments.js +146 -146
  103. package/dist/modules/sales/commands/payments.js.map +2 -2
  104. package/dist/modules/sales/commands/returns.js +68 -60
  105. package/dist/modules/sales/commands/returns.js.map +2 -2
  106. package/dist/modules/staff/acl.js +10 -1
  107. package/dist/modules/staff/acl.js.map +2 -2
  108. package/dist/modules/staff/analytics.js +33 -0
  109. package/dist/modules/staff/analytics.js.map +7 -0
  110. package/dist/modules/staff/api/guards.js +31 -0
  111. package/dist/modules/staff/api/guards.js.map +7 -0
  112. package/dist/modules/staff/api/interceptors.js +96 -0
  113. package/dist/modules/staff/api/interceptors.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
  115. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
  117. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
  119. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
  121. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
  122. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
  123. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
  124. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
  125. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
  126. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
  127. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
  128. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
  129. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
  130. package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
  131. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
  132. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
  133. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
  134. package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
  135. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
  136. package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
  137. package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
  138. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
  139. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
  140. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
  141. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
  142. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
  143. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
  144. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
  145. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
  146. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
  147. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
  148. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
  149. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
  150. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
  151. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
  152. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
  153. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
  154. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
  155. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
  156. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
  157. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
  158. package/dist/modules/staff/cli.js +38 -1
  159. package/dist/modules/staff/cli.js.map +2 -2
  160. package/dist/modules/staff/commands/index.js +2 -0
  161. package/dist/modules/staff/commands/index.js.map +2 -2
  162. package/dist/modules/staff/commands/leave-requests.js +30 -28
  163. package/dist/modules/staff/commands/leave-requests.js.map +3 -3
  164. package/dist/modules/staff/commands/team-members.js +21 -20
  165. package/dist/modules/staff/commands/team-members.js.map +2 -2
  166. package/dist/modules/staff/commands/timesheets-entries.js +409 -0
  167. package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
  168. package/dist/modules/staff/commands/timesheets-projects.js +618 -0
  169. package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
  170. package/dist/modules/staff/data/enrichers.js +104 -0
  171. package/dist/modules/staff/data/enrichers.js.map +7 -0
  172. package/dist/modules/staff/data/entities.js +226 -1
  173. package/dist/modules/staff/data/entities.js.map +2 -2
  174. package/dist/modules/staff/data/validators.js +113 -1
  175. package/dist/modules/staff/data/validators.js.map +2 -2
  176. package/dist/modules/staff/events.js +13 -1
  177. package/dist/modules/staff/events.js.map +2 -2
  178. package/dist/modules/staff/lib/crud.js +7 -1
  179. package/dist/modules/staff/lib/crud.js.map +2 -2
  180. package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
  181. package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
  182. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
  183. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
  184. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
  185. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
  186. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
  187. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
  188. package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
  189. package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
  190. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
  191. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
  192. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
  193. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
  194. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
  195. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
  196. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
  197. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
  198. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
  199. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
  200. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
  201. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
  202. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
  203. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
  204. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
  205. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
  206. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
  207. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
  208. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
  209. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
  210. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
  211. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
  212. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
  213. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
  214. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
  215. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
  216. package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
  217. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
  218. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
  219. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
  220. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
  221. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
  222. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
  223. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
  224. package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
  225. package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
  226. package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
  227. package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
  228. package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
  229. package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
  230. package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
  231. package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
  232. package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
  233. package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
  234. package/dist/modules/staff/search.js +35 -0
  235. package/dist/modules/staff/search.js.map +2 -2
  236. package/dist/modules/staff/setup.js +15 -1
  237. package/dist/modules/staff/setup.js.map +2 -2
  238. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
  239. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
  240. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
  241. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
  242. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
  243. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
  244. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
  245. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
  246. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
  247. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
  248. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
  249. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
  250. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
  251. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
  252. package/dist/modules/staff/widgets/injection-table.js +12 -0
  253. package/dist/modules/staff/widgets/injection-table.js.map +7 -0
  254. package/dist/modules/sync_excel/api/import/route.js +19 -17
  255. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  256. package/dist/modules/translations/commands/translations.js +22 -19
  257. package/dist/modules/translations/commands/translations.js.map +2 -2
  258. package/dist/modules/workflows/backend/events/[id]/page.js +24 -6
  259. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  260. package/dist/modules/workflows/backend/instances/[id]/page.js +27 -5
  261. package/dist/modules/workflows/backend/instances/[id]/page.js.map +2 -2
  262. package/dist/modules/workflows/backend/tasks/[id]/page.js +25 -6
  263. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  264. package/generated/entities/staff_time_entry/index.ts +17 -0
  265. package/generated/entities/staff_time_entry_segment/index.ts +10 -0
  266. package/generated/entities/staff_time_project/index.ts +16 -0
  267. package/generated/entities/staff_time_project_member/index.ts +13 -0
  268. package/generated/entities.ids.generated.ts +5 -1
  269. package/generated/entity-fields-registry.ts +64 -0
  270. package/package.json +7 -7
  271. package/src/helpers/integration/timesheetFixtures.ts +61 -0
  272. package/src/modules/attachments/api/library/[id]/route.ts +24 -17
  273. package/src/modules/attachments/api/route.ts +20 -14
  274. package/src/modules/auth/api/roles/acl/route.ts +11 -5
  275. package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
  276. package/src/modules/auth/api/users/acl/route.ts +17 -12
  277. package/src/modules/auth/commands/users.ts +96 -80
  278. package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
  279. package/src/modules/business_rules/backend/logs/[id]/page.tsx +32 -7
  280. package/src/modules/catalog/api/offers/route.ts +20 -5
  281. package/src/modules/catalog/commands/categories.ts +61 -12
  282. package/src/modules/catalog/commands/products.ts +93 -60
  283. package/src/modules/catalog/commands/variants.ts +29 -16
  284. package/src/modules/currencies/backend/currencies/[id]/page.tsx +21 -2
  285. package/src/modules/currencies/commands/currencies.ts +27 -14
  286. package/src/modules/currencies/i18n/de.json +1 -0
  287. package/src/modules/currencies/i18n/en.json +1 -0
  288. package/src/modules/currencies/i18n/es.json +1 -0
  289. package/src/modules/currencies/i18n/pl.json +1 -0
  290. package/src/modules/customer_accounts/api/admin/users.ts +31 -26
  291. package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
  292. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
  293. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -11
  294. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +34 -11
  295. package/src/modules/customers/backend/customers/people/[id]/page.tsx +35 -11
  296. package/src/modules/customers/commands/addresses.ts +35 -23
  297. package/src/modules/customers/commands/companies.ts +166 -165
  298. package/src/modules/customers/commands/deals.ts +2 -4
  299. package/src/modules/customers/commands/interactions.ts +20 -26
  300. package/src/modules/customers/commands/people.ts +18 -15
  301. package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
  302. package/src/modules/customers/commands/pipeline-stages.ts +31 -27
  303. package/src/modules/customers/commands/pipelines.ts +29 -23
  304. package/src/modules/customers/commands/tags.ts +13 -5
  305. package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
  306. package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
  307. package/src/modules/data_sync/lib/sync-engine.ts +4 -5
  308. package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
  309. package/src/modules/directory/commands/organizations.ts +203 -166
  310. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
  311. package/src/modules/messages/commands/messages.ts +82 -80
  312. package/src/modules/messages/commands/shared.ts +138 -133
  313. package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
  314. package/src/modules/progress/acl.ts +4 -0
  315. package/src/modules/resources/commands/resources.ts +127 -117
  316. package/src/modules/resources/commands/tags.ts +7 -3
  317. package/src/modules/sales/api/quotes/send/route.ts +17 -12
  318. package/src/modules/sales/commands/documents.ts +673 -481
  319. package/src/modules/sales/commands/payments.ts +158 -152
  320. package/src/modules/sales/commands/returns.ts +74 -63
  321. package/src/modules/staff/acl.ts +11 -0
  322. package/src/modules/staff/analytics.ts +30 -0
  323. package/src/modules/staff/api/guards.ts +59 -0
  324. package/src/modules/staff/api/interceptors.ts +122 -0
  325. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
  326. package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
  327. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
  328. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
  329. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
  330. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
  331. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
  332. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
  333. package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
  334. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
  335. package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
  336. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
  337. package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
  338. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
  339. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
  340. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
  341. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
  342. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
  343. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
  344. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
  345. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
  346. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
  347. package/src/modules/staff/cli.ts +40 -1
  348. package/src/modules/staff/commands/index.ts +2 -0
  349. package/src/modules/staff/commands/leave-requests.ts +37 -29
  350. package/src/modules/staff/commands/team-members.ts +25 -20
  351. package/src/modules/staff/commands/timesheets-entries.ts +504 -0
  352. package/src/modules/staff/commands/timesheets-projects.ts +699 -0
  353. package/src/modules/staff/data/enrichers.ts +134 -0
  354. package/src/modules/staff/data/entities.ts +198 -0
  355. package/src/modules/staff/data/validators.ts +129 -0
  356. package/src/modules/staff/events.ts +13 -0
  357. package/src/modules/staff/i18n/de.json +209 -1
  358. package/src/modules/staff/i18n/en.json +209 -1
  359. package/src/modules/staff/i18n/es.json +209 -1
  360. package/src/modules/staff/i18n/pl.json +209 -1
  361. package/src/modules/staff/lib/crud.ts +8 -0
  362. package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
  363. package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
  364. package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
  365. package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
  366. package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
  367. package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
  368. package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
  369. package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
  370. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
  371. package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
  372. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
  373. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
  374. package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
  375. package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
  376. package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
  377. package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
  378. package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
  379. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
  380. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
  381. package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
  382. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
  383. package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
  384. package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
  385. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
  386. package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
  387. package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
  388. package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
  389. package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
  390. package/src/modules/staff/search.ts +35 -0
  391. package/src/modules/staff/setup.ts +15 -0
  392. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
  393. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
  394. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
  395. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
  396. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
  397. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
  398. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
  399. package/src/modules/staff/widgets/injection-table.ts +10 -0
  400. package/src/modules/sync_excel/api/import/route.ts +23 -18
  401. package/src/modules/translations/commands/translations.ts +49 -41
  402. package/src/modules/workflows/backend/events/[id]/page.tsx +32 -10
  403. package/src/modules/workflows/backend/instances/[id]/page.tsx +33 -9
  404. package/src/modules/workflows/backend/tasks/[id]/page.tsx +33 -10
  405. package/src/modules/workflows/i18n/de.json +1 -0
  406. package/src/modules/workflows/i18n/en.json +1 -0
  407. package/src/modules/workflows/i18n/es.json +1 -0
  408. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -12,6 +12,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
12
12
  import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
13
13
  import { DataLoader } from '@open-mercato/ui/primitives/DataLoader'
14
14
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
15
+ import { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'
15
16
 
16
17
  type CurrencyData = {
17
18
  id: string
@@ -35,6 +36,7 @@ export default function EditCurrencyPage({ params }: { params?: { id?: string }
35
36
  const [currency, setCurrency] = React.useState<CurrencyData | null>(null)
36
37
  const [loading, setLoading] = React.useState(true)
37
38
  const [error, setError] = React.useState<string | null>(null)
39
+ const [isNotFound, setIsNotFound] = React.useState(false)
38
40
 
39
41
  React.useEffect(() => {
40
42
  async function loadCurrency() {
@@ -42,8 +44,10 @@ export default function EditCurrencyPage({ params }: { params?: { id?: string }
42
44
  const response = await apiCall<{ items: CurrencyData[] }>(`/api/currencies/currencies?id=${params?.id}`)
43
45
  if (response.ok && response.result && response.result.items.length > 0) {
44
46
  setCurrency(response.result.items[0])
47
+ } else if (!response.ok) {
48
+ setError(t('currencies.form.errors.load'))
45
49
  } else {
46
- setError(t('currencies.form.errors.notFound'))
50
+ setIsNotFound(true)
47
51
  }
48
52
  } catch (err) {
49
53
  setError(t('currencies.form.errors.load'))
@@ -163,11 +167,26 @@ export default function EditCurrencyPage({ params }: { params?: { id?: string }
163
167
  )
164
168
  }
165
169
 
170
+ if (isNotFound) {
171
+ return (
172
+ <Page>
173
+ <PageBody>
174
+ <RecordNotFoundState
175
+ label={t('currencies.form.errors.notFound', 'Currency not found.')}
176
+ backHref="/backend/currencies"
177
+ backLabel={t('currencies.form.actions.backToList', 'Back to currencies')}
178
+ />
179
+ </PageBody>
180
+ {ConfirmDialogElement}
181
+ </Page>
182
+ )
183
+ }
184
+
166
185
  if (error || !currency) {
167
186
  return (
168
187
  <Page>
169
188
  <PageBody>
170
- <div className="text-destructive">{error || t('currencies.form.errors.notFound')}</div>
189
+ <ErrorMessage label={error ?? t('currencies.form.errors.notFound', 'Currency not found.')} />
171
190
  </PageBody>
172
191
  {ConfirmDialogElement}
173
192
  </Page>
@@ -2,6 +2,7 @@ import { registerCommand } from '@open-mercato/shared/lib/commands'
2
2
  import type { CommandHandler } from '@open-mercato/shared/lib/commands'
3
3
  import { buildChanges, requireId, emitCrudSideEffects } from '@open-mercato/shared/lib/commands/helpers'
4
4
  import { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
5
6
  import type { EntityManager } from '@mikro-orm/postgresql'
6
7
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
7
8
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
@@ -119,13 +120,19 @@ const createCurrencyCommand: CommandHandler<CurrencyCreateInput, { currencyId: s
119
120
  updatedAt: now,
120
121
  })
121
122
  em.persist(record)
122
-
123
- // Enforce only one base currency before flush to prevent race conditions
124
- if (record.isBase) {
125
- await enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)
126
- }
127
-
128
- await em.flush()
123
+
124
+ // Demote any existing base currency and insert the new record in one
125
+ // transaction; a partial commit would leave zero or two base currencies.
126
+ await withAtomicFlush(
127
+ em,
128
+ [
129
+ () =>
130
+ record.isBase
131
+ ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)
132
+ : undefined,
133
+ ],
134
+ { transaction: true },
135
+ )
129
136
 
130
137
  const de = ctx.container.resolve('dataEngine') as DataEngine
131
138
  await emitCrudSideEffects({
@@ -227,13 +234,19 @@ const updateCurrencyCommand: CommandHandler<CurrencyUpdateInput, { currencyId: s
227
234
  ;(record as any)[key] = change.to
228
235
  }
229
236
  record.updatedAt = new Date()
230
-
231
- // Enforce only one base currency before flush to prevent race conditions
232
- if (parsed.isBase === true && record.isBase) {
233
- await enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)
234
- }
235
-
236
- await em.flush()
237
+
238
+ // Demote any existing base currency and persist the scalar changes in one
239
+ // transaction; a partial commit would leave zero or two base currencies.
240
+ await withAtomicFlush(
241
+ em,
242
+ [
243
+ () =>
244
+ parsed.isBase === true && record.isBase
245
+ ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId)
246
+ : undefined,
247
+ ],
248
+ { transaction: true },
249
+ )
237
250
 
238
251
  const de = ctx.container.resolve('dataEngine') as DataEngine
239
252
  await emitCrudSideEffects({
@@ -41,6 +41,7 @@
41
41
  "currencies.flash.updated": "Währung erfolgreich aktualisiert",
42
42
  "currencies.form.action.create": "Währung erstellen",
43
43
  "currencies.form.action.save": "Änderungen speichern",
44
+ "currencies.form.actions.backToList": "Zurück zu Währungen",
44
45
  "currencies.form.errors.codeFormat": "Währungscode muss genau 3 Großbuchstaben sein (z.B. USD)",
45
46
  "currencies.form.errors.delete": "Währung konnte nicht gelöscht werden",
46
47
  "currencies.form.errors.load": "Währung konnte nicht geladen werden",
@@ -41,6 +41,7 @@
41
41
  "currencies.flash.updated": "Currency updated successfully",
42
42
  "currencies.form.action.create": "Create Currency",
43
43
  "currencies.form.action.save": "Save Changes",
44
+ "currencies.form.actions.backToList": "Back to currencies",
44
45
  "currencies.form.errors.codeFormat": "Currency code must be exactly 3 uppercase letters (e.g., USD)",
45
46
  "currencies.form.errors.delete": "Failed to delete currency",
46
47
  "currencies.form.errors.load": "Failed to load currency",
@@ -41,6 +41,7 @@
41
41
  "currencies.flash.updated": "Moneda actualizada correctamente",
42
42
  "currencies.form.action.create": "Crear Moneda",
43
43
  "currencies.form.action.save": "Guardar Cambios",
44
+ "currencies.form.actions.backToList": "Volver a divisas",
44
45
  "currencies.form.errors.codeFormat": "El código de moneda debe ser exactamente 3 letras mayúsculas (ej. USD)",
45
46
  "currencies.form.errors.delete": "Error al eliminar moneda",
46
47
  "currencies.form.errors.load": "Error al cargar moneda",
@@ -41,6 +41,7 @@
41
41
  "currencies.flash.updated": "Waluta zaktualizowana pomyślnie",
42
42
  "currencies.form.action.create": "Utwórz Walutę",
43
43
  "currencies.form.action.save": "Zapisz Zmiany",
44
+ "currencies.form.actions.backToList": "Wróć do walut",
44
45
  "currencies.form.errors.codeFormat": "Kod waluty musi składać się z dokładnie 3 wielkich liter (np. USD)",
45
46
  "currencies.form.errors.delete": "Nie udało się usunąć waluty",
46
47
  "currencies.form.errors.load": "Nie udało się załadować waluty",
@@ -225,35 +225,40 @@ export async function POST(req: Request) {
225
225
  { tenantId: auth.tenantId!, organizationId: auth.orgId! },
226
226
  )
227
227
  user.emailVerifiedAt = new Date()
228
- em.persist(user)
229
- await em.flush()
230
228
 
231
- if (parsed.data.customerEntityId) {
232
- await em.nativeUpdate(CustomerUser, { id: user.id }, { customerEntityId: parsed.data.customerEntityId })
233
- }
229
+ // Persist the user, its company association, and its role links in one
230
+ // transaction so a flush failure on the role loop cannot leave a roleless
231
+ // user committed (privilege gap).
232
+ await em.transactional(async (tx) => {
233
+ tx.persist(user)
234
+ await tx.flush()
234
235
 
235
- if (parsed.data.roleIds && parsed.data.roleIds.length > 0) {
236
- const validRoles = await findWithDecryption(
237
- em,
238
- CustomerRole,
239
- {
240
- id: { $in: parsed.data.roleIds } as any,
241
- tenantId: auth.tenantId,
242
- deletedAt: null,
243
- } as any,
244
- undefined,
245
- { tenantId: auth.tenantId, organizationId: auth.orgId },
246
- )
247
- for (const role of validRoles) {
248
- const userRole = em.create(CustomerUserRole, {
249
- user,
250
- role,
251
- createdAt: new Date(),
252
- } as any)
253
- em.persist(userRole)
236
+ if (parsed.data.customerEntityId) {
237
+ await tx.nativeUpdate(CustomerUser, { id: user.id }, { customerEntityId: parsed.data.customerEntityId })
254
238
  }
255
- await em.flush()
256
- }
239
+
240
+ if (parsed.data.roleIds && parsed.data.roleIds.length > 0) {
241
+ const validRoles = await findWithDecryption(
242
+ tx,
243
+ CustomerRole,
244
+ {
245
+ id: { $in: parsed.data.roleIds } as any,
246
+ tenantId: auth.tenantId,
247
+ deletedAt: null,
248
+ } as any,
249
+ undefined,
250
+ { tenantId: auth.tenantId, organizationId: auth.orgId },
251
+ )
252
+ for (const role of validRoles) {
253
+ const userRole = tx.create(CustomerUserRole, {
254
+ user,
255
+ role,
256
+ createdAt: new Date(),
257
+ } as any)
258
+ tx.persist(userRole)
259
+ }
260
+ }
261
+ })
257
262
 
258
263
  void emitCustomerAccountsEvent('customer_accounts.user.created', {
259
264
  id: user.id,
@@ -44,14 +44,13 @@ export async function POST(req: Request) {
44
44
  await em.transactional(async (trx) => {
45
45
  await customerUserService.updatePassword(user, parsed.data.password, trx)
46
46
  await customerSessionService.revokeAllUserSessions(user.id, trx)
47
+ await trx.nativeUpdate(
48
+ CustomerUser,
49
+ { id: user.id, emailVerifiedAt: null },
50
+ { emailVerifiedAt: new Date() },
51
+ )
47
52
  })
48
53
 
49
- await em.nativeUpdate(
50
- CustomerUser,
51
- { id: user.id, emailVerifiedAt: null },
52
- { emailVerifiedAt: new Date() },
53
- )
54
-
55
54
  void emitCustomerAccountsEvent('customer_accounts.password.changed', {
56
55
  userId: user.id,
57
56
  tenantId: user.tenantId,
@@ -75,19 +75,20 @@ export async function PUT(req: Request, { params }: { params: { id: string } })
75
75
  validatedRoles.push(role)
76
76
  }
77
77
 
78
- // Remove existing roles
79
- await em.nativeDelete(CustomerUserRole, { user: targetUser.id as any })
80
-
81
- // Assign new roles
82
- for (const role of validatedRoles) {
83
- const userRole = em.create(CustomerUserRole, {
84
- user: targetUser,
85
- role,
86
- createdAt: new Date(),
87
- } as any)
88
- em.persist(userRole)
89
- }
90
- await em.flush()
78
+ // Replace the role set atomically: deleting the old roles and inserting the new
79
+ // ones in one transaction prevents a flush failure from leaving the user with
80
+ // zero roles (portal lockout / privilege loss) (#2337).
81
+ await em.transactional(async (tx) => {
82
+ await tx.nativeDelete(CustomerUserRole, { user: targetUser.id as any })
83
+ for (const role of validatedRoles) {
84
+ const userRole = tx.create(CustomerUserRole, {
85
+ user: targetUser,
86
+ role,
87
+ createdAt: new Date(),
88
+ } as any)
89
+ tx.persist(userRole)
90
+ }
91
+ })
91
92
 
92
93
  await customerRbacService.invalidateUserCache(targetUser.id)
93
94
 
@@ -11,6 +11,7 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
11
11
  import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
12
12
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
13
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
14
+ import { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'
14
15
 
15
16
  type RoleDetail = {
16
17
  id: string
@@ -146,10 +147,11 @@ export default function CustomerRoleDetailPage({ params }: { params?: { id?: str
146
147
  const [data, setData] = React.useState<RoleDetail | null>(null)
147
148
  const [isLoading, setIsLoading] = React.useState(true)
148
149
  const [error, setError] = React.useState<string | null>(null)
150
+ const [isNotFound, setIsNotFound] = React.useState(false)
149
151
 
150
152
  React.useEffect(() => {
151
153
  if (!id) {
152
- setError(t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found'))
154
+ setIsNotFound(true)
153
155
  setIsLoading(false)
154
156
  return
155
157
  }
@@ -157,6 +159,7 @@ export default function CustomerRoleDetailPage({ params }: { params?: { id?: str
157
159
  async function load() {
158
160
  setIsLoading(true)
159
161
  setError(null)
162
+ setIsNotFound(false)
160
163
  try {
161
164
  const payload = await readApiResultOrThrow<RoleDetail>(
162
165
  `/api/customer_accounts/admin/roles/${encodeURIComponent(id!)}`,
@@ -167,8 +170,12 @@ export default function CustomerRoleDetailPage({ params }: { params?: { id?: str
167
170
  setData(payload)
168
171
  } catch (err) {
169
172
  if (cancelled) return
170
- const message = err instanceof Error ? err.message : t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role')
171
- setError(message)
173
+ if ((err as { status?: number }).status === 404) {
174
+ setIsNotFound(true)
175
+ } else {
176
+ const message = err instanceof Error ? err.message : t('customer_accounts.admin.roleDetail.error.load', 'Failed to load role')
177
+ setError(message)
178
+ }
172
179
  } finally {
173
180
  if (!cancelled) setIsLoading(false)
174
181
  }
@@ -300,18 +307,34 @@ export default function CustomerRoleDetailPage({ params }: { params?: { id?: str
300
307
  )
301
308
  }
302
309
 
310
+ if (isNotFound) {
311
+ return (
312
+ <Page>
313
+ <PageBody>
314
+ <RecordNotFoundState
315
+ label={t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}
316
+ backHref="/backend/customer_accounts/roles"
317
+ backLabel={t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}
318
+ />
319
+ </PageBody>
320
+ </Page>
321
+ )
322
+ }
323
+
303
324
  if (error || !data) {
304
325
  return (
305
326
  <Page>
306
327
  <PageBody>
307
- <div className="flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground">
308
- <p>{error || t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}</p>
309
- <Button asChild variant="outline">
310
- <Link href="/backend/customer_accounts/roles">
311
- {t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}
312
- </Link>
313
- </Button>
314
- </div>
328
+ <ErrorMessage
329
+ label={error ?? t('customer_accounts.admin.roleDetail.error.notFound', 'Role not found')}
330
+ action={
331
+ <Button asChild variant="outline" size="sm">
332
+ <Link href="/backend/customer_accounts/roles">
333
+ {t('customer_accounts.admin.roleDetail.actions.backToList', 'Back to roles')}
334
+ </Link>
335
+ </Button>
336
+ }
337
+ />
315
338
  </PageBody>
316
339
  </Page>
317
340
  )
@@ -16,6 +16,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
16
16
  import { useT } from '@open-mercato/shared/lib/i18n/context'
17
17
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
18
18
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
19
+ import { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'
19
20
 
20
21
  type UserDetail = {
21
22
  id: string
@@ -147,6 +148,7 @@ export default function CustomerUserDetailPage({ params }: { params?: { id?: str
147
148
  const [data, setData] = React.useState<UserDetail | null>(null)
148
149
  const [isLoading, setIsLoading] = React.useState(true)
149
150
  const [error, setError] = React.useState<string | null>(null)
151
+ const [isNotFound, setIsNotFound] = React.useState(false)
150
152
  const [isSaving, setIsSaving] = React.useState(false)
151
153
  const [editActive, setEditActive] = React.useState<boolean | null>(null)
152
154
  const [editDisplayName, setEditDisplayName] = React.useState('')
@@ -186,7 +188,7 @@ export default function CustomerUserDetailPage({ params }: { params?: { id?: str
186
188
 
187
189
  React.useEffect(() => {
188
190
  if (!id) {
189
- setError(t('customer_accounts.admin.detail.error.notFound', 'User not found'))
191
+ setIsNotFound(true)
190
192
  setIsLoading(false)
191
193
  return
192
194
  }
@@ -194,6 +196,7 @@ export default function CustomerUserDetailPage({ params }: { params?: { id?: str
194
196
  async function load() {
195
197
  setIsLoading(true)
196
198
  setError(null)
199
+ setIsNotFound(false)
197
200
  try {
198
201
  const payload = await readApiResultOrThrow<UserDetail>(
199
202
  `/api/customer_accounts/admin/users/${encodeURIComponent(id!)}`,
@@ -209,8 +212,12 @@ export default function CustomerUserDetailPage({ params }: { params?: { id?: str
209
212
  setEditCustomerEntityId(payload.customerEntityId)
210
213
  } catch (err) {
211
214
  if (cancelled) return
212
- const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.load', 'Failed to load user')
213
- setError(message)
215
+ if ((err as { status?: number }).status === 404) {
216
+ setIsNotFound(true)
217
+ } else {
218
+ const message = err instanceof Error ? err.message : t('customer_accounts.admin.detail.error.load', 'Failed to load user')
219
+ setError(message)
220
+ }
214
221
  } finally {
215
222
  if (!cancelled) setIsLoading(false)
216
223
  }
@@ -463,18 +470,34 @@ export default function CustomerUserDetailPage({ params }: { params?: { id?: str
463
470
  )
464
471
  }
465
472
 
473
+ if (isNotFound) {
474
+ return (
475
+ <Page>
476
+ <PageBody>
477
+ <RecordNotFoundState
478
+ label={t('customer_accounts.admin.detail.error.notFound', 'User not found')}
479
+ backHref="/backend/customer_accounts/users"
480
+ backLabel={t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}
481
+ />
482
+ </PageBody>
483
+ </Page>
484
+ )
485
+ }
486
+
466
487
  if (error || !data) {
467
488
  return (
468
489
  <Page>
469
490
  <PageBody>
470
- <div className="flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground">
471
- <p>{error || t('customer_accounts.admin.detail.error.notFound', 'User not found')}</p>
472
- <Button asChild variant="outline">
473
- <Link href="/backend/customer_accounts/users">
474
- {t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}
475
- </Link>
476
- </Button>
477
- </div>
491
+ <ErrorMessage
492
+ label={error ?? t('customer_accounts.admin.detail.error.notFound', 'User not found')}
493
+ action={
494
+ <Button asChild variant="outline" size="sm">
495
+ <Link href="/backend/customer_accounts/users">
496
+ {t('customer_accounts.admin.detail.actions.backToList', 'Back to list')}
497
+ </Link>
498
+ </Button>
499
+ }
500
+ />
478
501
  </PageBody>
479
502
  </Page>
480
503
  )
@@ -20,6 +20,8 @@ import {
20
20
  NotesSection,
21
21
  type CommentSummary,
22
22
  type SectionAction,
23
+ RecordNotFoundState,
24
+ ErrorMessage,
23
25
  } from '@open-mercato/ui/backend/detail'
24
26
  import {
25
27
  TagsSection,
@@ -116,6 +118,7 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
116
118
  const [data, setData] = React.useState<PersonOverview | null>(null)
117
119
  const [isLoading, setIsLoading] = React.useState(true)
118
120
  const [error, setError] = React.useState<string | null>(null)
121
+ const [isNotFound, setIsNotFound] = React.useState(false)
119
122
  const [activeTab, setActiveTab] = React.useState<SectionKey>(initialTab)
120
123
  const [sectionAction, setSectionAction] = React.useState<SectionAction | null>(null)
121
124
  const [isDeleting, setIsDeleting] = React.useState(false)
@@ -276,7 +279,7 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
276
279
  const initialLoadDoneRef = React.useRef(false)
277
280
  const loadData = React.useCallback(async () => {
278
281
  if (!id) {
279
- setError(t('customers.people.detail.error.notFound'))
282
+ setIsNotFound(true)
280
283
  setIsLoading(false)
281
284
  return
282
285
  }
@@ -284,6 +287,7 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
284
287
  setIsLoading(true)
285
288
  }
286
289
  setError(null)
290
+ setIsNotFound(false)
287
291
  try {
288
292
  const payload = await readApiResultOrThrow<PersonOverview>(
289
293
  `/api/customers/people/${encodeURIComponent(id)}?include=todos`,
@@ -292,8 +296,12 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
292
296
  )
293
297
  setData(payload as PersonOverview)
294
298
  } catch (err) {
295
- const message = err instanceof Error ? err.message : t('customers.people.detail.error.load')
296
- setError(message)
299
+ if ((err as { status?: number }).status === 404) {
300
+ setIsNotFound(true)
301
+ } else {
302
+ const message = err instanceof Error ? err.message : t('customers.people.detail.error.load')
303
+ setError(message)
304
+ }
297
305
  if (!initialLoadDoneRef.current) setData(null)
298
306
  } finally {
299
307
  setIsLoading(false)
@@ -469,18 +477,34 @@ export default function CustomerPersonDetailPage({ params }: { params?: { id?: s
469
477
  )
470
478
  }
471
479
 
480
+ if (isNotFound) {
481
+ return (
482
+ <Page>
483
+ <PageBody>
484
+ <RecordNotFoundState
485
+ label={t('customers.people.detail.error.notFound', 'Person not found.')}
486
+ backHref="/backend/customers/people"
487
+ backLabel={t('customers.people.detail.actions.backToList', 'Back to people')}
488
+ />
489
+ </PageBody>
490
+ </Page>
491
+ )
492
+ }
493
+
472
494
  if (error || !data || !personId) {
473
495
  return (
474
496
  <Page>
475
497
  <PageBody>
476
- <div className="flex h-[50vh] flex-col items-center justify-center gap-2 text-muted-foreground">
477
- <p>{error || t('customers.people.detail.error.notFound')}</p>
478
- <Button asChild variant="outline">
479
- <Link href="/backend/customers/people">
480
- {t('customers.people.detail.actions.backToList')}
481
- </Link>
482
- </Button>
483
- </div>
498
+ <ErrorMessage
499
+ label={error ?? t('customers.people.detail.error.notFound', 'Person not found.')}
500
+ action={
501
+ <Button asChild variant="outline" size="sm">
502
+ <Link href="/backend/customers/people">
503
+ {t('customers.people.detail.actions.backToList', 'Back to people')}
504
+ </Link>
505
+ </Button>
506
+ }
507
+ />
484
508
  </PageBody>
485
509
  </Page>
486
510
  )
@@ -17,6 +17,7 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
17
17
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
18
18
  import type { CrudIndexerConfig, CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
19
19
  import { E } from '#generated/entities.ids.generated'
20
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
20
21
 
21
22
  const addressCrudIndexer: CrudIndexerConfig<CustomerAddress> = {
22
23
  entityType: E.customers.customer_address,
@@ -130,13 +131,15 @@ const createAddressCommand: CommandHandler<AddressCreateInput, { addressId: stri
130
131
  createdAt: new Date(),
131
132
  updatedAt: new Date(),
132
133
  })
133
- em.persist(address)
134
- await em.flush()
135
-
136
- if (address.isPrimary) {
137
- await enforcePrimaryAddress(em, entity.id, address.id)
138
- await em.flush()
139
- }
134
+ await withAtomicFlush(em, [
135
+ async () => {
136
+ em.persist(address)
137
+ await em.flush()
138
+ if (address.isPrimary) {
139
+ await enforcePrimaryAddress(em, entity.id, address.id)
140
+ }
141
+ },
142
+ ], { transaction: true })
140
143
 
141
144
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
142
145
  await emitCrudSideEffects({
@@ -225,12 +228,13 @@ const updateAddressCommand: CommandHandler<AddressUpdateInput, { addressId: stri
225
228
  if (parsed.longitude !== undefined) address.longitude = parsed.longitude ?? null
226
229
  if (parsed.isPrimary !== undefined) address.isPrimary = parsed.isPrimary
227
230
 
228
- await em.flush()
229
-
230
- if (address.isPrimary) {
231
- await enforcePrimaryAddress(em, typeof address.entity === 'string' ? address.entity : address.entity.id, address.id)
232
- await em.flush()
233
- }
231
+ await withAtomicFlush(em, [
232
+ async () => {
233
+ if (address.isPrimary) {
234
+ await enforcePrimaryAddress(em, typeof address.entity === 'string' ? address.entity : address.entity.id, address.id)
235
+ }
236
+ },
237
+ ], { transaction: true })
234
238
 
235
239
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
236
240
  await emitCrudSideEffects({
@@ -348,11 +352,15 @@ const updateAddressCommand: CommandHandler<AddressUpdateInput, { addressId: stri
348
352
  address.longitude = before.longitude
349
353
  address.isPrimary = before.isPrimary
350
354
  }
351
- await em.flush()
352
- if (before.isPrimary) {
353
- await enforcePrimaryAddress(em, before.entityId, before.id)
354
- await em.flush()
355
- }
355
+ await withAtomicFlush(em, [
356
+ async () => {
357
+ em.persist(address)
358
+ await em.flush()
359
+ if (before.isPrimary) {
360
+ await enforcePrimaryAddress(em, before.entityId, before.id)
361
+ }
362
+ },
363
+ ], { transaction: true })
356
364
 
357
365
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
358
366
  await emitCrudUndoSideEffects({
@@ -471,11 +479,15 @@ const deleteAddressCommand: CommandHandler<{ body?: Record<string, unknown>; que
471
479
  address.longitude = before.longitude
472
480
  address.isPrimary = before.isPrimary
473
481
  }
474
- await em.flush()
475
- if (before.isPrimary) {
476
- await enforcePrimaryAddress(em, before.entityId, before.id)
477
- await em.flush()
478
- }
482
+ await withAtomicFlush(em, [
483
+ async () => {
484
+ em.persist(address)
485
+ await em.flush()
486
+ if (before.isPrimary) {
487
+ await enforcePrimaryAddress(em, before.entityId, before.id)
488
+ }
489
+ },
490
+ ], { transaction: true })
479
491
 
480
492
  const de = (ctx.container.resolve('dataEngine') as DataEngine)
481
493
  await emitCrudUndoSideEffects({