@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
@@ -39,13 +39,13 @@ import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@ope
39
39
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
40
40
  import { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'
41
41
  import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
42
- import { StaffTimeTask, StaffTimeTaskComment } from '../data/entities'
42
+ import { StaffTimeTask, StaffTimeTaskComment, type StaffTimeTaskCommentVisibility } from '../data/entities'
43
43
  import { staffTimeTaskCommentCrudEvents } from '../lib/crud'
44
44
  import {
45
45
  staffTimeTaskCommentCreateSchema,
46
- staffTimeTaskCommentUpdateSchema,
46
+ staffTimeTaskCommentUpdateRequestSchema,
47
47
  type StaffTimeTaskCommentCreateInput,
48
- type StaffTimeTaskCommentUpdateInput,
48
+ type StaffTimeTaskCommentUpdateRequestInput,
49
49
  } from '../data/validators'
50
50
  import {
51
51
  applyScopeToWhere,
@@ -96,6 +96,43 @@ const commentCrudIndexer: CrudIndexerConfig<StaffTimeTaskComment> = {
96
96
  */
97
97
  const commentCrudEvents: CrudEventsConfig<StaffTimeTaskComment> = staffTimeTaskCommentCrudEvents
98
98
 
99
+ /**
100
+ * The comment CRUD events carry `portalVisible`: true when the comment is visible to
101
+ * the customer before OR after the change, so publishing, retracting and deleting a
102
+ * reply all refresh the portal. Internal notes never set it.
103
+ */
104
+ function commentEvents(portalVisible: boolean): CrudEventsConfig<StaffTimeTaskComment> {
105
+ const base = staffTimeTaskCommentCrudEvents
106
+ return {
107
+ ...base,
108
+ buildPayload: (context) => ({
109
+ ...((base.buildPayload ? base.buildPayload(context) : {}) as Record<string, unknown>),
110
+ portalVisible,
111
+ }),
112
+ }
113
+ }
114
+
115
+ export const TASK_COMMENT_CUSTOMER_IMMUTABLE_CODE = 'task_comment_customer_immutable'
116
+ export const TASK_COMMENT_VISIBILITY_INVALID_CODE = 'task_comment_visibility_invalid'
117
+
118
+ function customerCommentImmutableError(translate: Translate): CrudHttpError {
119
+ return new CrudHttpError(403, {
120
+ code: TASK_COMMENT_CUSTOMER_IMMUTABLE_CODE,
121
+ error: translate(
122
+ 'staff.time_tracking.taskComments.errors.customerImmutable',
123
+ 'Messages written by the customer cannot be edited or deleted.',
124
+ ),
125
+ })
126
+ }
127
+
128
+ function visibilityInvalidError(translate: Translate): CrudHttpError {
129
+ const message = translate(
130
+ 'staff.time_tracking.taskComments.errors.visibilityPortalOnly',
131
+ 'Only customer support tickets can carry replies visible to the customer.',
132
+ )
133
+ return new CrudHttpError(422, { code: TASK_COMMENT_VISIBILITY_INVALID_CODE, error: message, fieldErrors: { visibility: message } })
134
+ }
135
+
99
136
  type Translate = (key: string, fallback: string) => string
100
137
 
101
138
  type CommentSnapshot = {
@@ -105,6 +142,8 @@ type CommentSnapshot = {
105
142
  taskId: string
106
143
  body: string
107
144
  authorUserId: string | null
145
+ visibility: StaffTimeTaskCommentVisibility
146
+ authorKind: string
108
147
  createdAt: string | null
109
148
  deletedAt: string | null
110
149
  }
@@ -129,6 +168,8 @@ function toCommentSnapshot(comment: StaffTimeTaskComment): CommentSnapshot {
129
168
  taskId: comment.taskId,
130
169
  body: comment.body,
131
170
  authorUserId: comment.authorUserId ?? null,
171
+ visibility: comment.visibility ?? 'internal',
172
+ authorKind: comment.authorKind ?? 'staff',
132
173
  createdAt: comment.createdAt ? comment.createdAt.toISOString() : null,
133
174
  deletedAt: comment.deletedAt ? comment.deletedAt.toISOString() : null,
134
175
  }
@@ -211,6 +252,7 @@ async function requireEditableComment(
211
252
  ctx: CommandRuntimeContext,
212
253
  translate: Translate,
213
254
  ): Promise<void> {
255
+ if (comment.authorKind === 'customer') throw customerCommentImmutableError(translate)
214
256
  const actorId = stampAuthorUserId(ctx)
215
257
  if (actorId && comment.authorUserId === actorId) return
216
258
  if (await callerCanManageAll(ctx)) return
@@ -257,7 +299,10 @@ const createTaskCommentCommand: CommandHandler<
257
299
  await withAtomicFlush(
258
300
  em,
259
301
  [
260
- () => requireTaskInScope(em, parsed.taskId, scope, translate),
302
+ async () => {
303
+ const task = await requireTaskInScope(em, parsed.taskId, scope, translate)
304
+ if (parsed.visibility === 'customer' && task.source !== 'portal') throw visibilityInvalidError(translate)
305
+ },
261
306
  () => {
262
307
  created = em.create(StaffTimeTaskComment, {
263
308
  tenantId: parsed.tenantId,
@@ -265,6 +310,8 @@ const createTaskCommentCommand: CommandHandler<
265
310
  taskId: parsed.taskId,
266
311
  body: parsed.body,
267
312
  authorUserId,
313
+ visibility: parsed.visibility ?? 'internal',
314
+ authorKind: 'staff',
268
315
  createdAt: new Date(),
269
316
  updatedAt: new Date(),
270
317
  deletedAt: null,
@@ -283,7 +330,7 @@ const createTaskCommentCommand: CommandHandler<
283
330
  action: 'created',
284
331
  entity: record,
285
332
  identifiers: { id: record.id, organizationId: record.organizationId, tenantId: record.tenantId },
286
- events: commentCrudEvents,
333
+ events: commentEvents(record.visibility === 'customer'),
287
334
  indexer: commentCrudIndexer,
288
335
  })
289
336
 
@@ -340,19 +387,19 @@ const createTaskCommentCommand: CommandHandler<
340
387
  action: 'deleted',
341
388
  entity: comment,
342
389
  identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },
343
- events: commentCrudEvents,
390
+ events: commentEvents(comment.visibility === 'customer'),
344
391
  indexer: commentCrudIndexer,
345
392
  })
346
393
  },
347
394
  }
348
395
 
349
396
  const updateTaskCommentCommand: CommandHandler<
350
- StaffTimeTaskCommentUpdateInput,
397
+ StaffTimeTaskCommentUpdateRequestInput,
351
398
  { commentId: string; taskId: string }
352
399
  > = {
353
400
  id: staffTimeTaskCommentCommandIds.update,
354
401
  async prepare(rawInput, ctx) {
355
- const parsed = staffTimeTaskCommentUpdateSchema.safeParse(rawInput)
402
+ const parsed = staffTimeTaskCommentUpdateRequestSchema.safeParse(rawInput)
356
403
  if (!parsed.success) return {}
357
404
  const em = ctx.container.resolve('em') as EntityManager
358
405
  const before = await loadCommentSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))
@@ -360,7 +407,7 @@ const updateTaskCommentCommand: CommandHandler<
360
407
  return { before: { snapshot: before } }
361
408
  },
362
409
  async execute(rawInput, ctx) {
363
- const parsed = staffTimeTaskCommentUpdateSchema.parse(rawInput)
410
+ const parsed = staffTimeTaskCommentUpdateRequestSchema.parse(rawInput)
364
411
  const { translate } = await resolveTranslations()
365
412
  const em = (ctx.container.resolve('em') as EntityManager).fork()
366
413
  const scope = commandActorScope(ctx)
@@ -374,12 +421,18 @@ const updateTaskCommentCommand: CommandHandler<
374
421
  ensureOrganizationScope(ctx, comment.organizationId)
375
422
  assertCommentVersion(ctx, comment)
376
423
  await requireEditableComment(comment, ctx, translate)
424
+ const visibilityBefore = comment.visibility ?? 'internal'
425
+ if (parsed.visibility === 'customer' && visibilityBefore !== 'customer') {
426
+ const task = await requireTaskInScope(em, comment.taskId, scope, translate)
427
+ if (task.source !== 'portal') throw visibilityInvalidError(translate)
428
+ }
377
429
 
378
430
  await withAtomicFlush(
379
431
  em,
380
432
  [
381
433
  () => {
382
- comment.body = parsed.body
434
+ if (parsed.body !== undefined) comment.body = parsed.body
435
+ if (parsed.visibility !== undefined) comment.visibility = parsed.visibility
383
436
  comment.updatedAt = new Date()
384
437
  },
385
438
  ],
@@ -391,7 +444,7 @@ const updateTaskCommentCommand: CommandHandler<
391
444
  action: 'updated',
392
445
  entity: comment,
393
446
  identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },
394
- events: commentCrudEvents,
447
+ events: commentEvents(visibilityBefore === 'customer' || comment.visibility === 'customer'),
395
448
  indexer: commentCrudIndexer,
396
449
  })
397
450
 
@@ -417,7 +470,7 @@ const updateTaskCommentCommand: CommandHandler<
417
470
  changes: buildChanges(
418
471
  before as unknown as Record<string, unknown>,
419
472
  after as unknown as Record<string, unknown>,
420
- ['body'],
473
+ ['body', 'visibility'],
421
474
  ),
422
475
  payload: {
423
476
  undo: { before, after } satisfies CommentUndoPayload,
@@ -440,6 +493,7 @@ const updateTaskCommentCommand: CommandHandler<
440
493
  [
441
494
  () => {
442
495
  comment.body = before.body
496
+ comment.visibility = before.visibility ?? 'internal'
443
497
  comment.updatedAt = new Date()
444
498
  },
445
499
  ],
@@ -451,7 +505,7 @@ const updateTaskCommentCommand: CommandHandler<
451
505
  action: 'updated',
452
506
  entity: comment,
453
507
  identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },
454
- events: commentCrudEvents,
508
+ events: commentEvents(true),
455
509
  indexer: commentCrudIndexer,
456
510
  })
