@open-mercato/core 0.10.1-develop.7717.1.2befe9c634 → 0.10.1-develop.7721.1.b5c4a00a68

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 (384) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_support_routing_rule/index.js +21 -0
  3. package/dist/generated/entities/staff_support_routing_rule/index.js.map +7 -0
  4. package/dist/generated/entities/staff_support_ticket/index.js +37 -0
  5. package/dist/generated/entities/staff_support_ticket/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +2 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +2 -2
  8. package/dist/generated/entities/staff_time_project_member/index.js +2 -0
  9. package/dist/generated/entities/staff_time_project_member/index.js.map +2 -2
  10. package/dist/generated/entities/staff_time_task/index.js +4 -0
  11. package/dist/generated/entities/staff_time_task/index.js.map +2 -2
  12. package/dist/generated/entities/staff_time_task_comment/index.js +9 -1
  13. package/dist/generated/entities/staff_time_task_comment/index.js.map +2 -2
  14. package/dist/generated/entities.ids.generated.js +2 -0
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +38 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/helpers/integration/auth.js +1 -0
  19. package/dist/helpers/integration/auth.js.map +2 -2
  20. package/dist/helpers/integration/communicationChannelsFixtures.js +20 -0
  21. package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
  22. package/dist/modules/catalog/api/bulk-delete/route.js +12 -2
  23. package/dist/modules/catalog/api/bulk-delete/route.js.map +2 -2
  24. package/dist/modules/communication_channels/api/post/test-seed/route.js +17 -3
  25. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  26. package/dist/modules/communication_channels/lib/test-seed.js +38 -5
  27. package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
  28. package/dist/modules/messages/commands/messages.js +2 -3
  29. package/dist/modules/messages/commands/messages.js.map +2 -2
  30. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +2 -2
  31. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  32. package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js +299 -0
  33. package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js.map +7 -0
  34. package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js +91 -0
  35. package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js.map +7 -0
  36. package/dist/modules/staff/api/portal/support-tickets/[id]/route.js +146 -0
  37. package/dist/modules/staff/api/portal/support-tickets/[id]/route.js.map +7 -0
  38. package/dist/modules/staff/api/portal/support-tickets/options/route.js +60 -0
  39. package/dist/modules/staff/api/portal/support-tickets/options/route.js.map +7 -0
  40. package/dist/modules/staff/api/portal/support-tickets/route.js +220 -0
  41. package/dist/modules/staff/api/portal/support-tickets/route.js.map +7 -0
  42. package/dist/modules/staff/api/timesheets/settings/route.js +7 -1
  43. package/dist/modules/staff/api/timesheets/settings/route.js.map +2 -2
  44. package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js +122 -0
  45. package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js.map +7 -0
  46. package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js +180 -0
  47. package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js.map +7 -0
  48. package/dist/modules/staff/api/timesheets/support-routing-rules/route.js +147 -0
  49. package/dist/modules/staff/api/timesheets/support-routing-rules/route.js.map +7 -0
  50. package/dist/modules/staff/api/timesheets/support-tickets/options/route.js +133 -0
  51. package/dist/modules/staff/api/timesheets/support-tickets/options/route.js.map +7 -0
  52. package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js +33 -0
  53. package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js.map +7 -0
  54. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +25 -7
  55. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +2 -2
  56. package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js +32 -0
  57. package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js.map +7 -0
  58. package/dist/modules/staff/api/timesheets/tasks/route.js +94 -47
  59. package/dist/modules/staff/api/timesheets/tasks/route.js.map +2 -2
  60. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +3 -0
  61. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
  62. package/dist/modules/staff/api/timesheets/time-projects/route.js +3 -0
  63. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
  64. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +1 -0
  65. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +2 -2
  66. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +54 -4
  67. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  68. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +21 -5
  69. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +2 -2
  70. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +319 -254
  71. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +2 -2
  72. package/dist/modules/staff/backend/staff/time-tracking/support/page.js +363 -0
  73. package/dist/modules/staff/backend/staff/time-tracking/support/page.js.map +7 -0
  74. package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js +18 -0
  75. package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js.map +7 -0
  76. package/dist/modules/staff/cli.js +27 -0
  77. package/dist/modules/staff/cli.js.map +2 -2
  78. package/dist/modules/staff/commands/index.js +3 -0
  79. package/dist/modules/staff/commands/index.js.map +2 -2
  80. package/dist/modules/staff/commands/shared.js +10 -1
  81. package/dist/modules/staff/commands/shared.js.map +2 -2
  82. package/dist/modules/staff/commands/support-routing-rules.js +505 -0
  83. package/dist/modules/staff/commands/support-routing-rules.js.map +7 -0
  84. package/dist/modules/staff/commands/support-tickets.js +513 -0
  85. package/dist/modules/staff/commands/support-tickets.js.map +7 -0
  86. package/dist/modules/staff/commands/timesheets-projects.js +72 -14
  87. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  88. package/dist/modules/staff/commands/timesheets-task-assignment.js +270 -0
  89. package/dist/modules/staff/commands/timesheets-task-assignment.js.map +7 -0
  90. package/dist/modules/staff/commands/timesheets-task-comments.js +58 -12
  91. package/dist/modules/staff/commands/timesheets-task-comments.js.map +2 -2
  92. package/dist/modules/staff/commands/timesheets-tasks.js +91 -9
  93. package/dist/modules/staff/commands/timesheets-tasks.js.map +3 -3
  94. package/dist/modules/staff/data/enrichers.js +141 -0
  95. package/dist/modules/staff/data/enrichers.js.map +2 -2
  96. package/dist/modules/staff/data/entities.js +143 -2
  97. package/dist/modules/staff/data/entities.js.map +2 -2
  98. package/dist/modules/staff/data/extensions.js +8 -0
  99. package/dist/modules/staff/data/extensions.js.map +2 -2
  100. package/dist/modules/staff/data/validators.js +34 -5
  101. package/dist/modules/staff/data/validators.js.map +2 -2
  102. package/dist/modules/staff/encryption.js +6 -0
  103. package/dist/modules/staff/encryption.js.map +2 -2
  104. package/dist/modules/staff/events.js +25 -0
  105. package/dist/modules/staff/events.js.map +2 -2
  106. package/dist/modules/staff/events.payloads.js +54 -0
  107. package/dist/modules/staff/events.payloads.js.map +2 -2
  108. package/dist/modules/staff/extension-points.js +1 -0
  109. package/dist/modules/staff/extension-points.js.map +2 -2
  110. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js +468 -0
  111. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js.map +7 -0
  112. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js +12 -0
  113. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js.map +7 -0
  114. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js +316 -0
  115. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js.map +7 -0
  116. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js +12 -0
  117. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js.map +7 -0
  118. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js +190 -0
  119. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js.map +7 -0
  120. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js +19 -0
  121. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js.map +7 -0
  122. package/dist/modules/staff/index.js +1 -1
  123. package/dist/modules/staff/index.js.map +2 -2
  124. package/dist/modules/staff/lib/crud.js +2 -0
  125. package/dist/modules/staff/lib/crud.js.map +2 -2
  126. package/dist/modules/staff/lib/support/attachments.js +36 -0
  127. package/dist/modules/staff/lib/support/attachments.js.map +7 -0
  128. package/dist/modules/staff/lib/support/dictionaries.js +87 -0
  129. package/dist/modules/staff/lib/support/dictionaries.js.map +7 -0
  130. package/dist/modules/staff/lib/support/notifications.js +111 -0
  131. package/dist/modules/staff/lib/support/notifications.js.map +7 -0
  132. package/dist/modules/staff/lib/support/portalAttachments.js +100 -0
  133. package/dist/modules/staff/lib/support/portalAttachments.js.map +7 -0
  134. package/dist/modules/staff/lib/support/portalBroadcast.js +117 -0
  135. package/dist/modules/staff/lib/support/portalBroadcast.js.map +7 -0
  136. package/dist/modules/staff/lib/support/portalRoute.js +99 -0
  137. package/dist/modules/staff/lib/support/portalRoute.js.map +7 -0
  138. package/dist/modules/staff/lib/support/portalStatus.js +12 -0
  139. package/dist/modules/staff/lib/support/portalStatus.js.map +7 -0
  140. package/dist/modules/staff/lib/support/portalTickets.js +148 -0
  141. package/dist/modules/staff/lib/support/portalTickets.js.map +7 -0
  142. package/dist/modules/staff/lib/support/routing.js +81 -0
  143. package/dist/modules/staff/lib/support/routing.js.map +7 -0
  144. package/dist/modules/staff/lib/support/routingRuleDraft.js +25 -0
  145. package/dist/modules/staff/lib/support/routingRuleDraft.js.map +7 -0
  146. package/dist/modules/staff/lib/support/seedDefaults.js +114 -0
  147. package/dist/modules/staff/lib/support/seedDefaults.js.map +7 -0
  148. package/dist/modules/staff/lib/support/supportActivity.js +26 -0
  149. package/dist/modules/staff/lib/support/supportActivity.js.map +7 -0
  150. package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js +23 -0
  151. package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js.map +7 -0
  152. package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js +112 -0
  153. package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js.map +7 -0
  154. package/dist/modules/staff/lib/support-ui/portalPendingUploads.js +45 -0
  155. package/dist/modules/staff/lib/support-ui/portalPendingUploads.js.map +7 -0
  156. package/dist/modules/staff/lib/support-ui/portalSupport.js +51 -0
  157. package/dist/modules/staff/lib/support-ui/portalSupport.js.map +7 -0
  158. package/dist/modules/staff/lib/time-tracking/access.js +3 -39
  159. package/dist/modules/staff/lib/time-tracking/access.js.map +2 -2
  160. package/dist/modules/staff/lib/time-tracking/listProjectAccess.js +54 -0
  161. package/dist/modules/staff/lib/time-tracking/listProjectAccess.js.map +7 -0
  162. package/dist/modules/staff/lib/time-tracking/portalRecipients.js +92 -2
  163. package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +2 -2
  164. package/dist/modules/staff/lib/time-tracking/projectAssignment.js +112 -0
  165. package/dist/modules/staff/lib/time-tracking/projectAssignment.js.map +7 -0
  166. package/dist/modules/staff/lib/time-tracking/settingKeys.js +14 -0
  167. package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +2 -2
  168. package/dist/modules/staff/lib/time-tracking/settings.js +28 -1
  169. package/dist/modules/staff/lib/time-tracking/settings.js.map +2 -2
  170. package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js +143 -0
  171. package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js.map +7 -0
  172. package/dist/modules/staff/lib/time-tracking/workRecipients.js +92 -0
  173. package/dist/modules/staff/lib/time-tracking/workRecipients.js.map +7 -0
  174. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +32 -3
  175. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +2 -2
  176. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +29 -0
  177. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +2 -2
  178. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +4 -0
  179. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +2 -2
  180. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +141 -1
  181. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +2 -2
  182. package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js +153 -0
  183. package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js.map +7 -0
  184. package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js +67 -0
  185. package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js.map +7 -0
  186. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +3 -1
  187. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +2 -2
  188. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +80 -17
  189. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +2 -2
  190. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +230 -41
  191. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +2 -2
  192. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +21 -1
  193. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +2 -2
  194. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +16 -4
  195. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +2 -2
  196. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +3 -1
  197. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +2 -2
  198. package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js +68 -0
  199. package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js.map +7 -0
  200. package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js +7 -0
  201. package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js.map +7 -0
  202. package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js +50 -0
  203. package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js.map +7 -0
  204. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +13 -1
  205. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +2 -2
  206. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +36 -2
  207. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +2 -2
  208. package/dist/modules/staff/migrations/Migration20261009180543_staff.js +42 -0
  209. package/dist/modules/staff/migrations/Migration20261009180543_staff.js.map +7 -0
  210. package/dist/modules/staff/notifications.js +92 -0
  211. package/dist/modules/staff/notifications.js.map +2 -2
  212. package/dist/modules/staff/setup.js +6 -2
  213. package/dist/modules/staff/setup.js.map +2 -2
  214. package/dist/modules/staff/subscribers/support-portal-comment-created.js +26 -0
  215. package/dist/modules/staff/subscribers/support-portal-comment-created.js.map +7 -0
  216. package/dist/modules/staff/subscribers/support-portal-comment-deleted.js +26 -0
  217. package/dist/modules/staff/subscribers/support-portal-comment-deleted.js.map +7 -0
  218. package/dist/modules/staff/subscribers/support-portal-comment-updated.js +26 -0
  219. package/dist/modules/staff/subscribers/support-portal-comment-updated.js.map +7 -0
  220. package/dist/modules/staff/subscribers/support-portal-status-column-updated.js +24 -0
  221. package/dist/modules/staff/subscribers/support-portal-status-column-updated.js.map +7 -0
  222. package/dist/modules/staff/subscribers/support-portal-task-status-changed.js +25 -0
  223. package/dist/modules/staff/subscribers/support-portal-task-status-changed.js.map +7 -0
  224. package/dist/modules/staff/subscribers/support-portal-task-updated.js +25 -0
  225. package/dist/modules/staff/subscribers/support-portal-task-updated.js.map +7 -0
  226. package/dist/modules/staff/subscribers/support-ticket-created-notification.js +25 -0
  227. package/dist/modules/staff/subscribers/support-ticket-created-notification.js.map +7 -0
  228. package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js +25 -0
  229. package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js.map +7 -0
  230. package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js +26 -0
  231. package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js.map +7 -0
  232. package/dist/modules/staff/subscribers/time-task-assignee-notification.js +83 -0
  233. package/dist/modules/staff/subscribers/time-task-assignee-notification.js.map +7 -0
  234. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js +112 -0
  235. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js.map +7 -0
  236. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js +17 -0
  237. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js.map +7 -0
  238. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js +870 -0
  239. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js.map +7 -0
  240. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js +17 -0
  241. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js.map +7 -0
  242. package/dist/modules/staff/widgets/injection-table.js +16 -1
  243. package/dist/modules/staff/widgets/injection-table.js.map +2 -2
  244. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +4 -18
  245. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
  246. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +4 -18
  247. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
  248. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +4 -18
  249. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
  250. package/dist/modules/wms/lib/inventoryCsvExport.js +19 -0
  251. package/dist/modules/wms/lib/inventoryCsvExport.js.map +7 -0
  252. package/generated/entities/staff_support_routing_rule/index.ts +9 -0
  253. package/generated/entities/staff_support_ticket/index.ts +17 -0
  254. package/generated/entities/staff_time_project/index.ts +1 -0
  255. package/generated/entities/staff_time_project_member/index.ts +1 -0
  256. package/generated/entities/staff_time_task/index.ts +2 -0
  257. package/generated/entities/staff_time_task_comment/index.ts +4 -0
  258. package/generated/entities.ids.generated.ts +2 -0
  259. package/generated/entity-fields-registry.ts +38 -0
  260. package/package.json +8 -8
  261. package/src/helpers/integration/auth.ts +1 -1
  262. package/src/helpers/integration/communicationChannelsFixtures.ts +60 -2
  263. package/src/modules/catalog/api/bulk-delete/route.ts +12 -2
  264. package/src/modules/communication_channels/api/post/test-seed/route.ts +29 -12
  265. package/src/modules/communication_channels/lib/test-seed.ts +49 -10
  266. package/src/modules/messages/commands/messages.ts +8 -10
  267. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +2 -2
  268. package/src/modules/staff/AGENTS.md +22 -6
  269. package/src/modules/staff/api/portal/support-tickets/[id]/attachments/route.ts +327 -0
  270. package/src/modules/staff/api/portal/support-tickets/[id]/messages/route.ts +94 -0
  271. package/src/modules/staff/api/portal/support-tickets/[id]/route.ts +160 -0
  272. package/src/modules/staff/api/portal/support-tickets/options/route.ts +60 -0
  273. package/src/modules/staff/api/portal/support-tickets/route.ts +253 -0
  274. package/src/modules/staff/api/timesheets/settings/route.ts +7 -1
  275. package/src/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.ts +131 -0
  276. package/src/modules/staff/api/timesheets/support-routing-rules/preview/route.ts +248 -0
  277. package/src/modules/staff/api/timesheets/support-routing-rules/route.ts +150 -0
  278. package/src/modules/staff/api/timesheets/support-tickets/options/route.ts +181 -0
  279. package/src/modules/staff/api/timesheets/tasks/[id]/claim/route.ts +31 -0
  280. package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +25 -7
  281. package/src/modules/staff/api/timesheets/tasks/[id]/release/route.ts +30 -0
  282. package/src/modules/staff/api/timesheets/tasks/route.ts +119 -67
  283. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +3 -0
  284. package/src/modules/staff/api/timesheets/time-projects/route.ts +3 -0
  285. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +1 -0
  286. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +67 -2
  287. package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +22 -3
  288. package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +405 -308
  289. package/src/modules/staff/backend/staff/time-tracking/support/page.meta.ts +14 -0
  290. package/src/modules/staff/backend/staff/time-tracking/support/page.tsx +428 -0
  291. package/src/modules/staff/cli.ts +28 -0
  292. package/src/modules/staff/commands/index.ts +3 -0
  293. package/src/modules/staff/commands/shared.ts +22 -1
  294. package/src/modules/staff/commands/support-routing-rules.ts +595 -0
  295. package/src/modules/staff/commands/support-tickets.ts +580 -0
  296. package/src/modules/staff/commands/timesheets-projects.ts +84 -13
  297. package/src/modules/staff/commands/timesheets-task-assignment.ts +319 -0
  298. package/src/modules/staff/commands/timesheets-task-comments.ts +70 -15
  299. package/src/modules/staff/commands/timesheets-tasks.ts +106 -15
  300. package/src/modules/staff/data/enrichers.ts +203 -0
  301. package/src/modules/staff/data/entities.ts +133 -2
  302. package/src/modules/staff/data/extensions.ts +9 -1
  303. package/src/modules/staff/data/validators.ts +36 -0
  304. package/src/modules/staff/encryption.ts +6 -0
  305. package/src/modules/staff/events.payloads.ts +87 -1
  306. package/src/modules/staff/events.ts +26 -0
  307. package/src/modules/staff/extension-points.ts +1 -0
  308. package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.ts +10 -0
  309. package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.tsx +553 -0
  310. package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.ts +10 -0
  311. package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.tsx +346 -0
  312. package/src/modules/staff/frontend/[orgSlug]/portal/support/page.meta.ts +17 -0
  313. package/src/modules/staff/frontend/[orgSlug]/portal/support/page.tsx +253 -0
  314. package/src/modules/staff/i18n/de.json +291 -0
  315. package/src/modules/staff/i18n/en.json +291 -0
  316. package/src/modules/staff/i18n/es.json +291 -0
  317. package/src/modules/staff/i18n/ko.json +291 -0
  318. package/src/modules/staff/i18n/pl.json +291 -0
  319. package/src/modules/staff/index.ts +1 -1
  320. package/src/modules/staff/lib/crud.ts +2 -1
  321. package/src/modules/staff/lib/support/attachments.ts +43 -0
  322. package/src/modules/staff/lib/support/dictionaries.ts +120 -0
  323. package/src/modules/staff/lib/support/notifications.ts +138 -0
  324. package/src/modules/staff/lib/support/portalAttachments.ts +140 -0
  325. package/src/modules/staff/lib/support/portalBroadcast.ts +161 -0
  326. package/src/modules/staff/lib/support/portalRoute.ts +108 -0
  327. package/src/modules/staff/lib/support/portalStatus.ts +15 -0
  328. package/src/modules/staff/lib/support/portalTickets.ts +233 -0
  329. package/src/modules/staff/lib/support/routing.ts +117 -0
  330. package/src/modules/staff/lib/support/routingRuleDraft.ts +45 -0
  331. package/src/modules/staff/lib/support/seedDefaults.ts +151 -0
  332. package/src/modules/staff/lib/support/supportActivity.ts +39 -0
  333. package/src/modules/staff/lib/support-ui/PortalSupportNotLinked.tsx +20 -0
  334. package/src/modules/staff/lib/support-ui/portalAttachmentValidation.ts +125 -0
  335. package/src/modules/staff/lib/support-ui/portalPendingUploads.ts +49 -0
  336. package/src/modules/staff/lib/support-ui/portalSupport.ts +116 -0
  337. package/src/modules/staff/lib/time-tracking/access.ts +2 -62
  338. package/src/modules/staff/lib/time-tracking/listProjectAccess.ts +78 -0
  339. package/src/modules/staff/lib/time-tracking/portalRecipients.ts +173 -5
  340. package/src/modules/staff/lib/time-tracking/projectAssignment.ts +172 -0
  341. package/src/modules/staff/lib/time-tracking/settingKeys.ts +11 -0
  342. package/src/modules/staff/lib/time-tracking/settings.ts +46 -1
  343. package/src/modules/staff/lib/time-tracking/taskAssignmentRoute.ts +165 -0
  344. package/src/modules/staff/lib/time-tracking/workRecipients.ts +183 -0
  345. package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +36 -1
  346. package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +30 -0
  347. package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +9 -0
  348. package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +185 -6
  349. package/src/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.tsx +188 -0
  350. package/src/modules/staff/lib/time-tracking-ui/SupportRequestPanel.tsx +102 -0
  351. package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +2 -0
  352. package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +105 -22
  353. package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +278 -43
  354. package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +21 -0
  355. package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +16 -2
  356. package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +4 -0
  357. package/src/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.ts +117 -0
  358. package/src/modules/staff/lib/time-tracking-ui/settingsTabs.ts +17 -0
  359. package/src/modules/staff/lib/time-tracking-ui/supportTicketData.ts +71 -0
  360. package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +15 -0
  361. package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +48 -1
  362. package/src/modules/staff/migrations/.snapshot-open-mercato.json +714 -0
  363. package/src/modules/staff/migrations/Migration20261009180543_staff.ts +50 -0
  364. package/src/modules/staff/notifications.ts +92 -0
  365. package/src/modules/staff/setup.ts +6 -2
  366. package/src/modules/staff/subscribers/support-portal-comment-created.ts +35 -0
  367. package/src/modules/staff/subscribers/support-portal-comment-deleted.ts +35 -0
  368. package/src/modules/staff/subscribers/support-portal-comment-updated.ts +35 -0
  369. package/src/modules/staff/subscribers/support-portal-status-column-updated.ts +36 -0
  370. package/src/modules/staff/subscribers/support-portal-task-status-changed.ts +34 -0
  371. package/src/modules/staff/subscribers/support-portal-task-updated.ts +34 -0
  372. package/src/modules/staff/subscribers/support-ticket-created-notification.ts +27 -0
  373. package/src/modules/staff/subscribers/support-ticket-customer-replied-notification.ts +27 -0
  374. package/src/modules/staff/subscribers/support-ticket-unnotified-clear.ts +29 -0
  375. package/src/modules/staff/subscribers/time-task-assignee-notification.ts +102 -0
  376. package/src/modules/staff/widgets/injection/portal-support-tickets/widget.client.tsx +151 -0
  377. package/src/modules/staff/widgets/injection/portal-support-tickets/widget.ts +16 -0
  378. package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.tsx +1058 -0
  379. package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.ts +17 -0
  380. package/src/modules/staff/widgets/injection-table.ts +15 -0
  381. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -20
  382. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -20
  383. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -19
  384. package/src/modules/wms/lib/inventoryCsvExport.ts +18 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/staff/commands/timesheets-tasks.ts"],
