@open-mercato/core 0.10.1-develop.7716.1.cefe7975df → 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-task-comments.ts"],
4
- "sourcesContent": ["/**\n * Task comment commands (T3.4b) \u2014 the \"Komentarze (2)\" thread of screen 7.\n *\n * US-C4 asks for context that lives with the work rather than in chat, so a\n * comment is only useful if the two things rendered next to its body are true:\n * *who* wrote it and *when*. Three decisions follow from that:\n *\n * 1. **`author_user_id` is stamped from the authenticated caller and is never\n * read from input.** `staffTimeTaskCommentCreateSchema` still declares the\n * field (it is the shared scoped-create shape), but this command discards\n * whatever arrives there. Attribution a client can set is not attribution \u2014\n * it is a free-text field with an authoritative-looking name. Deliberately\n * stricter than the shared `normalizeAuthorUserId` helper, which lets a\n * super-admin post on someone else's behalf: on a client-facing thread that\n * escape hatch would turn the avatar into a claim rather than a fact.\n * 2. **Editing is the author's own comment only**, unless the caller holds\n * `staff.timesheets.manage_all`. Deleting follows the same rule \u2014 a thread\n * someone else can rewrite is not a record. The manager exemption exists\n * because a client-facing thread sometimes has to have something removed,\n * and the grant is checked through `authorizeFeatures` so a wildcard\n * (`staff.*`, `staff.timesheets.*`) counts the way it does everywhere else.\n * 3. **Delete is soft.** The row keeps its body, its author and its timestamps,\n * so the audit trail (and undo) survives a comment leaving the thread.\n *\n * The parent task's *project* access is NOT resolved here \u2014 it is the route's\n * job (see `api/timesheets/tasks/[id]/comments/route.ts`), because only the\n * route knows the task id the caller addressed. What this file does enforce is\n * that the task exists inside the caller's tenant and organization, so a direct\n * command invocation cannot write a comment onto a foreign row.\n */\n\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 { assertOptimisticLock, readOptimisticLockExpected } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { StaffTimeTask, StaffTimeTaskComment } from '../data/entities'\nimport { staffTimeTaskCommentCrudEvents } from '../lib/crud'\nimport {\n staffTimeTaskCommentCreateSchema,\n staffTimeTaskCommentUpdateSchema,\n type StaffTimeTaskCommentCreateInput,\n type StaffTimeTaskCommentUpdateInput,\n} from '../data/validators'\nimport {\n applyScopeToWhere,\n commandActorScope,\n commandInputScope,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n scopedStaffSnapshotWhere,\n staffSnapshotScopeFromContext,\n staffSnapshotScopeFromSnapshot,\n type StaffCommandScope,\n type StaffSnapshotScope,\n} from './shared'\n\nexport const staffTimeTaskCommentCommandIds = {\n create: 'staff.timesheets.task_comments.create',\n update: 'staff.timesheets.task_comments.update',\n delete: 'staff.timesheets.task_comments.delete',\n} as const\n\n/**\n * Resource kind used for the audit log, the mutation-guard registry and the\n * command-level optimistic lock, matching the `staff.timesheets.<entity>` shape\n * the other hand-written timesheets routes present so an `OM_OPTIMISTIC_LOCK`\n * allow-list can name it.\n */\nexport const STAFF_TIME_TASK_COMMENT_RESOURCE_KIND = 'staff.timesheets.task_comment'\n\nexport const TASK_COMMENT_NOT_AUTHOR_CODE = 'task_comment_not_author'\n\nconst MANAGE_ALL_FEATURE = 'staff.timesheets.manage_all'\n\nconst AUTHOR_UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/\n\nconst commentCrudIndexer: CrudIndexerConfig<StaffTimeTaskComment> = {\n entityType: 'staff:staff_time_task_comment',\n}\n\n/**\n * `staff.timesheets.time_task_comment.{created,updated,deleted}` from `events.ts`.\n *\n * The route is hand-written (it must answer 404 rather than an empty page for a\n * task the caller cannot see), so it takes no `events:` config the CRUD factory\n * could read \u2014 the command emits the lifecycle events itself. It uses the shared\n * export rather than a local copy so the ids the module publishes and the ids this\n * command emits cannot drift apart.\n */\nconst commentCrudEvents: CrudEventsConfig<StaffTimeTaskComment> = staffTimeTaskCommentCrudEvents\n\ntype Translate = (key: string, fallback: string) => string\n\ntype CommentSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n taskId: string\n body: string\n authorUserId: string | null\n createdAt: string | null\n deletedAt: string | null\n}\n\ntype CommentUndoPayload = {\n before?: CommentSnapshot | null\n after?: CommentSnapshot | null\n}\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n options: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n}\n\nfunction toCommentSnapshot(comment: StaffTimeTaskComment): CommentSnapshot {\n return {\n id: comment.id,\n tenantId: comment.tenantId,\n organizationId: comment.organizationId,\n taskId: comment.taskId,\n body: comment.body,\n authorUserId: comment.authorUserId ?? null,\n createdAt: comment.createdAt ? comment.createdAt.toISOString() : null,\n deletedAt: comment.deletedAt ? comment.deletedAt.toISOString() : null,\n }\n}\n\nasync function loadCommentSnapshot(\n em: EntityManager,\n id: string,\n scope?: StaffSnapshotScope | null,\n): Promise<CommentSnapshot | null> {\n const comment = await em.findOne(StaffTimeTaskComment, scopedStaffSnapshotWhere(id, scope))\n return comment ? toCommentSnapshot(comment) : null\n}\n\nfunction commentNotFoundError(translate: Translate): CrudHttpError {\n return new CrudHttpError(404, {\n error: translate('staff.time_tracking.taskComments.errors.notFound', 'Comment not found or not accessible.'),\n })\n}\n\nfunction taskNotFoundError(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 notAuthorError(translate: Translate): CrudHttpError {\n return new CrudHttpError(403, {\n code: TASK_COMMENT_NOT_AUTHOR_CODE,\n error: translate(\n 'staff.time_tracking.taskComments.errors.notAuthor',\n 'You can only edit or delete your own comments.',\n ),\n })\n}\n\n/**\n * The comment's author is whoever the request authenticated as \u2014 never the\n * `authorUserId` the payload carried. An API key has no human behind it, so it\n * writes an unattributed comment rather than borrowing an identity.\n */\nfunction stampAuthorUserId(ctx: CommandRuntimeContext): string | null {\n const auth = ctx.auth as { isApiKey?: boolean; sub?: string | null } | null | undefined\n if (!auth || auth.isApiKey === true) return null\n const sub = typeof auth.sub === 'string' ? auth.sub.trim() : ''\n if (!sub || !AUTHOR_UUID_REGEX.test(sub)) return null\n return sub\n}\n\n/**\n * Wildcard-aware `staff.timesheets.manage_all` check. Reads the grants from\n * `rbacService` rather than the token's feature array so a stale or trimmed\n * browser payload cannot widen the exemption, and fails closed when the grants\n * cannot be read \u2014 the caller then falls back to \"author only\", which is the\n * safe half of the rule.\n */\nasync function callerCanManageAll(ctx: CommandRuntimeContext): Promise<boolean> {\n if (ctx.auth?.isSuperAdmin === true || ctx.systemActor === true) return true\n const userId = ctx.auth?.sub ?? null\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!userId || !tenantId) return false\n let grantedFeatures: string[] = []\n try {\n const rbac = ctx.container.resolve('rbacService') as RbacServiceLike | undefined\n if (rbac?.getGrantedFeatures) {\n grantedFeatures = (await rbac.getGrantedFeatures(userId, { tenantId, organizationId })) ?? []\n }\n } catch {\n return false\n }\n return authorizeFeatures([MANAGE_ALL_FEATURE], {\n grantedFeatures,\n scopeAllowed: Boolean(tenantId) && Boolean(organizationId),\n })\n}\n\nasync function requireEditableComment(\n comment: StaffTimeTaskComment,\n ctx: CommandRuntimeContext,\n translate: Translate,\n): Promise<void> {\n const actorId = stampAuthorUserId(ctx)\n if (actorId && comment.authorUserId === actorId) return\n if (await callerCanManageAll(ctx)) return\n throw notAuthorError(translate)\n}\n\nfunction assertCommentVersion(ctx: CommandRuntimeContext, comment: StaffTimeTaskComment): void {\n assertOptimisticLock({\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: comment.id,\n expected: readOptimisticLockExpected(ctx.request ?? null),\n current: comment.updatedAt,\n })\n}\n\nasync function requireTaskInScope(\n em: EntityManager,\n taskId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTask> {\n const task = await em.findOne(StaffTimeTask, applyScopeToWhere<StaffTimeTask>({ id: taskId, deletedAt: null }, scope))\n if (!task) throw taskNotFoundError(translate)\n return task\n}\n\nconst createTaskCommentCommand: CommandHandler<\n StaffTimeTaskCommentCreateInput,\n { commentId: string; taskId: string; authorUserId: string | null }\n> = {\n id: staffTimeTaskCommentCommandIds.create,\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskCommentCreateSchema.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 // `parsed.authorUserId` is intentionally unread \u2014 see the file header.\n const authorUserId = stampAuthorUserId(ctx)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let created: StaffTimeTaskComment | null = null\n\n await withAtomicFlush(\n em,\n [\n () => requireTaskInScope(em, parsed.taskId, scope, translate),\n () => {\n created = em.create(StaffTimeTaskComment, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n taskId: parsed.taskId,\n body: parsed.body,\n authorUserId,\n createdAt: new Date(),\n updatedAt: new Date(),\n deletedAt: null,\n })\n em.persist(created)\n },\n ],\n { transaction: true, label: staffTimeTaskCommentCommandIds.create },\n )\n\n const record = created as StaffTimeTaskComment | null\n if (!record) throw commentNotFoundError(translate)\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: commentCrudEvents,\n indexer: commentCrudIndexer,\n })\n\n return { commentId: record.id, taskId: record.taskId, authorUserId: record.authorUserId ?? null }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadCommentSnapshot(em, result.commentId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n return { snapshot }\n },\n buildLog: async ({ result, snapshots }) => {\n const snapshot = (snapshots.after as { snapshot?: CommentSnapshot } | undefined)?.snapshot\n if (!snapshot) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.task_comments.create', 'Create task comment'),\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: result.commentId,\n parentResourceKind: 'staff.timesheets.task',\n parentResourceId: snapshot.taskId,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n snapshotAfter: snapshot,\n payload: {\n undo: { after: snapshot } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(\n StaffTimeTaskComment,\n scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)),\n )\n if (!comment) return\n\n await withAtomicFlush(\n em,\n [\n () => {\n comment.deletedAt = new Date()\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommentCommandIds.create}.undo` },\n )\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'deleted',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentCrudEvents,\n indexer: commentCrudIndexer,\n })\n },\n}\n\nconst updateTaskCommentCommand: CommandHandler<\n StaffTimeTaskCommentUpdateInput,\n { commentId: string; taskId: string }\n> = {\n id: staffTimeTaskCommentCommandIds.update,\n async prepare(rawInput, ctx) {\n const parsed = staffTimeTaskCommentUpdateSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadCommentSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskCommentUpdateSchema.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 comment = await em.findOne(\n StaffTimeTaskComment,\n applyScopeToWhere<StaffTimeTaskComment>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!comment) throw commentNotFoundError(translate)\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n assertCommentVersion(ctx, comment)\n await requireEditableComment(comment, ctx, translate)\n\n await withAtomicFlush(\n em,\n [\n () => {\n comment.body = parsed.body\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: staffTimeTaskCommentCommandIds.update },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentCrudEvents,\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id, taskId: comment.taskId }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: CommentSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadCommentSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.task_comments.update', 'Update task comment'),\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: before.id,\n parentResourceKind: 'staff.timesheets.task',\n parentResourceId: before.taskId,\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 ['body'],\n ),\n payload: {\n undo: { before, after } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(\n StaffTimeTaskComment,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!comment) return\n\n await withAtomicFlush(\n em,\n [\n () => {\n comment.body = before.body\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommentCommandIds.update}.undo` },\n )\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentCrudEvents,\n indexer: commentCrudIndexer,\n })\n },\n}\n\nconst deleteTaskCommentCommand: CommandHandler<{ id?: string }, { commentId: string; taskId: string }> = {\n id: staffTimeTaskCommentCommandIds.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 before = await loadCommentSnapshot(em, id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\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.taskComments.errors.idRequired', 'Comment id is required.'),\n })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n const comment = await em.findOne(\n StaffTimeTaskComment,\n applyScopeToWhere<StaffTimeTaskComment>({ id, deletedAt: null }, scope),\n )\n if (!comment) throw commentNotFoundError(translate)\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n assertCommentVersion(ctx, comment)\n await requireEditableComment(comment, ctx, translate)\n\n // Soft delete: the body, the author and the timestamps stay on the row so\n // the audit trail \u2014 and undo \u2014 still has something to point at.\n await withAtomicFlush(\n em,\n [\n () => {\n comment.deletedAt = new Date()\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: staffTimeTaskCommentCommandIds.delete },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'deleted',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentCrudEvents,\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id, taskId: comment.taskId }\n },\n buildLog: async ({ snapshots }) => {\n const before = (snapshots.before as { snapshot?: CommentSnapshot } | undefined)?.snapshot\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.task_comments.delete', 'Delete task comment'),\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: before.id,\n parentResourceKind: 'staff.timesheets.task',\n parentResourceId: before.taskId,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let comment = await em.findOne(\n StaffTimeTaskComment,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n\n await withAtomicFlush(\n em,\n [\n () => {\n if (!comment) {\n comment = em.create(StaffTimeTaskComment, {\n id: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n taskId: before.taskId,\n body: before.body,\n authorUserId: before.authorUserId,\n createdAt: before.createdAt ? new Date(before.createdAt) : new Date(),\n updatedAt: new Date(),\n deletedAt: null,\n })\n em.persist(comment)\n return\n }\n comment.body = before.body\n comment.authorUserId = before.authorUserId\n comment.deletedAt = null\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommentCommandIds.delete}.undo` },\n )\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'created',\n entity: comment,\n identifiers: { id: before.id, organizationId: before.organizationId, tenantId: before.tenantId },\n events: commentCrudEvents,\n indexer: commentCrudIndexer,\n })\n },\n}\n\nregisterCommand(createTaskCommentCommand)\nregisterCommand(updateTaskCommentCommand)\nregisterCommand(deleteTaskCommentCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Task comment commands (T3.4b) \u2014 the \"Komentarze (2)\" thread of screen 7.\n *\n * US-C4 asks for context that lives with the work rather than in chat, so a\n * comment is only useful if the two things rendered next to its body are true:\n * *who* wrote it and *when*. Three decisions follow from that:\n *\n * 1. **`author_user_id` is stamped from the authenticated caller and is never\n * read from input.** `staffTimeTaskCommentCreateSchema` still declares the\n * field (it is the shared scoped-create shape), but this command discards\n * whatever arrives there. Attribution a client can set is not attribution \u2014\n * it is a free-text field with an authoritative-looking name. Deliberately\n * stricter than the shared `normalizeAuthorUserId` helper, which lets a\n * super-admin post on someone else's behalf: on a client-facing thread that\n * escape hatch would turn the avatar into a claim rather than a fact.\n * 2. **Editing is the author's own comment only**, unless the caller holds\n * `staff.timesheets.manage_all`. Deleting follows the same rule \u2014 a thread\n * someone else can rewrite is not a record. The manager exemption exists\n * because a client-facing thread sometimes has to have something removed,\n * and the grant is checked through `authorizeFeatures` so a wildcard\n * (`staff.*`, `staff.timesheets.*`) counts the way it does everywhere else.\n * 3. **Delete is soft.** The row keeps its body, its author and its timestamps,\n * so the audit trail (and undo) survives a comment leaving the thread.\n *\n * The parent task's *project* access is NOT resolved here \u2014 it is the route's\n * job (see `api/timesheets/tasks/[id]/comments/route.ts`), because only the\n * route knows the task id the caller addressed. What this file does enforce is\n * that the task exists inside the caller's tenant and organization, so a direct\n * command invocation cannot write a comment onto a foreign row.\n */\n\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 { assertOptimisticLock, readOptimisticLockExpected } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { StaffTimeTask, StaffTimeTaskComment, type StaffTimeTaskCommentVisibility } from '../data/entities'\nimport { staffTimeTaskCommentCrudEvents } from '../lib/crud'\nimport {\n staffTimeTaskCommentCreateSchema,\n staffTimeTaskCommentUpdateRequestSchema,\n type StaffTimeTaskCommentCreateInput,\n type StaffTimeTaskCommentUpdateRequestInput,\n} from '../data/validators'\nimport {\n applyScopeToWhere,\n commandActorScope,\n commandInputScope,\n ensureOrganizationScope,\n ensureTenantScope,\n extractUndoPayload,\n scopedStaffSnapshotWhere,\n staffSnapshotScopeFromContext,\n staffSnapshotScopeFromSnapshot,\n type StaffCommandScope,\n type StaffSnapshotScope,\n} from './shared'\n\nexport const staffTimeTaskCommentCommandIds = {\n create: 'staff.timesheets.task_comments.create',\n update: 'staff.timesheets.task_comments.update',\n delete: 'staff.timesheets.task_comments.delete',\n} as const\n\n/**\n * Resource kind used for the audit log, the mutation-guard registry and the\n * command-level optimistic lock, matching the `staff.timesheets.<entity>` shape\n * the other hand-written timesheets routes present so an `OM_OPTIMISTIC_LOCK`\n * allow-list can name it.\n */\nexport const STAFF_TIME_TASK_COMMENT_RESOURCE_KIND = 'staff.timesheets.task_comment'\n\nexport const TASK_COMMENT_NOT_AUTHOR_CODE = 'task_comment_not_author'\n\nconst MANAGE_ALL_FEATURE = 'staff.timesheets.manage_all'\n\nconst AUTHOR_UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/\n\nconst commentCrudIndexer: CrudIndexerConfig<StaffTimeTaskComment> = {\n entityType: 'staff:staff_time_task_comment',\n}\n\n/**\n * `staff.timesheets.time_task_comment.{created,updated,deleted}` from `events.ts`.\n *\n * The route is hand-written (it must answer 404 rather than an empty page for a\n * task the caller cannot see), so it takes no `events:` config the CRUD factory\n * could read \u2014 the command emits the lifecycle events itself. It uses the shared\n * export rather than a local copy so the ids the module publishes and the ids this\n * command emits cannot drift apart.\n */\nconst commentCrudEvents: CrudEventsConfig<StaffTimeTaskComment> = staffTimeTaskCommentCrudEvents\n\n/**\n * The comment CRUD events carry `portalVisible`: true when the comment is visible to\n * the customer before OR after the change, so publishing, retracting and deleting a\n * reply all refresh the portal. Internal notes never set it.\n */\nfunction commentEvents(portalVisible: boolean): CrudEventsConfig<StaffTimeTaskComment> {\n const base = staffTimeTaskCommentCrudEvents\n return {\n ...base,\n buildPayload: (context) => ({\n ...((base.buildPayload ? base.buildPayload(context) : {}) as Record<string, unknown>),\n portalVisible,\n }),\n }\n}\n\nexport const TASK_COMMENT_CUSTOMER_IMMUTABLE_CODE = 'task_comment_customer_immutable'\nexport const TASK_COMMENT_VISIBILITY_INVALID_CODE = 'task_comment_visibility_invalid'\n\nfunction customerCommentImmutableError(translate: Translate): CrudHttpError {\n return new CrudHttpError(403, {\n code: TASK_COMMENT_CUSTOMER_IMMUTABLE_CODE,\n error: translate(\n 'staff.time_tracking.taskComments.errors.customerImmutable',\n 'Messages written by the customer cannot be edited or deleted.',\n ),\n })\n}\n\nfunction visibilityInvalidError(translate: Translate): CrudHttpError {\n const message = translate(\n 'staff.time_tracking.taskComments.errors.visibilityPortalOnly',\n 'Only customer support tickets can carry replies visible to the customer.',\n )\n return new CrudHttpError(422, { code: TASK_COMMENT_VISIBILITY_INVALID_CODE, error: message, fieldErrors: { visibility: message } })\n}\n\ntype Translate = (key: string, fallback: string) => string\n\ntype CommentSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n taskId: string\n body: string\n authorUserId: string | null\n visibility: StaffTimeTaskCommentVisibility\n authorKind: string\n createdAt: string | null\n deletedAt: string | null\n}\n\ntype CommentUndoPayload = {\n before?: CommentSnapshot | null\n after?: CommentSnapshot | null\n}\n\ntype RbacServiceLike = {\n getGrantedFeatures?: (\n userId: string,\n options: { tenantId: string | null; organizationId: string | null },\n ) => Promise<string[]>\n}\n\nfunction toCommentSnapshot(comment: StaffTimeTaskComment): CommentSnapshot {\n return {\n id: comment.id,\n tenantId: comment.tenantId,\n organizationId: comment.organizationId,\n taskId: comment.taskId,\n body: comment.body,\n authorUserId: comment.authorUserId ?? null,\n visibility: comment.visibility ?? 'internal',\n authorKind: comment.authorKind ?? 'staff',\n createdAt: comment.createdAt ? comment.createdAt.toISOString() : null,\n deletedAt: comment.deletedAt ? comment.deletedAt.toISOString() : null,\n }\n}\n\nasync function loadCommentSnapshot(\n em: EntityManager,\n id: string,\n scope?: StaffSnapshotScope | null,\n): Promise<CommentSnapshot | null> {\n const comment = await em.findOne(StaffTimeTaskComment, scopedStaffSnapshotWhere(id, scope))\n return comment ? toCommentSnapshot(comment) : null\n}\n\nfunction commentNotFoundError(translate: Translate): CrudHttpError {\n return new CrudHttpError(404, {\n error: translate('staff.time_tracking.taskComments.errors.notFound', 'Comment not found or not accessible.'),\n })\n}\n\nfunction taskNotFoundError(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 notAuthorError(translate: Translate): CrudHttpError {\n return new CrudHttpError(403, {\n code: TASK_COMMENT_NOT_AUTHOR_CODE,\n error: translate(\n 'staff.time_tracking.taskComments.errors.notAuthor',\n 'You can only edit or delete your own comments.',\n ),\n })\n}\n\n/**\n * The comment's author is whoever the request authenticated as \u2014 never the\n * `authorUserId` the payload carried. An API key has no human behind it, so it\n * writes an unattributed comment rather than borrowing an identity.\n */\nfunction stampAuthorUserId(ctx: CommandRuntimeContext): string | null {\n const auth = ctx.auth as { isApiKey?: boolean; sub?: string | null } | null | undefined\n if (!auth || auth.isApiKey === true) return null\n const sub = typeof auth.sub === 'string' ? auth.sub.trim() : ''\n if (!sub || !AUTHOR_UUID_REGEX.test(sub)) return null\n return sub\n}\n\n/**\n * Wildcard-aware `staff.timesheets.manage_all` check. Reads the grants from\n * `rbacService` rather than the token's feature array so a stale or trimmed\n * browser payload cannot widen the exemption, and fails closed when the grants\n * cannot be read \u2014 the caller then falls back to \"author only\", which is the\n * safe half of the rule.\n */\nasync function callerCanManageAll(ctx: CommandRuntimeContext): Promise<boolean> {\n if (ctx.auth?.isSuperAdmin === true || ctx.systemActor === true) return true\n const userId = ctx.auth?.sub ?? null\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (!userId || !tenantId) return false\n let grantedFeatures: string[] = []\n try {\n const rbac = ctx.container.resolve('rbacService') as RbacServiceLike | undefined\n if (rbac?.getGrantedFeatures) {\n grantedFeatures = (await rbac.getGrantedFeatures(userId, { tenantId, organizationId })) ?? []\n }\n } catch {\n return false\n }\n return authorizeFeatures([MANAGE_ALL_FEATURE], {\n grantedFeatures,\n scopeAllowed: Boolean(tenantId) && Boolean(organizationId),\n })\n}\n\nasync function requireEditableComment(\n comment: StaffTimeTaskComment,\n ctx: CommandRuntimeContext,\n translate: Translate,\n): Promise<void> {\n if (comment.authorKind === 'customer') throw customerCommentImmutableError(translate)\n const actorId = stampAuthorUserId(ctx)\n if (actorId && comment.authorUserId === actorId) return\n if (await callerCanManageAll(ctx)) return\n throw notAuthorError(translate)\n}\n\nfunction assertCommentVersion(ctx: CommandRuntimeContext, comment: StaffTimeTaskComment): void {\n assertOptimisticLock({\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: comment.id,\n expected: readOptimisticLockExpected(ctx.request ?? null),\n current: comment.updatedAt,\n })\n}\n\nasync function requireTaskInScope(\n em: EntityManager,\n taskId: string,\n scope: StaffCommandScope,\n translate: Translate,\n): Promise<StaffTimeTask> {\n const task = await em.findOne(StaffTimeTask, applyScopeToWhere<StaffTimeTask>({ id: taskId, deletedAt: null }, scope))\n if (!task) throw taskNotFoundError(translate)\n return task\n}\n\nconst createTaskCommentCommand: CommandHandler<\n StaffTimeTaskCommentCreateInput,\n { commentId: string; taskId: string; authorUserId: string | null }\n> = {\n id: staffTimeTaskCommentCommandIds.create,\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskCommentCreateSchema.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 // `parsed.authorUserId` is intentionally unread \u2014 see the file header.\n const authorUserId = stampAuthorUserId(ctx)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let created: StaffTimeTaskComment | null = null\n\n await withAtomicFlush(\n em,\n [\n async () => {\n const task = await requireTaskInScope(em, parsed.taskId, scope, translate)\n if (parsed.visibility === 'customer' && task.source !== 'portal') throw visibilityInvalidError(translate)\n },\n () => {\n created = em.create(StaffTimeTaskComment, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n taskId: parsed.taskId,\n body: parsed.body,\n authorUserId,\n visibility: parsed.visibility ?? 'internal',\n authorKind: 'staff',\n createdAt: new Date(),\n updatedAt: new Date(),\n deletedAt: null,\n })\n em.persist(created)\n },\n ],\n { transaction: true, label: staffTimeTaskCommentCommandIds.create },\n )\n\n const record = created as StaffTimeTaskComment | null\n if (!record) throw commentNotFoundError(translate)\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: commentEvents(record.visibility === 'customer'),\n indexer: commentCrudIndexer,\n })\n\n return { commentId: record.id, taskId: record.taskId, authorUserId: record.authorUserId ?? null }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadCommentSnapshot(em, result.commentId, staffSnapshotScopeFromContext(ctx))\n if (!snapshot) return null\n return { snapshot }\n },\n buildLog: async ({ result, snapshots }) => {\n const snapshot = (snapshots.after as { snapshot?: CommentSnapshot } | undefined)?.snapshot\n if (!snapshot) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.task_comments.create', 'Create task comment'),\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: result.commentId,\n parentResourceKind: 'staff.timesheets.task',\n parentResourceId: snapshot.taskId,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n snapshotAfter: snapshot,\n payload: {\n undo: { after: snapshot } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(\n StaffTimeTaskComment,\n scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)),\n )\n if (!comment) return\n\n await withAtomicFlush(\n em,\n [\n () => {\n comment.deletedAt = new Date()\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommentCommandIds.create}.undo` },\n )\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'deleted',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentEvents(comment.visibility === 'customer'),\n indexer: commentCrudIndexer,\n })\n },\n}\n\nconst updateTaskCommentCommand: CommandHandler<\n StaffTimeTaskCommentUpdateRequestInput,\n { commentId: string; taskId: string }\n> = {\n id: staffTimeTaskCommentCommandIds.update,\n async prepare(rawInput, ctx) {\n const parsed = staffTimeTaskCommentUpdateRequestSchema.safeParse(rawInput)\n if (!parsed.success) return {}\n const em = ctx.container.resolve('em') as EntityManager\n const before = await loadCommentSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\n },\n async execute(rawInput, ctx) {\n const parsed = staffTimeTaskCommentUpdateRequestSchema.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 comment = await em.findOne(\n StaffTimeTaskComment,\n applyScopeToWhere<StaffTimeTaskComment>({ id: parsed.id, deletedAt: null }, scope),\n )\n if (!comment) throw commentNotFoundError(translate)\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n assertCommentVersion(ctx, comment)\n await requireEditableComment(comment, ctx, translate)\n const visibilityBefore = comment.visibility ?? 'internal'\n if (parsed.visibility === 'customer' && visibilityBefore !== 'customer') {\n const task = await requireTaskInScope(em, comment.taskId, scope, translate)\n if (task.source !== 'portal') throw visibilityInvalidError(translate)\n }\n\n await withAtomicFlush(\n em,\n [\n () => {\n if (parsed.body !== undefined) comment.body = parsed.body\n if (parsed.visibility !== undefined) comment.visibility = parsed.visibility\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: staffTimeTaskCommentCommandIds.update },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentEvents(visibilityBefore === 'customer' || comment.visibility === 'customer'),\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id, taskId: comment.taskId }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = (snapshots.before as { snapshot?: CommentSnapshot } | undefined)?.snapshot\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadCommentSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))\n if (!after) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.task_comments.update', 'Update task comment'),\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: before.id,\n parentResourceKind: 'staff.timesheets.task',\n parentResourceId: before.taskId,\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 ['body', 'visibility'],\n ),\n payload: {\n undo: { before, after } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(\n StaffTimeTaskComment,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n if (!comment) return\n\n await withAtomicFlush(\n em,\n [\n () => {\n comment.body = before.body\n comment.visibility = before.visibility ?? 'internal'\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommentCommandIds.update}.undo` },\n )\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'updated',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentEvents(true),\n indexer: commentCrudIndexer,\n })\n },\n}\n\nconst deleteTaskCommentCommand: CommandHandler<{ id?: string }, { commentId: string; taskId: string }> = {\n id: staffTimeTaskCommentCommandIds.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 before = await loadCommentSnapshot(em, id, staffSnapshotScopeFromContext(ctx))\n if (!before) return {}\n return { before: { snapshot: before } }\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.taskComments.errors.idRequired', 'Comment id is required.'),\n })\n }\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n const comment = await em.findOne(\n StaffTimeTaskComment,\n applyScopeToWhere<StaffTimeTaskComment>({ id, deletedAt: null }, scope),\n )\n if (!comment) throw commentNotFoundError(translate)\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n assertCommentVersion(ctx, comment)\n await requireEditableComment(comment, ctx, translate)\n\n // Soft delete: the body, the author and the timestamps stay on the row so\n // the audit trail \u2014 and undo \u2014 still has something to point at.\n await withAtomicFlush(\n em,\n [\n () => {\n comment.deletedAt = new Date()\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: staffTimeTaskCommentCommandIds.delete },\n )\n\n await emitCrudSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'deleted',\n entity: comment,\n identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },\n events: commentEvents(comment.visibility === 'customer'),\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id, taskId: comment.taskId }\n },\n buildLog: async ({ snapshots }) => {\n const before = (snapshots.before as { snapshot?: CommentSnapshot } | undefined)?.snapshot\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('staff.audit.timesheets.task_comments.delete', 'Delete task comment'),\n resourceKind: STAFF_TIME_TASK_COMMENT_RESOURCE_KIND,\n resourceId: before.id,\n parentResourceKind: 'staff.timesheets.task',\n parentResourceId: before.taskId,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: { before } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let comment = await em.findOne(\n StaffTimeTaskComment,\n scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)),\n )\n\n await withAtomicFlush(\n em,\n [\n () => {\n if (!comment) {\n comment = em.create(StaffTimeTaskComment, {\n id: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n taskId: before.taskId,\n body: before.body,\n authorUserId: before.authorUserId,\n visibility: before.visibility ?? 'internal',\n createdAt: before.createdAt ? new Date(before.createdAt) : new Date(),\n updatedAt: new Date(),\n deletedAt: null,\n })\n em.persist(comment)\n return\n }\n comment.body = before.body\n comment.authorUserId = before.authorUserId\n comment.deletedAt = null\n comment.updatedAt = new Date()\n },\n ],\n { transaction: true, label: `${staffTimeTaskCommentCommandIds.delete}.undo` },\n )\n\n await emitCrudUndoSideEffects({\n dataEngine: ctx.container.resolve('dataEngine'),\n action: 'created',\n entity: comment,\n identifiers: { id: before.id, organizationId: before.organizationId, tenantId: before.tenantId },\n events: commentEvents(before.visibility === 'customer'),\n indexer: commentCrudIndexer,\n })\n },\n}\n\nregisterCommand(createTaskCommentCommand)\nregisterCommand(updateTaskCommentCommand)\nregisterCommand(deleteTaskCommentCommand)\n"],
5
+ "mappings": "AAiCA,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB,kCAAkC;AACjE,SAAS,2BAA2B;AACpC,SAAS,cAAc,qBAAqB,+BAA+B;AAC3E,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAElC,SAAS,eAAe,4BAAiE;AACzF,SAAS,sCAAsC;AAC/C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAEA,MAAM,iCAAiC;AAAA,EAC5C,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAQO,MAAM,wCAAwC;AAE9C,MAAM,+BAA+B;AAE5C,MAAM,qBAAqB;AAE3B,MAAM,oBAAoB;AAE1B,MAAM,qBAA8D;AAAA,EAClE,YAAY;AACd;AAWA,MAAM,oBAA4D;AAOlE,SAAS,cAAc,eAAgE;AACrF,QAAM,OAAO;AACb,SAAO;AAAA,IACL,GAAG;AAAA,IACH,cAAc,CAAC,aAAa;AAAA,MAC1B,GAAK,KAAK,eAAe,KAAK,aAAa,OAAO,IAAI,CAAC;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,uCAAuC;AAC7C,MAAM,uCAAuC;AAEpD,SAAS,8BAA8B,WAAqC;AAC1E,SAAO,IAAI,cAAc,KAAK;AAAA,IAC5B,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,SAAS,uBAAuB,WAAqC;AACnE,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACA,SAAO,IAAI,cAAc,KAAK,EAAE,MAAM,sCAAsC,OAAO,SAAS,aAAa,EAAE,YAAY,QAAQ,EAAE,CAAC;AACpI;AA6BA,SAAS,kBAAkB,SAAgD;AACzE,SAAO;AAAA,IACL,IAAI,QAAQ;AAAA,IACZ,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,IACxB,QAAQ,QAAQ;AAAA,IAChB,MAAM,QAAQ;AAAA,IACd,cAAc,QAAQ,gBAAgB;AAAA,IACtC,YAAY,QAAQ,cAAc;AAAA,IAClC,YAAY,QAAQ,cAAc;AAAA,IAClC,WAAW,QAAQ,YAAY,QAAQ,UAAU,YAAY,IAAI;AAAA,IACjE,WAAW,QAAQ,YAAY,QAAQ,UAAU,YAAY,IAAI;AAAA,EACnE;AACF;AAEA,eAAe,oBACb,IACA,IACA,OACiC;AACjC,QAAM,UAAU,MAAM,GAAG,QAAQ,sBAAsB,yBAAyB,IAAI,KAAK,CAAC;AAC1F,SAAO,UAAU,kBAAkB,OAAO,IAAI;AAChD;AAEA,SAAS,qBAAqB,WAAqC;AACjE,SAAO,IAAI,cAAc,KAAK;AAAA,IAC5B,OAAO,UAAU,oDAAoD,sCAAsC;AAAA,EAC7G,CAAC;AACH;AAEA,SAAS,kBAAkB,WAAqC;AAC9D,SAAO,IAAI,cAAc,KAAK;AAAA,IAC5B,OAAO,UAAU,6CAA6C,mCAAmC;AAAA,EACnG,CAAC;AACH;AAEA,SAAS,eAAe,WAAqC;AAC3D,SAAO,IAAI,cAAc,KAAK;AAAA,IAC5B,MAAM;AAAA,IACN,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAOA,SAAS,kBAAkB,KAA2C;AACpE,QAAM,OAAO,IAAI;AACjB,MAAI,CAAC,QAAQ,KAAK,aAAa,KAAM,QAAO;AAC5C,QAAM,MAAM,OAAO,KAAK,QAAQ,WAAW,KAAK,IAAI,KAAK,IAAI;AAC7D,MAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,GAAG,EAAG,QAAO;AACjD,SAAO;AACT;AASA,eAAe,mBAAmB,KAA8C;AAC9E,MAAI,IAAI,MAAM,iBAAiB,QAAQ,IAAI,gBAAgB,KAAM,QAAO;AACxE,QAAM,SAAS,IAAI,MAAM,OAAO;AAChC,QAAM,WAAW,IAAI,MAAM,YAAY;AACvC,QAAM,iBAAiB,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACxE,MAAI,CAAC,UAAU,CAAC,SAAU,QAAO;AACjC,MAAI,kBAA4B,CAAC;AACjC,MAAI;AACF,UAAM,OAAO,IAAI,UAAU,QAAQ,aAAa;AAChD,QAAI,MAAM,oBAAoB;AAC5B,wBAAmB,MAAM,KAAK,mBAAmB,QAAQ,EAAE,UAAU,eAAe,CAAC,KAAM,CAAC;AAAA,IAC9F;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,kBAAkB,CAAC,kBAAkB,GAAG;AAAA,IAC7C;AAAA,IACA,cAAc,QAAQ,QAAQ,KAAK,QAAQ,cAAc;AAAA,EAC3D,CAAC;AACH;AAEA,eAAe,uBACb,SACA,KACA,WACe;AACf,MAAI,QAAQ,eAAe,WAAY,OAAM,8BAA8B,SAAS;AACpF,QAAM,UAAU,kBAAkB,GAAG;AACrC,MAAI,WAAW,QAAQ,iBAAiB,QAAS;AACjD,MAAI,MAAM,mBAAmB,GAAG,EAAG;AACnC,QAAM,eAAe,SAAS;AAChC;AAEA,SAAS,qBAAqB,KAA4B,SAAqC;AAC7F,uBAAqB;AAAA,IACnB,cAAc;AAAA,IACd,YAAY,QAAQ;AAAA,IACpB,UAAU,2BAA2B,IAAI,WAAW,IAAI;AAAA,IACxD,SAAS,QAAQ;AAAA,EACnB,CAAC;AACH;AAEA,eAAe,mBACb,IACA,QACA,OACA,WACwB;AACxB,QAAM,OAAO,MAAM,GAAG,QAAQ,eAAe,kBAAiC,EAAE,IAAI,QAAQ,WAAW,KAAK,GAAG,KAAK,CAAC;AACrH,MAAI,CAAC,KAAM,OAAM,kBAAkB,SAAS;AAC5C,SAAO;AACT;AAEA,MAAM,2BAGF;AAAA,EACF,IAAI,+BAA+B;AAAA,EACnC,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,iCAAiC,MAAM,QAAQ;AAC9D,sBAAkB,KAAK,OAAO,QAAQ;AACtC,4BAAwB,KAAK,OAAO,cAAc;AAClD,UAAM,QAAQ,kBAAkB,KAAK,OAAO,UAAU,OAAO,cAAc;AAC3E,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAEhD,UAAM,eAAe,kBAAkB,GAAG;AAE1C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,QAAI,UAAuC;AAE3C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,YAAY;AACV,gBAAM,OAAO,MAAM,mBAAmB,IAAI,OAAO,QAAQ,OAAO,SAAS;AACzE,cAAI,OAAO,eAAe,cAAc,KAAK,WAAW,SAAU,OAAM,uBAAuB,SAAS;AAAA,QAC1G;AAAA,QACA,MAAM;AACJ,oBAAU,GAAG,OAAO,sBAAsB;AAAA,YACxC,UAAU,OAAO;AAAA,YACjB,gBAAgB,OAAO;AAAA,YACvB,QAAQ,OAAO;AAAA,YACf,MAAM,OAAO;AAAA,YACb;AAAA,YACA,YAAY,OAAO,cAAc;AAAA,YACjC,YAAY;AAAA,YACZ,WAAW,oBAAI,KAAK;AAAA,YACpB,WAAW,oBAAI,KAAK;AAAA,YACpB,WAAW;AAAA,UACb,CAAC;AACD,aAAG,QAAQ,OAAO;AAAA,QACpB;AAAA,MACF;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,+BAA+B,OAAO;AAAA,IACpE;AAEA,UAAM,SAAS;AACf,QAAI,CAAC,OAAQ,OAAM,qBAAqB,SAAS;AAEjD,UAAM,oBAAoB;AAAA,MACxB,YAAY,IAAI,UAAU,QAAQ,YAAY;AAAA,MAC9C,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa,EAAE,IAAI,OAAO,IAAI,gBAAgB,OAAO,gBAAgB,UAAU,OAAO,SAAS;AAAA,MAC/F,QAAQ,cAAc,OAAO,eAAe,UAAU;AAAA,MACtD,SAAS;AAAA,IACX,CAAC;AAED,WAAO,EAAE,WAAW,OAAO,IAAI,QAAQ,OAAO,QAAQ,cAAc,OAAO,gBAAgB,KAAK;AAAA,EAClG;AAAA,EACA,cAAc,OAAO,QAAQ,QAAQ,QAAQ;AAC3C,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,WAAW,MAAM,oBAAoB,IAAI,OAAO,WAAW,8BAA8B,GAAG,CAAC;AACnG,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,EAAE,SAAS;AAAA,EACpB;AAAA,EACA,UAAU,OAAO,EAAE,QAAQ,UAAU,MAAM;AACzC,UAAM,WAAY,UAAU,OAAsD;AAClF,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO;AAAA,MACL,aAAa,UAAU,+CAA+C,qBAAqB;AAAA,MAC3F,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,oBAAoB;AAAA,MACpB,kBAAkB,SAAS;AAAA,MAC3B,UAAU,SAAS;AAAA,MACnB,gBAAgB,SAAS;AAAA,MACzB,eAAe;AAAA,MACf,SAAS;AAAA,QACP,MAAM,EAAE,OAAO,SAAS;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAAuC,QAAQ;AAC/D,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AACZ,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,UAAU,MAAM,GAAG;AAAA,MACvB;AAAA,MACA,yBAAyB,MAAM,IAAI,+BAA+B,KAAK,CAAC;AAAA,IAC1E;AACA,QAAI,CAAC,QAAS;AAEd,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,kBAAQ,YAAY,oBAAI,KAAK;AAC7B,kBAAQ,YAAY,oBAAI,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,GAAG,+BAA+B,MAAM,QAAQ;AAAA,IAC9E;AAEA,UAAM,wBAAwB;AAAA,MAC5B,YAAY,IAAI,UAAU,QAAQ,YAAY;AAAA,MAC9C,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa,EAAE,IAAI,QAAQ,IAAI,gBAAgB,QAAQ,gBAAgB,UAAU,QAAQ,SAAS;AAAA,MAClG,QAAQ,cAAc,QAAQ,eAAe,UAAU;AAAA,MACvD,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,MAAM,2BAGF;AAAA,EACF,IAAI,+BAA+B;AAAA,EACnC,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,wCAAwC,UAAU,QAAQ;AACzE,QAAI,CAAC,OAAO,QAAS,QAAO,CAAC;AAC7B,UAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,UAAM,SAAS,MAAM,oBAAoB,IAAI,OAAO,KAAK,IAAI,8BAA8B,GAAG,CAAC;AAC/F,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,WAAO,EAAE,QAAQ,EAAE,UAAU,OAAO,EAAE;AAAA,EACxC;AAAA,EACA,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,SAAS,wCAAwC,MAAM,QAAQ;AACrE,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,QAAQ,kBAAkB,GAAG;AAEnC,UAAM,UAAU,MAAM,GAAG;AAAA,MACvB;AAAA,MACA,kBAAwC,EAAE,IAAI,OAAO,IAAI,WAAW,KAAK,GAAG,KAAK;AAAA,IACnF;AACA,QAAI,CAAC,QAAS,OAAM,qBAAqB,SAAS;AAClD,sBAAkB,KAAK,QAAQ,QAAQ;AACvC,4BAAwB,KAAK,QAAQ,cAAc;AACnD,yBAAqB,KAAK,OAAO;AACjC,UAAM,uBAAuB,SAAS,KAAK,SAAS;AACpD,UAAM,mBAAmB,QAAQ,cAAc;AAC/C,QAAI,OAAO,eAAe,cAAc,qBAAqB,YAAY;AACvE,YAAM,OAAO,MAAM,mBAAmB,IAAI,QAAQ,QAAQ,OAAO,SAAS;AAC1E,UAAI,KAAK,WAAW,SAAU,OAAM,uBAAuB,SAAS;AAAA,IACtE;AAEA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,cAAI,OAAO,SAAS,OAAW,SAAQ,OAAO,OAAO;AACrD,cAAI,OAAO,eAAe,OAAW,SAAQ,aAAa,OAAO;AACjE,kBAAQ,YAAY,oBAAI,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,+BAA+B,OAAO;AAAA,IACpE;AAEA,UAAM,oBAAoB;AAAA,MACxB,YAAY,IAAI,UAAU,QAAQ,YAAY;AAAA,MAC9C,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa,EAAE,IAAI,QAAQ,IAAI,gBAAgB,QAAQ,gBAAgB,UAAU,QAAQ,SAAS;AAAA,MAClG,QAAQ,cAAc,qBAAqB,cAAc,QAAQ,eAAe,UAAU;AAAA,MAC1F,SAAS;AAAA,IACX,CAAC;AAED,WAAO,EAAE,WAAW,QAAQ,IAAI,QAAQ,QAAQ,OAAO;AAAA,EACzD;AAAA,EACA,UAAU,OAAO,EAAE,WAAW,IAAI,MAAM;AACtC,UAAM,SAAU,UAAU,QAAuD;AACjF,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,QAAQ,MAAM,oBAAoB,IAAI,OAAO,IAAI,+BAA+B,MAAM,CAAC;AAC7F,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO;AAAA,MACL,aAAa,UAAU,+CAA+C,qBAAqB;AAAA,MAC3F,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,oBAAoB;AAAA,MACpB,kBAAkB,OAAO;AAAA,MACzB,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,SAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA,CAAC,QAAQ,YAAY;AAAA,MACvB;AAAA,MACA,SAAS;AAAA,QACP,MAAM,EAAE,QAAQ,MAAM;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAAuC,QAAQ;AAC/D,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,OAAQ;AACb,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,UAAU,MAAM,GAAG;AAAA,MACvB;AAAA,MACA,yBAAyB,OAAO,IAAI,+BAA+B,MAAM,CAAC;AAAA,IAC5E;AACA,QAAI,CAAC,QAAS;AAEd,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,kBAAQ,OAAO,OAAO;AACtB,kBAAQ,aAAa,OAAO,cAAc;AAC1C,kBAAQ,YAAY,oBAAI,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,GAAG,+BAA+B,MAAM,QAAQ;AAAA,IAC9E;AAEA,UAAM,wBAAwB;AAAA,MAC5B,YAAY,IAAI,UAAU,QAAQ,YAAY;AAAA,MAC9C,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa,EAAE,IAAI,QAAQ,IAAI,gBAAgB,QAAQ,gBAAgB,UAAU,QAAQ,SAAS;AAAA,MAClG,QAAQ,cAAc,IAAI;AAAA,MAC1B,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,MAAM,2BAAmG;AAAA,EACvG,IAAI,+BAA+B;AAAA,EACnC,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,KAAK,OAAO;AAClB,QAAI,CAAC,GAAI,QAAO,CAAC;AACjB,UAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,UAAM,SAAS,MAAM,oBAAoB,IAAI,IAAI,8BAA8B,GAAG,CAAC;AACnF,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,WAAO,EAAE,QAAQ,EAAE,UAAU,OAAO,EAAE;AAAA,EACxC;AAAA,EACA,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,KAAK,OAAO;AAClB,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,cAAc,KAAK;AAAA,QAC3B,OAAO,UAAU,sDAAsD,yBAAyB;AAAA,MAClG,CAAC;AAAA,IACH;AACA,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,UAAM,QAAQ,kBAAkB,GAAG;AACnC,UAAM,UAAU,MAAM,GAAG;AAAA,MACvB;AAAA,MACA,kBAAwC,EAAE,IAAI,WAAW,KAAK,GAAG,KAAK;AAAA,IACxE;AACA,QAAI,CAAC,QAAS,OAAM,qBAAqB,SAAS;AAClD,sBAAkB,KAAK,QAAQ,QAAQ;AACvC,4BAAwB,KAAK,QAAQ,cAAc;AACnD,yBAAqB,KAAK,OAAO;AACjC,UAAM,uBAAuB,SAAS,KAAK,SAAS;AAIpD,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,kBAAQ,YAAY,oBAAI,KAAK;AAC7B,kBAAQ,YAAY,oBAAI,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,+BAA+B,OAAO;AAAA,IACpE;AAEA,UAAM,oBAAoB;AAAA,MACxB,YAAY,IAAI,UAAU,QAAQ,YAAY;AAAA,MAC9C,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa,EAAE,IAAI,QAAQ,IAAI,gBAAgB,QAAQ,gBAAgB,UAAU,QAAQ,SAAS;AAAA,MAClG,QAAQ,cAAc,QAAQ,eAAe,UAAU;AAAA,MACvD,SAAS;AAAA,IACX,CAAC;AAED,WAAO,EAAE,WAAW,QAAQ,IAAI,QAAQ,QAAQ,OAAO;AAAA,EACzD;AAAA,EACA,UAAU,OAAO,EAAE,UAAU,MAAM;AACjC,UAAM,SAAU,UAAU,QAAuD;AACjF,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO;AAAA,MACL,aAAa,UAAU,+CAA+C,qBAAqB;AAAA,MAC3F,cAAc;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,oBAAoB;AAAA,MACpB,kBAAkB,OAAO;AAAA,MACzB,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO;AAAA,MACvB,gBAAgB;AAAA,MAChB,SAAS;AAAA,QACP,MAAM,EAAE,OAAO;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,UAAU,IAAI,MAAM;AACjC,UAAM,UAAU,mBAAuC,QAAQ;AAC/D,UAAM,SAAS,SAAS;AACxB,QAAI,CAAC,OAAQ;AACb,UAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,QAAI,UAAU,MAAM,GAAG;AAAA,MACrB;AAAA,MACA,yBAAyB,OAAO,IAAI,+BAA+B,MAAM,CAAC;AAAA,IAC5E;AAEA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE,MAAM;AACJ,cAAI,CAAC,SAAS;AACZ,sBAAU,GAAG,OAAO,sBAAsB;AAAA,cACxC,IAAI,OAAO;AAAA,cACX,UAAU,OAAO;AAAA,cACjB,gBAAgB,OAAO;AAAA,cACvB,QAAQ,OAAO;AAAA,cACf,MAAM,OAAO;AAAA,cACb,cAAc,OAAO;AAAA,cACrB,YAAY,OAAO,cAAc;AAAA,cACjC,WAAW,OAAO,YAAY,IAAI,KAAK,OAAO,SAAS,IAAI,oBAAI,KAAK;AAAA,cACpE,WAAW,oBAAI,KAAK;AAAA,cACpB,WAAW;AAAA,YACb,CAAC;AACD,eAAG,QAAQ,OAAO;AAClB;AAAA,UACF;AACA,kBAAQ,OAAO,OAAO;AACtB,kBAAQ,eAAe,OAAO;AAC9B,kBAAQ,YAAY;AACpB,kBAAQ,YAAY,oBAAI,KAAK;AAAA,QAC/B;AAAA,MACF;AAAA,MACA,EAAE,aAAa,MAAM,OAAO,GAAG,+BAA+B,MAAM,QAAQ;AAAA,IAC9E;AAEA,UAAM,wBAAwB;AAAA,MAC5B,YAAY,IAAI,UAAU,QAAQ,YAAY;AAAA,MAC9C,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa,EAAE,IAAI,OAAO,IAAI,gBAAgB,OAAO,gBAAgB,UAAU,OAAO,SAAS;AAAA,MAC/F,QAAQ,cAAc,OAAO,eAAe,UAAU;AAAA,MACtD,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAEA,gBAAgB,wBAAwB;AACxC,gBAAgB,wBAAwB;AACxC,gBAAgB,wBAAwB;",
6
6
  "names": []
