@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,50 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ import { buildEncryptionMapBackfillSql } from '@open-mercato/shared/lib/encryption/migration-backfill';
3
+
4
+ export class Migration20261009180543_staff extends Migration {
5
+
6
+ override name = 'Migration20261009180543';
7
+
8
+ override up(): void | Promise<void> {
9
+ this.addSql(`create table "staff_support_routing_rules" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid not null, "category_value" text null, "time_project_id" uuid not null, "is_active" boolean not null default true, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, primary key ("id"));`);
10
+ this.addSql(`create index "staff_support_routing_rules_tenant_org_idx" on "staff_support_routing_rules" ("tenant_id", "organization_id");`);
11
+ this.addSql(`create unique index "staff_support_routing_rules_category_unique_idx" on "staff_support_routing_rules" ("tenant_id", "organization_id", coalesce("category_value", '')) where "deleted_at" is null;`);
12
+
13
+ this.addSql(`create table "staff_support_tickets" ("id" uuid not null default gen_random_uuid(), "tenant_id" uuid not null, "organization_id" uuid not null, "task_id" uuid not null, "customer_snapshot" jsonb null, "reported_by_customer_user_id" uuid null, "support_reference" text not null, "support_sequence_number" int not null, "category_value" text not null, "category_snapshot" jsonb not null, "routed_via" text not null, "customer_request_snapshot" jsonb not null, "unnotified_at" timestamptz null, "portal_status_published" text null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, primary key ("id"));`);
14
+ this.addSql(`create unique index "staff_support_tickets_reference_unique_idx" on "staff_support_tickets" ("tenant_id", "organization_id", "support_reference") where "deleted_at" is null;`);
15
+ this.addSql(`create index "staff_support_tickets_sequence_idx" on "staff_support_tickets" ("tenant_id", "organization_id", "support_sequence_number");`);
16
+ this.addSql(`create unique index "staff_support_tickets_task_unique_idx" on "staff_support_tickets" ("tenant_id", "organization_id", "task_id") where "deleted_at" is null;`);
17
+ this.addSql(`create index "staff_support_tickets_tenant_org_idx" on "staff_support_tickets" ("tenant_id", "organization_id");`);
18
+
19
+ this.addSql(`alter table "staff_time_projects" add "default_assignee_staff_member_id" uuid null;`);
20
+
21
+ this.addSql(`alter table "staff_time_project_members" add "is_assigner" boolean not null default false;`);
22
+ this.addSql(`create index "staff_time_project_members_assigner_idx" on "staff_time_project_members" ("tenant_id", "organization_id", "time_project_id") where "is_assigner" = true and "status" = 'active' and "deleted_at" is null;`);
23
+
24
+ this.addSql(`alter table "staff_time_tasks" add "source" text not null default 'internal', add "customer_id" uuid null;`);
25
+ this.addSql(`create index "staff_time_tasks_portal_customer_idx" on "staff_time_tasks" ("tenant_id", "organization_id", "customer_id", "updated_at") where "source" = 'portal' and "deleted_at" is null;`);
26
+
27
+ this.addSql(`alter table "staff_time_task_comments" add "visibility" text not null default 'internal', add "author_kind" text not null default 'staff', add "author_customer_user_id" uuid null, add "author_display_snapshot" jsonb null;`);
28
+
29
+ this.addSql(buildEncryptionMapBackfillSql({
30
+ entityId: 'staff:staff_support_ticket',
31
+ fields: [{ field: 'customer_request_snapshot' }],
32
+ }));
33
+ }
34
+
35
+ override down(): void | Promise<void> {
36
+ this.addSql(`drop table if exists "staff_support_tickets";`);
37
+ this.addSql(`drop table if exists "staff_support_routing_rules";`);
38
+
39
+ this.addSql(`drop index "staff_time_project_members_assigner_idx";`);
40
+ this.addSql(`alter table "staff_time_project_members" drop column "is_assigner";`);
41
+
42
+ this.addSql(`alter table "staff_time_projects" drop column "default_assignee_staff_member_id";`);
43
+
44
+ this.addSql(`alter table "staff_time_task_comments" drop column "visibility", drop column "author_kind", drop column "author_customer_user_id", drop column "author_display_snapshot";`);
45
+
46
+ this.addSql(`drop index "staff_time_tasks_portal_customer_idx";`);
47
+ this.addSql(`alter table "staff_time_tasks" drop column "source", drop column "customer_id";`);
48
+ }
49
+
50
+ }
@@ -235,6 +235,98 @@ export const notificationTypes: NotificationTypeDefinition[] = [
235
235
  linkHref: '/backend/staff/time-tracking/timesheet',
236
236
  expiresAfterHours: 168, // 7 days
237
237
  },