4
- "sourcesContent": ["/**\n * Task commands for the Kanban board of screen 6 and the drawer of screen 7 (D-2).\n *\n * A subtask is not a second entity \u2014 it is a task with `parent_task_id` set, carrying\n * its own status, assignee and time entries. Everything that follows from that is\n * enforced here rather than trusted from the request:\n *\n * 1. **Depth is capped at one.** Creating or re-parenting under a task that itself has\n * a parent is refused with `400 subtask_depth_exceeded`, and a task that already\n * holds children may not become one. The board therefore renders exactly the\n * top-level tasks and the drawer exactly one level below them.\n * 2. **Soft-deleting a parent soft-deletes its children in the same transaction.**\n * Time entries are deliberately left alone: they keep pointing at the deleted task\n * so a closed report still resolves its line labels.\n * 3. **A task lives in exactly one project (\u00A72).** Moving one between projects would\n * invalidate its reference, its column and every report line quoting it, so the\n * request is refused instead of silently ignored.\n * 4. **Done-ness has one source of truth** \u2014 the status column's `is_done`. There is\n * no `is_done` on the task; landing on a terminal column stamps `closed_at` and\n * leaving one clears it.\n *\n * `title` is the only field a caller must supply beyond scoping and the project\n * (US-C1): the status defaults to the project's `is_default` column and the assignee\n * to the creator, both overridable.\n *\n * The rollup (`ownMinutes` / `loggedMinutes` / `childCount`) is NOT computed here \u2014 it\n * is a read-side concern owned by `lib/time-tracking/rollup.ts`.\n */\n\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { StaffTeamMember, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from '../data/entities'\nimport {\n staffTimeTaskCreateSchema,\n staffTimeTaskStatusChangeSchema,\n staffTimeTaskUpdateSchema,\n type StaffTimeTaskCreateInput,\n type StaffTimeTaskUpdateInput,\n} from '../data/validators'\nimport { emitStaffEvent } from '../events'\nimport { nextStatusPosition } from '../lib/timesheets-tasks/statusPositions'\nimport {\n allocateTaskReference,\n isTaskReferenceConflict,\n withTaskReferenceRetry,\n} from '../lib/timesheets-tasks/taskReference'\nimport {\n SUBTASK_DEPTH_EXCEEDED_CODE,\n planTaskSoftDelete,\n rejectTaskParent,\n type TaskParentRejection,\n} from '../lib/timesheets-tasks/taskHierarchy'\nimport {\n applyScopeToWhere,\n commandActorScope,\n commandInputScope,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n scopeForDecryption,\n scopedStaffSnapshotWhere,\n staffSnapshotScopeFromContext,\n staffSnapshotScopeFromSnapshot,\n type StaffCommandScope,\n type StaffSnapshotScope,\n} from './shared'\n\nexport const staffTimeTaskCommandIds = {\n create: 'staff.timesheets.tasks.create',\n update: 'staff.timesheets.tasks.update',\n delete: 'staff.timesheets.tasks.delete',\n statusChange: 'staff.timesheets.tasks.status_change',\n} as const\n\n/**\n * Resource kind the board's drag endpoint presents to the mutation-guard registry\n * and to the command-level optimistic lock. It matches the entity naming the other\n * hand-written timesheets routes use (`staff.timesheets.time_entry`,\n * `staff.timesheets.time_project`) so an `OM_OPTIMISTIC_LOCK` allow-list can name\n * it. The audit `resourceKind` stays `staff.timesheets.task`, which is what the\n * other task commands already write.\n */\nexport const STAFF_TIME_TASK_RESOURCE_KIND = 'staff.timesheets.time_task'\n\nexport const TASK_DEPTH_EXCEEDED_CODE = SUBTASK_DEPTH_EXCEEDED_CODE\nexport const TASK_PARENT_SELF_CODE = 'task_parent_self'\nexport const TASK_PARENT_PROJECT_MISMATCH_CODE = 'task_parent_project_mismatch'\nexport const TASK_PROJECT_MOVE_UNSUPPORTED_CODE = 'task_project_move_unsupported'\nexport const TASK_STATUS_MISSING_CODE = 'task_status_missing'\nexport const TASK_REFERENCE_CONFLICT_CODE = 'task_reference_conflict'\n\nconst logger = createLogger('staff').child({ component: 'commands/timesheets-tasks' })\n\nconst taskCrudIndexer: CrudIndexerConfig<StaffTimeTask> = {\n entityType: 'staff:staff_time_task',\n}\n\n/**\n * `staff.timesheets.time_task.{created,updated,deleted}` from `events.ts`. The\n * lifecycle `\u2026status_changed` event belongs to the dedicated status endpoint that\n * owns the drag-and-drop move, not to these generic writes.\n */\nconst taskCrudEvents: CrudEventsConfig<StaffTimeTask> = {\n module: 'staff',\n entity: 'timesheets.time_task',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\n/**\n * The resource tag the tasks CRUD route caches its list pages under \u2014\n * `makeCrudRoute` derives it from `events.module` + `events.entity`, so it is read\n * from the same config here rather than restated.\n *\n * Commands executed straight from a route (the board's move endpoint) are\n * invalidated by the command bus from the audit `resourceKind`, which for tasks is\n * `staff.timesheets.task` \u2014 a different tag. Without this alias a drag left every\n * cached page of the tasks list in place, so the board's refetch after a successful\n * move was answered with the pre-move page: the card snapped back with no error,\n * and the stale `updated_at` that page carried made the next move answer 409\n * \"changed by someone else\" until the entry expired. `ENABLE_CRUD_API_CACHE` off\n * hides it; the ephemeral QA and integration environments run with it on.\n */\nconst TASK_LIST_CACHE_RESOURCE = `${taskCrudEvents.module}.${taskCrudEvents.entity}`\n\ntype Translate = (key: string, fallback: string) => string\n\ntype TaskSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n timeProjectId: string\n parentTaskId: string | null\n taskStatusId: string\n sequenceNumber: number\n reference: string\n title: string\n description: string | null\n assigneeStaffMemberId: string | null\n position: number\n createdByUserId: string | null\n closedAt: string | null\n deletedAt: string | null\n}\n\ntype TaskUndoPayload = {\n before?: TaskSnapshot | null\n after?: TaskSnapshot | null\n /** Children the delete took with it, so undo can put the whole set back. */\n cascadedChildren?: TaskSnapshot[] | null\n}\n\nconst DIFFED_FIELDS = [\n 'parentTaskId',\n 'taskStatusId',\n 'title',\n 'description',\n 'assigneeStaffMemberId',\n 'position',\n 'closedAt',\n] as const\n\nfunction toSnapshot(task: StaffTimeTask): TaskSnapshot {\n return {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n timeProjectId: task.timeProjectId,\n parentTaskId: task.parentTaskId ?? null,\n taskStatusId: task.taskStatusId,\n sequenceNumber: task.sequenceNumber,\n reference: task.reference,\n title: task.title,\n description: task.description ?? null,\n assigneeStaffMemberId: task.assigneeStaffMemberId ?? null,\n position: task.position,\n createdByUserId: task.createdByUserId ?? null,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n deletedAt: task.deletedAt ? task.deletedAt.toISOString() : null,\n }\n}\n\nfunction snapshotScopeWhere(scope?: StaffSnapshotScope | null): { tenantId?: string; organizationId?: string } {\n const where: { tenantId?: string; organizationId?: string } = {}\n if (scope?.tenantId) where.tenantId = scope.tenantId\n if (scope?.organizationId) where.organizationId = scope.organizationId\n return where\n}\n\nasync function loadTaskSnapshot(\n em: EntityManager,\n id: string,\n scope?: StaffSnapshotScope | null,\n): Promise<TaskSnapshot | null> {\n const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(id, scope))\n return task ? toSnapshot(task) : null\n}\n\nasync function loadChildSnapshots(\n em: EntityManager,\n parentTaskId: string,\n scope?: StaffSnapshotScope | null,\n): Promise<TaskSnapshot[]> {\n const children = await em.find(StaffTimeTask, {\n parentTaskId,\n deletedAt: null,\n ...snapshotScopeWhere(scope),\n })\n return children.map(toSnapshot)\n}\n\nfunction notFoundError(translate: Translate): CrudHttpError {\n return new CrudHttpError(404, {\n error: translate('staff.time_tracking.tasks.errors.notFound', 'Task not found or not accessible.'),\n })\n}\n\nfunction projectNotFoundError(translate: Translate): CrudHttpError {\n const message = translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.')\n return new CrudHttpError(422, { error: message, fieldErrors: { timeProjectId: message } })\n}\n\nfunction statusNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.time_tracking.tasks.errors.statusNotFound',\n 'That column does not belong to this project.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { taskStatusId: message } })\n}\n\nfunction statusMissingError(translate: Translate): CrudHttpError {\n return new CrudHttpError(422, {\n code: TASK_STATUS_MISSING_CODE,\n error: translate(\n 'staff.time_tracking.tasks.errors.statusMissing',\n 'This project has no columns yet, so a task cannot be placed on its board.',\n ),\n })\n}\n\nfunction parentNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentNotFound',\n 'Parent task not found or not accessible.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { parentTaskId: message } })\n}\n\nfunction assigneeNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.timesheets.errors.staffMemberNotFound',\n 'Staff member not found or not accessible.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { assigneeStaffMemberId: message } })\n}\n\n/**\n * The one refusal the drawer has to be able to explain: nesting is one level deep, so\n * a subtask can neither gain a subtask nor be handed to another subtask.\n */\nfunction parentRejectionError(translate: Translate, rejection: TaskParentRejection): CrudHttpError {\n if (rejection === 'self_parent') {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentSelf',\n 'A task cannot be its own subtask.',\n )\n return new CrudHttpError(400, {\n code: TASK_PARENT_SELF_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n }\n if (rejection === 'parent_project_mismatch') {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentProjectMismatch',\n 'A subtask must stay in the same project as its parent.',\n )\n return new CrudHttpError(422, {\n code: TASK_PARENT_PROJECT_MISMATCH_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n }\n const message = translate(\n 'staff.time_tracking.tasks.errors.subtaskDepthExceeded',\n 'Subtasks are one level deep. A subtask cannot have subtasks of its own.',\n )\n return new CrudHttpError(400, {\n code: TASK_DEPTH_EXCEEDED_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n}\n\nfunction referenceConflictError(translate: Translate): CrudHttpError {\n return new CrudHttpError(409, {\n code: TASK_REFERENCE_CONFLICT_CODE,\n error: translate(\n 'staff.time_tracking.tasks.errors.referenceConflict',\n 'Could not allocate a task number right now. Try again.',\n ),\n })\n}\n\nasync function requireProject(\n em: EntityManager,\n timeProjectId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeProject> {\n const project = await em.findOne(\n StaffTimeProject,\n applyScopeToWhere<StaffTimeProject>({ id: timeProjectId, deletedAt: null }, scope),\n )\n if (!project) throw projectNotFoundError(translate)\n return project\n}\n\n/**\n * The column a task lands on: the requested one when it belongs to this project, and\n * otherwise the project's `is_default` entry point. The board invariants guarantee one\n * exists; the positional fallback only covers a board repaired by hand.\n */\nasync function resolveTaskStatus(\n em: EntityManager,\n timeProjectId: string,\n requestedStatusId: string | null | undefined,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTaskStatus> {\n if (requestedStatusId) {\n const status = await em.findOne(\n StaffTimeTaskStatus,\n applyScopeToWhere<StaffTimeTaskStatus>({ id: requestedStatusId, timeProjectId, deletedAt: null }, scope),\n )\n if (!status) throw statusNotFoundError(translate)\n return status\n }\n const board = await em.find(\n StaffTimeTaskStatus,\n applyScopeToWhere<StaffTimeTaskStatus>({ timeProjectId, deletedAt: null }, scope),\n )\n if (board.length === 0) throw statusMissingError(translate)\n const ordered = [...board].sort((left, right) => left.position - right.position)\n return ordered.find((status) => status.isDefault) ?? ordered[0]\n}\n\nasync function requireParentTask(\n em: EntityManager,\n parentTaskId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTask> {\n const parent = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parentTaskId, deletedAt: null }, scope),\n )\n if (!parent) throw parentNotFoundError(translate)\n return parent\n}\n\nasync function resolveStaffMemberIdForUser(\n em: EntityManager,\n userId: string | null | undefined,\n scope: StaffCommandScope,\n): Promise<string | null> {\n if (!userId) return null\n const member = await findOneWithDecryption(\n em,\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ userId, deletedAt: null }, scope),\n undefined,\n scopeForDecryption(scope),\n )\n return member?.id ?? null\n}\n\nasync function requireStaffMember(\n em: EntityManager,\n staffMemberId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<string> {\n const member = await em.findOne(\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ id: staffMemberId, deletedAt: null }, scope),\n )\n if (!member) throw assigneeNotFoundError(translate)\n return member.id\n}\n\n/**\n * Order within a column, on the same gap grid the columns themselves use: a drop\n * between two cards writes one row instead of renumbering the stack.\n */\nasync function nextTaskPosition(\n em: EntityManager,\n timeProjectId: string,\n taskStatusId: string,\n scope: StaffCommandScope,\n): Promise<number> {\n const siblings = await em.find(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ timeProjectId, taskStatusId, deletedAt: null }, scope),\n )\n return nextStatusPosition(siblings.map((task) => task.position))\n}\n\n/**\n * The highest number the project has EVER handed out, deleted rows included. The\n * unique index only covers live rows, so a soft-deleted `TT-142` would otherwise let\n * the next task reuse that reference \u2014 and a closed report quoting the old one would\n * suddenly read as the new one.\n */\nasync function readHighestSequenceNumber(\n em: EntityManager,\n timeProjectId: string,\n scope: StaffCommandScope,\n): Promise<number> {\n const tasks = await em.find(StaffTimeTask, applyScopeToWhere<StaffTimeTask>({ timeProjectId }, scope))\n return tasks.reduce((highest, task) => Math.max(highest, task.sequenceNumber ?? 0), 0)\n}\n\n/**\n * \u00A72: a task lives in exactly one project. The update schema has no `timeProjectId`,\n * so a caller sending one would otherwise have it silently dropped and believe the\n * move succeeded.\n */\nfunction assertNoProjectMove(rawInput: unknown, task: StaffTimeTask, translate: Translate): void {\n if (!rawInput || typeof rawInput !== 'object') return\n const requested = (rawInput as Record<string, unknown>).timeProjectId\n if (requested === undefined || requested === null) return\n if (typeof requested === 'string' && requested === task.timeProjectId) return\n const message = translate(\n 'staff.time_tracking.tasks.errors.projectMoveUnsupported',\n 'A task cannot be moved to another project. Create it in the target project instead.',\n )\n throw new CrudHttpError(400, {\n code: TASK_PROJECT_MOVE_UNSUPPORTED_CODE,\n error: message,\n fieldErrors: { timeProjectId: message },\n })\n}\n\nfunction applyStatusToTask(task: StaffTimeTask, status: StaffTimeTaskStatus, now: Date): void {\n task.taskStatusId = status.id\n // Done-ness comes from the column, never from a boolean on the task: landing on a\n // terminal column closes it, leaving one re-opens it.\n if (status.isDone) {\n if (!task.closedAt) task.closedAt = now\n } else {\n task.closedAt = null\n }\n}\n\nfunction actorUserId(ctx: CommandRuntimeContext): string | null {\n return typeof ctx.auth?.sub === 'string' ? ctx.auth.sub : null\n}\n\nconst createTaskCommand: CommandHandler<StaffTimeTaskCreateInput, { taskId: string }> = {\n id: staffTimeTaskCommandIds.create,\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)\n const { translate } = await resolveTranslations()\n const baseEm = ctx.container.resolve('em') as EntityManager\n const createdByUserId = actorUserId(ctx)\n\n let record: StaffTimeTask\n try {\n record = await withTaskReferenceRetry(async () => {\n // A fresh fork per attempt: a lost race aborts the transaction, so the retry\n // re-reads the project's highest number rather than reusing a stale max.\n const em = baseEm.fork()\n let created: StaffTimeTask | null = null\n let plan: {\n project: StaffTimeProject\n status: StaffTimeTaskStatus\n parentTaskId: string | null\n assigneeStaffMemberId: string | null\n position: number\n highestSequenceNumber: number\n } | null = null\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const project = await requireProject(em, parsed.timeProjectId, scope, translate)\n const status = await resolveTaskStatus(em, parsed.timeProjectId, parsed.taskStatusId, scope, translate)\n\n let parentTaskId: string | null = null\n if (parsed.parentTaskId) {\n const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)\n const rejection = rejectTaskParent(parent, { timeProjectId: parsed.timeProjectId })\n if (rejection) throw parentRejectionError(translate, rejection)\n parentTaskId = parent.id\n }\n\n // US-C1: the creator owns the task unless the request says otherwise.\n const assigneeStaffMemberId = parsed.assigneeStaffMemberId\n ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)\n : await resolveStaffMemberIdForUser(em, createdByUserId, scope)\n\n plan = {\n project,\n status,\n parentTaskId,\n assigneeStaffMemberId,\n position:\n parsed.position ?? (await nextTaskPosition(em, parsed.timeProjectId, status.id, scope)),\n highestSequenceNumber: await readHighestSequenceNumber(em, parsed.timeProjectId, scope),\n }\n },\n () => {\n if (!plan) throw notFoundError(translate)\n const now = new Date()\n const allocation = allocateTaskReference(plan.project.code, plan.highestSequenceNumber)\n created = em.create(StaffTimeTask, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n timeProjectId: parsed.timeProjectId,\n parentTaskId: plan.parentTaskId,\n taskStatusId: plan.status.id,\n sequenceNumber: allocation.sequenceNumber,\n reference: allocation.reference,\n title: parsed.title,\n description: parsed.description ?? null,\n assigneeStaffMemberId: plan.assigneeStaffMemberId,\n position: plan.position,\n createdByUserId,\n closedAt: plan.status.isDone ? now : null,\n createdAt: now,\n updatedAt: now,\n deletedAt: null,\n })\n em.persist(created)\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.create },\n )\n\n if (!created) throw notFoundError(translate)\n return created as StaffTimeTask\n })\n } catch (err) {\n // The retry budget ran out \u2014 the caller retries rather than getting a duplicate.\n if (isTaskReferenceConflict(err)) throw referenceConflictError(translate)\n throw err\n }\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'created',\n entity: record,\n identifiers: { id: record.id, organizationId: record.organizationId, tenantId: record.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n return { taskId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n return { snapshot }\n },\n buildLog: async ({ result, ctx }) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.create', 'Create task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n snapshotAfter: snapshot,\n payload: {\n undo: { after: snapshot } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))\n if (!task) return\n task.deletedAt = new Date()\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'deleted',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n },\n}\n\nconst updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: string }> = {\n id: staffTimeTaskCommandIds.update,\n async prepare(rawInput, ctx) {\n const parsed = staffTimeTaskUpdateSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskUpdateSchema.parse(rawInput)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n assertNoProjectMove(rawInput, task, translate)\n\n let targetStatus: StaffTimeTaskStatus | null = null\n let targetParentTaskId: string | null | undefined\n let targetAssigneeStaffMemberId: string | null | undefined\n\n await withAtomicFlush(\n em,\n [\n async () => {\n if (parsed.taskStatusId !== undefined) {\n targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)\n }\n\n if (parsed.parentTaskId !== undefined) {\n if (parsed.parentTaskId === null) {\n targetParentTaskId = null\n } else {\n const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)\n const childCount = await em.count(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),\n )\n const rejection = rejectTaskParent(parent, {\n id: task.id,\n timeProjectId: task.timeProjectId,\n childCount,\n })\n if (rejection) throw parentRejectionError(translate, rejection)\n targetParentTaskId = parent.id\n }\n }\n\n if (parsed.assigneeStaffMemberId !== undefined) {\n targetAssigneeStaffMemberId = parsed.assigneeStaffMemberId\n ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)\n : null\n }\n },\n () => {\n const now = new Date()\n if (parsed.title !== undefined) task.title = parsed.title\n if (parsed.description !== undefined) task.description = parsed.description ?? null\n if (parsed.position !== undefined) task.position = parsed.position\n if (targetParentTaskId !== undefined) task.parentTaskId = targetParentTaskId\n if (targetAssigneeStaffMemberId !== undefined) task.assigneeStaffMemberId = targetAssigneeStaffMemberId\n if (targetStatus) applyStatusToTask(task, targetStatus, now)\n task.updatedAt = now\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.update },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n return { taskId: task.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.update', 'Update task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n changes: buildChanges(\n before as unknown as Record<string, unknown>,\n after as unknown as Record<string, unknown>,\n [...DIFFED_FIELDS],\n ),\n payload: {\n undo: { before, after } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(\n StaffTimeTask,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!task) return\n\n task.parentTaskId = before.parentTaskId\n task.taskStatusId = before.taskStatusId\n task.title = before.title\n task.description = before.description\n task.assigneeStaffMemberId = before.assigneeStaffMemberId\n task.position = before.position\n task.closedAt = before.closedAt ? new Date(before.closedAt) : null\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n },\n}\n\nconst deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; childIds: string[] }> = {\n id: staffTimeTaskCommandIds.delete,\n async prepare(input, ctx) {\n const id = input?.id\n if (!id) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const scope = staffSnapshotScopeFromContext(ctx)\n const before = await loadTaskSnapshot(em, id, scope)\n if (!before) return {}\n const cascadedChildren = before.parentTaskId ? [] : await loadChildSnapshots(em, before.id, scope)\n return { before: { snapshot: before, cascadedChildren } }\n },\n async execute(input, ctx) {\n const { translate } = await resolveTranslations()\n const id = input?.id\n if (!id) {\n throw new CrudHttpError(400, {\n error: translate('staff.time_tracking.tasks.errors.idRequired', 'Task id is required.'),\n })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n\n let children: StaffTimeTask[] = []\n let childIds: string[] = []\n\n await withAtomicFlush(\n em,\n [\n async () => {\n // Only a top-level task can carry a cascade; the depth rule guarantees a\n // child has none, so the walk is exactly one level.\n children = task.parentTaskId\n ? []\n : await em.find(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),\n )\n },\n () => {\n const plan = planTaskSoftDelete(task, children)\n childIds = plan.childIds\n const now = new Date()\n const byId = new Map(children.map((child) => [child.id, child]))\n task.deletedAt = now\n task.updatedAt = now\n for (const childId of childIds) {\n const child = byId.get(childId)\n if (!child) continue\n child.deletedAt = now\n child.updatedAt = now\n }\n // Time entries are untouched on purpose: a closed report still has to\n // resolve this task's label months after it was removed from the board.\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.delete },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const removed of [task, ...children.filter((child) => childIds.includes(child.id))]) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: removed,\n identifiers: { id: removed.id, organizationId: removed.organizationId, tenantId: removed.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n }\n\n return { taskId: task.id, childIds }\n },\n buildLog: async ({ snapshots }) => {\n const prepared = snapshots.before as\n | { snapshot?: TaskSnapshot; cascadedChildren?: TaskSnapshot[] }\n | undefined\n const before = prepared?.snapshot\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.delete', 'Delete task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n cascadedChildren: prepared?.cascadedChildren ?? null,\n } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const snapshotScope = staffSnapshotScopeFromSnapshot(before)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ids = [before.id, ...(payload?.cascadedChildren ?? []).map((child) => child.id)]\n const restored: StaffTimeTask[] = []\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const tasks = await em.find(StaffTimeTask, {\n id: { $in: ids },\n ...snapshotScopeWhere(snapshotScope),\n })\n restored.push(...tasks)\n },\n () => {\n // Undo restores the whole set the cascade removed, not just the parent.\n for (const task of restored) {\n task.deletedAt = null\n task.updatedAt = new Date()\n }\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommandIds.delete}.undo` },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const task of restored) {\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'created',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n }\n },\n}\n\n/**\n * US-C2: the board drag, and the drawer's subtask tick, which is the same move\n * expressed without a position.\n *\n * It is a command of its own rather than a flavour of `update` for three reasons:\n * the move is the one write on this entity where two people racing the same record\n * is genuinely likely, so it enforces the `updated_at` version and answers 409 on a\n * stale one; the board needs a lifecycle event carrying enough to relocate the card\n * on another client without a refetch; and the column change is the whole payload,\n * so the audit row reads \"Move task\" instead of a generic update.\n *\n * `closedAt` is NOT re-derived here \u2014 `applyStatusToTask` above is the single place\n * that reads the column's `is_done`, shared verbatim with `update`.\n */\nconst statusChangeInputSchema = staffTimeTaskStatusChangeSchema.extend({\n id: z.string().uuid(),\n})\n\nexport type StaffTimeTaskStatusChangeCommandInput = z.infer<typeof statusChangeInputSchema>\n\nexport type StaffTimeTaskStatusChangeResult = {\n taskId: string\n timeProjectId: string\n previousTaskStatusId: string\n taskStatusId: string\n position: number\n closedAt: string | null\n updatedAt: string | null\n}\n\nconst STATUS_CHANGE_DIFFED_FIELDS = ['taskStatusId', 'position', 'closedAt'] as const\n\nconst statusChangeTaskCommand: CommandHandler<\n StaffTimeTaskStatusChangeCommandInput,\n StaffTimeTaskStatusChangeResult\n> = {\n id: staffTimeTaskCommandIds.statusChange,\n async prepare(rawInput, ctx) {\n const parsed = statusChangeInputSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = statusChangeInputSchema.parse(rawInput)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n\n // Two people dragging the same card is the realistic conflict on this board,\n // so a stale version loses here and the client rolls the card back to the\n // column it came from.\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: STAFF_TIME_TASK_RESOURCE_KIND,\n resourceId: task.id,\n current: task.updatedAt,\n request: ctx.request,\n })\n\n const previousTaskStatusId = task.taskStatusId\n let targetStatus: StaffTimeTaskStatus | null = null\n let targetPosition = task.position\n\n await withAtomicFlush(\n em,\n [\n async () => {\n // Refuses a column belonging to another project: `resolveTaskStatus`\n // looks the status up inside this task's project, so a foreign id is a\n // 422 rather than a silent cross-board move.\n targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)\n targetPosition =\n parsed.position ?? (await nextTaskPosition(em, task.timeProjectId, targetStatus.id, scope))\n },\n () => {\n if (!targetStatus) throw notFoundError(translate)\n const now = new Date()\n task.position = targetPosition\n applyStatusToTask(task, targetStatus, now)\n task.updatedAt = now\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.statusChange },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n const result: StaffTimeTaskStatusChangeResult = {\n taskId: task.id,\n timeProjectId: task.timeProjectId,\n previousTaskStatusId,\n taskStatusId: task.taskStatusId,\n position: task.position,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,\n }\n\n // `clientBroadcast: true` (events.ts) puts this on the DOM Event Bridge, so the\n // payload carries everything a second board needs to move the card itself \u2014\n // origin column, destination column and the new slot \u2014 without a refetch. The\n // scope fields are what the SSE stream filters its audience on. A failed\n // broadcast must never fail a committed drag.\n void emitStaffEvent('staff.timesheets.time_task.status_changed', {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n ...result,\n }).catch((err) => {\n logger.error('staff.timesheets emit time_task.status_changed failed', { err })\n })\n\n return result\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.statusChange', 'Move task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n // Flushes the cached tasks list the board refetches right after the move,\n // and again after an undo \u2014 the audit resourceKind above names a different\n // tag. See TASK_LIST_CACHE_RESOURCE.\n context: { cacheAliases: [TASK_LIST_CACHE_RESOURCE] },\n snapshotBefore: before,\n snapshotAfter: after,\n changes: buildChanges(\n before as unknown as Record<string, unknown>,\n after as unknown as Record<string, unknown>,\n [...STATUS_CHANGE_DIFFED_FIELDS],\n ),\n payload: {\n undo: { before, after } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(\n StaffTimeTask,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!task) return\n\n const previousTaskStatusId = task.taskStatusId\n task.taskStatusId = before.taskStatusId\n task.position = before.position\n task.closedAt = before.closedAt ? new Date(before.closedAt) : null\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n void emitStaffEvent('staff.timesheets.time_task.status_changed', {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n taskId: task.id,\n timeProjectId: task.timeProjectId,\n previousTaskStatusId,\n taskStatusId: task.taskStatusId,\n position: task.position,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,\n }).catch((err) => {\n logger.error('staff.timesheets emit time_task.status_changed failed', { err })\n })\n },\n}\n\nregisterCommand(createTaskCommand)\nregisterCommand(updateTaskCommand)\nregisterCommand(deleteTaskCommand)\nregisterCommand(statusChangeTaskCommand)\n"],
5
- "mappings": "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",
6
- "names": ["message"]
4
+ "sourcesContent": ["/**\n * Task commands for the Kanban board of screen 6 and the drawer of screen 7 (D-2).\n *\n * A subtask is not a second entity \u2014 it is a task with `parent_task_id` set, carrying\n * its own status, assignee and time entries. Everything that follows from that is\n * enforced here rather than trusted from the request:\n *\n * 1. **Depth is capped at one.** Creating or re-parenting under a task that itself has\n * a parent is refused with `400 subtask_depth_exceeded`, and a task that already\n * holds children may not become one. The board therefore renders exactly the\n * top-level tasks and the drawer exactly one level below them.\n * 2. **Soft-deleting a parent soft-deletes its children in the same transaction.**\n * Time entries are deliberately left alone: they keep pointing at the deleted task\n * so a closed report still resolves its line labels.\n * 3. **A task lives in exactly one project (\u00A72).** Moving one between projects would\n * invalidate its reference, its column and every report line quoting it, so the\n * request is refused instead of silently ignored.\n * 4. **Done-ness has one source of truth** \u2014 the status column's `is_done`. There is\n * no `is_done` on the task; landing on a terminal column stamps `closed_at` and\n * leaving one clears it.\n *\n * `title` is the only field a caller must supply beyond scoping and the project\n * (US-C1): the status defaults to the project's `is_default` column and the assignee\n * to the creator, both overridable.\n *\n * The rollup (`ownMinutes` / `loggedMinutes` / `childCount`) is NOT computed here \u2014 it\n * is a read-side concern owned by `lib/time-tracking/rollup.ts`.\n */\n\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { StaffSupportTicket, StaffTeamMember, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from '../data/entities'\nimport {\n staffTimeTaskCreateSchema,\n staffTimeTaskStatusChangeSchema,\n staffTimeTaskUpdateSchema,\n type StaffTimeTaskCreateInput,\n type StaffTimeTaskUpdateInput,\n} from '../data/validators'\nimport { emitStaffEvent } from '../events'\nimport { nextStatusPosition } from '../lib/timesheets-tasks/statusPositions'\nimport {\n allocateTaskReference,\n isTaskReferenceConflict,\n withTaskReferenceRetry,\n} from '../lib/timesheets-tasks/taskReference'\nimport {\n SUBTASK_DEPTH_EXCEEDED_CODE,\n planTaskSoftDelete,\n rejectTaskParent,\n type TaskParentRejection,\n} from '../lib/timesheets-tasks/taskHierarchy'\nimport {\n applyScopeToWhere,\n commandActorScope,\n commandInputScope,\n ensureOrganizationScope,\n ensureTenantScope,\n explicitStaffCommandScope,\n extractUndoPayload,\n lockLiveProjectForWrite,\n scopeForDecryption,\n scopedStaffSnapshotWhere,\n staffSnapshotScopeFromContext,\n staffSnapshotScopeFromSnapshot,\n type StaffCommandScope,\n type StaffSnapshotScope,\n} from './shared'\n\nexport const staffTimeTaskCommandIds = {\n create: 'staff.timesheets.tasks.create',\n update: 'staff.timesheets.tasks.update',\n delete: 'staff.timesheets.tasks.delete',\n statusChange: 'staff.timesheets.tasks.status_change',\n} as const\n\n/**\n * Resource kind the board's drag endpoint presents to the mutation-guard registry\n * and to the command-level optimistic lock. It matches the entity naming the other\n * hand-written timesheets routes use (`staff.timesheets.time_entry`,\n * `staff.timesheets.time_project`) so an `OM_OPTIMISTIC_LOCK` allow-list can name\n * it. The audit `resourceKind` stays `staff.timesheets.task`, which is what the\n * other task commands already write.\n */\nexport const STAFF_TIME_TASK_RESOURCE_KIND = 'staff.timesheets.time_task'\n\nexport const TASK_DEPTH_EXCEEDED_CODE = SUBTASK_DEPTH_EXCEEDED_CODE\nexport const TASK_PARENT_SELF_CODE = 'task_parent_self'\nexport const TASK_PARENT_PROJECT_MISMATCH_CODE = 'task_parent_project_mismatch'\nexport const TASK_PROJECT_MOVE_UNSUPPORTED_CODE = 'task_project_move_unsupported'\nexport const TASK_STATUS_MISSING_CODE = 'task_status_missing'\nexport const TASK_REFERENCE_CONFLICT_CODE = 'task_reference_conflict'\n\nconst logger = createLogger('staff').child({ component: 'commands/timesheets-tasks' })\n\nexport const taskCrudIndexer: CrudIndexerConfig<StaffTimeTask> = {\n entityType: 'staff:staff_time_task',\n}\n\n/**\n * `staff.timesheets.time_task.{created,updated,deleted}` from `events.ts`. The\n * lifecycle `\u2026status_changed` event belongs to the dedicated status endpoint that\n * owns the drag-and-drop move, not to these generic writes.\n */\nexport const taskCrudEvents: CrudEventsConfig<StaffTimeTask> = {\n module: 'staff',\n entity: 'timesheets.time_task',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\n/**\n * The resource tag the tasks CRUD route caches its list pages under \u2014\n * `makeCrudRoute` derives it from `events.module` + `events.entity`, so it is read\n * from the same config here rather than restated.\n *\n * Commands executed straight from a route (the board's move endpoint) are\n * invalidated by the command bus from the audit `resourceKind`, which for tasks is\n * `staff.timesheets.task` \u2014 a different tag. Without this alias a drag left every\n * cached page of the tasks list in place, so the board's refetch after a successful\n * move was answered with the pre-move page: the card snapped back with no error,\n * and the stale `updated_at` that page carried made the next move answer 409\n * \"changed by someone else\" until the entry expired. `ENABLE_CRUD_API_CACHE` off\n * hides it; the ephemeral QA and integration environments run with it on.\n */\nexport const TASK_LIST_CACHE_RESOURCE = `${taskCrudEvents.module}.${taskCrudEvents.entity}`\n\ntype Translate = (key: string, fallback: string) => string\n\ntype TaskSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n timeProjectId: string\n parentTaskId: string | null\n taskStatusId: string\n sequenceNumber: number\n reference: string\n title: string\n description: string | null\n assigneeStaffMemberId: string | null\n position: number\n createdByUserId: string | null\n closedAt: string | null\n deletedAt: string | null\n}\n\ntype TaskUndoPayload = {\n before?: TaskSnapshot | null\n after?: TaskSnapshot | null\n /** Children the delete took with it, so undo can put the whole set back. */\n cascadedChildren?: TaskSnapshot[] | null\n /** Support tickets this delete soft-deleted; undo restores exactly these and no others. */\n cascadedSupportTicketIds?: string[] | null\n}\n\nconst DIFFED_FIELDS = [\n 'parentTaskId',\n 'taskStatusId',\n 'title',\n 'description',\n 'assigneeStaffMemberId',\n 'position',\n 'closedAt',\n] as const\n\nfunction toSnapshot(task: StaffTimeTask): TaskSnapshot {\n return {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n timeProjectId: task.timeProjectId,\n parentTaskId: task.parentTaskId ?? null,\n taskStatusId: task.taskStatusId,\n sequenceNumber: task.sequenceNumber,\n reference: task.reference,\n title: task.title,\n description: task.description ?? null,\n assigneeStaffMemberId: task.assigneeStaffMemberId ?? null,\n position: task.position,\n createdByUserId: task.createdByUserId ?? null,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n deletedAt: task.deletedAt ? task.deletedAt.toISOString() : null,\n }\n}\n\nfunction snapshotScopeWhere(scope?: StaffSnapshotScope | null): { tenantId?: string; organizationId?: string } {\n const where: { tenantId?: string; organizationId?: string } = {}\n if (scope?.tenantId) where.tenantId = scope.tenantId\n if (scope?.organizationId) where.organizationId = scope.organizationId\n return where\n}\n\nasync function loadTaskSnapshot(\n em: EntityManager,\n id: string,\n scope?: StaffSnapshotScope | null,\n): Promise<TaskSnapshot | null> {\n const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(id, scope))\n return task ? toSnapshot(task) : null\n}\n\nasync function loadChildSnapshots(\n em: EntityManager,\n parentTaskId: string,\n scope?: StaffSnapshotScope | null,\n): Promise<TaskSnapshot[]> {\n const children = await em.find(StaffTimeTask, {\n parentTaskId,\n deletedAt: null,\n ...snapshotScopeWhere(scope),\n })\n return children.map(toSnapshot)\n}\n\nfunction notFoundError(translate: Translate): CrudHttpError {\n return new CrudHttpError(404, {\n error: translate('staff.time_tracking.tasks.errors.notFound', 'Task not found or not accessible.'),\n })\n}\n\nfunction projectNotFoundError(translate: Translate): CrudHttpError {\n const message = translate('staff.timesheets.errors.projectNotFound', 'Time project not found or not accessible.')\n return new CrudHttpError(422, { error: message, fieldErrors: { timeProjectId: message } })\n}\n\nfunction statusNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.time_tracking.tasks.errors.statusNotFound',\n 'That column does not belong to this project.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { taskStatusId: message } })\n}\n\nfunction statusMissingError(translate: Translate): CrudHttpError {\n return new CrudHttpError(422, {\n code: TASK_STATUS_MISSING_CODE,\n error: translate(\n 'staff.time_tracking.tasks.errors.statusMissing',\n 'This project has no columns yet, so a task cannot be placed on its board.',\n ),\n })\n}\n\nfunction parentNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentNotFound',\n 'Parent task not found or not accessible.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { parentTaskId: message } })\n}\n\nfunction assigneeNotFoundError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.timesheets.errors.staffMemberNotFound',\n 'Staff member not found or not accessible.',\n )\n return new CrudHttpError(422, { error: message, fieldErrors: { assigneeStaffMemberId: message } })\n}\n\n/**\n * The one refusal the drawer has to be able to explain: nesting is one level deep, so\n * a subtask can neither gain a subtask nor be handed to another subtask.\n */\nfunction parentRejectionError(translate: Translate, rejection: TaskParentRejection): CrudHttpError {\n if (rejection === 'self_parent') {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentSelf',\n 'A task cannot be its own subtask.',\n )\n return new CrudHttpError(400, {\n code: TASK_PARENT_SELF_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n }\n if (rejection === 'parent_project_mismatch') {\n const message = translate(\n 'staff.time_tracking.tasks.errors.parentProjectMismatch',\n 'A subtask must stay in the same project as its parent.',\n )\n return new CrudHttpError(422, {\n code: TASK_PARENT_PROJECT_MISMATCH_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n }\n const message = translate(\n 'staff.time_tracking.tasks.errors.subtaskDepthExceeded',\n 'Subtasks are one level deep. A subtask cannot have subtasks of its own.',\n )\n return new CrudHttpError(400, {\n code: TASK_DEPTH_EXCEEDED_CODE,\n error: message,\n fieldErrors: { parentTaskId: message },\n })\n}\n\nfunction referenceConflictError(translate: Translate): CrudHttpError {\n return new CrudHttpError(409, {\n code: TASK_REFERENCE_CONFLICT_CODE,\n error: translate(\n 'staff.time_tracking.tasks.errors.referenceConflict',\n 'Could not allocate a task number right now. Try again.',\n ),\n })\n}\n\nasync function requireProject(\n em: EntityManager,\n timeProjectId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeProject> {\n const project = await lockLiveProjectForWrite(em, timeProjectId, scope)\n if (!project) throw projectNotFoundError(translate)\n return project\n}\n\n/**\n * The column a task lands on: the requested one when it belongs to this project, and\n * otherwise the project's `is_default` entry point. The board invariants guarantee one\n * exists; the positional fallback only covers a board repaired by hand.\n */\nexport async function resolveTaskStatus(\n em: EntityManager,\n timeProjectId: string,\n requestedStatusId: string | null | undefined,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTaskStatus> {\n if (requestedStatusId) {\n const status = await em.findOne(\n StaffTimeTaskStatus,\n applyScopeToWhere<StaffTimeTaskStatus>({ id: requestedStatusId, timeProjectId, deletedAt: null }, scope),\n )\n if (!status) throw statusNotFoundError(translate)\n return status\n }\n const board = await em.find(\n StaffTimeTaskStatus,\n applyScopeToWhere<StaffTimeTaskStatus>({ timeProjectId, deletedAt: null }, scope),\n )\n if (board.length === 0) throw statusMissingError(translate)\n const ordered = [...board].sort((left, right) => left.position - right.position)\n return ordered.find((status) => status.isDefault) ?? ordered[0]\n}\n\nasync function requireParentTask(\n em: EntityManager,\n parentTaskId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTask> {\n const parent = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parentTaskId, deletedAt: null }, scope),\n )\n if (!parent) throw parentNotFoundError(translate)\n return parent\n}\n\nasync function resolveStaffMemberIdForUser(\n em: EntityManager,\n userId: string | null | undefined,\n scope: StaffCommandScope,\n): Promise<string | null> {\n if (!userId) return null\n const member = await findOneWithDecryption(\n em,\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ userId, deletedAt: null }, scope),\n undefined,\n scopeForDecryption(scope),\n )\n return member?.id ?? null\n}\n\nasync function requireStaffMember(\n em: EntityManager,\n staffMemberId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<string> {\n const member = await em.findOne(\n StaffTeamMember,\n applyScopeToWhere<StaffTeamMember>({ id: staffMemberId, deletedAt: null }, scope),\n )\n if (!member) throw assigneeNotFoundError(translate)\n return member.id\n}\n\n/**\n * Order within a column, on the same gap grid the columns themselves use: a drop\n * between two cards writes one row instead of renumbering the stack.\n */\nexport async function nextTaskPosition(\n em: EntityManager,\n timeProjectId: string,\n taskStatusId: string,\n scope: StaffCommandScope,\n): Promise<number> {\n const siblings = await em.find(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ timeProjectId, taskStatusId, deletedAt: null }, scope),\n )\n return nextStatusPosition(siblings.map((task) => task.position))\n}\n\n/**\n * The highest number the project has EVER handed out, deleted rows included. The\n * unique index only covers live rows, so a soft-deleted `TT-142` would otherwise let\n * the next task reuse that reference \u2014 and a closed report quoting the old one would\n * suddenly read as the new one.\n */\nexport async function readHighestSequenceNumber(\n em: EntityManager,\n timeProjectId: string,\n scope: StaffCommandScope,\n): Promise<number> {\n const tasks = await em.find(StaffTimeTask, applyScopeToWhere<StaffTimeTask>({ timeProjectId }, scope))\n return tasks.reduce((highest, task) => Math.max(highest, task.sequenceNumber ?? 0), 0)\n}\n\n/**\n * \u00A72: a task lives in exactly one project. The update schema has no `timeProjectId`,\n * so a caller sending one would otherwise have it silently dropped and believe the\n * move succeeded.\n */\nfunction assertNoProjectMove(rawInput: unknown, task: StaffTimeTask, translate: Translate): void {\n if (!rawInput || typeof rawInput !== 'object') return\n const requested = (rawInput as Record<string, unknown>).timeProjectId\n if (requested === undefined || requested === null) return\n if (typeof requested === 'string' && requested === task.timeProjectId) return\n const message = translate(\n 'staff.time_tracking.tasks.errors.projectMoveUnsupported',\n 'A task cannot be moved to another project. Create it in the target project instead.',\n )\n throw new CrudHttpError(400, {\n code: TASK_PROJECT_MOVE_UNSUPPORTED_CODE,\n error: message,\n fieldErrors: { timeProjectId: message },\n })\n}\n\nexport function applyStatusToTask(task: StaffTimeTask, status: StaffTimeTaskStatus, now: Date): void {\n task.taskStatusId = status.id\n // Done-ness comes from the column, never from a boolean on the task: landing on a\n // terminal column closes it, leaving one re-opens it.\n if (status.isDone) {\n if (!task.closedAt) task.closedAt = now\n } else {\n task.closedAt = null\n }\n}\n\nfunction actorUserId(ctx: CommandRuntimeContext): string | null {\n return typeof ctx.auth?.sub === 'string' ? ctx.auth.sub : null\n}\n\nconst createTaskCommand: CommandHandler<StaffTimeTaskCreateInput, { taskId: string }> = {\n id: staffTimeTaskCommandIds.create,\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)\n const { translate } = await resolveTranslations()\n const baseEm = ctx.container.resolve('em') as EntityManager\n const createdByUserId = actorUserId(ctx)\n\n let record: StaffTimeTask\n try {\n record = await withTaskReferenceRetry(async () => {\n // A fresh fork per attempt: a lost race aborts the transaction, so the retry\n // re-reads the project's highest number rather than reusing a stale max.\n const em = baseEm.fork()\n let created: StaffTimeTask | null = null\n let plan: {\n project: StaffTimeProject\n status: StaffTimeTaskStatus\n parentTaskId: string | null\n assigneeStaffMemberId: string | null\n position: number\n highestSequenceNumber: number\n } | null = null\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const project = await requireProject(em, parsed.timeProjectId, scope, translate)\n const status = await resolveTaskStatus(em, parsed.timeProjectId, parsed.taskStatusId, scope, translate)\n\n let parentTaskId: string | null = null\n if (parsed.parentTaskId) {\n const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)\n const rejection = rejectTaskParent(parent, { timeProjectId: parsed.timeProjectId })\n if (rejection) throw parentRejectionError(translate, rejection)\n parentTaskId = parent.id\n }\n\n // US-C1: the creator owns the task unless the request says otherwise.\n const assigneeStaffMemberId = parsed.assigneeStaffMemberId\n ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)\n : await resolveStaffMemberIdForUser(em, createdByUserId, scope)\n\n plan = {\n project,\n status,\n parentTaskId,\n assigneeStaffMemberId,\n position:\n parsed.position ?? (await nextTaskPosition(em, parsed.timeProjectId, status.id, scope)),\n highestSequenceNumber: await readHighestSequenceNumber(em, parsed.timeProjectId, scope),\n }\n },\n () => {\n if (!plan) throw notFoundError(translate)\n const now = new Date()\n const allocation = allocateTaskReference(plan.project.code, plan.highestSequenceNumber)\n created = em.create(StaffTimeTask, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n timeProjectId: parsed.timeProjectId,\n parentTaskId: plan.parentTaskId,\n taskStatusId: plan.status.id,\n sequenceNumber: allocation.sequenceNumber,\n reference: allocation.reference,\n title: parsed.title,\n description: parsed.description ?? null,\n assigneeStaffMemberId: plan.assigneeStaffMemberId,\n position: plan.position,\n createdByUserId,\n closedAt: plan.status.isDone ? now : null,\n createdAt: now,\n updatedAt: now,\n deletedAt: null,\n })\n em.persist(created)\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.create },\n )\n\n if (!created) throw notFoundError(translate)\n return created as StaffTimeTask\n })\n } catch (err) {\n // The retry budget ran out \u2014 the caller retries rather than getting a duplicate.\n if (isTaskReferenceConflict(err)) throw referenceConflictError(translate)\n throw err\n }\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'created',\n entity: record,\n identifiers: { id: record.id, organizationId: record.organizationId, tenantId: record.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n return { taskId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n return { snapshot }\n },\n buildLog: async ({ result, ctx }) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadTaskSnapshot(em, result.taskId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.create', 'Create task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n snapshotAfter: snapshot,\n payload: {\n undo: { after: snapshot } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(StaffTimeTask, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))\n if (!task) return\n task.deletedAt = new Date()\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'deleted',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n },\n}\n\nexport type TaskAssigneeChangeReason = 'default' | 'claim' | 'release' | 'manual'\n\nexport function emitTaskAssigneeChanged(\n task: Pick<StaffTimeTask, 'id' | 'tenantId' | 'organizationId' | 'timeProjectId' | 'assigneeStaffMemberId'>,\n previousAssigneeStaffMemberId: string | null,\n changeKind: TaskAssigneeChangeReason,\n actorUserId: string | null = null,\n): void {\n void emitStaffEvent(\n 'staff.timesheets.time_task.assignee_changed',\n {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n timeProjectId: task.timeProjectId,\n previousAssigneeStaffMemberId,\n assigneeStaffMemberId: task.assigneeStaffMemberId ?? null,\n changeKind,\n actorUserId,\n },\n { persistent: true },\n ).catch((err) => {\n logger.error('staff.timesheets.time_task.assignee_changed emit failed', { err, taskId: task.id })\n getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.task_assignee_changed_emit_failed' })\n })\n}\n\nconst updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: string }> = {\n id: staffTimeTaskCommandIds.update,\n async prepare(rawInput, ctx) {\n const parsed = staffTimeTaskUpdateSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskUpdateSchema.parse(rawInput)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n assertNoProjectMove(rawInput, task, translate)\n\n let targetStatus: StaffTimeTaskStatus | null = null\n let targetParentTaskId: string | null | undefined\n let targetAssigneeStaffMemberId: string | null | undefined\n const previousAssigneeStaffMemberId = task.assigneeStaffMemberId ?? null\n\n await withAtomicFlush(\n em,\n [\n async () => {\n if (parsed.taskStatusId !== undefined) {\n targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)\n }\n\n if (parsed.parentTaskId !== undefined) {\n if (parsed.parentTaskId === null) {\n targetParentTaskId = null\n } else {\n const parent = await requireParentTask(em, parsed.parentTaskId, scope, translate)\n const childCount = await em.count(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),\n )\n const rejection = rejectTaskParent(parent, {\n id: task.id,\n timeProjectId: task.timeProjectId,\n childCount,\n })\n if (rejection) throw parentRejectionError(translate, rejection)\n targetParentTaskId = parent.id\n }\n }\n\n if (parsed.assigneeStaffMemberId !== undefined) {\n targetAssigneeStaffMemberId = parsed.assigneeStaffMemberId\n ? await requireStaffMember(em, parsed.assigneeStaffMemberId, scope, translate)\n : null\n }\n },\n () => {\n const now = new Date()\n if (parsed.title !== undefined) task.title = parsed.title\n if (parsed.description !== undefined) task.description = parsed.description ?? null\n if (parsed.position !== undefined) task.position = parsed.position\n if (targetParentTaskId !== undefined) task.parentTaskId = targetParentTaskId\n if (targetAssigneeStaffMemberId !== undefined) task.assigneeStaffMemberId = targetAssigneeStaffMemberId\n if (targetStatus) applyStatusToTask(task, targetStatus, now)\n task.updatedAt = now\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.update },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n const nextAssigneeStaffMemberId = task.assigneeStaffMemberId ?? null\n if (targetAssigneeStaffMemberId !== undefined && nextAssigneeStaffMemberId !== previousAssigneeStaffMemberId) {\n emitTaskAssigneeChanged(task, previousAssigneeStaffMemberId, 'manual', ctx.auth?.sub ?? null)\n }\n\n return { taskId: task.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.update', 'Update task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n changes: buildChanges(\n before as unknown as Record<string, unknown>,\n after as unknown as Record<string, unknown>,\n [...DIFFED_FIELDS],\n ),\n payload: {\n undo: { before, after } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(\n StaffTimeTask,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!task) return\n\n task.parentTaskId = before.parentTaskId\n task.taskStatusId = before.taskStatusId\n task.title = before.title\n task.description = before.description\n task.assigneeStaffMemberId = before.assigneeStaffMemberId\n task.position = before.position\n task.closedAt = before.closedAt ? new Date(before.closedAt) : null\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n },\n}\n\nexport type StaffTimeTaskDeleteResult = {\n taskId: string\n childIds: string[]\n supportTicketIds: string[]\n}\n\nconst deleteTaskCommand: CommandHandler<{ id?: string }, StaffTimeTaskDeleteResult> = {\n id: staffTimeTaskCommandIds.delete,\n async prepare(input, ctx) {\n const id = input?.id\n if (!id) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const scope = staffSnapshotScopeFromContext(ctx)\n const before = await loadTaskSnapshot(em, id, scope)\n if (!before) return {}\n const cascadedChildren = before.parentTaskId ? [] : await loadChildSnapshots(em, before.id, scope)\n return { before: { snapshot: before, cascadedChildren } }\n },\n async execute(input, ctx) {\n const { translate } = await resolveTranslations()\n const id = input?.id\n if (!id) {\n throw new CrudHttpError(400, {\n error: translate('staff.time_tracking.tasks.errors.idRequired', 'Task id is required.'),\n })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n\n let children: StaffTimeTask[] = []\n let childIds: string[] = []\n let supportTickets: StaffSupportTicket[] = []\n const supportTicketIds: string[] = []\n\n await withAtomicFlush(\n em,\n [\n async () => {\n // Only a top-level task can carry a cascade; the depth rule guarantees a\n // child has none, so the walk is exactly one level.\n children = task.parentTaskId\n ? []\n : await em.find(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),\n )\n supportTickets = await findWithDecryption(\n em,\n StaffSupportTicket,\n {\n taskId: { $in: [task.id, ...children.map((child) => child.id)] },\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n deletedAt: null,\n },\n undefined,\n { tenantId: task.tenantId, organizationId: task.organizationId },\n )\n },\n () => {\n const plan = planTaskSoftDelete(task, children)\n childIds = plan.childIds\n const now = new Date()\n const byId = new Map(children.map((child) => [child.id, child]))\n task.deletedAt = now\n task.updatedAt = now\n for (const childId of childIds) {\n const child = byId.get(childId)\n if (!child) continue\n child.deletedAt = now\n child.updatedAt = now\n }\n const removedTaskIds = new Set([task.id, ...childIds])\n for (const ticket of supportTickets) {\n if (!removedTaskIds.has(ticket.taskId)) continue\n ticket.deletedAt = now\n ticket.updatedAt = now\n supportTicketIds.push(ticket.id)\n }\n // Time entries are untouched on purpose: a closed report still has to\n // resolve this task's label months after it was removed from the board.\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.delete },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const removed of [task, ...children.filter((child) => childIds.includes(child.id))]) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: removed,\n identifiers: { id: removed.id, organizationId: removed.organizationId, tenantId: removed.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n }\n\n return { taskId: task.id, childIds, supportTicketIds }\n },\n buildLog: async ({ snapshots, result }) => {\n const prepared = snapshots.before as\n | { snapshot?: TaskSnapshot; cascadedChildren?: TaskSnapshot[] }\n | undefined\n const before = prepared?.snapshot\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.delete', 'Delete task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n cascadedChildren: prepared?.cascadedChildren ?? null,\n cascadedSupportTicketIds: result?.supportTicketIds ?? [],\n } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const snapshotScope = staffSnapshotScopeFromSnapshot(before)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ids = [before.id, ...(payload?.cascadedChildren ?? []).map((child) => child.id)]\n const restored: StaffTimeTask[] = []\n const restoredTickets: StaffSupportTicket[] = []\n const ticketIds = payload?.cascadedSupportTicketIds ?? []\n const { translate } = await resolveTranslations()\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const project = await lockLiveProjectForWrite(\n em,\n before.timeProjectId,\n explicitStaffCommandScope(before.tenantId, before.organizationId),\n )\n if (!project) throw projectNotFoundError(translate)\n const tasks = await em.find(StaffTimeTask, {\n id: { $in: ids },\n ...snapshotScopeWhere(snapshotScope),\n })\n restored.push(...tasks)\n if (ticketIds.length === 0) return\n const tickets = await findWithDecryption(\n em,\n StaffSupportTicket,\n {\n id: { $in: ticketIds },\n taskId: { $in: ids },\n ...snapshotScopeWhere(snapshotScope),\n deletedAt: { $ne: null },\n },\n undefined,\n { tenantId: before.tenantId, organizationId: before.organizationId },\n )\n restoredTickets.push(...tickets)\n },\n () => {\n // Undo restores the whole set the cascade removed, not just the parent.\n for (const task of restored) {\n task.deletedAt = null\n task.updatedAt = new Date()\n }\n for (const ticket of restoredTickets) {\n ticket.deletedAt = null\n ticket.updatedAt = new Date()\n }\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommandIds.delete}.undo` },\n )\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const task of restored) {\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'created',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n }\n },\n}\n\n/**\n * US-C2: the board drag, and the drawer's subtask tick, which is the same move\n * expressed without a position.\n *\n * It is a command of its own rather than a flavour of `update` for three reasons:\n * the move is the one write on this entity where two people racing the same record\n * is genuinely likely, so it enforces the `updated_at` version and answers 409 on a\n * stale one; the board needs a lifecycle event carrying enough to relocate the card\n * on another client without a refetch; and the column change is the whole payload,\n * so the audit row reads \"Move task\" instead of a generic update.\n *\n * `closedAt` is NOT re-derived here \u2014 `applyStatusToTask` above is the single place\n * that reads the column's `is_done`, shared verbatim with `update`.\n */\nconst statusChangeInputSchema = staffTimeTaskStatusChangeSchema.extend({\n id: z.string().uuid(),\n})\n\nexport type StaffTimeTaskStatusChangeCommandInput = z.infer<typeof statusChangeInputSchema>\n\nexport type StaffTimeTaskStatusChangeResult = {\n taskId: string\n timeProjectId: string\n previousTaskStatusId: string\n taskStatusId: string\n position: number\n closedAt: string | null\n updatedAt: string | null\n}\n\nconst STATUS_CHANGE_DIFFED_FIELDS = ['taskStatusId', 'position', 'closedAt'] as const\n\nconst statusChangeTaskCommand: CommandHandler<\n StaffTimeTaskStatusChangeCommandInput,\n StaffTimeTaskStatusChangeResult\n> = {\n id: staffTimeTaskCommandIds.statusChange,\n async prepare(rawInput, ctx) {\n const parsed = statusChangeInputSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadTaskSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = statusChangeInputSchema.parse(rawInput)\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n\n const task = await em.findOne(\n StaffTimeTask,\n applyScopeToWhere<StaffTimeTask>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!task) throw notFoundError(translate)\n ensureTenantScope(ctx, task.tenantId)\n ensureOrganizationScope(ctx, task.organizationId)\n\n // Two people dragging the same card is the realistic conflict on this board,\n // so a stale version loses here and the client rolls the card back to the\n // column it came from.\n await enforceCommandOptimisticLockWithGuards(ctx.container, {\n resourceKind: STAFF_TIME_TASK_RESOURCE_KIND,\n resourceId: task.id,\n current: task.updatedAt,\n request: ctx.request,\n })\n\n const previousTaskStatusId = task.taskStatusId\n let targetStatus: StaffTimeTaskStatus | null = null\n let targetPosition = task.position\n\n await withAtomicFlush(\n em,\n [\n async () => {\n // Refuses a column belonging to another project: `resolveTaskStatus`\n // looks the status up inside this task's project, so a foreign id is a\n // 422 rather than a silent cross-board move.\n targetStatus = await resolveTaskStatus(em, task.timeProjectId, parsed.taskStatusId, scope, translate)\n targetPosition =\n parsed.position ?? (await nextTaskPosition(em, task.timeProjectId, targetStatus.id, scope))\n },\n () => {\n if (!targetStatus) throw notFoundError(translate)\n const now = new Date()\n task.position = targetPosition\n applyStatusToTask(task, targetStatus, now)\n task.updatedAt = now\n },\n ],\n { transaction: true, label: staffTimeTaskCommandIds.statusChange },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n const result: StaffTimeTaskStatusChangeResult = {\n taskId: task.id,\n timeProjectId: task.timeProjectId,\n previousTaskStatusId,\n taskStatusId: task.taskStatusId,\n position: task.position,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,\n }\n\n // `clientBroadcast: true` (events.ts) puts this on the DOM Event Bridge, so the\n // payload carries everything a second board needs to move the card itself \u2014\n // origin column, destination column and the new slot \u2014 without a refetch. The\n // scope fields are what the SSE stream filters its audience on. A failed\n // broadcast must never fail a committed drag.\n void emitStaffEvent('staff.timesheets.time_task.status_changed', {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n ...result,\n }).catch((err) => {\n logger.error('staff.timesheets emit time_task.status_changed failed', { err })\n })\n\n return result\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: TaskSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadTaskSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.tasks.statusChange', 'Move task'),\n resourceKind: 'staff.timesheets.task',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n // Flushes the cached tasks list the board refetches right after the move,\n // and again after an undo \u2014 the audit resourceKind above names a different\n // tag. See TASK_LIST_CACHE_RESOURCE.\n context: { cacheAliases: [TASK_LIST_CACHE_RESOURCE] },\n snapshotBefore: before,\n snapshotAfter: after,\n changes: buildChanges(\n before as unknown as Record<string, unknown>,\n after as unknown as Record<string, unknown>,\n [...STATUS_CHANGE_DIFFED_FIELDS],\n ),\n payload: {\n undo: { before, after } satisfies TaskUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<TaskUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const task = await em.findOne(\n StaffTimeTask,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!task) return\n\n const previousTaskStatusId = task.taskStatusId\n task.taskStatusId = before.taskStatusId\n task.position = before.position\n task.closedAt = before.closedAt ? new Date(before.closedAt) : null\n task.updatedAt = new Date()\n await em.flush()\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: task,\n identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },\n events: taskCrudEvents,\n indexer: taskCrudIndexer,\n })\n\n void emitStaffEvent('staff.timesheets.time_task.status_changed', {\n id: task.id,\n tenantId: task.tenantId,\n organizationId: task.organizationId,\n taskId: task.id,\n timeProjectId: task.timeProjectId,\n previousTaskStatusId,\n taskStatusId: task.taskStatusId,\n position: task.position,\n closedAt: task.closedAt ? task.closedAt.toISOString() : null,\n updatedAt: task.updatedAt ? task.updatedAt.toISOString() : null,\n }).catch((err) => {\n logger.error('staff.timesheets emit time_task.status_changed failed', { err })\n })\n },\n}\n\nregisterCommand(createTaskCommand)\nregisterCommand(updateTaskCommand)\nregisterCommand(deleteTaskCommand)\nregisterCommand(statusChangeTaskCommand)\n"],
5
+ "mappings": "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",
6
+ "names": ["message", "actorUserId"]
7
7
  }