457
511
  },
@@ -505,7 +559,7 @@ const deleteTaskCommentCommand: CommandHandler<{ id?: string }, { commentId: str
505
559
  action: 'deleted',
506
560
  entity: comment,
507
561
  identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },
508
- events: commentCrudEvents,
562
+ events: commentEvents(comment.visibility === 'customer'),
509
563
  indexer: commentCrudIndexer,
510
564
  })
511
565
 
@@ -551,6 +605,7 @@ const deleteTaskCommentCommand: CommandHandler<{ id?: string }, { commentId: str
551
605
  taskId: before.taskId,
552
606
  body: before.body,
553
607
  authorUserId: before.authorUserId,
608
+ visibility: before.visibility ?? 'internal',
554
609
  createdAt: before.createdAt ? new Date(before.createdAt) : new Date(),
555
610
  updatedAt: new Date(),
556
611
  deletedAt: null,
@@ -572,7 +627,7 @@ const deleteTaskCommentCommand: CommandHandler<{ id?: string }, { commentId: str
572
627
  action: 'created',
573
628
  entity: comment,
574
629
  identifiers: { id: before.id, organizationId: before.organizationId, tenantId: before.tenantId },
575
- events: commentCrudEvents,
630
+ events: commentEvents(before.visibility === 'customer'),
576
631
  indexer: commentCrudIndexer,
577
632
  })
