@open-mercato/core 0.6.4-develop.4210.1.d412061cfe → 0.6.4-develop.4236.1.9fa6806b34

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 (370) 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/catalog/commands/categories.js +61 -12
  31. package/dist/modules/catalog/commands/categories.js.map +2 -2
  32. package/dist/modules/catalog/commands/products.js +79 -54
  33. package/dist/modules/catalog/commands/products.js.map +2 -2
  34. package/dist/modules/catalog/commands/variants.js +29 -16
  35. package/dist/modules/catalog/commands/variants.js.map +2 -2
  36. package/dist/modules/currencies/commands/currencies.js +15 -8
  37. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/users.js +27 -26
  39. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
  41. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  42. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
  43. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  44. package/dist/modules/customers/commands/addresses.js +35 -21
  45. package/dist/modules/customers/commands/addresses.js.map +2 -2
  46. package/dist/modules/customers/commands/companies.js +163 -162
  47. package/dist/modules/customers/commands/companies.js.map +2 -2
  48. package/dist/modules/customers/commands/deals.js +3 -4
  49. package/dist/modules/customers/commands/deals.js.map +2 -2
  50. package/dist/modules/customers/commands/interactions.js +19 -22
  51. package/dist/modules/customers/commands/interactions.js.map +2 -2
  52. package/dist/modules/customers/commands/people.js +18 -15
  53. package/dist/modules/customers/commands/people.js.map +2 -2
  54. package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
  55. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  56. package/dist/modules/customers/commands/pipeline-stages.js +30 -23
  57. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  58. package/dist/modules/customers/commands/pipelines.js +27 -20
  59. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  60. package/dist/modules/customers/commands/tags.js +13 -5
  61. package/dist/modules/customers/commands/tags.js.map +2 -2
  62. package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
  63. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  64. package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
  65. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  66. package/dist/modules/data_sync/lib/sync-engine.js +4 -4
  67. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  68. package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
  69. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  70. package/dist/modules/directory/commands/organizations.js +192 -158
  71. package/dist/modules/directory/commands/organizations.js.map +3 -3
  72. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
  73. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  74. package/dist/modules/messages/commands/messages.js +77 -75
  75. package/dist/modules/messages/commands/messages.js.map +2 -2
  76. package/dist/modules/messages/commands/shared.js +132 -132
  77. package/dist/modules/messages/commands/shared.js.map +2 -2
  78. package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
  79. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  80. package/dist/modules/resources/commands/resources.js +125 -117
  81. package/dist/modules/resources/commands/resources.js.map +2 -2
  82. package/dist/modules/resources/commands/tags.js +7 -3
  83. package/dist/modules/resources/commands/tags.js.map +2 -2
  84. package/dist/modules/sales/api/quotes/send/route.js +12 -11
  85. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  86. package/dist/modules/sales/commands/documents.js +629 -478
  87. package/dist/modules/sales/commands/documents.js.map +2 -2
  88. package/dist/modules/sales/commands/payments.js +146 -146
  89. package/dist/modules/sales/commands/payments.js.map +2 -2
  90. package/dist/modules/sales/commands/returns.js +68 -60
  91. package/dist/modules/sales/commands/returns.js.map +2 -2
  92. package/dist/modules/staff/acl.js +10 -1
  93. package/dist/modules/staff/acl.js.map +2 -2
  94. package/dist/modules/staff/analytics.js +33 -0
  95. package/dist/modules/staff/analytics.js.map +7 -0
  96. package/dist/modules/staff/api/guards.js +31 -0
  97. package/dist/modules/staff/api/guards.js.map +7 -0
  98. package/dist/modules/staff/api/interceptors.js +96 -0
  99. package/dist/modules/staff/api/interceptors.js.map +7 -0
  100. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
  101. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
  102. package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
  103. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
  104. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
  105. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
  106. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
  107. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
  108. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
  109. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
  110. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
  111. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
  112. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
  113. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
  115. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
  117. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
  119. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
  121. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
  122. package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
  123. package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
  124. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
  125. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
  126. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
  127. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
  128. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
  129. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
  130. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
  131. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
  132. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
  133. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
  134. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
  135. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
  136. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
  137. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
  138. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
  139. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
  140. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
  141. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
  142. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
  143. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
  144. package/dist/modules/staff/cli.js +38 -1
  145. package/dist/modules/staff/cli.js.map +2 -2
  146. package/dist/modules/staff/commands/index.js +2 -0
  147. package/dist/modules/staff/commands/index.js.map +2 -2
  148. package/dist/modules/staff/commands/leave-requests.js +30 -28
  149. package/dist/modules/staff/commands/leave-requests.js.map +3 -3
  150. package/dist/modules/staff/commands/team-members.js +21 -20
  151. package/dist/modules/staff/commands/team-members.js.map +2 -2
  152. package/dist/modules/staff/commands/timesheets-entries.js +409 -0
  153. package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
  154. package/dist/modules/staff/commands/timesheets-projects.js +618 -0
  155. package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
  156. package/dist/modules/staff/data/enrichers.js +104 -0
  157. package/dist/modules/staff/data/enrichers.js.map +7 -0
  158. package/dist/modules/staff/data/entities.js +226 -1
  159. package/dist/modules/staff/data/entities.js.map +2 -2
  160. package/dist/modules/staff/data/validators.js +113 -1
  161. package/dist/modules/staff/data/validators.js.map +2 -2
  162. package/dist/modules/staff/events.js +13 -1
  163. package/dist/modules/staff/events.js.map +2 -2
  164. package/dist/modules/staff/lib/crud.js +7 -1
  165. package/dist/modules/staff/lib/crud.js.map +2 -2
  166. package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
  167. package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
  168. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
  169. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
  170. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
  171. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
  172. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
  173. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
  174. package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
  175. package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
  176. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
  177. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
  178. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
  179. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
  180. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
  181. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
  182. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
  183. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
  184. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
  185. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
  186. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
  187. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
  188. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
  189. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
  190. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
  191. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
  192. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
  193. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
  194. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
  195. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
  196. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
  197. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
  198. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
  199. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
  200. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
  201. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
  202. package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
  203. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
  204. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
  205. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
  206. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
  207. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
  208. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
  209. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
  210. package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
  211. package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
  212. package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
  213. package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
  214. package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
  215. package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
  216. package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
  217. package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
  218. package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
  219. package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
  220. package/dist/modules/staff/search.js +35 -0
  221. package/dist/modules/staff/search.js.map +2 -2
  222. package/dist/modules/staff/setup.js +15 -1
  223. package/dist/modules/staff/setup.js.map +2 -2
  224. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
  225. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
  226. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
  227. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
  228. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
  229. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
  230. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
  231. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
  232. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
  233. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
  234. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
  235. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
  236. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
  237. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
  238. package/dist/modules/staff/widgets/injection-table.js +12 -0
  239. package/dist/modules/staff/widgets/injection-table.js.map +7 -0
  240. package/dist/modules/sync_excel/api/import/route.js +19 -17
  241. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  242. package/dist/modules/translations/commands/translations.js +22 -19
  243. package/dist/modules/translations/commands/translations.js.map +2 -2
  244. package/generated/entities/staff_time_entry/index.ts +17 -0
  245. package/generated/entities/staff_time_entry_segment/index.ts +10 -0
  246. package/generated/entities/staff_time_project/index.ts +16 -0
  247. package/generated/entities/staff_time_project_member/index.ts +13 -0
  248. package/generated/entities.ids.generated.ts +5 -1
  249. package/generated/entity-fields-registry.ts +64 -0
  250. package/package.json +7 -7
  251. package/src/helpers/integration/timesheetFixtures.ts +61 -0
  252. package/src/modules/attachments/api/library/[id]/route.ts +24 -17
  253. package/src/modules/attachments/api/route.ts +20 -14
  254. package/src/modules/auth/api/roles/acl/route.ts +11 -5
  255. package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
  256. package/src/modules/auth/api/users/acl/route.ts +17 -12
  257. package/src/modules/auth/commands/users.ts +96 -80
  258. package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
  259. package/src/modules/catalog/commands/categories.ts +61 -12
  260. package/src/modules/catalog/commands/products.ts +93 -60
  261. package/src/modules/catalog/commands/variants.ts +29 -16
  262. package/src/modules/currencies/commands/currencies.ts +27 -14
  263. package/src/modules/customer_accounts/api/admin/users.ts +31 -26
  264. package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
  265. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
  266. package/src/modules/customers/commands/addresses.ts +35 -23
  267. package/src/modules/customers/commands/companies.ts +166 -165
  268. package/src/modules/customers/commands/deals.ts +2 -4
  269. package/src/modules/customers/commands/interactions.ts +20 -26
  270. package/src/modules/customers/commands/people.ts +18 -15
  271. package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
  272. package/src/modules/customers/commands/pipeline-stages.ts +31 -27
  273. package/src/modules/customers/commands/pipelines.ts +29 -23
  274. package/src/modules/customers/commands/tags.ts +13 -5
  275. package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
  276. package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
  277. package/src/modules/data_sync/lib/sync-engine.ts +4 -5
  278. package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
  279. package/src/modules/directory/commands/organizations.ts +203 -166
  280. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
  281. package/src/modules/messages/commands/messages.ts +82 -80
  282. package/src/modules/messages/commands/shared.ts +138 -133
  283. package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
  284. package/src/modules/resources/commands/resources.ts +127 -117
  285. package/src/modules/resources/commands/tags.ts +7 -3
  286. package/src/modules/sales/api/quotes/send/route.ts +17 -12
  287. package/src/modules/sales/commands/documents.ts +673 -481
  288. package/src/modules/sales/commands/payments.ts +158 -152
  289. package/src/modules/sales/commands/returns.ts +74 -63
  290. package/src/modules/staff/acl.ts +11 -0
  291. package/src/modules/staff/analytics.ts +30 -0
  292. package/src/modules/staff/api/guards.ts +59 -0
  293. package/src/modules/staff/api/interceptors.ts +122 -0
  294. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
  295. package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
  296. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
  297. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
  298. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
  299. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
  300. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
  301. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
  302. package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
  303. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
  304. package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
  305. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
  306. package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
  307. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
  308. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
  309. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
  310. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
  311. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
  312. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
  313. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
  314. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
  315. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
  316. package/src/modules/staff/cli.ts +40 -1
  317. package/src/modules/staff/commands/index.ts +2 -0
  318. package/src/modules/staff/commands/leave-requests.ts +37 -29
  319. package/src/modules/staff/commands/team-members.ts +25 -20
  320. package/src/modules/staff/commands/timesheets-entries.ts +504 -0
  321. package/src/modules/staff/commands/timesheets-projects.ts +699 -0
  322. package/src/modules/staff/data/enrichers.ts +134 -0
  323. package/src/modules/staff/data/entities.ts +198 -0
  324. package/src/modules/staff/data/validators.ts +129 -0
  325. package/src/modules/staff/events.ts +13 -0
  326. package/src/modules/staff/i18n/de.json +209 -1
  327. package/src/modules/staff/i18n/en.json +209 -1
  328. package/src/modules/staff/i18n/es.json +209 -1
  329. package/src/modules/staff/i18n/pl.json +209 -1
  330. package/src/modules/staff/lib/crud.ts +8 -0
  331. package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
  332. package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
  333. package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
  334. package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
  335. package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
  336. package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
  337. package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
  338. package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
  339. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
  340. package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
  341. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
  342. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
  343. package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
  344. package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
  345. package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
  346. package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
  347. package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
  348. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
  349. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
  350. package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
  351. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
  352. package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
  353. package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
  354. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
  355. package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
  356. package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
  357. package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
  358. package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
  359. package/src/modules/staff/search.ts +35 -0
  360. package/src/modules/staff/setup.ts +15 -0
  361. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
  362. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
  363. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
  364. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
  365. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
  366. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
  367. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
  368. package/src/modules/staff/widgets/injection-table.ts +10 -0
  369. package/src/modules/sync_excel/api/import/route.ts +23 -18
  370. package/src/modules/translations/commands/translations.ts +49 -41