@@ -1,11 +1,13 @@
1
1
  import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
2
2
  import { CustomerEntity } from "@open-mercato/core/modules/customers/data/entities";
3
3
  import {
4
+ StaffSupportTicket,
4
5
  StaffTeamMember,
5
6
  StaffTimeProject,
6
7
  StaffTimeReport,
7
8
  StaffTimeReportEntry,
8
9
  StaffTimeReportEvent,
10
+ StaffTimeProjectMember,
9
11
  StaffTimeTask,
10
12
  StaffTimeTaskStatus
11
13
  } from "./entities.js";
@@ -16,7 +18,14 @@ import {
16
18
  listProjectMembersPreview
17
19
  } from "../lib/timesheets-projects/listProjectMembersPreview.js";
18
20
  import { computeTaskRollups, EMPTY_TASK_ROLLUP } from "../lib/timesheets-tasks/computeTaskRollups.js";
21
+ import { derivePortalStatus } from "../lib/support/portalStatus.js";
22
+ import {
23
+ isActiveAssignment,
24
+ normalizeAssignmentGraceDays,
25
+ resolveAssignmentGraceDaysFromContainer
26
+ } from "../lib/time-tracking/projectAssignment.js";
19
27
  const MANAGE_FEATURE = "staff.timesheets.projects.manage";