578
633
  },
@@ -34,13 +34,15 @@ import { registerCommand } from '@open-mercato/shared/lib/commands'
34
34
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
35
35
  import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
36
36
  import { createLogger } from '@open-mercato/shared/lib/logger'
37
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
37
38
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
38
39
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
39
40
  import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
41
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
40
42
  import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
41
43
  import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
42
44
  import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
43
- import { StaffTeamMember, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from '../data/entities'
45
+ import { StaffSupportTicket, StaffTeamMember, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from '../data/entities'
44
46
  import {
45
47
  staffTimeTaskCreateSchema,
46
48
  staffTimeTaskStatusChangeSchema,
@@ -67,7 +69,9 @@ import {
67
69
  commandInputScope,
68
70
  ensureOrganizationScope,
69
71
  ensureTenantScope,
72
+ explicitStaffCommandScope,
70
73
  extractUndoPayload,
74
+ lockLiveProjectForWrite,
71
75
  scopeForDecryption,
72
76
  scopedStaffSnapshotWhere,
73
77
  staffSnapshotScopeFromContext,
@@ -102,7 +106,7 @@ export const TASK_REFERENCE_CONFLICT_CODE = 'task_reference_conflict'
102
106
 
103
107
  const logger = createLogger('staff').child({ component: 'commands/timesheets-tasks' })
104
108
 
105
- const taskCrudIndexer: CrudIndexerConfig<StaffTimeTask> = {
109
+ export const taskCrudIndexer: CrudIndexerConfig<StaffTimeTask> = {
106
110
  entityType: 'staff:staff_time_task',
107
111
  }
108
112
 
@@ -111,7 +115,7 @@ const taskCrudIndexer: CrudIndexerConfig<StaffTimeTask> = {
111
115
  * lifecycle `…status_changed` event belongs to the dedicated status endpoint that
112
116
  * owns the drag-and-drop move, not to these generic writes.
113
117
  */
114
- const taskCrudEvents: CrudEventsConfig<StaffTimeTask> = {
118
+ export const taskCrudEvents: CrudEventsConfig<StaffTimeTask> = {
115
119
  module: 'staff',
116
120
  entity: 'timesheets.time_task',
117
121
  persistent: true,
@@ -136,7 +140,7 @@ const taskCrudEvents: CrudEventsConfig<StaffTimeTask> = {
136
140
  * "changed by someone else" until the entry expired. `ENABLE_CRUD_API_CACHE` off
137
141
  * hides it; the ephemeral QA and integration environments run with it on.
138
142
  */
139
- const TASK_LIST_CACHE_RESOURCE = `${taskCrudEvents.module}.${taskCrudEvents.entity}`
143
+ export const TASK_LIST_CACHE_RESOURCE = `${taskCrudEvents.module}.${taskCrudEvents.entity}`
140
144
 
141
145
  type Translate = (key: string, fallback: string) => string
142
146
 
@@ -163,6 +167,8 @@ type TaskUndoPayload = {
163
167
  after?: TaskSnapshot | null
164
168
  /** Children the delete took with it, so undo can put the whole set back. */
165
169
  cascadedChildren?: TaskSnapshot[] | null
170
+ /** Support tickets this delete soft-deleted; undo restores exactly these and no others. */
171
+ cascadedSupportTicketIds?: string[] | null
166
172
  }
167
173
 
168
174
  const DIFFED_FIELDS = [
@@ -323,10 +329,7 @@ async function requireProject(
323
329
  scope: StaffCommandScope,
324
330
  translate: Translate,
325
331
  ): Promise<StaffTimeProject> {
326
- const project = await em.findOne(
327
- StaffTimeProject,
328
- applyScopeToWhere<StaffTimeProject>({ id: timeProjectId, deletedAt: null }, scope),
329
- )
332
+ const project = await lockLiveProjectForWrite(em, timeProjectId, scope)
330
333
  if (!project) throw projectNotFoundError(translate)
331
334
  return project
332
335
  }
@@ -336,7 +339,7 @@ async function requireProject(
336
339
  * otherwise the project's `is_default` entry point. The board invariants guarantee one
337
340
  * exists; the positional fallback only covers a board repaired by hand.
338
341
  */
339
- async function resolveTaskStatus(
342
+ export async function resolveTaskStatus(
340
343
  em: EntityManager,
341
344
  timeProjectId: string,
342
345
  requestedStatusId: string | null | undefined,
@@ -408,7 +411,7 @@ async function requireStaffMember(
408
411
  * Order within a column, on the same gap grid the columns themselves use: a drop
409
412
  * between two cards writes one row instead of renumbering the stack.
410
413
  */
411
- async function nextTaskPosition(
414
+ export async function nextTaskPosition(
412
415
  em: EntityManager,
413
416
  timeProjectId: string,
414
417
  taskStatusId: string,
@@ -427,7 +430,7 @@ async function nextTaskPosition(
427
430
  * the next task reuse that reference — and a closed report quoting the old one would
428
431
  * suddenly read as the new one.
429
432
  */
430
- async function readHighestSequenceNumber(
433
+ export async function readHighestSequenceNumber(
431
434
  em: EntityManager,
432
435
  timeProjectId: string,
433
436
  scope: StaffCommandScope,
@@ -457,7 +460,7 @@ function assertNoProjectMove(rawInput: unknown, task: StaffTimeTask, translate:
457
460
  })
458
461
  }
459
462
 
460
- function applyStatusToTask(task: StaffTimeTask, status: StaffTimeTaskStatus, now: Date): void {
463
+ export function applyStatusToTask(task: StaffTimeTask, status: StaffTimeTaskStatus, now: Date): void {
461
464
  task.taskStatusId = status.id
462
465
  // Done-ness comes from the column, never from a boolean on the task: landing on a
463
466
  // terminal column closes it, leaving one re-opens it.
@@ -622,6 +625,33 @@ const createTaskCommand: CommandHandler<StaffTimeTaskCreateInput, { taskId: stri
622
625
  },
623
626
  }
624
627
 
628
+ export type TaskAssigneeChangeReason = 'default' | 'claim' | 'release' | 'manual'
629
+
630
+ export function emitTaskAssigneeChanged(
631
+ task: Pick<StaffTimeTask, 'id' | 'tenantId' | 'organizationId' | 'timeProjectId' | 'assigneeStaffMemberId'>,
632
+ previousAssigneeStaffMemberId: string | null,
633
+ changeKind: TaskAssigneeChangeReason,
634
+ actorUserId: string | null = null,
635
+ ): void {
636
+ void emitStaffEvent(
637
+ 'staff.timesheets.time_task.assignee_changed',
638
+ {
639
+ id: task.id,
640
+ tenantId: task.tenantId,
641
+ organizationId: task.organizationId,
642
+ timeProjectId: task.timeProjectId,
643
+ previousAssigneeStaffMemberId,
644
+ assigneeStaffMemberId: task.assigneeStaffMemberId ?? null,
645
+ changeKind,
646
+ actorUserId,
647
+ },
648
+ { persistent: true },
649
+ ).catch((err) => {
650
+ logger.error('staff.timesheets.time_task.assignee_changed emit failed', { err, taskId: task.id })
651
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.task_assignee_changed_emit_failed' })
652
+ })
653
+ }
654
+
625
655
  const updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: string }> = {
626
656
  id: staffTimeTaskCommandIds.update,
627
657
  async prepare(rawInput, ctx) {
@@ -650,6 +680,7 @@ const updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: stri
650
680
  let targetStatus: StaffTimeTaskStatus | null = null
651
681
  let targetParentTaskId: string | null | undefined
652
682
  let targetAssigneeStaffMemberId: string | null | undefined
683
+ const previousAssigneeStaffMemberId = task.assigneeStaffMemberId ?? null
653
684
 
654
685
  await withAtomicFlush(
655
686
  em,
@@ -707,6 +738,11 @@ const updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: stri
707
738
  indexer: taskCrudIndexer,
708
739
  })
709
740
 
741
+ const nextAssigneeStaffMemberId = task.assigneeStaffMemberId ?? null
742
+ if (targetAssigneeStaffMemberId !== undefined && nextAssigneeStaffMemberId !== previousAssigneeStaffMemberId) {
743
+ emitTaskAssigneeChanged(task, previousAssigneeStaffMemberId, 'manual', ctx.auth?.sub ?? null)
744
+ }
745
+
710
746
  return { taskId: task.id }
711
747
  },
712
748
  buildLog: async ({ snapshots, ctx }) => {
@@ -766,7 +802,13 @@ const updateTaskCommand: CommandHandler<StaffTimeTaskUpdateInput, { taskId: stri
766
802
  },
767
803
  }
768
804
 
769
- const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; childIds: string[] }> = {
805
+ export type StaffTimeTaskDeleteResult = {
806
+ taskId: string
807
+ childIds: string[]
808
+ supportTicketIds: string[]
809
+ }
810
+
811
+ const deleteTaskCommand: CommandHandler<{ id?: string }, StaffTimeTaskDeleteResult> = {
770
812
  id: staffTimeTaskCommandIds.delete,
771
813
  async prepare(input, ctx) {
772
814
  const id = input?.id
@@ -798,6 +840,8 @@ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; child
798
840
 
799
841
  let children: StaffTimeTask[] = []
800
842
  let childIds: string[] = []
843
+ let supportTickets: StaffSupportTicket[] = []
844
+ const supportTicketIds: string[] = []
801
845
 
802
846
  await withAtomicFlush(
803
847
  em,
@@ -811,6 +855,18 @@ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; child
811
855
  StaffTimeTask,
812
856
  applyScopeToWhere<StaffTimeTask>({ parentTaskId: task.id, deletedAt: null }, scope),
813
857
  )
858
+ supportTickets = await findWithDecryption(
859
+ em,
860
+ StaffSupportTicket,
861
+ {
862
+ taskId: { $in: [task.id, ...children.map((child) => child.id)] },
863
+ tenantId: task.tenantId,
864
+ organizationId: task.organizationId,
865
+ deletedAt: null,
866
+ },
867
+ undefined,
868
+ { tenantId: task.tenantId, organizationId: task.organizationId },
869
+ )
814
870
  },
815
871
  () => {
816
872
  const plan = planTaskSoftDelete(task, children)
@@ -825,6 +881,13 @@ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; child
825
881
  child.deletedAt = now
826
882
  child.updatedAt = now
827
883
  }
884
+ const removedTaskIds = new Set([task.id, ...childIds])
885
+ for (const ticket of supportTickets) {
886
+ if (!removedTaskIds.has(ticket.taskId)) continue
887
+ ticket.deletedAt = now
888
+ ticket.updatedAt = now
889
+ supportTicketIds.push(ticket.id)
890
+ }
828
891
  // Time entries are untouched on purpose: a closed report still has to
829
892
  // resolve this task's label months after it was removed from the board.
830
893
  },
@@ -844,9 +907,9 @@ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; child
844
907
  })
845
908
  }
846
909
 
847
- return { taskId: task.id, childIds }
910
+ return { taskId: task.id, childIds, supportTicketIds }
848
911
  },
849
- buildLog: async ({ snapshots }) => {
912
+ buildLog: async ({ snapshots, result }) => {
850
913
  const prepared = snapshots.before as
851
914
  | { snapshot?: TaskSnapshot; cascadedChildren?: TaskSnapshot[] }
852
915
  | undefined
@@ -864,6 +927,7 @@ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; child
864
927
  undo: {
865
928
  before,
866
929
  cascadedChildren: prepared?.cascadedChildren ?? null,
930
+ cascadedSupportTicketIds: result?.supportTicketIds ?? [],
867
931
  } satisfies TaskUndoPayload,
868
932
  },
869
933
  }
@@ -876,16 +940,39 @@ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; child
876
940
  const em = (ctx.container.resolve('em') as EntityManager).fork()
877
941
  const ids = [before.id, ...(payload?.cascadedChildren ?? []).map((child) => child.id)]
878
942
  const restored: StaffTimeTask[] = []
943
+ const restoredTickets: StaffSupportTicket[] = []
944
+ const ticketIds = payload?.cascadedSupportTicketIds ?? []
945
+ const { translate } = await resolveTranslations()
879
946
 
880
947
  await withAtomicFlush(
881
948
  em,
882
949
  [
883
950
  async () => {
951
+ const project = await lockLiveProjectForWrite(
952
+ em,
953
+ before.timeProjectId,
954
+ explicitStaffCommandScope(before.tenantId, before.organizationId),
955
+ )
956
+ if (!project) throw projectNotFoundError(translate)
884
957
  const tasks = await em.find(StaffTimeTask, {
885
958
  id: { $in: ids },
886
959
  ...snapshotScopeWhere(snapshotScope),
887
960
  })
888
961
  restored.push(...tasks)
962
+ if (ticketIds.length === 0) return
963
+ const tickets = await findWithDecryption(
964
+ em,
965
+ StaffSupportTicket,
966
+ {
967
+ id: { $in: ticketIds },
968
+ taskId: { $in: ids },
969
+ ...snapshotScopeWhere(snapshotScope),
970
+ deletedAt: { $ne: null },
971
+ },
972
+ undefined,
973
+ { tenantId: before.tenantId, organizationId: before.organizationId },
974
+ )
975
+ restoredTickets.push(...tickets)
889
976
  },
890
977
  () => {
891
978
  // Undo restores the whole set the cascade removed, not just the parent.
@@ -893,6 +980,10 @@ const deleteTaskCommand: CommandHandler<{ id?: string }, { taskId: string; child
893
980
  task.deletedAt = null
894
981
  task.updatedAt = new Date()
895
982
  }
983
+ for (const ticket of restoredTickets) {
984
+ ticket.deletedAt = null
985
+ ticket.updatedAt = new Date()
986
+ }
896
987
  },
897
988
  ],
898
989
  { transaction: true, label: `${staffTimeTaskCommandIds.delete}.undo` },