7
7
  }
@@ -3,11 +3,13 @@ import { registerCommand } from "@open-mercato/shared/lib/commands";
3
3
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
4
4
  import { enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
5
5
  import { createLogger } from "@open-mercato/shared/lib/logger";
6
+ import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
6
7
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
7
8
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
9
  import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from "@open-mercato/shared/lib/commands/helpers";
10
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
9
11
  import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
10
- import { StaffTeamMember, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from "../data/entities.js";
12
+ import { StaffSupportTicket, StaffTeamMember, StaffTimeTask, StaffTimeTaskStatus } from "../data/entities.js";
11
13
  import {
12
14
  staffTimeTaskCreateSchema,
13
15
  staffTimeTaskStatusChangeSchema,
@@ -31,7 +33,9 @@ import {
31
33
  commandInputScope,
32
34
  ensureOrganizationScope,
33
35
  ensureTenantScope,
36
+ explicitStaffCommandScope,
34
37
  extractUndoPayload,
38
+ lockLiveProjectForWrite,
35
39
  scopeForDecryption,
36
40
  scopedStaffSnapshotWhere,
37
41
  staffSnapshotScopeFromContext,
@@ -193,10 +197,7 @@ function referenceConflictError(translate) {
193
197
  });
194
198
  }
195
199
  async function requireProject(em, timeProjectId, scope, translate) {
196
- const project = await em.findOne(
197
- StaffTimeProject,
198
- applyScopeToWhere({ id: timeProjectId, deletedAt: null }, scope)
199
- );
200
+ const project = await lockLiveProjectForWrite(em, timeProjectId, scope);
200
201
  if (!project) throw projectNotFoundError(translate);
201
202
  return project;
202
203
  }
@@ -407,6 +408,25 @@ const createTaskCommand = {
407
408
  });
408
409
  }