28
+ const TASKS_VIEW_FEATURE = "staff.timesheets.tasks.view";
20
29
  const RATES_FEATURE = "staff.timesheets.rates.view";
21
30
  function withQueryEngineSurface(enricher) {
22
31
  return {
@@ -399,6 +408,136 @@ const taskContextEnricher = {
399
408
  });
400
409
  }
401
410
  };
411
+ const EMPTY_SUPPORT_TICKET = { _staffSupport: { ticket: null } };
412
+ const supportTicketEnricher = {
413
+ id: "staff.timesheets-tasks-support-ticket",
414
+ targetEntity: "staff:staff_time_task",
415
+ priority: 4,
416
+ timeout: 3e3,
417
+ critical: false,
418
+ fallback: { ...EMPTY_SUPPORT_TICKET },
419
+ cacheableOnListHit: false,
420
+ async enrichOne(record, context) {
421
+ const enriched = await this.enrichMany([record], context);
422
+ return enriched[0];
423
+ },
424
+ async enrichMany(records, context) {
425
+ if (records.length === 0) return records;
426
+ const ctx = context;
427
+ if (!ctx.tenantId || !ctx.organizationId) {
428
+ return records.map((record) => ({ ...record, ...EMPTY_SUPPORT_TICKET }));
429
+ }
430
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
431
+ const portalRecordIds = records.filter((record) => record.source === "portal" || record.source === void 0).map((record) => record.id).filter((id) => typeof id === "string");
432
+ if (portalRecordIds.length === 0) {
433
+ return records.map((record) => ({ ...record, ...EMPTY_SUPPORT_TICKET }));
434
+ }
435
+ const tickets = await findWithDecryption(
436
+ ctx.em.fork(),
437
+ StaffSupportTicket,
438
+ { taskId: { $in: portalRecordIds }, ...scope, deletedAt: null },
439
+ void 0,
440
+ scope
441
+ );
442
+ if (tickets.length === 0) {
443
+ return records.map((record) => ({ ...record, ...EMPTY_SUPPORT_TICKET }));
444
+ }
445
+ const ticketByTask = new Map(tickets.map((ticket) => [ticket.taskId, ticket]));
446
+ const tasks = await ctx.em.fork().find(
447
+ StaffTimeTask,
448
+ { id: { $in: tickets.map((ticket) => ticket.taskId) }, ...scope },
449
+ { fields: ["id", "taskStatusId"] }
450
+ );
451
+ const statusIds = [...new Set(tasks.map((task) => task.taskStatusId))];
452
+ const statuses = statusIds.length ? await ctx.em.fork().find(StaffTimeTaskStatus, { id: { $in: statusIds }, ...scope }, { fields: ["id", "isDefault", "isDone"] }) : [];
453
+ const statusById = new Map(statuses.map((status) => [status.id, status]));
454
+ const statusByTask = new Map(tasks.map((task) => [task.id, statusById.get(task.taskStatusId) ?? null]));
455
+ return records.map((record) => {
456
+ const ticket = typeof record.id === "string" ? ticketByTask.get(record.id) : void 0;
457
+ if (!ticket) return { ...record, ...EMPTY_SUPPORT_TICKET };
458
+ const request = ticket.customerRequestSnapshot;
459
+ return {
460
+ ...record,
461
+ _staffSupport: {
462
+ ticket: {
463
+ id: ticket.id,
464
+ supportReference: ticket.supportReference,
465
+ category: ticket.categorySnapshot,
466
+ routedVia: ticket.routedVia,
467
+ portalStatus: derivePortalStatus(statusByTask.get(ticket.taskId) ?? null),
468
+ customerSnapshot: ticket.customerSnapshot ?? null,
469
+ reportedByCustomerUserId: ticket.reportedByCustomerUserId ?? null,
470
+ unnotifiedAt: ticket.unnotifiedAt ? ticket.unnotifiedAt.toISOString() : null,
471
+ customerRequest: request && typeof request === "object" && typeof request.subject === "string" ? { subject: request.subject, body: typeof request.body === "string" ? request.body : "" } : null
472
+ }
473
+ }
474
+ };
475
+ });
476
+ }
477
+ };
478
+ const NO_TASK_ASSIGNMENT = { _staffAssignment: { canClaim: false, canRelease: false } };
479
+ const taskAssignmentEnricher = {
480
+ id: "staff.timesheets-tasks-assignment",
481
+ targetEntity: "staff:staff_time_task",
482
+ features: [TASKS_VIEW_FEATURE],
483
+ priority: 3,
484
+ timeout: 3e3,
485
+ critical: false,
486
+ fallback: { ...NO_TASK_ASSIGNMENT },
487
+ cacheableOnListHit: false,
488
+ async enrichOne(record, context) {
489
+ const enriched = await this.enrichMany([record], context);
490
+ return enriched[0];
491
+ },
492
+ async enrichMany(records, context) {
493
+ if (records.length === 0) return records;
494
+ const ctx = context;
495
+ if (!ctx.tenantId || !ctx.organizationId || !ctx.userId) {
496
+ return records.map((record) => ({ ...record, ...NO_TASK_ASSIGNMENT }));
497
+ }
498
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
499
+ const callerStaffMemberId = await resolveCallerStaffMemberId(ctx);
500
+ if (!callerStaffMemberId) return records.map((record) => ({ ...record, ...NO_TASK_ASSIGNMENT }));
501
+ const taskIds = records.map((record) => record.id).filter((id) => typeof id === "string");
502
+ const tasks = await ctx.em.fork().find(
503
+ StaffTimeTask,
504
+ { id: { $in: taskIds }, ...scope, deletedAt: null },
505
+ { fields: ["id", "timeProjectId", "taskStatusId", "assigneeStaffMemberId"] }
506
+ );
507
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
508
+ const projectIds = [...new Set(tasks.map((task) => task.timeProjectId).filter(Boolean))];
509
+ const statusIds = [...new Set(tasks.map((task) => task.taskStatusId).filter(Boolean))];
510
+ const [statuses, memberships, graceDays] = await Promise.all([
511
+ statusIds.length ? ctx.em.fork().find(StaffTimeTaskStatus, { id: { $in: statusIds }, ...scope }, { fields: ["id", "isDone"] }) : Promise.resolve([]),
512
+ projectIds.length ? ctx.em.fork().find(StaffTimeProjectMember, {
513
+ timeProjectId: { $in: projectIds },
514
+ staffMemberId: callerStaffMemberId,
515
+ isAssigner: true,
516
+ status: "active",
517
+ deletedAt: null,
518
+ ...scope
519
+ }) : Promise.resolve([]),
520
+ resolveAssignmentGraceDaysFromContainer(ctx.container, ctx.tenantId)
521
+ ]);
522
+ const doneStatusIds = new Set(statuses.filter((status) => status.isDone).map((status) => status.id));
523
+ const now = /* @__PURE__ */ new Date();
524
+ const assignerProjectIds = new Set(
525
+ memberships.filter((membership) => isActiveAssignment(membership, now, normalizeAssignmentGraceDays(graceDays))).map((membership) => membership.timeProjectId)
526
+ );
527
+ return records.map((record) => {
528
+ const task = taskById.get(record.id);
529
+ if (!task) return { ...record, ...NO_TASK_ASSIGNMENT };
530
+ const assignee = task.assigneeStaffMemberId ?? null;
531
+ return {
532
+ ...record,
533
+ _staffAssignment: {
534
+ canClaim: !assignee && assignerProjectIds.has(task.timeProjectId) && !doneStatusIds.has(task.taskStatusId),
535
+ canRelease: assignee === callerStaffMemberId
536
+ }
537
+ };
538
+ });
539
+ }
540
+ };
402
541
  const EMPTY_REPORT_ENRICHMENT = {
403
542
  _staff: {
404
543
  isClosed: false,
@@ -503,6 +642,8 @@ const apiEnrichers = [
503
642
  taskRollupEnricher,
504
643
  taskTagEnricher,
505
644
  taskContextEnricher,
645
+ supportTicketEnricher,
646
+ taskAssignmentEnricher,
506
647
  timeEntryEnricher,
507
648
  reportEnricher
508
649
  ];