238
+ {
239
+ // Customer support tickets: raised for every portal ticket. The audience is the
240
+ // assignee (when the project default applied) plus the project's active
241
+ // assigners, falling back through owner and organization project managers.
242
+ type: 'staff.support_ticket.created',
243
+ channels: ['in_app', 'email'],
244
+ module: 'staff',
245
+ titleKey: 'staff.notifications.supportTicket.created.title',
246
+ bodyKey: 'staff.notifications.supportTicket.created.body',
247
+ icon: 'life-buoy',
248
+ severity: 'info',
249
+ actions: [
250
+ {
251
+ id: 'open-ticket',
252
+ labelKey: 'staff.notifications.supportTicket.actions.open',
253
+ variant: 'default',
254
+ icon: 'external-link',
255
+ href: '/backend/staff/time-tracking/support?task={sourceEntityId}',
256
+ },
257
+ ],
258
+ primaryActionId: 'open-ticket',
259
+ linkHref: '/backend/staff/time-tracking/support?task={sourceEntityId}',
260
+ expiresAfterHours: 336,
261
+ },
262
+ {
263
+ type: 'staff.support_ticket.customer_replied',
264
+ channels: ['in_app', 'email'],
265
+ module: 'staff',
266
+ titleKey: 'staff.notifications.supportTicket.customerReplied.title',
267
+ bodyKey: 'staff.notifications.supportTicket.customerReplied.body',
268
+ icon: 'message-square',
269
+ severity: 'info',
270
+ actions: [
271
+ {
272
+ id: 'open-ticket',
273
+ labelKey: 'staff.notifications.supportTicket.actions.open',
274
+ variant: 'default',
275
+ icon: 'external-link',
276
+ href: '/backend/staff/time-tracking/support?task={sourceEntityId}',
277
+ },
278
+ ],
279
+ primaryActionId: 'open-ticket',
280
+ linkHref: '/backend/staff/time-tracking/support?task={sourceEntityId}',
281
+ expiresAfterHours: 336,
282
+ },
283
+ {
284
+ // A task in the project became unassigned (released or cleared): tells the
285
+ // project's assigners there is claimable work.
286
+ type: 'staff.timesheets.time_task.released',
287
+ channels: ['in_app'],
288
+ module: 'staff',
289
+ titleKey: 'staff.notifications.timeTask.released.title',
290
+ bodyKey: 'staff.notifications.timeTask.released.body',
291
+ icon: 'hand',
292
+ severity: 'info',
293
+ actions: [
294
+ {
295
+ id: 'open-task',
296
+ labelKey: 'staff.notifications.timeTask.actions.open',
297
+ variant: 'default',
298
+ icon: 'external-link',
299
+ href: '/backend/staff/time-tracking/board?task={sourceEntityId}',
300
+ },
301
+ ],
302
+ primaryActionId: 'open-task',
303
+ linkHref: '/backend/staff/time-tracking/board?task={sourceEntityId}',
304
+ expiresAfterHours: 168,
305
+ },
306
+ {
307
+ // Someone else assigned the recipient to a task through the task editor. Not sent
308
+ // for claims (self-assignment) or for the portal default assignee, who hears
309
+ // about the intake once through `staff.support_ticket.created`.
310
+ type: 'staff.timesheets.time_task.assigned',
311
+ channels: ['in_app'],
312
+ module: 'staff',
313
+ titleKey: 'staff.notifications.timeTask.assigned.title',
314
+ bodyKey: 'staff.notifications.timeTask.assigned.body',
315
+ icon: 'user-check',
316
+ severity: 'info',
317
+ actions: [
318
+ {
319
+ id: 'open-task',
320
+ labelKey: 'staff.notifications.timeTask.actions.open',
321
+ variant: 'default',
322
+ icon: 'external-link',
323
+ href: '/backend/staff/time-tracking/board?task={sourceEntityId}',
324
+ },
325
+ ],
326
+ primaryActionId: 'open-task',
327
+ linkHref: '/backend/staff/time-tracking/board?task={sourceEntityId}',
328
+ expiresAfterHours: 168,
329
+ },
238
330
  ]