409
410
  };
411
+ function emitTaskAssigneeChanged(task, previousAssigneeStaffMemberId, changeKind, actorUserId2 = null) {
412
+ void emitStaffEvent(
413
+ "staff.timesheets.time_task.assignee_changed",
414
+ {
415
+ id: task.id,
416
+ tenantId: task.tenantId,
417
+ organizationId: task.organizationId,
418
+ timeProjectId: task.timeProjectId,
419
+ previousAssigneeStaffMemberId,
420
+ assigneeStaffMemberId: task.assigneeStaffMemberId ?? null,
421
+ changeKind,
422
+ actorUserId: actorUserId2
423
+ },
424
+ { persistent: true }
425
+ ).catch((err) => {
426
+ logger.error("staff.timesheets.time_task.assignee_changed emit failed", { err, taskId: task.id });
427
+ getTelemetryRuntime()?.reportError(err, { module: "staff", code: "staff.task_assignee_changed_emit_failed" });
428
+ });
429
+ }
410
430
  const updateTaskCommand = {
411
431
  id: staffTimeTaskCommandIds.update,
412
432
  async prepare(rawInput, ctx) {
@@ -433,6 +453,7 @@ const updateTaskCommand = {
433
453
  let targetStatus = null;
434
454
  let targetParentTaskId;
435
455
  let targetAssigneeStaffMemberId;
456
+ const previousAssigneeStaffMemberId = task.assigneeStaffMemberId ?? null;
436
457
  await withAtomicFlush(
437
458
  em,
438
459
  [
@@ -483,6 +504,10 @@ const updateTaskCommand = {
483
504
  events: taskCrudEvents,
484
505
  indexer: taskCrudIndexer
485
506
  });
507
+ const nextAssigneeStaffMemberId = task.assigneeStaffMemberId ?? null;
508
+ if (targetAssigneeStaffMemberId !== void 0 && nextAssigneeStaffMemberId !== previousAssigneeStaffMemberId) {
509
+ emitTaskAssigneeChanged(task, previousAssigneeStaffMemberId, "manual", ctx.auth?.sub ?? null);
510
+ }
486
511
  return { taskId: task.id };
487
512
  },
488
513
  buildLog: async ({ snapshots, ctx }) => {
@@ -570,6 +595,8 @@ const deleteTaskCommand = {
570
595
  ensureOrganizationScope(ctx, task.organizationId);
571
596
  let children = [];
572
597
  let childIds = [];
598
+ let supportTickets = [];
599
+ const supportTicketIds = [];
573
600
  await withAtomicFlush(
574
601
  em,
575
602
  [
@@ -578,6 +605,18 @@ const deleteTaskCommand = {
578
605
  StaffTimeTask,
579
606
  applyScopeToWhere({ parentTaskId: task.id, deletedAt: null }, scope)
580
607
  );
608
+ supportTickets = await findWithDecryption(
609
+ em,
610
+ StaffSupportTicket,
611
+ {
612
+ taskId: { $in: [task.id, ...children.map((child) => child.id)] },
613
+ tenantId: task.tenantId,
614
+ organizationId: task.organizationId,
615
+ deletedAt: null
616
+ },
617
+ void 0,
618
+ { tenantId: task.tenantId, organizationId: task.organizationId }
619
+ );
581
620
  },
582
621
  () => {
583
622
  const plan = planTaskSoftDelete(task, children);
@@ -592,6 +631,13 @@ const deleteTaskCommand = {
592
631
  child.deletedAt = now;
593
632
  child.updatedAt = now;
594
633
  }
634
+ const removedTaskIds = /* @__PURE__ */ new Set([task.id, ...childIds]);
635
+ for (const ticket of supportTickets) {
636
+ if (!removedTaskIds.has(ticket.taskId)) continue;
637
+ ticket.deletedAt = now;
638
+ ticket.updatedAt = now;
639
+ supportTicketIds.push(ticket.id);
640
+ }
595
641
  }
596
642
  ],
597
643
  { transaction: true, label: staffTimeTaskCommandIds.delete }
@@ -607,9 +653,9 @@ const deleteTaskCommand = {
607
653
  indexer: taskCrudIndexer
608
654
  });
609
655
  }
610
- return { taskId: task.id, childIds };
656
+ return { taskId: task.id, childIds, supportTicketIds };
611
657
  },
612
- buildLog: async ({ snapshots }) => {
658
+ buildLog: async ({ snapshots, result }) => {
613
659
  const prepared = snapshots.before;
614
660
  const before = prepared?.snapshot;
615
661
  if (!before) return null;
@@ -624,7 +670,8 @@ const deleteTaskCommand = {
624
670
  payload: {
625
671
  undo: {
626
672
  before,
627
- cascadedChildren: prepared?.cascadedChildren ?? null
673
+ cascadedChildren: prepared?.cascadedChildren ?? null,
674
+ cascadedSupportTicketIds: result?.supportTicketIds ?? []
628
675
  }
629
676
  }
630
677
  };
@@ -637,21 +684,48 @@ const deleteTaskCommand = {
637
684
  const em = ctx.container.resolve("em").fork();
638
685
  const ids = [before.id, ...(payload?.cascadedChildren ?? []).map((child) => child.id)];
639
686
  const restored = [];
687
+ const restoredTickets = [];
688
+ const ticketIds = payload?.cascadedSupportTicketIds ?? [];
689
+ const { translate } = await resolveTranslations();
640
690
  await withAtomicFlush(
641
691
  em,
642
692
  [
643
693
  async () => {
694
+ const project = await lockLiveProjectForWrite(
695
+ em,
696
+ before.timeProjectId,
697
+ explicitStaffCommandScope(before.tenantId, before.organizationId)
698
+ );
699
+ if (!project) throw projectNotFoundError(translate);
644
700
  const tasks = await em.find(StaffTimeTask, {
645
701
  id: { $in: ids },
646
702
  ...snapshotScopeWhere(snapshotScope)
647
703
  });
648
704
  restored.push(...tasks);
705
+ if (ticketIds.length === 0) return;
706
+ const tickets = await findWithDecryption(
707
+ em,
708
+ StaffSupportTicket,
709
+ {
710
+ id: { $in: ticketIds },
711
+ taskId: { $in: ids },
712
+ ...snapshotScopeWhere(snapshotScope),
713
+ deletedAt: { $ne: null }
714
+ },
715
+ void 0,
716
+ { tenantId: before.tenantId, organizationId: before.organizationId }
717
+ );
718
+ restoredTickets.push(...tickets);
649
719
  },
650
720
  () => {
651
721
  for (const task of restored) {
652
722
  task.deletedAt = null;
653
723
  task.updatedAt = /* @__PURE__ */ new Date();
654
724
  }
725
+ for (const ticket of restoredTickets) {
726
+ ticket.deletedAt = null;
727
+ ticket.updatedAt = /* @__PURE__ */ new Date();
728
+ }
655
729
  }
656
730
  ],
657
731
  { transaction: true, label: `${staffTimeTaskCommandIds.delete}.undo` }
@@ -824,11 +898,19 @@ registerCommand(statusChangeTaskCommand);
824
898
  export {
825
899
  STAFF_TIME_TASK_RESOURCE_KIND,
826
900
  TASK_DEPTH_EXCEEDED_CODE,
901
+ TASK_LIST_CACHE_RESOURCE,
827
902
  TASK_PARENT_PROJECT_MISMATCH_CODE,
828
903
  TASK_PARENT_SELF_CODE,
829
904
  TASK_PROJECT_MOVE_UNSUPPORTED_CODE,
830
905
  TASK_REFERENCE_CONFLICT_CODE,
831
906
  TASK_STATUS_MISSING_CODE,
832
- staffTimeTaskCommandIds
907
+ applyStatusToTask,
908
+ emitTaskAssigneeChanged,
909
+ nextTaskPosition,
910
+ readHighestSequenceNumber,
911
+ resolveTaskStatus,
912
+ staffTimeTaskCommandIds,
913
+ taskCrudEvents,
914
+ taskCrudIndexer
833
915
  };
834
916
  //# sourceMappingURL=timesheets-tasks.js.map