@@ -654,167 +654,170 @@ const updatePaymentCommand: CommandHandler<
654
654
  )
655
655
  ensureSameScope(payment, resolvedOrganizationId, resolvedTenantId)
656
656
  const previousOrder = payment.order as SalesOrder | null
657
- if (input.orderId !== undefined) {
658
- if (!input.orderId) {
659
- payment.order = null
660
- } else {
661
- const order = assertFound(
662
- await findOneWithDecryption(em, SalesOrder, { id: input.orderId }, {}, { tenantId: resolvedTenantId, organizationId: resolvedOrganizationId }),
663
- 'sales.payments.order_not_found'
664
- )
665
- ensureSameScope(order, resolvedOrganizationId, resolvedTenantId)
666
- if (
667
- order.currencyCode &&
668
- input.currencyCode &&
669
- order.currencyCode.toUpperCase() !== input.currencyCode.toUpperCase()
670
- ) {
657
+ // Apply payment scalar fields, order/line status changes and the
658
+ // allocations rebuild in one transaction so a mid-write failure cannot
659
+ // leave the payment and its allocations partially committed (#2336).
660
+ await em.transactional(async (tx) => {
661
+ if (input.orderId !== undefined) {
662
+ if (!input.orderId) {
663
+ payment.order = null
664
+ } else {
665
+ const order = assertFound(
666
+ await findOneWithDecryption(tx, SalesOrder, { id: input.orderId }, {}, { tenantId: resolvedTenantId, organizationId: resolvedOrganizationId }),
667
+ 'sales.payments.order_not_found'
668
+ )
669
+ ensureSameScope(order, resolvedOrganizationId, resolvedTenantId)
670
+ if (
671
+ order.currencyCode &&
672
+ input.currencyCode &&
673
+ order.currencyCode.toUpperCase() !== input.currencyCode.toUpperCase()
674
+ ) {
675
+ throw new CrudHttpError(400, {
676
+ error: translate('sales.payments.currency_mismatch', 'Payment currency must match the order currency.'),
677
+ })
678
+ }
679
+ payment.order = order
680
+ }
681
+ }
682
+ if (input.paymentMethodId !== undefined) {
683
+ if (!input.paymentMethodId) {
684
+ payment.paymentMethod = null
685
+ } else {
686
+ const method = assertFound(
687
+ await findOneWithDecryption(tx, SalesPaymentMethod, { id: input.paymentMethodId }, {}, { tenantId: resolvedTenantId, organizationId: resolvedOrganizationId }),
688
+ 'sales.payments.method_not_found'
689
+ )
690
+ ensureSameScope(method, resolvedOrganizationId, resolvedTenantId)
691
+ payment.paymentMethod = method
692
+ }
693
+ }
694
+ const currentOrder = payment.order as SalesOrder | null
695
+ if ((input.documentStatusEntryId !== undefined || input.lineStatusEntryId !== undefined) && !currentOrder) {
696
+ throw new CrudHttpError(400, { error: translate('sales.payments.order_required', 'Order is required for payments.') })
697
+ }
698
+ if (currentOrder && input.documentStatusEntryId !== undefined) {
699
+ const orderStatus = await resolveDictionaryEntryValue(tx, input.documentStatusEntryId ?? null)
700
+ if (input.documentStatusEntryId && !orderStatus) {
671
701
  throw new CrudHttpError(400, {
672
- error: translate('sales.payments.currency_mismatch', 'Payment currency must match the order currency.'),
702
+ error: translate('sales.documents.detail.statusInvalid', 'Selected status could not be found.'),
673
703
  })
674
704
  }
675
- payment.order = order
705
+ currentOrder.statusEntryId = input.documentStatusEntryId ?? null
706
+ currentOrder.status = orderStatus
707
+ currentOrder.updatedAt = new Date()
708
+ tx.persist(currentOrder)
676
709
  }
677
- }
678
- if (input.paymentMethodId !== undefined) {
679
- if (!input.paymentMethodId) {
680
- payment.paymentMethod = null
681
- } else {
682
- const method = assertFound(
683
- await findOneWithDecryption(em, SalesPaymentMethod, { id: input.paymentMethodId }, {}, { tenantId: resolvedTenantId, organizationId: resolvedOrganizationId }),
684
- 'sales.payments.method_not_found'
685
- )
686
- ensureSameScope(method, resolvedOrganizationId, resolvedTenantId)
687
- payment.paymentMethod = method
688
- }
689
- }
690
- const currentOrder = payment.order as SalesOrder | null
691
- if ((input.documentStatusEntryId !== undefined || input.lineStatusEntryId !== undefined) && !currentOrder) {
692
- throw new CrudHttpError(400, { error: translate('sales.payments.order_required', 'Order is required for payments.') })
693
- }
694
- if (currentOrder && input.documentStatusEntryId !== undefined) {
695
- const orderStatus = await resolveDictionaryEntryValue(em, input.documentStatusEntryId ?? null)
696
- if (input.documentStatusEntryId && !orderStatus) {
697
- throw new CrudHttpError(400, {
698
- error: translate('sales.documents.detail.statusInvalid', 'Selected status could not be found.'),
710
+ if (currentOrder && input.lineStatusEntryId !== undefined) {
711
+ const lineStatus = await resolveDictionaryEntryValue(tx, input.lineStatusEntryId ?? null)
712
+ if (input.lineStatusEntryId && !lineStatus) {
713
+ throw new CrudHttpError(400, {
714
+ error: translate('sales.documents.detail.statusInvalid', 'Selected status could not be found.'),
715
+ })
716
+ }
717
+ const orderLines = await findWithDecryption(tx, SalesOrderLine, { order: currentOrder }, {}, { tenantId: resolvedTenantId, organizationId: resolvedOrganizationId })
718
+ orderLines.forEach((line) => {
719
+ line.statusEntryId = input.lineStatusEntryId ?? null
720
+ line.status = lineStatus
721
+ line.updatedAt = new Date()
699
722
  })
723
+ orderLines.forEach((line) => tx.persist(line))
700
724
  }
701
- currentOrder.statusEntryId = input.documentStatusEntryId ?? null
702
- currentOrder.status = orderStatus
703
- currentOrder.updatedAt = new Date()
704
- em.persist(currentOrder)
705
- }
706
- if (currentOrder && input.lineStatusEntryId !== undefined) {
707
- const lineStatus = await resolveDictionaryEntryValue(em, input.lineStatusEntryId ?? null)
708
- if (input.lineStatusEntryId && !lineStatus) {
709
- throw new CrudHttpError(400, {
710
- error: translate('sales.documents.detail.statusInvalid', 'Selected status could not be found.'),
711
- })
725
+ if (input.paymentReference !== undefined) payment.paymentReference = input.paymentReference ?? null
726
+ if (input.statusEntryId !== undefined) {
727
+ payment.statusEntryId = input.statusEntryId ?? null
728
+ payment.status = await resolveDictionaryEntryValue(tx, input.statusEntryId ?? null)
712
729
  }
713
- const orderLines = await findWithDecryption(em, SalesOrderLine, { order: currentOrder }, {}, { tenantId: resolvedTenantId, organizationId: resolvedOrganizationId })
714
- orderLines.forEach((line) => {
715
- line.statusEntryId = input.lineStatusEntryId ?? null
716
- line.status = lineStatus
717
- line.updatedAt = new Date()
718
- })
719
- orderLines.forEach((line) => em.persist(line))
720
- }
721
- if (input.paymentReference !== undefined) payment.paymentReference = input.paymentReference ?? null
722
- if (input.statusEntryId !== undefined) {
723
- payment.statusEntryId = input.statusEntryId ?? null
724
- payment.status = await resolveDictionaryEntryValue(em, input.statusEntryId ?? null)
725
- }
726
- if (input.amount !== undefined) payment.amount = toNumericString(input.amount) ?? '0'
727
- if (input.currencyCode !== undefined) payment.currencyCode = input.currencyCode
728
- if (input.capturedAmount !== undefined) {
729
- payment.capturedAmount = toNumericString(input.capturedAmount) ?? '0'
730
- }
731
- if (input.refundedAmount !== undefined) {
732
- payment.refundedAmount = toNumericString(input.refundedAmount) ?? '0'
733
- }
734
- if (input.receivedAt !== undefined) payment.receivedAt = input.receivedAt ?? null
735
- if (input.capturedAt !== undefined) payment.capturedAt = input.capturedAt ?? null
736
- if (input.metadata !== undefined) {
737
- payment.metadata = input.metadata ? cloneJson(input.metadata) : null
738
- }
739
- if (input.customFieldSetId !== undefined) {
740
- payment.customFieldSetId = input.customFieldSetId ?? null
741
- }
742
- if (input.customFields !== undefined) {
743
- if (!payment.id) {
744
- await em.flush()
730
+ if (input.amount !== undefined) payment.amount = toNumericString(input.amount) ?? '0'
731
+ if (input.currencyCode !== undefined) payment.currencyCode = input.currencyCode
732
+ if (input.capturedAmount !== undefined) {
733
+ payment.capturedAmount = toNumericString(input.capturedAmount) ?? '0'
745
734
  }
746
- await setRecordCustomFields(em, {
747
- entityId: E.sales.sales_payment,
748
- recordId: payment.id,
749
- organizationId: payment.organizationId,
750
- tenantId: payment.tenantId,
751
- values: normalizeCustomFieldsInput(input.customFields),
752
- })
753
- }
754
- payment.updatedAt = new Date()
755
- await em.flush()
756
-
757
- if (input.allocations !== undefined) {
758
- const existingAllocations = await findWithDecryption(em, SalesPaymentAllocation, { payment }, {}, { tenantId: payment.tenantId, organizationId: payment.organizationId })
759
- existingAllocations.forEach((allocation) => em.remove(allocation))
760
- const allocationInputs = Array.isArray(input.allocations) ? input.allocations : []
761
- const paymentOrderId =
762
- (typeof payment.order === 'string' ? payment.order : payment.order?.id) ?? null
763
- const orderCache = new Map<string, SalesOrder>()
764
- if (currentOrder) orderCache.set(currentOrder.id, currentOrder)
765
- const invoiceCache = new Map<string, SalesInvoice>()
766
- for (const allocation of allocationInputs) {
767
- const orderId = allocation.orderId ?? paymentOrderId
768
- let order: SalesOrder | null = null
769
- if (orderId && typeof orderId === 'string') {
770
- order = orderCache.get(orderId) ?? null
771
- if (!order) {
772
- order = assertFound(
773
- await findOneWithDecryption(
774
- em,
775
- SalesOrder,
776
- { id: orderId },
777
- {},
778
- { tenantId: payment.tenantId, organizationId: payment.organizationId },
779
- ),
780
- 'sales.payments.order_not_found',
781
- )
782
- ensureSameScope(order, payment.organizationId, payment.tenantId)
783
- orderCache.set(orderId, order)
784
- }
785
- }
786
- let invoice: SalesInvoice | null = null
787
- if (allocation.invoiceId) {
788
- invoice = invoiceCache.get(allocation.invoiceId) ?? null
789
- if (!invoice) {
790
- invoice = assertFound(
791
- await findOneWithDecryption(
792
- em,
793
- SalesInvoice,
794
- { id: allocation.invoiceId },
795
- {},
796
- { tenantId: payment.tenantId, organizationId: payment.organizationId },
797
- ),
798
- 'sales.payments.invoice_not_found',
799
- )
800
- ensureSameScope(invoice, payment.organizationId, payment.tenantId)
801
- invoiceCache.set(allocation.invoiceId, invoice)
802
- }
735
+ if (input.refundedAmount !== undefined) {
736
+ payment.refundedAmount = toNumericString(input.refundedAmount) ?? '0'
737
+ }
738
+ if (input.receivedAt !== undefined) payment.receivedAt = input.receivedAt ?? null
739
+ if (input.capturedAt !== undefined) payment.capturedAt = input.capturedAt ?? null
740
+ if (input.metadata !== undefined) {
741
+ payment.metadata = input.metadata ? cloneJson(input.metadata) : null
742
+ }
743
+ if (input.customFieldSetId !== undefined) {
744
+ payment.customFieldSetId = input.customFieldSetId ?? null
745
+ }
746
+ if (input.customFields !== undefined) {
747
+ if (!payment.id) {
748
+ await tx.flush()
803
749
  }
804
- const entity = em.create(SalesPaymentAllocation, {
805
- payment,
806
- order,
807
- invoice,
750
+ await setRecordCustomFields(tx, {
751
+ entityId: E.sales.sales_payment,
752
+ recordId: payment.id,
808
753
  organizationId: payment.organizationId,
809
754
  tenantId: payment.tenantId,
810
- amount: toNumericString(allocation.amount) ?? '0',
811
- currencyCode: allocation.currencyCode,
812
- metadata: allocation.metadata ? cloneJson(allocation.metadata) : null,
755
+ values: normalizeCustomFieldsInput(input.customFields),
813
756
  })
814
- em.persist(entity)
815
757
  }
816
- await em.flush()
817
- }
758
+ payment.updatedAt = new Date()
759
+
760
+ if (input.allocations !== undefined) {
761
+ const existingAllocations = await findWithDecryption(tx, SalesPaymentAllocation, { payment }, {}, { tenantId: payment.tenantId, organizationId: payment.organizationId })
762
+ existingAllocations.forEach((allocation) => tx.remove(allocation))
763
+ const allocationInputs = Array.isArray(input.allocations) ? input.allocations : []
764
+ const paymentOrderId =
765
+ (typeof payment.order === 'string' ? payment.order : payment.order?.id) ?? null
766
+ const orderCache = new Map<string, SalesOrder>()
767
+ if (currentOrder) orderCache.set(currentOrder.id, currentOrder)
768
+ const invoiceCache = new Map<string, SalesInvoice>()
769
+ for (const allocation of allocationInputs) {
770
+ const orderId = allocation.orderId ?? paymentOrderId
771
+ let order: SalesOrder | null = null
772
+ if (orderId && typeof orderId === 'string') {
773
+ order = orderCache.get(orderId) ?? null
774
+ if (!order) {
775
+ order = assertFound(
776
+ await findOneWithDecryption(
777
+ tx,
778
+ SalesOrder,
779
+ { id: orderId },
780
+ {},
781
+ { tenantId: payment.tenantId, organizationId: payment.organizationId },
782
+ ),
783
+ 'sales.payments.order_not_found',
784
+ )
785
+ ensureSameScope(order, payment.organizationId, payment.tenantId)
786
+ orderCache.set(orderId, order)
787
+ }
788
+ }
789
+ let invoice: SalesInvoice | null = null
790
+ if (allocation.invoiceId) {
791
+ invoice = invoiceCache.get(allocation.invoiceId) ?? null
792
+ if (!invoice) {
793
+ invoice = assertFound(
794
+ await findOneWithDecryption(
795
+ tx,
796
+ SalesInvoice,
797
+ { id: allocation.invoiceId },
798
+ {},
799
+ { tenantId: payment.tenantId, organizationId: payment.organizationId },
800
+ ),
801
+ 'sales.payments.invoice_not_found',
802
+ )
803
+ ensureSameScope(invoice, payment.organizationId, payment.tenantId)
804
+ invoiceCache.set(allocation.invoiceId, invoice)
805
+ }
806
+ }
807
+ const entity = tx.create(SalesPaymentAllocation, {
808
+ payment,
809
+ order,
810
+ invoice,
811
+ organizationId: payment.organizationId,
812
+ tenantId: payment.tenantId,
813
+ amount: toNumericString(allocation.amount) ?? '0',
814
+ currencyCode: allocation.currencyCode,
815
+ metadata: allocation.metadata ? cloneJson(allocation.metadata) : null,
816
+ })
817
+ tx.persist(entity)
818
+ }
819
+ }
820
+ })
818
821
 
819
822
  const nextOrderId =
820
823
  (payment.order as SalesOrder | null)?.id ??
@@ -941,10 +944,13 @@ const deletePaymentCommand: CommandHandler<
941
944
  : allocation.order?.id ?? null
942
945
  )
943
946
  .filter((value): value is string => typeof value === 'string' && value.length > 0)
944
- allocations.forEach((allocation) => em.remove(allocation))
945
- await em.flush()
946
- em.remove(payment)
947
- await em.flush()
947
+ // Remove the allocations and the payment in one transaction so a failure
948
+ // between the two deletes cannot leave orphaned allocations committed
949
+ // without their payment (#2336).
950
+ await em.transactional(async (tx) => {
951
+ allocations.forEach((allocation) => tx.remove(allocation))
952
+ tx.remove(payment)
953
+ })
948
954
  let totals: { paidTotalAmount: number; refundedTotalAmount: number; outstandingAmount: number } | undefined
949
955
  const orderIds = Array.from(
950
956
  new Set(
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from 'crypto'
2
2
  import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
3
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
3
4
  import { LockMode } from '@mikro-orm/core'
4
5
  import type { EntityManager } from '@mikro-orm/postgresql'
5
6
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
@@ -470,72 +471,82 @@ const createReturnCommand: CommandHandler<ReturnCreateInput, { returnId: string
470
471
  )
471
472
  if (!order) return
472
473
 
473
- const lines = await findWithDecryption(
474
- em,
475
- SalesOrderLine,
476
- { order: order.id, deletedAt: null },
477
- {},
478
- { tenantId: after.tenantId, organizationId: after.organizationId },
479
- )
480
- const lineMap = new Map(lines.map((line) => [line.id, line]))
481
- after.lines.forEach((entry) => {
482
- const line = lineMap.get(entry.orderLineId)
483
- if (!line) return
484
- const next = Math.max(0, toNumeric(line.returnedQuantity) - entry.quantityReturned)
485
- line.returnedQuantity = next.toString()
486
- line.updatedAt = new Date()
487
- em.persist(line)
488
- })
489
-
490
- if (after.adjustmentIds.length) {
491
- const adjustments = await findWithDecryption(
492
- em,
493
- SalesOrderAdjustment,
494
- { id: { $in: after.adjustmentIds }, deletedAt: null },
495
- {},
496
- { tenantId: after.tenantId, organizationId: after.organizationId },
497
- )
498
- adjustments.forEach((adj) => em.remove(adj))
499
- }
500
-
501
- const header = await findOneWithDecryption(
502
- em,
503
- SalesReturn,
504
- { id: after.id, deletedAt: null },
505
- {},
506
- { tenantId: after.tenantId, organizationId: after.organizationId },
507
- )
508
- const returnLines = await findWithDecryption(
509
- em,
510
- SalesReturnLine,
511
- { salesReturn: after.id, deletedAt: null },
512
- {},
513
- { tenantId: after.tenantId, organizationId: after.organizationId },
514
- )
515
- returnLines.forEach((line) => em.remove(line))
516
- if (header) em.remove(header)
474
+ const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')
517
475
 
518
- const existingAdjustments = await findWithDecryption(
476
+ // Line reversals, adjustment/return removals, and the order-total recompute
477
+ // interleave queries on the same EntityManager with scalar mutations, so they
478
+ // must run inside an atomic flush to avoid lost updates and partial commits.
479
+ await withAtomicFlush(
519
480
  em,
520
- SalesOrderAdjustment,
521
- { order: order.id, deletedAt: null },
522
- { orderBy: { position: 'asc' } },
523
- { tenantId: after.tenantId, organizationId: after.organizationId },
481
+ [
482
+ async () => {
483
+ const lines = await findWithDecryption(
484
+ em,
485
+ SalesOrderLine,
486
+ { order: order.id, deletedAt: null },
487
+ {},
488
+ { tenantId: after.tenantId, organizationId: after.organizationId },
489
+ )
490
+ const lineMap = new Map(lines.map((line) => [line.id, line]))
491
+ after.lines.forEach((entry) => {
492
+ const line = lineMap.get(entry.orderLineId)
493
+ if (!line) return
494
+ const next = Math.max(0, toNumeric(line.returnedQuantity) - entry.quantityReturned)
495
+ line.returnedQuantity = next.toString()
496
+ line.updatedAt = new Date()
497
+ em.persist(line)
498
+ })
499
+
500
+ if (after.adjustmentIds.length) {
501
+ const adjustments = await findWithDecryption(
502
+ em,
503
+ SalesOrderAdjustment,
504
+ { id: { $in: after.adjustmentIds }, deletedAt: null },
505
+ {},
506
+ { tenantId: after.tenantId, organizationId: after.organizationId },
507
+ )
508
+ adjustments.forEach((adj) => em.remove(adj))
509
+ }
510
+
511
+ const header = await findOneWithDecryption(
512
+ em,
513
+ SalesReturn,
514
+ { id: after.id, deletedAt: null },
515
+ {},
516
+ { tenantId: after.tenantId, organizationId: after.organizationId },
517
+ )
518
+ const returnLines = await findWithDecryption(
519
+ em,
520
+ SalesReturnLine,
521
+ { salesReturn: after.id, deletedAt: null },
522
+ {},
523
+ { tenantId: after.tenantId, organizationId: after.organizationId },
524
+ )
525
+ returnLines.forEach((line) => em.remove(line))
526
+ if (header) em.remove(header)
527
+
528
+ const existingAdjustments = await findWithDecryption(
529
+ em,
530
+ SalesOrderAdjustment,
531
+ { order: order.id, deletedAt: null },
532
+ { orderBy: { position: 'asc' } },
533
+ { tenantId: after.tenantId, organizationId: after.organizationId },
534
+ )
535
+ const lineSnapshots: SalesLineSnapshot[] = lines.map(mapOrderLineEntityToSnapshot)
536
+ const adjustmentDrafts: SalesAdjustmentDraft[] = existingAdjustments.map(mapOrderAdjustmentToDraft)
537
+ const calculation = await salesCalculationService.calculateDocumentTotals({
538
+ documentKind: 'order',
539
+ lines: lineSnapshots,
540
+ adjustments: adjustmentDrafts,
541
+ context: buildCalculationContext(order),
542
+ })
543
+ applyOrderTotals(order, calculation.totals, calculation.lines.length)
544
+ order.updatedAt = new Date()
545
+ em.persist(order)
546
+ },
547
+ ],
548
+ { transaction: true },
524
549
  )
525
- const salesCalculationService = ctx.container.resolve<SalesCalculationService>('salesCalculationService')
526
- const lineSnapshots: SalesLineSnapshot[] = lines.map(mapOrderLineEntityToSnapshot)
527
- const adjustmentDrafts: SalesAdjustmentDraft[] = existingAdjustments.map(mapOrderAdjustmentToDraft)
528
- const calculation = await salesCalculationService.calculateDocumentTotals({
529
- documentKind: 'order',
530
- lines: lineSnapshots,
531
- adjustments: adjustmentDrafts,
532
- context: buildCalculationContext(order),
533
- })
534
- applyOrderTotals(order, calculation.totals, calculation.lines.length)
535
- order.updatedAt = new Date()
536
- em.persist(order)
537
-
538
- await em.flush()
539
550
  },
540
551
  }
541
552
 
@@ -8,6 +8,17 @@ export const features = [
8
8
  { id: 'staff.my_availability.unavailability', title: 'Manage my unavailability', module: 'staff' },
9
9
  { id: 'staff.my_leave_requests.view', title: 'View my leave requests', module: 'staff' },
10
10
  { id: 'staff.my_leave_requests.send', title: 'Send my leave requests', module: 'staff' },
11
+
12
+ // Timesheets (Phase 1)
13
+ { id: 'staff.timesheets.view', title: 'View own time entries', module: 'staff' },
14
+ { id: 'staff.timesheets.manage_own', title: 'Create/edit/delete own entries', module: 'staff' },
15
+ { id: 'staff.timesheets.manage_all', title: 'Manage all employees entries', module: 'staff' },
16
+ { id: 'staff.timesheets.projects.view', title: 'View time projects', module: 'staff' },
17
+ { id: 'staff.timesheets.projects.manage', title: 'Manage time projects', module: 'staff' },
18
+
19
+ // Timesheets (Phase 2)
20
+ { id: 'staff.timesheets.approve', title: 'Approve reportee time', module: 'staff' },
21
+ { id: 'staff.timesheets.lock', title: 'Lock time periods', module: 'staff' },
11
22
  ]
12
23
 
13
24
  export default features
@@ -0,0 +1,30 @@
1
+ import type { AnalyticsModuleConfig } from '@open-mercato/shared/modules/analytics'
2
+
3
+ export const analyticsConfig: AnalyticsModuleConfig = {
4
+ entities: [
5
+ {
6
+ entityId: 'staff:staff_time_entries',
7
+ requiredFeatures: ['staff.timesheets.view'],
8
+ entityConfig: {
9
+ tableName: 'staff_time_entries',
10
+ dateField: 'date',
11
+ defaultScopeFields: ['tenant_id', 'organization_id'],
12
+ },
13
+ fieldMappings: {
14
+ id: { dbColumn: 'id', type: 'uuid' },
15
+ durationMinutes: { dbColumn: 'duration_minutes', type: 'numeric' },
16
+ date: { dbColumn: 'date', type: 'timestamp' },
17
+ timeProjectId: { dbColumn: 'time_project_id', type: 'uuid' },
18
+ staffMemberId: { dbColumn: 'staff_member_id', type: 'uuid' },
19
+ source: { dbColumn: 'source', type: 'text' },
20
+ },
21
+ labelResolvers: {
22
+ timeProjectId: { table: 'staff_time_projects', idColumn: 'id', labelColumn: 'name' },
23
+ staffMemberId: { table: 'staff_team_members', idColumn: 'id', labelColumn: 'display_name' },
24
+ },
25
+ },
26
+ ],
27
+ }
28
+
29
+ export const config = analyticsConfig
30
+ export default analyticsConfig
@@ -0,0 +1,59 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import {
3
+ bridgeLegacyGuard,
4
+ runMutationGuards,
5
+ type MutationGuard,
6
+ type MutationGuardInput,
7
+ } from '@open-mercato/shared/lib/crud/mutation-guard-registry'
8
+
9
+ type GuardAfterCallback = {
10
+ guard: MutationGuard
11
+ metadata: Record<string, unknown> | null
12
+ }
13
+
14
+ export function resolveUserFeatures(auth: unknown): string[] {
15
+ const features = (auth as { features?: unknown })?.features
16
+ if (!Array.isArray(features)) return []
17
+ return features.filter((value): value is string => typeof value === 'string')
18
+ }
19
+
20
+ export async function runStaffMutationGuards(
21
+ container: AwilixContainer,
22
+ input: MutationGuardInput,
23
+ userFeatures: string[],
24
+ ): Promise<{
25
+ ok: boolean
26
+ errorBody?: Record<string, unknown>
27
+ errorStatus?: number
28
+ modifiedPayload?: Record<string, unknown>
29
+ afterSuccessCallbacks: GuardAfterCallback[]
30
+ }> {
31
+ const legacyGuard = bridgeLegacyGuard(container)
32
+ if (!legacyGuard) {
33
+ return { ok: true, afterSuccessCallbacks: [] }
34
+ }
35
+
36
+ return runMutationGuards([legacyGuard], input, { userFeatures })
37
+ }
38
+
39
+ export async function runStaffMutationGuardAfterSuccess(
40
+ callbacks: GuardAfterCallback[],
41
+ input: {
42
+ tenantId: string
43
+ organizationId: string | null
44
+ userId: string
45
+ resourceKind: string
46
+ resourceId: string
47
+ operation: 'create' | 'update' | 'delete'
48
+ requestMethod: string
49
+ requestHeaders: Headers
50
+ },
51
+ ): Promise<void> {
52
+ for (const callback of callbacks) {
53
+ if (!callback.guard.afterSuccess) continue
54
+ await callback.guard.afterSuccess({
55
+ ...input,
56
+ metadata: callback.metadata ?? null,
57
+ })
58
+ }
59
+ }