@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
@@ -0,0 +1,580 @@
1
+ import { z } from 'zod'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
4
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
5
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
+ import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
8
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
9
+ import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
10
+ import type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'
11
+ import { createLogger } from '@open-mercato/shared/lib/logger'
12
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
13
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
14
+ import {
15
+ StaffSupportTicket,
16
+ StaffTimeProject,
17
+ StaffTimeTask,
18
+ StaffTimeTaskComment,
19
+ StaffTimeTaskStatus,
20
+ } from '../data/entities'
21
+ import { staffTimeTaskCommentCrudEvents } from '../lib/crud'
22
+ import { findSupportTicketCategory } from '../lib/support/dictionaries'
23
+ import { resolveSupportRoute } from '../lib/support/routing'
24
+ import { allocateTaskReference, withTaskReferenceRetry, isTaskReferenceConflict } from '../lib/timesheets-tasks/taskReference'
25
+ import {
26
+ resolveAssignmentGraceDaysFromContainer,
27
+ resolveUsableDefaultAssignee,
28
+ } from '../lib/time-tracking/projectAssignment'
29
+ import { readTimeTrackingSettings } from '../lib/time-tracking/settings'
30
+ import { DEFAULT_SUPPORT_REFERENCE_PREFIX } from '../lib/time-tracking/settingKeys'
31
+ import { emitStaffEvent } from '../events'
32
+ import {
33
+ applyStatusToTask,
34
+ emitTaskAssigneeChanged,
35
+ nextTaskPosition,
36
+ readHighestSequenceNumber,
37
+ taskCrudEvents,
38
+ taskCrudIndexer,
39
+ TASK_LIST_CACHE_RESOURCE,
40
+ } from './timesheets-tasks'
41
+ import { explicitStaffCommandScope, extractUndoPayload, lockLiveProjectForWrite } from './shared'
42
+
43
+ const logger = createLogger('staff').child({ component: 'commands/support-tickets' })
44
+
45
+ export const staffSupportTicketCommandIds = {
46
+ createFromPortal: 'staff.support_tickets.create_from_portal',
47
+ portalMessage: 'staff.support_tickets.portal_message',
48
+ } as const
49
+
50
+ export const SUPPORT_TICKET_RESOURCE_KIND = 'staff.support_ticket'
51
+ export const SUPPORT_CATEGORY_UNAVAILABLE_CODE = 'support_category_unavailable'
52
+
53
+ const customerScopeFields = {
54
+ tenantId: z.string().uuid(),
55
+ organizationId: z.string().uuid(),
56
+ customerId: z.string().uuid(),
57
+ customerUserId: z.string().uuid(),
58
+ customerDisplayName: z.string().max(255).optional().nullable(),
59
+ customerKind: z.string().max(50).optional().nullable(),
60
+ reporterDisplayName: z.string().max(255).optional().nullable(),
61
+ }
62
+
63
+ export const supportTicketCreateFromPortalSchema = z.object({
64
+ ...customerScopeFields,
65
+ categoryValue: z.string().trim().min(1).max(150),
66
+ subject: z.string().trim().min(3).max(200),
67
+ description: z.string().trim().min(1).max(10000),
68
+ })
69
+
70
+ export const supportTicketPortalMessageSchema = z.object({
71
+ ...customerScopeFields,
72
+ taskId: z.string().uuid(),
73
+ body: z.string().trim().min(1).max(10000),
74
+ })
75
+
76
+ export type SupportTicketCreateFromPortalInput = z.infer<typeof supportTicketCreateFromPortalSchema>
77
+ export type SupportTicketPortalMessageInput = z.infer<typeof supportTicketPortalMessageSchema>
78
+
79
+ export type SupportTicketCreateResult = {
80
+ taskId: string
81
+ ticketId: string
82
+ supportReference: string
83
+ timeProjectId: string
84
+ tenantId: string
85
+ organizationId: string
86
+ }
87
+
88
+ export type SupportTicketMessageResult = {
89
+ commentId: string
90
+ taskId: string
91
+ reopened: boolean
92
+ previousTaskStatusId: string | null
93
+ tenantId: string
94
+ organizationId: string
95
+ }
96
+
97
+ type Translate = (key: string, fallback: string) => string
98
+
99
+ type CreateUndoPayload = {
100
+ taskId: string
101
+ ticketId: string
102
+ tenantId: string
103
+ organizationId: string
104
+ }
105
+
106
+ type MessageUndoPayload = {
107
+ commentId: string
108
+ taskId: string
109
+ tenantId: string
110
+ organizationId: string
111
+ reopenedFromStatusId: string | null
112
+ }
113
+
114
+ const portalVisibleCommentEvents: CrudEventsConfig<StaffTimeTaskComment> = {
115
+ ...staffTimeTaskCommentCrudEvents,
116
+ buildPayload: (context) => ({
117
+ ...((staffTimeTaskCommentCrudEvents.buildPayload ? staffTimeTaskCommentCrudEvents.buildPayload(context) : {}) as Record<string, unknown>),
118
+ portalVisible: true,
119
+ }),
120
+ }
121
+
122
+ function categoryUnavailableError(translate: Translate): CrudHttpError {
123
+ const message = translate(
124
+ 'staff.support.errors.categoryUnavailable',
125
+ 'Support requests in this category are not available right now.',
126
+ )
127
+ return new CrudHttpError(422, {
128
+ code: SUPPORT_CATEGORY_UNAVAILABLE_CODE,
129
+ error: message,
130
+ fieldErrors: { categoryValue: message },
131
+ })
132
+ }
133
+
134
+ function ticketNotFoundError(translate: Translate): CrudHttpError {
135
+ return new CrudHttpError(404, {
136
+ error: translate('staff.support.errors.ticketNotFound', 'Support ticket not found.'),
137
+ })
138
+ }
139
+
140
+ export function formatSupportReference(prefix: string, sequenceNumber: number): string {
141
+ return `${prefix}-${sequenceNumber}`
142
+ }
143
+
144
+ async function readSupportReferencePrefix(ctx: CommandRuntimeContext, tenantId: string): Promise<string> {
145
+ try {
146
+ const configService = ctx.container.resolve('moduleConfigService') as Parameters<typeof readTimeTrackingSettings>[0]
147
+ const settings = await readTimeTrackingSettings(configService, { tenantId })
148
+ const prefix = settings.support?.referencePrefix
149
+ return typeof prefix === 'string' && prefix.length > 0 ? prefix : DEFAULT_SUPPORT_REFERENCE_PREFIX
150
+ } catch {
151
+ return DEFAULT_SUPPORT_REFERENCE_PREFIX
152
+ }
153
+ }
154
+
155
+ export async function readHighestSupportSequence(em: EntityManager, tenantId: string, organizationId: string): Promise<number> {
156
+ const highest = await findOneWithDecryption(
157
+ em,
158
+ StaffSupportTicket,
159
+ { tenantId, organizationId },
160
+ { fields: ['id', 'supportSequenceNumber'], orderBy: { supportSequenceNumber: 'desc' } },
161
+ { tenantId, organizationId },
162
+ )
163
+ return highest?.supportSequenceNumber ?? 0
164
+ }
165
+
166
+ function emitSupportEvent(eventId: Parameters<typeof emitStaffEvent>[0], payload: Record<string, unknown>): void {
167
+ void emitStaffEvent(eventId, payload, { persistent: true }).catch((err) => {
168
+ logger.error('staff support event emit failed', { err, eventId })
169
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_event_emit_failed' })
170
+ })
171
+ }
172
+
173
+ const createFromPortalCommand: CommandHandler<SupportTicketCreateFromPortalInput, SupportTicketCreateResult> = {
174
+ id: staffSupportTicketCommandIds.createFromPortal,
175
+ async execute(rawInput, ctx) {
176
+ const parsed = supportTicketCreateFromPortalSchema.parse(rawInput)
177
+ const { translate } = await resolveTranslations()
178
+ const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
179
+ const commandScope = explicitStaffCommandScope(parsed.tenantId, parsed.organizationId)
180
+ const baseEm = ctx.container.resolve('em') as EntityManager
181
+
182
+ const category = await findSupportTicketCategory(baseEm.fork(), scope, parsed.categoryValue)
183
+ if (!category) throw categoryUnavailableError(translate)
184
+ const route = await resolveSupportRoute(baseEm.fork(), scope, category.value)
185
+ if (!route) throw categoryUnavailableError(translate)
186
+
187
+ const prefix = await readSupportReferencePrefix(ctx, parsed.tenantId)
188
+ const graceDays = await resolveAssignmentGraceDaysFromContainer(ctx.container, parsed.tenantId)
189
+
190
+ let result: { task: StaffTimeTask; ticket: StaffSupportTicket } | null = null
191
+ try {
192
+ result = await withTaskReferenceRetry(async () => {
193
+ const em = baseEm.fork()
194
+ let task: StaffTimeTask | null = null
195
+ let ticket: StaffSupportTicket | null = null
196
+ let plan: {
197
+ project: StaffTimeProject
198
+ status: StaffTimeTaskStatus
199
+ position: number
200
+ highestTaskSequence: number
201
+ highestSupportSequence: number
202
+ assigneeStaffMemberId: string | null
203
+ } | null = null
204
+
205
+ await withAtomicFlush(
206
+ em,
207
+ [
208
+ async () => {
209
+ const project = await lockLiveProjectForWrite(em, route.timeProjectId, commandScope)
210
+ if (!project) throw categoryUnavailableError(translate)
211
+ const status = await em.findOne(StaffTimeTaskStatus, {
212
+ timeProjectId: project.id,
213
+ tenantId: parsed.tenantId,
214
+ organizationId: parsed.organizationId,
215
+ isDefault: true,
216
+ deletedAt: null,
217
+ })
218
+ if (!status) throw categoryUnavailableError(translate)
219
+ plan = {
220
+ project,
221
+ status,
222
+ position: await nextTaskPosition(em, project.id, status.id, commandScope),
223
+ highestTaskSequence: await readHighestSequenceNumber(em, project.id, commandScope),
224
+ highestSupportSequence: await readHighestSupportSequence(em, parsed.tenantId, parsed.organizationId),
225
+ assigneeStaffMemberId: await resolveUsableDefaultAssignee(em, scope, project, { graceDays }),
226
+ }
227
+ },
228
+ () => {
229
+ if (!plan) throw categoryUnavailableError(translate)
230
+ const now = new Date()
231
+ const allocation = allocateTaskReference(plan.project.code, plan.highestTaskSequence)
232
+ task = em.create(StaffTimeTask, {
233
+ tenantId: parsed.tenantId,
234
+ organizationId: parsed.organizationId,
235
+ timeProjectId: plan.project.id,
236
+ parentTaskId: null,
237
+ taskStatusId: plan.status.id,
238
+ sequenceNumber: allocation.sequenceNumber,
239
+ reference: allocation.reference,
240
+ title: parsed.subject,
241
+ description: parsed.description,
242
+ assigneeStaffMemberId: plan.assigneeStaffMemberId,
243
+ position: plan.position,
244
+ createdByUserId: null,
245
+ closedAt: plan.status.isDone ? now : null,
246
+ source: 'portal',
247
+ customerId: parsed.customerId,
248
+ createdAt: now,
249
+ updatedAt: now,
250
+ deletedAt: null,
251
+ })
252
+ em.persist(task)
253
+ },
254
+ () => {
255
+ if (!plan || !task) throw categoryUnavailableError(translate)
256
+ const now = new Date()
257
+ const supportSequenceNumber = plan.highestSupportSequence + 1
258
+ ticket = em.create(StaffSupportTicket, {
259
+ tenantId: parsed.tenantId,
260
+ organizationId: parsed.organizationId,
261
+ taskId: (task as StaffTimeTask).id,
262
+ customerSnapshot: {
263
+ displayName: parsed.customerDisplayName ?? null,
264
+ kind: parsed.customerKind ?? null,
265
+ reporterDisplayName: parsed.reporterDisplayName ?? null,
266
+ },
267
+ reportedByCustomerUserId: parsed.customerUserId,
268
+ supportReference: formatSupportReference(prefix, supportSequenceNumber),
269
+ supportSequenceNumber,
270
+ categoryValue: category.value,
271
+ categorySnapshot: { value: category.value, label: category.label },
272
+ routedVia: route.via,
273
+ customerRequestSnapshot: { subject: parsed.subject, body: parsed.description },
274
+ unnotifiedAt: null,
275
+ portalStatusPublished: 'received',
276
+ createdAt: now,
277
+ updatedAt: now,
278
+ deletedAt: null,
279
+ })
280
+ em.persist(ticket)
281
+ },
282
+ ],
283
+ { transaction: true, label: staffSupportTicketCommandIds.createFromPortal },
284
+ )
285
+ if (!task || !ticket) throw ticketNotFoundError(translate)
286
+ return { task: task as StaffTimeTask, ticket: ticket as StaffSupportTicket }
287
+ })
288
+ } catch (err) {
289
+ if (isTaskReferenceConflict(err)) {
290
+ throw new CrudHttpError(409, {
291
+ code: 'support_reference_conflict',
292
+ error: translate('staff.support.errors.referenceConflict', 'Too many tickets were filed at once. Please submit again.'),
293
+ })
294
+ }
295
+ throw err
296
+ }
297
+
298
+ const { task, ticket } = result
299
+ await emitCrudSideEffects({
300
+ dataEngine: ctx.container.resolve('dataEngine'),
301
+ action: 'created',
302
+ entity: task,
303
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
304
+ events: taskCrudEvents,
305
+ indexer: taskCrudIndexer,
306
+ })
307
+ const basePayload = { tenantId: task.tenantId, organizationId: task.organizationId }
308
+ emitSupportEvent('staff.support_ticket.created', {
309
+ ...basePayload,
310
+ id: ticket.id,
311
+ taskId: task.id,
312
+ projectId: task.timeProjectId,
313
+ customerId: parsed.customerId,
314
+ supportReference: ticket.supportReference,
315
+ categoryValue: ticket.categoryValue,
316
+ })
317
+ emitSupportEvent('staff.support_ticket.activity_recorded', {
318
+ ...basePayload,
319
+ id: ticket.id,
320
+ taskId: task.id,
321
+ projectId: task.timeProjectId,
322
+ kind: 'created',
323
+ })
324
+ if (task.assigneeStaffMemberId) emitTaskAssigneeChanged(task, null, 'default', null)
325
+
326
+ return {
327
+ taskId: task.id,
328
+ ticketId: ticket.id,
329
+ supportReference: ticket.supportReference,
330
+ timeProjectId: task.timeProjectId,
331
+ tenantId: task.tenantId,
332
+ organizationId: task.organizationId,
333
+ }
334
+ },
335
+ buildLog: async ({ result }) => {
336
+ const { translate } = await resolveTranslations()
337
+ return {
338
+ actionLabel: translate('staff.audit.support.tickets.create', 'File support ticket'),
339
+ resourceKind: SUPPORT_TICKET_RESOURCE_KIND,
340
+ resourceId: result.ticketId,
341
+ parentResourceKind: 'staff.timesheets.task',
342
+ parentResourceId: result.taskId,
343
+ tenantId: result.tenantId,
344
+ organizationId: result.organizationId,
345
+ context: { cacheAliases: [TASK_LIST_CACHE_RESOURCE] },
346
+ payload: {
347
+ undo: {
348
+ taskId: result.taskId,
349
+ ticketId: result.ticketId,
350
+ tenantId: result.tenantId,
351
+ organizationId: result.organizationId,
352
+ } satisfies CreateUndoPayload,
353
+ },
354
+ }
355
+ },
356
+ undo: async ({ logEntry, ctx }) => {
357
+ const payload = extractUndoPayload<CreateUndoPayload>(logEntry)
358
+ if (!payload?.taskId) return
359
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
360
+ const where = { tenantId: payload.tenantId, organizationId: payload.organizationId }
361
+ const task = await em.findOne(StaffTimeTask, { id: payload.taskId, ...where })
362
+ const ticket = await findOneWithDecryption(em, StaffSupportTicket, { id: payload.ticketId, ...where }, undefined, where)
363
+ await withAtomicFlush(em, [() => {
364
+ const now = new Date()
365
+ if (task && !task.deletedAt) {
366
+ task.deletedAt = now
367
+ task.updatedAt = now
368
+ }
369
+ if (ticket && !ticket.deletedAt) {
370
+ ticket.deletedAt = now
371
+ ticket.updatedAt = now
372
+ }
373
+ }], { transaction: true, label: `${staffSupportTicketCommandIds.createFromPortal}.undo` })
374
+ if (task) {
375
+ await emitCrudUndoSideEffects({
376
+ dataEngine: ctx.container.resolve('dataEngine'),
377
+ action: 'deleted',
378
+ entity: task,
379
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
380
+ events: taskCrudEvents,
381
+ indexer: taskCrudIndexer,
382
+ })
383
+ }
384
+ },
385
+ }
386
+
387
+ const portalMessageCommand: CommandHandler<SupportTicketPortalMessageInput, SupportTicketMessageResult> = {
388
+ id: staffSupportTicketCommandIds.portalMessage,
389
+ async execute(rawInput, ctx) {
390
+ const parsed = supportTicketPortalMessageSchema.parse(rawInput)
391
+ const { translate } = await resolveTranslations()
392
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
393
+ const task = await em.findOne(StaffTimeTask, {
394
+ id: parsed.taskId,
395
+ tenantId: parsed.tenantId,
396
+ organizationId: parsed.organizationId,
397
+ customerId: parsed.customerId,
398
+ source: 'portal',
399
+ deletedAt: null,
400
+ })
401
+ if (!task) throw ticketNotFoundError(translate)
402
+ const ticket = await findOneWithDecryption(
403
+ em,
404
+ StaffSupportTicket,
405
+ { taskId: task.id, tenantId: parsed.tenantId, organizationId: parsed.organizationId, deletedAt: null },
406
+ undefined,
407
+ { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
408
+ )
409
+ if (!ticket) throw ticketNotFoundError(translate)
410
+
411
+ let comment: StaffTimeTaskComment | null = null
412
+ let reopenedFromStatusId: string | null = null
413
+ await withAtomicFlush(
414
+ em,
415
+ [
416
+ async () => {
417
+ const currentStatus = await em.findOne(StaffTimeTaskStatus, {
418
+ id: task.taskStatusId,
419
+ tenantId: task.tenantId,
420
+ organizationId: task.organizationId,
421
+ })
422
+ if (!currentStatus?.isDone) return
423
+ const defaultStatus = await em.findOne(StaffTimeTaskStatus, {
424
+ timeProjectId: task.timeProjectId,
425
+ tenantId: task.tenantId,
426
+ organizationId: task.organizationId,
427
+ isDefault: true,
428
+ deletedAt: null,
429
+ })
430
+ if (!defaultStatus) return
431
+ reopenedFromStatusId = task.taskStatusId
432
+ applyStatusToTask(task, defaultStatus, new Date())
433
+ task.updatedAt = new Date()
434
+ },
435
+ () => {
436
+ const now = new Date()
437
+ comment = em.create(StaffTimeTaskComment, {
438
+ tenantId: parsed.tenantId,
439
+ organizationId: parsed.organizationId,
440
+ taskId: task.id,
441
+ body: parsed.body,
442
+ authorUserId: null,
443
+ visibility: 'customer',
444
+ authorKind: 'customer',
445
+ authorCustomerUserId: parsed.customerUserId,
446
+ authorDisplaySnapshot: { displayName: parsed.reporterDisplayName ?? parsed.customerDisplayName ?? '' },
447
+ createdAt: now,
448
+ updatedAt: now,
449
+ deletedAt: null,
450
+ })
451
+ em.persist(comment)
452
+ },
453
+ ],
454
+ { transaction: true, label: staffSupportTicketCommandIds.portalMessage },
455
+ )
456
+ const record = comment as StaffTimeTaskComment | null
457
+ if (!record) throw ticketNotFoundError(translate)
458
+
459
+ await emitCrudSideEffects({
460
+ dataEngine: ctx.container.resolve('dataEngine'),
461
+ action: 'created',
462
+ entity: record,
463
+ identifiers: { id: record.id, organizationId: record.organizationId, tenantId: record.tenantId },
464
+ events: portalVisibleCommentEvents,
465
+ })
466
+ const basePayload = { tenantId: task.tenantId, organizationId: task.organizationId }
467
+ if (reopenedFromStatusId) {
468
+ await emitCrudSideEffects({
469
+ dataEngine: ctx.container.resolve('dataEngine'),
470
+ action: 'updated',
471
+ entity: task,
472
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
473
+ events: taskCrudEvents,
474
+ indexer: taskCrudIndexer,
475
+ })
476
+ void emitStaffEvent('staff.timesheets.time_task.status_changed', {
477
+ id: task.id,
478
+ ...basePayload,
479
+ taskId: task.id,
480
+ timeProjectId: task.timeProjectId,
481
+ previousTaskStatusId: reopenedFromStatusId,
482
+ taskStatusId: task.taskStatusId,
483
+ }).catch((err) => {
484
+ logger.error('staff support reopen status event failed', { err, taskId: task.id })
485
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_reopen_event_failed' })
486
+ })
487
+ }
488
+ emitSupportEvent('staff.support_ticket.customer_replied', {
489
+ ...basePayload,
490
+ id: ticket.id,
491
+ taskId: task.id,
492
+ projectId: task.timeProjectId,
493
+ commentId: record.id,
494
+ supportReference: ticket.supportReference,
495
+ })
496
+ emitSupportEvent('staff.support_ticket.activity_recorded', {
497
+ ...basePayload,
498
+ id: ticket.id,
499
+ taskId: task.id,
500
+ projectId: task.timeProjectId,
501
+ kind: 'customer_replied',
502
+ })
503
+
504
+ return {
505
+ commentId: record.id,
506
+ taskId: task.id,
507
+ reopened: reopenedFromStatusId !== null,
508
+ previousTaskStatusId: reopenedFromStatusId,
509
+ tenantId: task.tenantId,
510
+ organizationId: task.organizationId,
511
+ }
512
+ },
513
+ buildLog: async ({ result }) => {
514
+ const { translate } = await resolveTranslations()
515
+ return {
516
+ actionLabel: translate('staff.audit.support.tickets.message', 'Customer message on support ticket'),
517
+ resourceKind: 'staff.timesheets.task_comment',
518
+ resourceId: result.commentId,
519
+ parentResourceKind: 'staff.timesheets.task',
520
+ parentResourceId: result.taskId,
521
+ tenantId: result.tenantId,
522
+ organizationId: result.organizationId,
523
+ context: { cacheAliases: [TASK_LIST_CACHE_RESOURCE] },
524
+ payload: {
525
+ undo: {
526
+ commentId: result.commentId,
527
+ taskId: result.taskId,
528
+ tenantId: result.tenantId,
529
+ organizationId: result.organizationId,
530
+ reopenedFromStatusId: result.previousTaskStatusId,
531
+ } satisfies MessageUndoPayload,
532
+ },
533
+ }
534
+ },
535
+ undo: async ({ logEntry, ctx }) => {
536
+ const payload = extractUndoPayload<MessageUndoPayload>(logEntry)
537
+ if (!payload?.commentId) return
538
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
539
+ const where = { tenantId: payload.tenantId, organizationId: payload.organizationId }
540
+ const comment = await em.findOne(StaffTimeTaskComment, { id: payload.commentId, ...where })
541
+ const task = payload.reopenedFromStatusId ? await em.findOne(StaffTimeTask, { id: payload.taskId, ...where }) : null
542
+ const previousStatus = payload.reopenedFromStatusId
543
+ ? await em.findOne(StaffTimeTaskStatus, { id: payload.reopenedFromStatusId, ...where })
544
+ : null
545
+ await withAtomicFlush(em, [() => {
546
+ const now = new Date()
547
+ if (comment && !comment.deletedAt) {
548
+ comment.deletedAt = now
549
+ comment.updatedAt = now
550
+ }
551
+ if (task && previousStatus) {
552
+ applyStatusToTask(task, previousStatus, now)
553
+ task.updatedAt = now
554
+ }
555
+ }], { transaction: true, label: `${staffSupportTicketCommandIds.portalMessage}.undo` })
556
+ const dataEngine = ctx.container.resolve('dataEngine') as DataEngine
557
+ if (comment) {
558
+ await emitCrudUndoSideEffects({
559
+ dataEngine,
560
+ action: 'deleted',
561
+ entity: comment,
562
+ identifiers: { id: comment.id, organizationId: comment.organizationId, tenantId: comment.tenantId },
563
+ events: portalVisibleCommentEvents,
564
+ })
565
+ }
566
+ if (task && previousStatus) {
567
+ await emitCrudUndoSideEffects({
568
+ dataEngine,
569
+ action: 'updated',
570
+ entity: task,
571
+ identifiers: { id: task.id, organizationId: task.organizationId, tenantId: task.tenantId },
572
+ events: taskCrudEvents,
573
+ indexer: taskCrudIndexer,
574
+ })
575
+ }
576
+ },
577
+ }
578
+
579
+ registerCommand(createFromPortalCommand)
580
+ registerCommand(portalMessageCommand)