239
331
 
240
332
  export default notificationTypes
@@ -2,6 +2,8 @@ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
2
  import { appendWidgetsToRoles } from '@open-mercato/core/modules/dashboards/lib/role-widgets'
3
3
  import { seedStaffAddressTypes, seedStaffTeamExamples } from './lib/seeds'
4
4
  import { seedStaffTimeTrackingExamples } from './lib/timeTrackingSeeds'
5
+ import { resolveContainerProjectCodeGenerator, seedSupportDefaults } from './lib/support/seedDefaults'
6
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
7
 
6
8
  const TIMESHEETS_DASHBOARD_WIDGET_IDS = [
7
9
  'staff.timesheets.timeReporting',
@@ -12,6 +14,8 @@ export const setup: ModuleSetupConfig = {
12
14
  seedDefaults: async (ctx) => {
13
15
  const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
14
16
  await seedStaffAddressTypes(ctx.em, scope)
17
+ const { translate } = await resolveTranslations()
18
+ await seedSupportDefaults(ctx.em, scope, { translate, generateCode: resolveContainerProjectCodeGenerator(ctx.container) })
15
19
  await appendWidgetsToRoles(ctx.em, {
16
20
  tenantId: ctx.tenantId,
17
21
  organizationId: ctx.organizationId,
@@ -56,8 +60,8 @@ export const setup: ModuleSetupConfig = {
56
60
  * `portal_admin` holds `portal.*` and needs no entry.
57
61
  */
58
62
  defaultCustomerRoleFeatures: {
59
- buyer: ['portal.time_reports.view'],
60
- viewer: ['portal.time_reports.view'],
63
+ buyer: ['portal.time_reports.view', 'portal.support_tickets.view', 'portal.support_tickets.manage'],
64
+ viewer: ['portal.time_reports.view', 'portal.support_tickets.view'],
61
65
  },
62
66
  }
63
67
 
@@ -0,0 +1,35 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
4
+ import { publishTicketReply, readEventScope } from '../lib/support/portalBroadcast'
5
+
6
+ const logger = createLogger('staff').child({ component: 'subscribers/support-portal-comment-created' })
7
+
8
+ /**
9
+ * Ephemeral on purpose: `portal_updated` is portal-only, and the event bus does not
10
+ * relay portal-only events across processes (open issue #2973). Run inline in the web
11
+ * process that executed the command, where the portal SSE stream lives. Make this
12
+ * persistent again once #2973 lands.
13
+ */
14
+ export const metadata = {
15
+ event: 'staff.timesheets.time_task_comment.created',
16
+ persistent: false,
17
+ id: 'staff:support-portal-comment-created',
18
+ }
19
+
20
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
21
+
22
+ type Payload = Record<string, unknown>
23
+
24
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
25
+ try {
26
+ if (payload.portalVisible !== true) return
27
+ const scope = readEventScope(payload)
28
+ const commentId = typeof payload.id === 'string' ? payload.id : null
29
+ if (!scope || !commentId) return
30
+ await publishTicketReply((ctx.resolve('em') as EntityManager).fork(), commentId, scope)
31
+ } catch (err) {
32
+ logger.error('staff:support-portal-comment-created failed', { err, id: payload?.id ?? null })
33
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_portal_comment_created_failed' })
34
+ }
35
+ }
@@ -0,0 +1,35 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
4
+ import { publishTicketReply, readEventScope } from '../lib/support/portalBroadcast'
5
+
6
+ const logger = createLogger('staff').child({ component: 'subscribers/support-portal-comment-deleted' })
7
+
8
+ /**
9
+ * Ephemeral on purpose: `portal_updated` is portal-only, and the event bus does not
10
+ * relay portal-only events across processes (open issue #2973). Run inline in the web
11
+ * process that executed the command, where the portal SSE stream lives. Make this
12
+ * persistent again once #2973 lands.
13
+ */
14
+ export const metadata = {
15
+ event: 'staff.timesheets.time_task_comment.deleted',
16
+ persistent: false,
17
+ id: 'staff:support-portal-comment-deleted',
18
+ }
19
+
20
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
21
+
22
+ type Payload = Record<string, unknown>
23
+
24
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
25
+ try {
26
+ if (payload.portalVisible !== true) return
27
+ const scope = readEventScope(payload)
28
+ const commentId = typeof payload.id === 'string' ? payload.id : null
29
+ if (!scope || !commentId) return
30
+ await publishTicketReply((ctx.resolve('em') as EntityManager).fork(), commentId, scope)
31
+ } catch (err) {
32
+ logger.error('staff:support-portal-comment-deleted failed', { err, id: payload?.id ?? null })
33
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_portal_comment_deleted_failed' })
34
+ }
35
+ }
@@ -0,0 +1,35 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
4
+ import { publishTicketReply, readEventScope } from '../lib/support/portalBroadcast'
5
+
6
+ const logger = createLogger('staff').child({ component: 'subscribers/support-portal-comment-updated' })
7
+
8
+ /**
9
+ * Ephemeral on purpose: `portal_updated` is portal-only, and the event bus does not
10
+ * relay portal-only events across processes (open issue #2973). Run inline in the web
11
+ * process that executed the command, where the portal SSE stream lives. Make this
12
+ * persistent again once #2973 lands.
13
+ */
14
+ export const metadata = {
15
+ event: 'staff.timesheets.time_task_comment.updated',
16
+ persistent: false,
17
+ id: 'staff:support-portal-comment-updated',
18
+ }
19
+
20
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
21
+
22
+ type Payload = Record<string, unknown>
23
+
24
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
25
+ try {
26
+ if (payload.portalVisible !== true) return
27
+ const scope = readEventScope(payload)
28
+ const commentId = typeof payload.id === 'string' ? payload.id : null
29
+ if (!scope || !commentId) return
30
+ await publishTicketReply((ctx.resolve('em') as EntityManager).fork(), commentId, scope)
31
+ } catch (err) {
32
+ logger.error('staff:support-portal-comment-updated failed', { err, id: payload?.id ?? null })
33
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_portal_comment_updated_failed' })
34
+ }
35
+ }
@@ -0,0 +1,36 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { fanOutStatusColumn, readEventScope } from '../lib/support/portalBroadcast'
4
+
5
+ const logger = createLogger('staff').child({ component: 'subscribers/support-portal-status-column-updated' })
6
+
7
+ /**
8
+ * Persistent, unlike the other support-portal subscribers: re-deriving every portal
9
+ * ticket in a column is the one potentially expensive fan-out, so it runs on the
10
+ * queue (retried on failure; `publishTaskPortalStatus` is compare-and-publish, so a
11
+ * retry is idempotent) instead of inside the admin's request. Column flag edits are
12
+ * rare, and until #2973 lands a `portal_updated` emitted from the worker may not
13
+ * reach an open portal page live; the portal derives status on read, so the page is
14
+ * correct on its next load.
15
+ */
16
+ export const metadata = {
17
+ event: 'staff.timesheets.time_task_status.updated',
18
+ persistent: true,
19
+ id: 'staff:support-portal-status-column-updated',
20
+ }
21
+
22
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
23
+
24
+ type Payload = Record<string, unknown>
25
+
26
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
27
+ try {
28
+ const scope = readEventScope(payload)
29
+ const statusId = typeof payload.id === 'string' ? payload.id : null
30
+ if (!scope || !statusId) return
31
+ await fanOutStatusColumn((ctx.resolve('em') as EntityManager).fork(), statusId, scope)
32
+ } catch (err) {
33
+ logger.error('staff:support-portal-status-column-updated failed', { err, id: payload?.id ?? null })
34
+ throw err
35
+ }
36
+ }
@@ -0,0 +1,34 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
4
+ import { publishTaskPortalStatus, readEventScope } from '../lib/support/portalBroadcast'
5
+
6
+ const logger = createLogger('staff').child({ component: 'subscribers/support-portal-task-status-changed' })
7
+
8
+ /**
9
+ * Ephemeral on purpose: `portal_updated` is portal-only, and the event bus does not
10
+ * relay portal-only events across processes (open issue #2973). Run inline in the web
11
+ * process that executed the command, where the portal SSE stream lives. Make this
12
+ * persistent again once #2973 lands.
13
+ */
14
+ export const metadata = {
15
+ event: 'staff.timesheets.time_task.status_changed',
16
+ persistent: false,
17
+ id: 'staff:support-portal-task-status-changed',
18
+ }
19
+
20
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
21
+
22
+ type Payload = Record<string, unknown>
23
+
24
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
25
+ try {
26
+ const scope = readEventScope(payload)
27
+ const taskId = typeof payload.id === 'string' ? payload.id : null
28
+ if (!scope || !taskId) return
29
+ await publishTaskPortalStatus((ctx.resolve('em') as EntityManager).fork(), taskId, scope, { always: true })
30
+ } catch (err) {
31
+ logger.error('staff:support-portal-task-status-changed failed', { err, id: payload?.id ?? null })
32
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_portal_task_status_changed_failed' })
33
+ }
34
+ }
@@ -0,0 +1,34 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
4
+ import { publishTaskPortalStatus, readEventScope } from '../lib/support/portalBroadcast'
5
+
6
+ const logger = createLogger('staff').child({ component: 'subscribers/support-portal-task-updated' })
7
+
8
+ /**
9
+ * Ephemeral on purpose: `portal_updated` is portal-only, and the event bus does not
10
+ * relay portal-only events across processes (open issue #2973). Run inline in the web
11
+ * process that executed the command, where the portal SSE stream lives. Make this
12
+ * persistent again once #2973 lands.
13
+ */
14
+ export const metadata = {
15
+ event: 'staff.timesheets.time_task.updated',
16
+ persistent: false,
17
+ id: 'staff:support-portal-task-updated',
18
+ }
19
+
20
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
21
+
22
+ type Payload = Record<string, unknown>
23
+
24
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
25
+ try {
26
+ const scope = readEventScope(payload)
27
+ const taskId = typeof payload.id === 'string' ? payload.id : null
28
+ if (!scope || !taskId) return
29
+ await publishTaskPortalStatus((ctx.resolve('em') as EntityManager).fork(), taskId, scope)
30
+ } catch (err) {
31
+ logger.error('staff:support-portal-task-updated failed', { err, id: payload?.id ?? null })
32
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_portal_task_updated_failed' })
33
+ }
34
+ }
@@ -0,0 +1,27 @@
1
+ import { createLogger } from '@open-mercato/shared/lib/logger'
2
+ import { notifySupportTicket } from '../lib/support/notifications'
3
+
4
+ const logger = createLogger('staff').child({ component: 'subscribers/support-ticket-created-notification' })
5
+
6
+ export const metadata = {
7
+ event: 'staff.support_ticket.created',
8
+ persistent: true,
9
+ id: 'staff:support-ticket-created-notification',
10
+ }
11
+
12
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
13
+
14
+ type Payload = Record<string, unknown>
15
+
16
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
17
+ try {
18
+ const taskId = typeof payload.taskId === 'string' ? payload.taskId : null
19
+ const tenantId = typeof payload.tenantId === 'string' ? payload.tenantId : null
20
+ const organizationId = typeof payload.organizationId === 'string' ? payload.organizationId : null
21
+ if (!taskId || !tenantId || !organizationId) return
22
+ await notifySupportTicket(ctx, { taskId, tenantId, organizationId, kind: 'created' })
23
+ } catch (err) {
24
+ logger.error('staff:support-ticket-created-notification failed', { err, id: payload?.id ?? null })
25
+ throw err
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ import { createLogger } from '@open-mercato/shared/lib/logger'
2
+ import { notifySupportTicket } from '../lib/support/notifications'
3
+
4
+ const logger = createLogger('staff').child({ component: 'subscribers/support-ticket-customer-replied-notification' })
5
+
6
+ export const metadata = {
7
+ event: 'staff.support_ticket.customer_replied',
8
+ persistent: true,
9
+ id: 'staff:support-ticket-customer-replied-notification',
10
+ }
11
+
12
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
13
+
14
+ type Payload = Record<string, unknown>
15
+
16
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
17
+ try {
18
+ const taskId = typeof payload.taskId === 'string' ? payload.taskId : null
19
+ const tenantId = typeof payload.tenantId === 'string' ? payload.tenantId : null
20
+ const organizationId = typeof payload.organizationId === 'string' ? payload.organizationId : null
21
+ if (!taskId || !tenantId || !organizationId) return
22
+ await notifySupportTicket(ctx, { taskId, tenantId, organizationId, kind: 'customer_replied' })
23
+ } catch (err) {
24
+ logger.error('staff:support-ticket-customer-replied-notification failed', { err, id: payload?.id ?? null })
25
+ throw err
26
+ }
27
+ }
@@ -0,0 +1,29 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { clearSupportTicketUnnotified } from '../lib/support/notifications'
4
+
5
+ const logger = createLogger('staff').child({ component: 'subscribers/support-ticket-unnotified-clear' })
6
+
7
+ export const metadata = {
8
+ event: 'staff.timesheets.time_task.assignee_changed',
9
+ persistent: true,
10
+ id: 'staff:support-ticket-unnotified-clear',
11
+ }
12
+
13
+ type ResolverContext = { resolve: <T = unknown>(name: string) => T }
14
+
15
+ type Payload = Record<string, unknown>
16
+
17
+ export default async function handle(payload: Payload, ctx: ResolverContext): Promise<void> {
18
+ try {
19
+ const taskId = typeof payload.id === 'string' ? payload.id : null
20
+ const tenantId = typeof payload.tenantId === 'string' ? payload.tenantId : null
21
+ const organizationId = typeof payload.organizationId === 'string' ? payload.organizationId : null
22
+ if (!taskId || !tenantId || !organizationId || !payload.assigneeStaffMemberId) return
23
+ const em = (ctx.resolve('em') as EntityManager).fork()
24
+ await clearSupportTicketUnnotified(em, { taskId, tenantId, organizationId })
25
+ } catch (err) {
26
+ logger.error('staff:support-ticket-unnotified-clear failed', { err, id: payload?.id ?? null })
27
+ throw err
28
+ }
29
+ }
@@ -0,0 +1,102 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
+ import { notificationTypes } from '../notifications'
5
+ import { StaffTeamMember, StaffTimeTask } from '../data/entities'
6
+ import { resolveAssignmentGraceDaysFromContainer } from '../lib/time-tracking/projectAssignment'
7
+ import { notifyProjectWork, resolveProjectWorkRecipients } from '../lib/time-tracking/workRecipients'
8
+
9
+ const logger = createLogger('staff').child({ component: 'subscribers/time-task-assignee-notification' })
10
+
11
+ const RELEASED_TYPE = 'staff.timesheets.time_task.released'
12
+ const ASSIGNED_TYPE = 'staff.timesheets.time_task.assigned'
13
+
14
+ export const metadata = {
15
+ event: 'staff.timesheets.time_task.assignee_changed',
16
+ persistent: true,
17
+ id: 'staff:time-task-assignee-notification',
18
+ }
19
+
20
+ export type TaskAssigneeChangedPayload = {
21
+ id?: string | null
22
+ tenantId?: string | null
23
+ organizationId?: string | null
24
+ timeProjectId?: string | null
25
+ previousAssigneeStaffMemberId?: string | null
26
+ assigneeStaffMemberId?: string | null
27
+ changeKind?: 'default' | 'claim' | 'release' | 'manual' | null
28
+ actorUserId?: string | null
29
+ }
30
+
31
+ type ResolverContext = {
32
+ resolve: <T = unknown>(name: string) => T
33
+ }
34
+
35
+ export type AssigneeNotificationPlan = 'released' | 'assigned' | null
36
+
37
+ export function planAssigneeNotification(payload: TaskAssigneeChangedPayload): AssigneeNotificationPlan {
38
+ const kind = payload.changeKind ?? null
39
+ const next = payload.assigneeStaffMemberId ?? null
40
+ if (kind === 'release') return 'released'
41
+ if (kind === 'manual') return next ? 'assigned' : 'released'
42
+ return null
43
+ }
44
+
45
+ export default async function handle(payload: TaskAssigneeChangedPayload, ctx: ResolverContext): Promise<void> {
46
+ try {
47
+ const plan = planAssigneeNotification(payload)
48
+ const taskId = payload.id ?? null
49
+ const tenantId = payload.tenantId ?? null
50
+ const organizationId = payload.organizationId ?? null
51
+ if (!plan || !taskId || !tenantId || !organizationId) return
52
+ const scope = { tenantId, organizationId }
53
+ const em = (ctx.resolve('em') as EntityManager).fork()
54
+ const task = await em.findOne(StaffTimeTask, { id: taskId, tenantId, organizationId, deletedAt: null })
55
+ if (!task) return
56
+ const build = {
57
+ bodyVariables: { reference: task.reference, title: task.title },
58
+ sourceEntityType: 'staff:staff_time_task',
59
+ sourceEntityId: task.id,
60
+ linkHref: `/backend/staff/time-tracking/board?projectId=${encodeURIComponent(task.timeProjectId)}&task=${encodeURIComponent(task.id)}`,
61
+ groupKey: `staff:time-task-${plan}:${task.id}`,
62
+ }
63
+
64
+ if (plan === 'assigned') {
65
+ const typeDef = notificationTypes.find((type) => type.type === ASSIGNED_TYPE)
66
+ if (!typeDef || !task.assigneeStaffMemberId) return
67
+ const assignee = await findOneWithDecryption(
68
+ em,
69
+ StaffTeamMember,
70
+ { id: task.assigneeStaffMemberId, tenantId, organizationId, deletedAt: null },
71
+ undefined,
72
+ scope,
73
+ )
74
+ const assigneeUserId = assignee?.userId ?? null
75
+ if (!assigneeUserId || assigneeUserId === (payload.actorUserId ?? null)) return
76
+ await notifyProjectWork(ctx, scope, { typeDef, recipients: { step: 1, userIds: [assigneeUserId] }, build })
77
+ return
78
+ }
79
+
80
+ const typeDef = notificationTypes.find((type) => type.type === RELEASED_TYPE)
81
+ if (!typeDef) return
82
+ const graceDays = await resolveAssignmentGraceDaysFromContainer(ctx, tenantId)
83
+ const recipients = await resolveProjectWorkRecipients(em, scope, {
84
+ timeProjectId: task.timeProjectId,
85
+ actorUserId: payload.actorUserId ?? null,
86
+ graceDays,
87
+ })
88
+ const result = await notifyProjectWork(ctx, scope, { typeDef, recipients, build })
89
+ if (result.notifiedCount === 0) {
90
+ logger.warn('staff released-task notification reached nobody', {
91
+ tenantId,
92
+ organizationId,
93
+ timeProjectId: task.timeProjectId,
94
+ taskId: task.id,
95
+ step: result.step,
96
+ })
97
+ }
98
+ } catch (err) {
99
+ logger.error('staff task assignee notification failed', { err, taskId: payload.id ?? null })
100
+ throw err
101
+ }
102
+ }