@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,595 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { LockMode } from '@mikro-orm/core'
3
+ import type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
4
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
5
+ import { CrudHttpError, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'
6
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
+ import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
8
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
9
+ import type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
10
+ import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
11
+ import { StaffSupportRoutingRule, StaffTimeProject, StaffTimeTask, StaffTimeTaskStatus } from '../data/entities'
12
+ import { staffTimeProjectCrudEvents, staffTimeProjectCrudIndexer } from '../lib/crud'
13
+ import {
14
+ staffSupportFallbackProjectCreateSchema,
15
+ staffSupportRoutingRuleCreateSchema,
16
+ staffSupportRoutingRuleUpdateSchema,
17
+ type StaffSupportFallbackProjectCreateInput,
18
+ type StaffSupportRoutingRuleCreateInput,
19
+ type StaffSupportRoutingRuleUpdateInput,
20
+ } from '../data/validators'
21
+ import { findSupportTicketCategory } from '../lib/support/dictionaries'
22
+ import { ensureFallbackProjectAndRule, resolveContainerProjectCodeGenerator } from '../lib/support/seedDefaults'
23
+ import {
24
+ applyScopeToWhere,
25
+ commandActorScope,
26
+ commandInputScope,
27
+ ensureOrganizationScope,
28
+ ensureTenantScope,
29
+ explicitStaffCommandScope,
30
+ extractUndoPayload,
31
+ lockLiveProjectForWrite,
32
+ scopedStaffSnapshotWhere,
33
+ staffSnapshotScopeFromContext,
34
+ staffSnapshotScopeFromSnapshot,
35
+ type StaffSnapshotScope,
36
+ } from './shared'
37
+
38
+ export const staffSupportRoutingRuleCommandIds = {
39
+ create: 'staff.support.routing_rules.create',
40
+ update: 'staff.support.routing_rules.update',
41
+ delete: 'staff.support.routing_rules.delete',
42
+ createFallbackProject: 'staff.support.routing_rules.create_fallback_project',
43
+ } as const
44
+
45
+ export const SUPPORT_ROUTING_RULE_RESOURCE_KIND = 'staff.support.routing_rule'
46
+
47
+ const routingRuleIndexer: CrudIndexerConfig<StaffSupportRoutingRule> = {
48
+ entityType: 'staff:staff_support_routing_rule',
49
+ }
50
+
51
+ type Translate = (key: string, fallback: string) => string
52
+
53
+ type RoutingRuleSnapshot = {
54
+ id: string
55
+ tenantId: string
56
+ organizationId: string
57
+ categoryValue: string | null
58
+ timeProjectId: string
59
+ isActive: boolean
60
+ deletedAt: string | null
61
+ }
62
+
63
+ type RoutingRuleUndoPayload = {
64
+ before?: RoutingRuleSnapshot | null
65
+ after?: RoutingRuleSnapshot | null
66
+ }
67
+
68
+ function toSnapshot(rule: StaffSupportRoutingRule): RoutingRuleSnapshot {
69
+ return {
70
+ id: rule.id,
71
+ tenantId: rule.tenantId,
72
+ organizationId: rule.organizationId,
73
+ categoryValue: rule.categoryValue ?? null,
74
+ timeProjectId: rule.timeProjectId,
75
+ isActive: rule.isActive,
76
+ deletedAt: rule.deletedAt ? rule.deletedAt.toISOString() : null,
77
+ }
78
+ }
79
+
80
+ async function loadSnapshot(
81
+ em: EntityManager,
82
+ id: string,
83
+ scope?: StaffSnapshotScope | null,
84
+ ): Promise<RoutingRuleSnapshot | null> {
85
+ const rule = await em.findOne(StaffSupportRoutingRule, scopedStaffSnapshotWhere(id, scope))
86
+ return rule ? toSnapshot(rule) : null
87
+ }
88
+
89
+ function notFoundError(translate: Translate): CrudHttpError {
90
+ return new CrudHttpError(404, {
91
+ error: translate('staff.support.errors.routingRuleNotFound', 'Routing rule not found.'),
92
+ })
93
+ }
94
+
95
+ function fallbackRequiredError(translate: Translate): CrudHttpError {
96
+ return new CrudHttpError(422, {
97
+ code: 'support_fallback_rule_required',
98
+ error: translate(
99
+ 'staff.support.errors.fallbackRuleRequired',
100
+ 'The fallback rule cannot be deleted or paused. Point it at another project instead.',
101
+ ),
102
+ })
103
+ }
104
+
105
+ function duplicateRuleError(translate: Translate, isFallback: boolean): CrudHttpError {
106
+ const message = isFallback
107
+ ? translate('staff.support.errors.fallbackRuleExists', 'This organization already has a fallback rule.')
108
+ : translate('staff.support.errors.categoryRuleExists', 'This category already has a routing rule.')
109
+ return new CrudHttpError(409, {
110
+ code: 'support_routing_rule_duplicate',
111
+ error: message,
112
+ fieldErrors: { categoryValue: message },
113
+ })
114
+ }
115
+
116
+ async function requireRoutableProject(
117
+ em: EntityManager,
118
+ timeProjectId: string,
119
+ scope: { tenantId: string; organizationId: string },
120
+ translate: Translate,
121
+ options: { requireDefaultStatus?: boolean } = {},
122
+ ): Promise<void> {
123
+ const project = await lockLiveProjectForWrite(em, timeProjectId, explicitStaffCommandScope(scope.tenantId, scope.organizationId))
124
+ if (!project) {
125
+ const message = translate('staff.support.errors.projectNotFound', 'The selected project does not exist in this organization.')
126
+ throw new CrudHttpError(422, { code: 'support_project_not_found', error: message, fieldErrors: { timeProjectId: message } })
127
+ }
128
+ if (options.requireDefaultStatus === false) return
129
+ const defaultStatus = await em.findOne(
130
+ StaffTimeTaskStatus,
131
+ { timeProjectId, tenantId: scope.tenantId, organizationId: scope.organizationId, isDefault: true, deletedAt: null },
132
+ { fields: ['id'] },
133
+ )
134
+ if (!defaultStatus) {
135
+ const message = translate(
136
+ 'staff.support.errors.projectHasNoDefaultStatus',
137
+ 'The selected project has no default task status, so tickets cannot be filed on its board.',
138
+ )
139
+ throw new CrudHttpError(422, { code: 'support_project_no_default_status', error: message, fieldErrors: { timeProjectId: message } })
140
+ }
141
+ }
142
+
143
+ async function requireKnownCategory(
144
+ em: EntityManager,
145
+ categoryValue: string,
146
+ scope: { tenantId: string; organizationId: string },
147
+ translate: Translate,
148
+ ): Promise<void> {
149
+ const category = await findSupportTicketCategory(em, scope, categoryValue)
150
+ if (category) return
151
+ const message = translate('staff.support.errors.categoryNotFound', 'This category does not exist in the support categories dictionary.')
152
+ throw new CrudHttpError(422, { code: 'support_category_not_found', error: message, fieldErrors: { categoryValue: message } })
153
+ }
154
+
155
+ async function emitRuleSideEffects(
156
+ ctx: CommandRuntimeContext,
157
+ action: 'created' | 'updated' | 'deleted',
158
+ rule: StaffSupportRoutingRule,
159
+ ): Promise<void> {
160
+ await emitCrudSideEffects({
161
+ dataEngine: ctx.container.resolve('dataEngine'),
162
+ action,
163
+ entity: rule,
164
+ identifiers: { id: rule.id, organizationId: rule.organizationId, tenantId: rule.tenantId },
165
+ indexer: routingRuleIndexer,
166
+ })
167
+ }
168
+
169
+ const createRoutingRuleCommand: CommandHandler<StaffSupportRoutingRuleCreateInput, { ruleId: string }> = {
170
+ id: staffSupportRoutingRuleCommandIds.create,
171
+ async execute(rawInput, ctx) {
172
+ const parsed = staffSupportRoutingRuleCreateSchema.parse(rawInput)
173
+ const scope = commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
174
+ const { translate } = await resolveTranslations()
175
+ const target = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
176
+ const categoryValue = parsed.categoryValue ?? null
177
+ const isFallback = categoryValue === null
178
+ if (isFallback && parsed.isActive === false) throw fallbackRequiredError(translate)
179
+
180
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
181
+ let created: StaffSupportRoutingRule | null = null
182
+ try {
183
+ await withAtomicFlush(
184
+ em,
185
+ [
186
+ async () => {
187
+ await requireRoutableProject(em, parsed.timeProjectId, target, translate)
188
+ if (categoryValue !== null) await requireKnownCategory(em, categoryValue, target, translate)
189
+ const duplicate = await em.findOne(
190
+ StaffSupportRoutingRule,
191
+ applyScopeToWhere<StaffSupportRoutingRule>({ categoryValue, deletedAt: null }, scope),
192
+ { fields: ['id'] },
193
+ )
194
+ if (duplicate) throw duplicateRuleError(translate, isFallback)
195
+ },
196
+ () => {
197
+ created = em.create(StaffSupportRoutingRule, {
198
+ tenantId: parsed.tenantId,
199
+ organizationId: parsed.organizationId,
200
+ categoryValue,
201
+ timeProjectId: parsed.timeProjectId,
202
+ isActive: isFallback ? true : parsed.isActive ?? true,
203
+ createdAt: new Date(),
204
+ updatedAt: new Date(),
205
+ deletedAt: null,
206
+ })
207
+ em.persist(created)
208
+ },
209
+ ],
210
+ { transaction: true, label: staffSupportRoutingRuleCommandIds.create },
211
+ )
212
+ } catch (err) {
213
+ if (isUniqueViolation(err)) throw duplicateRuleError(translate, isFallback)
214
+ throw err
215
+ }
216
+ const record = created as StaffSupportRoutingRule | null
217
+ if (!record) throw notFoundError(translate)
218
+ await emitRuleSideEffects(ctx, 'created', record)
219
+ return { ruleId: record.id }
220
+ },
221
+ buildLog: async ({ result, ctx }) => {
222
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
223
+ const snapshot = await loadSnapshot(em, result.ruleId, staffSnapshotScopeFromContext(ctx))
224
+ if (!snapshot) return null
225
+ const { translate } = await resolveTranslations()
226
+ return {
227
+ actionLabel: translate('staff.audit.support.routingRules.create', 'Create support routing rule'),
228
+ resourceKind: SUPPORT_ROUTING_RULE_RESOURCE_KIND,
229
+ resourceId: snapshot.id,
230
+ tenantId: snapshot.tenantId,
231
+ organizationId: snapshot.organizationId,
232
+ snapshotAfter: snapshot,
233
+ payload: { undo: { after: snapshot } satisfies RoutingRuleUndoPayload },
234
+ }
235
+ },
236
+ undo: async ({ logEntry, ctx }) => {
237
+ const after = extractUndoPayload<RoutingRuleUndoPayload>(logEntry)?.after
238
+ if (!after) return
239
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
240
+ const rule = await em.findOne(StaffSupportRoutingRule, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)))
241
+ if (!rule) return
242
+ await withAtomicFlush(em, [() => {
243
+ rule.deletedAt = new Date()
244
+ rule.updatedAt = new Date()
245
+ }], { transaction: true, label: `${staffSupportRoutingRuleCommandIds.create}.undo` })
246
+ await emitCrudUndoSideEffects({
247
+ dataEngine: ctx.container.resolve('dataEngine'),
248
+ action: 'deleted',
249
+ entity: rule,
250
+ identifiers: { id: rule.id, organizationId: rule.organizationId, tenantId: rule.tenantId },
251
+ indexer: routingRuleIndexer,
252
+ })
253
+ },
254
+ }
255
+
256
+ const updateRoutingRuleCommand: CommandHandler<StaffSupportRoutingRuleUpdateInput, { ruleId: string }> = {
257
+ id: staffSupportRoutingRuleCommandIds.update,
258
+ async prepare(rawInput, ctx) {
259
+ const parsed = staffSupportRoutingRuleUpdateSchema.safeParse(rawInput)
260
+ if (!parsed.success) return {}
261
+ const em = ctx.container.resolve('em') as EntityManager
262
+ const before = await loadSnapshot(em, parsed.data.id, staffSnapshotScopeFromContext(ctx))
263
+ return before ? { before: { snapshot: before } } : {}
264
+ },
265
+ async execute(rawInput, ctx) {
266
+ const parsed = staffSupportRoutingRuleUpdateSchema.parse(rawInput)
267
+ const { translate } = await resolveTranslations()
268
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
269
+ const scope = commandActorScope(ctx)
270
+ const rule = await em.findOne(
271
+ StaffSupportRoutingRule,
272
+ applyScopeToWhere<StaffSupportRoutingRule>({ id: parsed.id, deletedAt: null }, scope),
273
+ )
274
+ if (!rule) throw notFoundError(translate)
275
+ ensureTenantScope(ctx, rule.tenantId)
276
+ ensureOrganizationScope(ctx, rule.organizationId)
277
+ const isFallback = (rule.categoryValue ?? null) === null
278
+ if (isFallback && parsed.isActive === false) throw fallbackRequiredError(translate)
279
+ const target = { tenantId: rule.tenantId, organizationId: rule.organizationId }
280
+
281
+ await withAtomicFlush(
282
+ em,
283
+ [
284
+ async () => {
285
+ if (parsed.timeProjectId !== undefined) {
286
+ await requireRoutableProject(em, parsed.timeProjectId, target, translate, {
287
+ requireDefaultStatus: parsed.timeProjectId !== rule.timeProjectId,
288
+ })
289
+ }
290
+ const locked = await em.findOne(
291
+ StaffSupportRoutingRule,
292
+ { id: rule.id, ...target, deletedAt: null },
293
+ { lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true },
294
+ )
295
+ if (!locked) throw notFoundError(translate)
296
+ },
297
+ () => {
298
+ if (parsed.timeProjectId !== undefined) rule.timeProjectId = parsed.timeProjectId
299
+ if (parsed.isActive !== undefined && !isFallback) rule.isActive = parsed.isActive
300
+ rule.updatedAt = new Date()
301
+ },
302
+ ],
303
+ { transaction: true, label: staffSupportRoutingRuleCommandIds.update },
304
+ )
305
+ await emitRuleSideEffects(ctx, 'updated', rule)
306
+ return { ruleId: rule.id }
307
+ },
308
+ buildLog: async ({ snapshots, ctx }) => {
309
+ const before = (snapshots.before as { snapshot?: RoutingRuleSnapshot } | undefined)?.snapshot
310
+ if (!before) return null
311
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
312
+ const after = await loadSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before))
313
+ if (!after) return null
314
+ const { translate } = await resolveTranslations()
315
+ return {
316
+ actionLabel: translate('staff.audit.support.routingRules.update', 'Update support routing rule'),
317
+ resourceKind: SUPPORT_ROUTING_RULE_RESOURCE_KIND,
318
+ resourceId: before.id,
319
+ tenantId: before.tenantId,
320
+ organizationId: before.organizationId,
321
+ snapshotBefore: before,
322
+ snapshotAfter: after,
323
+ changes: buildChanges(
324
+ before as unknown as Record<string, unknown>,
325
+ after as unknown as Record<string, unknown>,
326
+ ['timeProjectId', 'isActive'],
327
+ ),
328
+ payload: { undo: { before, after } satisfies RoutingRuleUndoPayload },
329
+ }
330
+ },
331
+ undo: async ({ logEntry, ctx }) => {
332
+ const before = extractUndoPayload<RoutingRuleUndoPayload>(logEntry)?.before
333
+ if (!before) return
334
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
335
+ const rule = await em.findOne(StaffSupportRoutingRule, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
336
+ if (!rule) return
337
+ const { translate } = await resolveTranslations()
338
+ const target = { tenantId: rule.tenantId, organizationId: rule.organizationId }
339
+ await withAtomicFlush(em, [async () => {
340
+ await requireRoutableProject(em, before.timeProjectId, target, translate, {
341
+ requireDefaultStatus: before.timeProjectId !== rule.timeProjectId,
342
+ })
343
+ const locked = await em.findOne(
344
+ StaffSupportRoutingRule,
345
+ { id: rule.id, ...target, deletedAt: null },
346
+ { lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true },
347
+ )
348
+ if (!locked) throw notFoundError(translate)
349
+ }, () => {
350
+ rule.timeProjectId = before.timeProjectId
351
+ rule.isActive = before.isActive
352
+ rule.updatedAt = new Date()
353
+ }], { transaction: true, label: `${staffSupportRoutingRuleCommandIds.update}.undo` })
354
+ await emitCrudUndoSideEffects({
355
+ dataEngine: ctx.container.resolve('dataEngine'),
356
+ action: 'updated',
357
+ entity: rule,
358
+ identifiers: { id: rule.id, organizationId: rule.organizationId, tenantId: rule.tenantId },
359
+ indexer: routingRuleIndexer,
360
+ })
361
+ },
362
+ }
363
+
364
+ const deleteRoutingRuleCommand: CommandHandler<{ id?: string }, { ruleId: string }> = {
365
+ id: staffSupportRoutingRuleCommandIds.delete,
366
+ async prepare(rawInput, ctx) {
367
+ if (!rawInput?.id) return {}
368
+ const em = ctx.container.resolve('em') as EntityManager
369
+ const before = await loadSnapshot(em, rawInput.id, staffSnapshotScopeFromContext(ctx))
370
+ return before ? { before: { snapshot: before } } : {}
371
+ },
372
+ async execute(rawInput, ctx) {
373
+ const { translate } = await resolveTranslations()
374
+ if (!rawInput?.id) throw notFoundError(translate)
375
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
376
+ const scope = commandActorScope(ctx)
377
+ const rule = await em.findOne(
378
+ StaffSupportRoutingRule,
379
+ applyScopeToWhere<StaffSupportRoutingRule>({ id: rawInput.id, deletedAt: null }, scope),
380
+ )
381
+ if (!rule) throw notFoundError(translate)
382
+ ensureTenantScope(ctx, rule.tenantId)
383
+ ensureOrganizationScope(ctx, rule.organizationId)
384
+ if ((rule.categoryValue ?? null) === null) throw fallbackRequiredError(translate)
385
+ await withAtomicFlush(em, [() => {
386
+ rule.deletedAt = new Date()
387
+ rule.updatedAt = new Date()
388
+ }], { transaction: true, label: staffSupportRoutingRuleCommandIds.delete })
389
+ await emitRuleSideEffects(ctx, 'deleted', rule)
390
+ return { ruleId: rule.id }
391
+ },
392
+ buildLog: async ({ snapshots }) => {
393
+ const before = (snapshots.before as { snapshot?: RoutingRuleSnapshot } | undefined)?.snapshot
394
+ if (!before) return null
395
+ const { translate } = await resolveTranslations()
396
+ return {
397
+ actionLabel: translate('staff.audit.support.routingRules.delete', 'Delete support routing rule'),
398
+ resourceKind: SUPPORT_ROUTING_RULE_RESOURCE_KIND,
399
+ resourceId: before.id,
400
+ tenantId: before.tenantId,
401
+ organizationId: before.organizationId,
402
+ snapshotBefore: before,
403
+ payload: { undo: { before } satisfies RoutingRuleUndoPayload },
404
+ }
405
+ },
406
+ undo: async ({ logEntry, ctx }) => {
407
+ const before = extractUndoPayload<RoutingRuleUndoPayload>(logEntry)?.before
408
+ if (!before) return
409
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
410
+ const rule = await em.findOne(StaffSupportRoutingRule, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)))
411
+ if (!rule || !rule.deletedAt) return
412
+ const { translate } = await resolveTranslations()
413
+ const categoryValue = rule.categoryValue ?? null
414
+ const isFallback = categoryValue === null
415
+ try {
416
+ await withAtomicFlush(em, [
417
+ async () => {
418
+ await requireRoutableProject(em, rule.timeProjectId, { tenantId: rule.tenantId, organizationId: rule.organizationId }, translate)
419
+ const newer = await em.findOne(
420
+ StaffSupportRoutingRule,
421
+ {
422
+ tenantId: rule.tenantId,
423
+ organizationId: rule.organizationId,
424
+ categoryValue,
425
+ deletedAt: null,
426
+ id: { $ne: rule.id },
427
+ },
428
+ { fields: ['id'] },
429
+ )
430
+ if (newer) throw duplicateRuleError(translate, isFallback)
431
+ },
432
+ () => {
433
+ rule.deletedAt = null
434
+ rule.updatedAt = new Date()
435
+ },
436
+ ], { transaction: true, label: `${staffSupportRoutingRuleCommandIds.delete}.undo` })
437
+ } catch (err) {
438
+ if (isUniqueViolation(err)) throw duplicateRuleError(translate, isFallback)
439
+ throw err
440
+ }
441
+ await emitCrudUndoSideEffects({
442
+ dataEngine: ctx.container.resolve('dataEngine'),
443
+ action: 'created',
444
+ entity: rule,
445
+ identifiers: { id: rule.id, organizationId: rule.organizationId, tenantId: rule.tenantId },
446
+ indexer: routingRuleIndexer,
447
+ })
448
+ },
449
+ }
450
+
451
+ export type SupportFallbackProjectResult = {
452
+ ruleId: string
453
+ timeProjectId: string
454
+ created: boolean
455
+ taskStatusIds: string[]
456
+ tenantId: string
457
+ organizationId: string
458
+ }
459
+
460
+ type FallbackProjectUndoPayload = {
461
+ ruleId: string
462
+ timeProjectId: string
463
+ taskStatusIds: string[]
464
+ tenantId: string
465
+ organizationId: string
466
+ }
467
+
468
+ async function emitProjectSideEffects(
469
+ ctx: CommandRuntimeContext,
470
+ action: 'created' | 'deleted',
471
+ project: StaffTimeProject,
472
+ undo: boolean,
473
+ ): Promise<void> {
474
+ const params = {
475
+ dataEngine: ctx.container.resolve('dataEngine') as DataEngine,
476
+ action,
477
+ entity: project,
478
+ identifiers: { id: project.id, organizationId: project.organizationId, tenantId: project.tenantId },
479
+ events: staffTimeProjectCrudEvents,
480
+ indexer: staffTimeProjectCrudIndexer,
481
+ }
482
+ if (undo) await emitCrudUndoSideEffects(params)
483
+ else await emitCrudSideEffects(params)
484
+ }
485
+
486
+ const createFallbackProjectCommand: CommandHandler<StaffSupportFallbackProjectCreateInput, SupportFallbackProjectResult> = {
487
+ id: staffSupportRoutingRuleCommandIds.createFallbackProject,
488
+ async execute(rawInput, ctx) {
489
+ const parsed = staffSupportFallbackProjectCreateSchema.parse(rawInput)
490
+ commandInputScope(ctx, parsed.tenantId, parsed.organizationId)
491
+ const { translate } = await resolveTranslations()
492
+ const target = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
493
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
494
+ const result = await ensureFallbackProjectAndRule(em, target, {
495
+ translate,
496
+ generateCode: resolveContainerProjectCodeGenerator(ctx.container),
497
+ projectName: translate('staff.support.fallbackProject.name', 'Customer support'),
498
+ })
499
+ if (result.created) {
500
+ const project = await em.findOne(StaffTimeProject, { id: result.timeProjectId, ...target })
501
+ if (project) await emitProjectSideEffects(ctx, 'created', project, false)
502
+ const rule = await em.findOne(StaffSupportRoutingRule, { id: result.ruleId, ...target })
503
+ if (rule) await emitRuleSideEffects(ctx, 'created', rule)
504
+ }
505
+ return { ...result, ...target }
506
+ },
507
+ buildLog: async ({ result }) => {
508
+ if (!result.created) return null
509
+ const { translate } = await resolveTranslations()
510
+ return {
511
+ actionLabel: translate('staff.audit.support.routingRules.createFallbackProject', 'Create support fallback project'),
512
+ resourceKind: SUPPORT_ROUTING_RULE_RESOURCE_KIND,
513
+ resourceId: result.ruleId,
514
+ tenantId: result.tenantId,
515
+ organizationId: result.organizationId,
516
+ context: { cacheAliases: [`${staffTimeProjectCrudEvents.module}.${staffTimeProjectCrudEvents.entity}`] },
517
+ payload: {
518
+ undo: {
519
+ ruleId: result.ruleId,
520
+ timeProjectId: result.timeProjectId,
521
+ taskStatusIds: result.taskStatusIds,
522
+ tenantId: result.tenantId,
523
+ organizationId: result.organizationId,
524
+ } satisfies FallbackProjectUndoPayload,
525
+ },
526
+ }
527
+ },
528
+ undo: async ({ logEntry, ctx }) => {
529
+ const payload = extractUndoPayload<FallbackProjectUndoPayload>(logEntry)
530
+ if (!payload?.ruleId || !payload.timeProjectId) return
531
+ const where = { tenantId: payload.tenantId, organizationId: payload.organizationId }
532
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
533
+ const statusIds = Array.isArray(payload.taskStatusIds) ? payload.taskStatusIds : []
534
+ const removal: {
535
+ project: StaffTimeProject | null
536
+ rule: StaffSupportRoutingRule | null
537
+ statuses: StaffTimeTaskStatus[]
538
+ } = { project: null, rule: null, statuses: [] }
539
+ await withAtomicFlush(em, [async () => {
540
+ removal.project = await em.findOne(
541
+ StaffTimeProject,
542
+ { id: payload.timeProjectId, ...where, deletedAt: null },
543
+ { lockMode: LockMode.PESSIMISTIC_WRITE },
544
+ )
545
+ const liveTaskCount = await em.count(StaffTimeTask, { timeProjectId: payload.timeProjectId, ...where, deletedAt: null })
546
+ if (liveTaskCount > 0) {
547
+ const { translate } = await resolveTranslations()
548
+ throw new CrudHttpError(409, {
549
+ code: 'support_fallback_project_in_use',
550
+ error: translate(
551
+ 'staff.support.errors.fallbackProjectInUse',
552
+ 'The support project already has tasks, so creating it cannot be undone. Point the fallback rule at another project instead.',
553
+ ),
554
+ })
555
+ }
556
+ removal.statuses = statusIds.length > 0
557
+ ? await em.find(StaffTimeTaskStatus, { id: { $in: statusIds }, timeProjectId: payload.timeProjectId, ...where, deletedAt: null })
558
+ : []
559
+ }, async () => {
560
+ const now = new Date()
561
+ const removedRuleCount = await em.nativeUpdate(
562
+ StaffSupportRoutingRule,
563
+ { id: payload.ruleId, ...where, timeProjectId: payload.timeProjectId, deletedAt: null },
564
+ { deletedAt: now, updatedAt: now },
565
+ )
566
+ removal.rule = removedRuleCount > 0
567
+ ? await em.findOne(StaffSupportRoutingRule, { id: payload.ruleId, ...where }, { refresh: true })
568
+ : null
569
+ if (removal.project) {
570
+ removal.project.deletedAt = now
571
+ removal.project.updatedAt = now
572
+ }
573
+ for (const status of removal.statuses) {
574
+ status.deletedAt = now
575
+ status.updatedAt = now
576
+ }
577
+ }], { transaction: true, label: `${staffSupportRoutingRuleCommandIds.createFallbackProject}.undo` })
578
+ const { project, rule: ruleToRemove } = removal
579
+ if (ruleToRemove) {
580
+ await emitCrudUndoSideEffects({
581
+ dataEngine: ctx.container.resolve('dataEngine'),
582
+ action: 'deleted',
583
+ entity: ruleToRemove,
584
+ identifiers: { id: ruleToRemove.id, organizationId: ruleToRemove.organizationId, tenantId: ruleToRemove.tenantId },
585
+ indexer: routingRuleIndexer,
586
+ })
587
+ }
588
+ if (project) await emitProjectSideEffects(ctx, 'deleted', project, true)
589
+ },
590
+ }
591
+
592
+ registerCommand(createRoutingRuleCommand)
593
+ registerCommand(updateRoutingRuleCommand)
594
+ registerCommand(deleteRoutingRuleCommand)
595
+ registerCommand(createFallbackProjectCommand)