@open-mercato/core 0.10.1-develop.7717.1.2befe9c634 → 0.10.1-develop.7721.1.b5c4a00a68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_support_routing_rule/index.js +21 -0
  3. package/dist/generated/entities/staff_support_routing_rule/index.js.map +7 -0
  4. package/dist/generated/entities/staff_support_ticket/index.js +37 -0
  5. package/dist/generated/entities/staff_support_ticket/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +2 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +2 -2
  8. package/dist/generated/entities/staff_time_project_member/index.js +2 -0
  9. package/dist/generated/entities/staff_time_project_member/index.js.map +2 -2
  10. package/dist/generated/entities/staff_time_task/index.js +4 -0
  11. package/dist/generated/entities/staff_time_task/index.js.map +2 -2
  12. package/dist/generated/entities/staff_time_task_comment/index.js +9 -1
  13. package/dist/generated/entities/staff_time_task_comment/index.js.map +2 -2
  14. package/dist/generated/entities.ids.generated.js +2 -0
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +38 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/helpers/integration/auth.js +1 -0
  19. package/dist/helpers/integration/auth.js.map +2 -2
  20. package/dist/helpers/integration/communicationChannelsFixtures.js +20 -0
  21. package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
  22. package/dist/modules/catalog/api/bulk-delete/route.js +12 -2
  23. package/dist/modules/catalog/api/bulk-delete/route.js.map +2 -2
  24. package/dist/modules/communication_channels/api/post/test-seed/route.js +17 -3
  25. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  26. package/dist/modules/communication_channels/lib/test-seed.js +38 -5
  27. package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
  28. package/dist/modules/messages/commands/messages.js +2 -3
  29. package/dist/modules/messages/commands/messages.js.map +2 -2
  30. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +2 -2
  31. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  32. package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js +299 -0
  33. package/dist/modules/staff/api/portal/support-tickets/[id]/attachments/route.js.map +7 -0
  34. package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js +91 -0
  35. package/dist/modules/staff/api/portal/support-tickets/[id]/messages/route.js.map +7 -0
  36. package/dist/modules/staff/api/portal/support-tickets/[id]/route.js +146 -0
  37. package/dist/modules/staff/api/portal/support-tickets/[id]/route.js.map +7 -0
  38. package/dist/modules/staff/api/portal/support-tickets/options/route.js +60 -0
  39. package/dist/modules/staff/api/portal/support-tickets/options/route.js.map +7 -0
  40. package/dist/modules/staff/api/portal/support-tickets/route.js +220 -0
  41. package/dist/modules/staff/api/portal/support-tickets/route.js.map +7 -0
  42. package/dist/modules/staff/api/timesheets/settings/route.js +7 -1
  43. package/dist/modules/staff/api/timesheets/settings/route.js.map +2 -2
  44. package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js +122 -0
  45. package/dist/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.js.map +7 -0
  46. package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js +180 -0
  47. package/dist/modules/staff/api/timesheets/support-routing-rules/preview/route.js.map +7 -0
  48. package/dist/modules/staff/api/timesheets/support-routing-rules/route.js +147 -0
  49. package/dist/modules/staff/api/timesheets/support-routing-rules/route.js.map +7 -0
  50. package/dist/modules/staff/api/timesheets/support-tickets/options/route.js +133 -0
  51. package/dist/modules/staff/api/timesheets/support-tickets/options/route.js.map +7 -0
  52. package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js +33 -0
  53. package/dist/modules/staff/api/timesheets/tasks/[id]/claim/route.js.map +7 -0
  54. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js +25 -7
  55. package/dist/modules/staff/api/timesheets/tasks/[id]/comments/route.js.map +2 -2
  56. package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js +32 -0
  57. package/dist/modules/staff/api/timesheets/tasks/[id]/release/route.js.map +7 -0
  58. package/dist/modules/staff/api/timesheets/tasks/route.js +94 -47
  59. package/dist/modules/staff/api/timesheets/tasks/route.js.map +2 -2
  60. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +3 -0
  61. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +2 -2
  62. package/dist/modules/staff/api/timesheets/time-projects/route.js +3 -0
  63. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +2 -2
  64. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js +1 -0
  65. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.js.map +2 -2
  66. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +54 -4
  67. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  68. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js +21 -5
  69. package/dist/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.js.map +2 -2
  70. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js +319 -254
  71. package/dist/modules/staff/backend/staff/time-tracking/settings/page.js.map +2 -2
  72. package/dist/modules/staff/backend/staff/time-tracking/support/page.js +363 -0
  73. package/dist/modules/staff/backend/staff/time-tracking/support/page.js.map +7 -0
  74. package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js +18 -0
  75. package/dist/modules/staff/backend/staff/time-tracking/support/page.meta.js.map +7 -0
  76. package/dist/modules/staff/cli.js +27 -0
  77. package/dist/modules/staff/cli.js.map +2 -2
  78. package/dist/modules/staff/commands/index.js +3 -0
  79. package/dist/modules/staff/commands/index.js.map +2 -2
  80. package/dist/modules/staff/commands/shared.js +10 -1
  81. package/dist/modules/staff/commands/shared.js.map +2 -2
  82. package/dist/modules/staff/commands/support-routing-rules.js +505 -0
  83. package/dist/modules/staff/commands/support-routing-rules.js.map +7 -0
  84. package/dist/modules/staff/commands/support-tickets.js +513 -0
  85. package/dist/modules/staff/commands/support-tickets.js.map +7 -0
  86. package/dist/modules/staff/commands/timesheets-projects.js +72 -14
  87. package/dist/modules/staff/commands/timesheets-projects.js.map +2 -2
  88. package/dist/modules/staff/commands/timesheets-task-assignment.js +270 -0
  89. package/dist/modules/staff/commands/timesheets-task-assignment.js.map +7 -0
  90. package/dist/modules/staff/commands/timesheets-task-comments.js +58 -12
  91. package/dist/modules/staff/commands/timesheets-task-comments.js.map +2 -2
  92. package/dist/modules/staff/commands/timesheets-tasks.js +91 -9
  93. package/dist/modules/staff/commands/timesheets-tasks.js.map +3 -3
  94. package/dist/modules/staff/data/enrichers.js +141 -0
  95. package/dist/modules/staff/data/enrichers.js.map +2 -2
  96. package/dist/modules/staff/data/entities.js +143 -2
  97. package/dist/modules/staff/data/entities.js.map +2 -2
  98. package/dist/modules/staff/data/extensions.js +8 -0
  99. package/dist/modules/staff/data/extensions.js.map +2 -2
  100. package/dist/modules/staff/data/validators.js +34 -5
  101. package/dist/modules/staff/data/validators.js.map +2 -2
  102. package/dist/modules/staff/encryption.js +6 -0
  103. package/dist/modules/staff/encryption.js.map +2 -2
  104. package/dist/modules/staff/events.js +25 -0
  105. package/dist/modules/staff/events.js.map +2 -2
  106. package/dist/modules/staff/events.payloads.js +54 -0
  107. package/dist/modules/staff/events.payloads.js.map +2 -2
  108. package/dist/modules/staff/extension-points.js +1 -0
  109. package/dist/modules/staff/extension-points.js.map +2 -2
  110. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js +468 -0
  111. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.js.map +7 -0
  112. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js +12 -0
  113. package/dist/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.js.map +7 -0
  114. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js +316 -0
  115. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.js.map +7 -0
  116. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js +12 -0
  117. package/dist/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.js.map +7 -0
  118. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js +190 -0
  119. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.js.map +7 -0
  120. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js +19 -0
  121. package/dist/modules/staff/frontend/[orgSlug]/portal/support/page.meta.js.map +7 -0
  122. package/dist/modules/staff/index.js +1 -1
  123. package/dist/modules/staff/index.js.map +2 -2
  124. package/dist/modules/staff/lib/crud.js +2 -0
  125. package/dist/modules/staff/lib/crud.js.map +2 -2
  126. package/dist/modules/staff/lib/support/attachments.js +36 -0
  127. package/dist/modules/staff/lib/support/attachments.js.map +7 -0
  128. package/dist/modules/staff/lib/support/dictionaries.js +87 -0
  129. package/dist/modules/staff/lib/support/dictionaries.js.map +7 -0
  130. package/dist/modules/staff/lib/support/notifications.js +111 -0
  131. package/dist/modules/staff/lib/support/notifications.js.map +7 -0
  132. package/dist/modules/staff/lib/support/portalAttachments.js +100 -0
  133. package/dist/modules/staff/lib/support/portalAttachments.js.map +7 -0
  134. package/dist/modules/staff/lib/support/portalBroadcast.js +117 -0
  135. package/dist/modules/staff/lib/support/portalBroadcast.js.map +7 -0
  136. package/dist/modules/staff/lib/support/portalRoute.js +99 -0
  137. package/dist/modules/staff/lib/support/portalRoute.js.map +7 -0
  138. package/dist/modules/staff/lib/support/portalStatus.js +12 -0
  139. package/dist/modules/staff/lib/support/portalStatus.js.map +7 -0
  140. package/dist/modules/staff/lib/support/portalTickets.js +148 -0
  141. package/dist/modules/staff/lib/support/portalTickets.js.map +7 -0
  142. package/dist/modules/staff/lib/support/routing.js +81 -0
  143. package/dist/modules/staff/lib/support/routing.js.map +7 -0
  144. package/dist/modules/staff/lib/support/routingRuleDraft.js +25 -0
  145. package/dist/modules/staff/lib/support/routingRuleDraft.js.map +7 -0
  146. package/dist/modules/staff/lib/support/seedDefaults.js +114 -0
  147. package/dist/modules/staff/lib/support/seedDefaults.js.map +7 -0
  148. package/dist/modules/staff/lib/support/supportActivity.js +26 -0
  149. package/dist/modules/staff/lib/support/supportActivity.js.map +7 -0
  150. package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js +23 -0
  151. package/dist/modules/staff/lib/support-ui/PortalSupportNotLinked.js.map +7 -0
  152. package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js +112 -0
  153. package/dist/modules/staff/lib/support-ui/portalAttachmentValidation.js.map +7 -0
  154. package/dist/modules/staff/lib/support-ui/portalPendingUploads.js +45 -0
  155. package/dist/modules/staff/lib/support-ui/portalPendingUploads.js.map +7 -0
  156. package/dist/modules/staff/lib/support-ui/portalSupport.js +51 -0
  157. package/dist/modules/staff/lib/support-ui/portalSupport.js.map +7 -0
  158. package/dist/modules/staff/lib/time-tracking/access.js +3 -39
  159. package/dist/modules/staff/lib/time-tracking/access.js.map +2 -2
  160. package/dist/modules/staff/lib/time-tracking/listProjectAccess.js +54 -0
  161. package/dist/modules/staff/lib/time-tracking/listProjectAccess.js.map +7 -0
  162. package/dist/modules/staff/lib/time-tracking/portalRecipients.js +92 -2
  163. package/dist/modules/staff/lib/time-tracking/portalRecipients.js.map +2 -2
  164. package/dist/modules/staff/lib/time-tracking/projectAssignment.js +112 -0
  165. package/dist/modules/staff/lib/time-tracking/projectAssignment.js.map +7 -0
  166. package/dist/modules/staff/lib/time-tracking/settingKeys.js +14 -0
  167. package/dist/modules/staff/lib/time-tracking/settingKeys.js.map +2 -2
  168. package/dist/modules/staff/lib/time-tracking/settings.js +28 -1
  169. package/dist/modules/staff/lib/time-tracking/settings.js.map +2 -2
  170. package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js +143 -0
  171. package/dist/modules/staff/lib/time-tracking/taskAssignmentRoute.js.map +7 -0
  172. package/dist/modules/staff/lib/time-tracking/workRecipients.js +92 -0
  173. package/dist/modules/staff/lib/time-tracking/workRecipients.js.map +7 -0
  174. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js +32 -3
  175. package/dist/modules/staff/lib/time-tracking-ui/BoardFilterChips.js.map +2 -2
  176. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js +29 -0
  177. package/dist/modules/staff/lib/time-tracking-ui/KanbanBoard.js.map +2 -2
  178. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js +4 -0
  179. package/dist/modules/staff/lib/time-tracking-ui/KanbanCard.js.map +2 -2
  180. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js +141 -1
  181. package/dist/modules/staff/lib/time-tracking-ui/ProjectFormSections.js.map +2 -2
  182. package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js +153 -0
  183. package/dist/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.js.map +7 -0
  184. package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js +67 -0
  185. package/dist/modules/staff/lib/time-tracking-ui/SupportRequestPanel.js.map +7 -0
  186. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js +3 -1
  187. package/dist/modules/staff/lib/time-tracking-ui/TaskBoardScreen.js.map +2 -2
  188. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js +80 -17
  189. package/dist/modules/staff/lib/time-tracking-ui/TaskCommentThread.js.map +2 -2
  190. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js +230 -41
  191. package/dist/modules/staff/lib/time-tracking-ui/TaskDrawer.js.map +2 -2
  192. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js +21 -1
  193. package/dist/modules/staff/lib/time-tracking-ui/boardDirectory.js.map +2 -2
  194. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js +16 -4
  195. package/dist/modules/staff/lib/time-tracking-ui/boardFilters.js.map +2 -2
  196. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js +3 -1
  197. package/dist/modules/staff/lib/time-tracking-ui/kanbanBoardData.js.map +2 -2
  198. package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js +68 -0
  199. package/dist/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.js.map +7 -0
  200. package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js +7 -0
  201. package/dist/modules/staff/lib/time-tracking-ui/settingsTabs.js.map +7 -0
  202. package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js +50 -0
  203. package/dist/modules/staff/lib/time-tracking-ui/supportTicketData.js.map +7 -0
  204. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js +13 -1
  205. package/dist/modules/staff/lib/time-tracking-ui/taskDrawerData.js.map +2 -2
  206. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js +36 -2
  207. package/dist/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.js.map +2 -2
  208. package/dist/modules/staff/migrations/Migration20261009180543_staff.js +42 -0
  209. package/dist/modules/staff/migrations/Migration20261009180543_staff.js.map +7 -0
  210. package/dist/modules/staff/notifications.js +92 -0
  211. package/dist/modules/staff/notifications.js.map +2 -2
  212. package/dist/modules/staff/setup.js +6 -2
  213. package/dist/modules/staff/setup.js.map +2 -2
  214. package/dist/modules/staff/subscribers/support-portal-comment-created.js +26 -0
  215. package/dist/modules/staff/subscribers/support-portal-comment-created.js.map +7 -0
  216. package/dist/modules/staff/subscribers/support-portal-comment-deleted.js +26 -0
  217. package/dist/modules/staff/subscribers/support-portal-comment-deleted.js.map +7 -0
  218. package/dist/modules/staff/subscribers/support-portal-comment-updated.js +26 -0
  219. package/dist/modules/staff/subscribers/support-portal-comment-updated.js.map +7 -0
  220. package/dist/modules/staff/subscribers/support-portal-status-column-updated.js +24 -0
  221. package/dist/modules/staff/subscribers/support-portal-status-column-updated.js.map +7 -0
  222. package/dist/modules/staff/subscribers/support-portal-task-status-changed.js +25 -0
  223. package/dist/modules/staff/subscribers/support-portal-task-status-changed.js.map +7 -0
  224. package/dist/modules/staff/subscribers/support-portal-task-updated.js +25 -0
  225. package/dist/modules/staff/subscribers/support-portal-task-updated.js.map +7 -0
  226. package/dist/modules/staff/subscribers/support-ticket-created-notification.js +25 -0
  227. package/dist/modules/staff/subscribers/support-ticket-created-notification.js.map +7 -0
  228. package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js +25 -0
  229. package/dist/modules/staff/subscribers/support-ticket-customer-replied-notification.js.map +7 -0
  230. package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js +26 -0
  231. package/dist/modules/staff/subscribers/support-ticket-unnotified-clear.js.map +7 -0
  232. package/dist/modules/staff/subscribers/time-task-assignee-notification.js +83 -0
  233. package/dist/modules/staff/subscribers/time-task-assignee-notification.js.map +7 -0
  234. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js +112 -0
  235. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.client.js.map +7 -0
  236. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js +17 -0
  237. package/dist/modules/staff/widgets/injection/portal-support-tickets/widget.js.map +7 -0
  238. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js +870 -0
  239. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.js.map +7 -0
  240. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js +17 -0
  241. package/dist/modules/staff/widgets/injection/time-tracking-settings-support/widget.js.map +7 -0
  242. package/dist/modules/staff/widgets/injection-table.js +16 -1
  243. package/dist/modules/staff/widgets/injection-table.js.map +2 -2
  244. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js +4 -18
  245. package/dist/modules/wms/components/backend/WmsLocationDetailPage.js.map +2 -2
  246. package/dist/modules/wms/components/backend/WmsLotDetailPage.js +4 -18
  247. package/dist/modules/wms/components/backend/WmsLotDetailPage.js.map +2 -2
  248. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js +4 -18
  249. package/dist/modules/wms/components/backend/WmsSkuDetailPage.js.map +2 -2
  250. package/dist/modules/wms/lib/inventoryCsvExport.js +19 -0
  251. package/dist/modules/wms/lib/inventoryCsvExport.js.map +7 -0
  252. package/generated/entities/staff_support_routing_rule/index.ts +9 -0
  253. package/generated/entities/staff_support_ticket/index.ts +17 -0
  254. package/generated/entities/staff_time_project/index.ts +1 -0
  255. package/generated/entities/staff_time_project_member/index.ts +1 -0
  256. package/generated/entities/staff_time_task/index.ts +2 -0
  257. package/generated/entities/staff_time_task_comment/index.ts +4 -0
  258. package/generated/entities.ids.generated.ts +2 -0
  259. package/generated/entity-fields-registry.ts +38 -0
  260. package/package.json +8 -8
  261. package/src/helpers/integration/auth.ts +1 -1
  262. package/src/helpers/integration/communicationChannelsFixtures.ts +60 -2
  263. package/src/modules/catalog/api/bulk-delete/route.ts +12 -2
  264. package/src/modules/communication_channels/api/post/test-seed/route.ts +29 -12
  265. package/src/modules/communication_channels/lib/test-seed.ts +49 -10
  266. package/src/modules/messages/commands/messages.ts +8 -10
  267. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +2 -2
  268. package/src/modules/staff/AGENTS.md +22 -6
  269. package/src/modules/staff/api/portal/support-tickets/[id]/attachments/route.ts +327 -0
  270. package/src/modules/staff/api/portal/support-tickets/[id]/messages/route.ts +94 -0
  271. package/src/modules/staff/api/portal/support-tickets/[id]/route.ts +160 -0
  272. package/src/modules/staff/api/portal/support-tickets/options/route.ts +60 -0
  273. package/src/modules/staff/api/portal/support-tickets/route.ts +253 -0
  274. package/src/modules/staff/api/timesheets/settings/route.ts +7 -1
  275. package/src/modules/staff/api/timesheets/support-routing-rules/fallback-project/route.ts +131 -0
  276. package/src/modules/staff/api/timesheets/support-routing-rules/preview/route.ts +248 -0
  277. package/src/modules/staff/api/timesheets/support-routing-rules/route.ts +150 -0
  278. package/src/modules/staff/api/timesheets/support-tickets/options/route.ts +181 -0
  279. package/src/modules/staff/api/timesheets/tasks/[id]/claim/route.ts +31 -0
  280. package/src/modules/staff/api/timesheets/tasks/[id]/comments/route.ts +25 -7
  281. package/src/modules/staff/api/timesheets/tasks/[id]/release/route.ts +30 -0
  282. package/src/modules/staff/api/timesheets/tasks/route.ts +119 -67
  283. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +3 -0
  284. package/src/modules/staff/api/timesheets/time-projects/route.ts +3 -0
  285. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/edit/page.tsx +1 -0
  286. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +67 -2
  287. package/src/modules/staff/backend/staff/time-tracking/projects/projectFormConfig.ts +22 -3
  288. package/src/modules/staff/backend/staff/time-tracking/settings/page.tsx +405 -308
  289. package/src/modules/staff/backend/staff/time-tracking/support/page.meta.ts +14 -0
  290. package/src/modules/staff/backend/staff/time-tracking/support/page.tsx +428 -0
  291. package/src/modules/staff/cli.ts +28 -0
  292. package/src/modules/staff/commands/index.ts +3 -0
  293. package/src/modules/staff/commands/shared.ts +22 -1
  294. package/src/modules/staff/commands/support-routing-rules.ts +595 -0
  295. package/src/modules/staff/commands/support-tickets.ts +580 -0
  296. package/src/modules/staff/commands/timesheets-projects.ts +84 -13
  297. package/src/modules/staff/commands/timesheets-task-assignment.ts +319 -0
  298. package/src/modules/staff/commands/timesheets-task-comments.ts +70 -15
  299. package/src/modules/staff/commands/timesheets-tasks.ts +106 -15
  300. package/src/modules/staff/data/enrichers.ts +203 -0
  301. package/src/modules/staff/data/entities.ts +133 -2
  302. package/src/modules/staff/data/extensions.ts +9 -1
  303. package/src/modules/staff/data/validators.ts +36 -0
  304. package/src/modules/staff/encryption.ts +6 -0
  305. package/src/modules/staff/events.payloads.ts +87 -1
  306. package/src/modules/staff/events.ts +26 -0
  307. package/src/modules/staff/extension-points.ts +1 -0
  308. package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.meta.ts +10 -0
  309. package/src/modules/staff/frontend/[orgSlug]/portal/support/[id]/page.tsx +553 -0
  310. package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.meta.ts +10 -0
  311. package/src/modules/staff/frontend/[orgSlug]/portal/support/new/page.tsx +346 -0
  312. package/src/modules/staff/frontend/[orgSlug]/portal/support/page.meta.ts +17 -0
  313. package/src/modules/staff/frontend/[orgSlug]/portal/support/page.tsx +253 -0
  314. package/src/modules/staff/i18n/de.json +291 -0
  315. package/src/modules/staff/i18n/en.json +291 -0
  316. package/src/modules/staff/i18n/es.json +291 -0
  317. package/src/modules/staff/i18n/ko.json +291 -0
  318. package/src/modules/staff/i18n/pl.json +291 -0
  319. package/src/modules/staff/index.ts +1 -1
  320. package/src/modules/staff/lib/crud.ts +2 -1
  321. package/src/modules/staff/lib/support/attachments.ts +43 -0
  322. package/src/modules/staff/lib/support/dictionaries.ts +120 -0
  323. package/src/modules/staff/lib/support/notifications.ts +138 -0
  324. package/src/modules/staff/lib/support/portalAttachments.ts +140 -0
  325. package/src/modules/staff/lib/support/portalBroadcast.ts +161 -0
  326. package/src/modules/staff/lib/support/portalRoute.ts +108 -0
  327. package/src/modules/staff/lib/support/portalStatus.ts +15 -0
  328. package/src/modules/staff/lib/support/portalTickets.ts +233 -0
  329. package/src/modules/staff/lib/support/routing.ts +117 -0
  330. package/src/modules/staff/lib/support/routingRuleDraft.ts +45 -0
  331. package/src/modules/staff/lib/support/seedDefaults.ts +151 -0
  332. package/src/modules/staff/lib/support/supportActivity.ts +39 -0
  333. package/src/modules/staff/lib/support-ui/PortalSupportNotLinked.tsx +20 -0
  334. package/src/modules/staff/lib/support-ui/portalAttachmentValidation.ts +125 -0
  335. package/src/modules/staff/lib/support-ui/portalPendingUploads.ts +49 -0
  336. package/src/modules/staff/lib/support-ui/portalSupport.ts +116 -0
  337. package/src/modules/staff/lib/time-tracking/access.ts +2 -62
  338. package/src/modules/staff/lib/time-tracking/listProjectAccess.ts +78 -0
  339. package/src/modules/staff/lib/time-tracking/portalRecipients.ts +173 -5
  340. package/src/modules/staff/lib/time-tracking/projectAssignment.ts +172 -0
  341. package/src/modules/staff/lib/time-tracking/settingKeys.ts +11 -0
  342. package/src/modules/staff/lib/time-tracking/settings.ts +46 -1
  343. package/src/modules/staff/lib/time-tracking/taskAssignmentRoute.ts +165 -0
  344. package/src/modules/staff/lib/time-tracking/workRecipients.ts +183 -0
  345. package/src/modules/staff/lib/time-tracking-ui/BoardFilterChips.tsx +36 -1
  346. package/src/modules/staff/lib/time-tracking-ui/KanbanBoard.tsx +30 -0
  347. package/src/modules/staff/lib/time-tracking-ui/KanbanCard.tsx +9 -0
  348. package/src/modules/staff/lib/time-tracking-ui/ProjectFormSections.tsx +185 -6
  349. package/src/modules/staff/lib/time-tracking-ui/SupportAttachmentsSection.tsx +188 -0
  350. package/src/modules/staff/lib/time-tracking-ui/SupportRequestPanel.tsx +102 -0
  351. package/src/modules/staff/lib/time-tracking-ui/TaskBoardScreen.tsx +2 -0
  352. package/src/modules/staff/lib/time-tracking-ui/TaskCommentThread.tsx +105 -22
  353. package/src/modules/staff/lib/time-tracking-ui/TaskDrawer.tsx +278 -43
  354. package/src/modules/staff/lib/time-tracking-ui/boardDirectory.ts +21 -0
  355. package/src/modules/staff/lib/time-tracking-ui/boardFilters.ts +16 -2
  356. package/src/modules/staff/lib/time-tracking-ui/kanbanBoardData.ts +4 -0
  357. package/src/modules/staff/lib/time-tracking-ui/projectDefaultAssignee.ts +117 -0
  358. package/src/modules/staff/lib/time-tracking-ui/settingsTabs.ts +17 -0
  359. package/src/modules/staff/lib/time-tracking-ui/supportTicketData.ts +71 -0
  360. package/src/modules/staff/lib/time-tracking-ui/taskDrawerData.ts +15 -0
  361. package/src/modules/staff/lib/time-tracking-ui/timeTrackingSettingsForm.ts +48 -1
  362. package/src/modules/staff/migrations/.snapshot-open-mercato.json +714 -0
  363. package/src/modules/staff/migrations/Migration20261009180543_staff.ts +50 -0
  364. package/src/modules/staff/notifications.ts +92 -0
  365. package/src/modules/staff/setup.ts +6 -2
  366. package/src/modules/staff/subscribers/support-portal-comment-created.ts +35 -0
  367. package/src/modules/staff/subscribers/support-portal-comment-deleted.ts +35 -0
  368. package/src/modules/staff/subscribers/support-portal-comment-updated.ts +35 -0
  369. package/src/modules/staff/subscribers/support-portal-status-column-updated.ts +36 -0
  370. package/src/modules/staff/subscribers/support-portal-task-status-changed.ts +34 -0
  371. package/src/modules/staff/subscribers/support-portal-task-updated.ts +34 -0
  372. package/src/modules/staff/subscribers/support-ticket-created-notification.ts +27 -0
  373. package/src/modules/staff/subscribers/support-ticket-customer-replied-notification.ts +27 -0
  374. package/src/modules/staff/subscribers/support-ticket-unnotified-clear.ts +29 -0
  375. package/src/modules/staff/subscribers/time-task-assignee-notification.ts +102 -0
  376. package/src/modules/staff/widgets/injection/portal-support-tickets/widget.client.tsx +151 -0
  377. package/src/modules/staff/widgets/injection/portal-support-tickets/widget.ts +16 -0
  378. package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.client.tsx +1058 -0
  379. package/src/modules/staff/widgets/injection/time-tracking-settings-support/widget.ts +17 -0
  380. package/src/modules/staff/widgets/injection-table.ts +15 -0
  381. package/src/modules/wms/components/backend/WmsLocationDetailPage.tsx +4 -20
  382. package/src/modules/wms/components/backend/WmsLotDetailPage.tsx +4 -20
  383. package/src/modules/wms/components/backend/WmsSkuDetailPage.tsx +4 -19
  384. package/src/modules/wms/lib/inventoryCsvExport.ts +18 -0
@@ -0,0 +1,14 @@
1
+ export const metadata = {
2
+ requireAuth: true,
3
+ requireFeatures: ['staff.timesheets.tasks.view'],
4
+ pageTitle: 'Support Inbox',
5
+ pageTitleKey: 'staff.support.tickets.title',
6
+ pageGroup: 'Time tracking',
7
+ pageGroupKey: 'staff.time_tracking.nav.group',
8
+ pageOrder: 35,
9
+ icon: 'life-buoy',
10
+ breadcrumb: [
11
+ { label: 'Time tracking', labelKey: 'staff.time_tracking.nav.group', href: '/backend/staff/time-tracking' },
12
+ { label: 'Support Inbox', labelKey: 'staff.support.tickets.title' },
13
+ ],
14
+ }
@@ -0,0 +1,428 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { useRouter, useSearchParams } from 'next/navigation'
5
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
6
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
7
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
8
+ import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'
9
+ import { ErrorMessage } from '@open-mercato/ui/backend/detail'
10
+ import { Badge } from '@open-mercato/ui/primitives/badge'
11
+ import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
12
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
13
+ import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
14
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
15
+ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
16
+ import { formatDateTime } from '@open-mercato/shared/lib/time'
17
+ import { TaskDrawer } from '../../../../lib/time-tracking-ui/TaskDrawer'
18
+ import { readItems, readString, readTotal, type BoardApiRow } from '../../../../lib/time-tracking-ui/kanbanBoardData'
19
+ import {
20
+ readSupportTicket,
21
+ SUPPORT_ACTIVITY_EVENT,
22
+ SUPPORT_PORTAL_STATUS_VARIANT,
23
+ type SupportTicketView,
24
+ } from '../../../../lib/time-tracking-ui/supportTicketData'
25
+ import { supportPortalStatusLabel } from '../../../../lib/time-tracking-ui/SupportRequestPanel'
26
+ import { searchCustomers } from '../../../../lib/time-tracking-ui/CustomerPicker'
27
+
28
+ const PAGE_SIZE = 25
29
+ const PROJECT_SEARCH_PAGE_SIZE = 50
30
+ const OPTIONS_URL = '/api/staff/timesheets/support-tickets/options'
31
+ const UNASSIGNED_VALUE = '__unassigned__'
32
+ const ASSIGNEE_CHANGED_EVENT = 'staff.timesheets.time_task.assignee_changed'
33
+
34
+ type SupportRow = {
35
+ id: string
36
+ ticket: SupportTicketView
37
+ projectName: string | null
38
+ assigneeName: string | null
39
+ updatedAt: string | null
40
+ }
41
+
42
+ type Option = { value: string; label: string }
43
+
44
+ type InboxOptionsResponse = {
45
+ categories?: Array<{ value?: unknown; label?: unknown }>
46
+ assignees?: Array<{ id?: unknown; displayName?: unknown }>
47
+ }
48
+
49
+ function toCategoryOptions(response: InboxOptionsResponse | null | undefined): Option[] {
50
+ return (response?.categories ?? [])
51
+ .map((category) =>
52
+ typeof category.value === 'string'
53
+ ? { value: category.value, label: typeof category.label === 'string' ? category.label : category.value }
54
+ : null,
55
+ )
56
+ .filter((option): option is Option => option !== null)
57
+ }
58
+
59
+ function toAssigneeOptions(response: InboxOptionsResponse | null | undefined): Option[] {
60
+ return (response?.assignees ?? [])
61
+ .map((assignee) =>
62
+ typeof assignee.id === 'string'
63
+ ? { value: assignee.id, label: typeof assignee.displayName === 'string' ? assignee.displayName : assignee.id }
64
+ : null,
65
+ )
66
+ .filter((option): option is Option => option !== null)
67
+ }
68
+
69
+ function readContext(row: BoardApiRow): Record<string, unknown> | null {
70
+ const context = (row as Record<string, unknown>)._staff
71
+ return context && typeof context === 'object' ? (context as Record<string, unknown>) : null
72
+ }
73
+
74
+ function toSupportRow(row: BoardApiRow): SupportRow | null {
75
+ const id = readString(row, 'id')
76
+ const ticket = readSupportTicket(row)
77
+ if (!id || !ticket) return null
78
+ const context = readContext(row)
79
+ return {
80
+ id,
81
+ ticket,
82
+ projectName: typeof context?.projectName === 'string' ? context.projectName : null,
83
+ assigneeName: typeof context?.assigneeName === 'string' ? context.assigneeName : null,
84
+ updatedAt: readString(row, 'updated_at', 'updatedAt'),
85
+ }
86
+ }
87
+
88
+ async function loadOptions(url: string, labelOf: (row: BoardApiRow) => string | null): Promise<Option[]> {
89
+ const call = await apiCall<Record<string, unknown>>(url)
90
+ if (!call.ok) return []
91
+ return readItems(call.result)
92
+ .map((row) => {
93
+ const id = readString(row, 'id')
94
+ const label = labelOf(row)
95
+ return id && label ? { value: id, label } : null
96
+ })
97
+ .filter((option): option is Option => option !== null)
98
+ }
99
+
100
+ export default function SupportTicketsPage() {
101
+ const t = useT()
102
+ const router = useRouter()
103
+ const searchParams = useSearchParams()
104
+ const scopeVersion = useOrganizationScopeVersion()
105
+
106
+ const [rows, setRows] = React.useState<SupportRow[]>([])
107
+ const [total, setTotal] = React.useState(0)
108
+ const [totalIsCapped, setTotalIsCapped] = React.useState(false)
109
+ const [page, setPage] = React.useState(1)
110
+ const [isLoading, setIsLoading] = React.useState(true)
111
+ const [loadError, setLoadError] = React.useState(false)
112
+ const [search, setSearch] = React.useState('')
113
+ const [filterValues, setFilterValues] = React.useState<FilterValues>(() =>
114
+ searchParams?.get('supportUnnotified') === 'true' ? { supportUnnotified: true } : {},
115
+ )
116
+ const [refreshKey, setRefreshKey] = React.useState(0)
117
+ const [projectLabels, setProjectLabels] = React.useState<Record<string, string>>({})
118
+ const [assigneeOptions, setAssigneeOptions] = React.useState<Option[]>([])
119
+ const [categoryOptions, setCategoryOptions] = React.useState<Option[]>([])
120
+ const [customerLabels, setCustomerLabels] = React.useState<Record<string, string>>({})
121
+ const [optionsError, setOptionsError] = React.useState(false)
122
+
123
+ const drawerTaskId = searchParams?.get('task') ?? null
124
+
125
+ const refresh = React.useCallback(() => setRefreshKey((value) => value + 1), [])
126
+
127
+ React.useEffect(() => {
128
+ let cancelled = false
129
+ void (async () => {
130
+ try {
131
+ const options = await apiCall<InboxOptionsResponse>(OPTIONS_URL)
132
+ if (cancelled) return
133
+ if (!options.ok) {
134
+ setOptionsError(true)
135
+ return
136
+ }
137
+ setOptionsError(false)
138
+ setCategoryOptions(toCategoryOptions(options.result))
139
+ setAssigneeOptions(toAssigneeOptions(options.result))
140
+ } catch {
141
+ if (!cancelled) setOptionsError(true)
142
+ }
143
+ })()
144
+ return () => {
145
+ cancelled = true
146
+ }
147
+ }, [scopeVersion])
148
+
149
+ React.useEffect(() => {
150
+ let cancelled = false
151
+ setIsLoading(true)
152
+ const params = new URLSearchParams({
153
+ source: 'portal',
154
+ page: String(page),
155
+ pageSize: String(PAGE_SIZE),
156
+ sortField: 'updatedAt',
157
+ sortDir: 'desc',
158
+ })
159
+ const portalStatus = typeof filterValues.portalStatus === 'string' ? filterValues.portalStatus : ''
160
+ if (portalStatus) params.set('portalStatus', portalStatus)
161
+ const category = typeof filterValues.supportCategory === 'string' ? filterValues.supportCategory : ''
162
+ if (category) params.set('supportCategory', category)
163
+ const projectId = typeof filterValues.timeProjectId === 'string' ? filterValues.timeProjectId : ''
164
+ if (projectId) params.set('timeProjectId', projectId)
165
+ const customerId = typeof filterValues.customerId === 'string' ? filterValues.customerId : ''
166
+ if (customerId) params.set('customerId', customerId)
167
+ const assignee = typeof filterValues.assignee === 'string' ? filterValues.assignee : ''
168
+ if (assignee === UNASSIGNED_VALUE) params.set('unassigned', 'true')
169
+ else if (assignee) params.set('assigneeStaffMemberId', assignee)
170
+ if (filterValues.supportUnnotified === true || filterValues.supportUnnotified === 'true') {
171
+ params.set('supportUnnotified', 'true')
172
+ }
173
+ if (search.trim()) params.set('supportReference', search.trim())
174
+ void (async () => {
175
+ try {
176
+ const call = await apiCall<Record<string, unknown>>(`/api/staff/timesheets/tasks?${params.toString()}`)
177
+ if (cancelled) return
178
+ if (!call.ok) {
179
+ setLoadError(true)
180
+ return
181
+ }
182
+ setLoadError(false)
183
+ setRows(readItems(call.result).map(toSupportRow).filter((row): row is SupportRow => row !== null))
184
+ setTotal(readTotal(call.result, 0))
185
+ setTotalIsCapped(call.result?.totalIsCapped === true)
186
+ } catch {
187
+ if (!cancelled) setLoadError(true)
188
+ } finally {
189
+ if (!cancelled) setIsLoading(false)
190
+ }
191
+ })()
192
+ return () => {
193
+ cancelled = true
194
+ }
195
+ }, [filterValues, page, refreshKey, scopeVersion, search])
196
+
197
+ useAppEvent(SUPPORT_ACTIVITY_EVENT, () => refresh(), [refresh])
198
+ useAppEvent(ASSIGNEE_CHANGED_EVENT, () => refresh(), [refresh])
199
+
200
+ const openTask = React.useCallback(
201
+ (taskId: string) => {
202
+ const params = new URLSearchParams(searchParams?.toString() ?? '')
203
+ params.set('task', taskId)
204
+ router.push(`?${params.toString()}`, { scroll: false })
205
+ },
206
+ [router, searchParams],
207
+ )
208
+
209
+ const closeTask = React.useCallback(() => {
210
+ const params = new URLSearchParams(searchParams?.toString() ?? '')
211
+ params.delete('task')
212
+ const query = params.toString()
213
+ router.push(query.length > 0 ? `?${query}` : '?', { scroll: false })
214
+ }, [router, searchParams])
215
+
216
+ const loadCustomerOptions = React.useCallback(
217
+ async (query?: string) => {
218
+ const customers = await searchCustomers(query ?? '')
219
+ setCustomerLabels((current) => ({
220
+ ...current,
221
+ ...Object.fromEntries(customers.map((customer) => [customer.id, customer.name])),
222
+ }))
223
+ return customers.map((customer) => ({
224
+ value: customer.id,
225
+ label: customer.name,
226
+ description:
227
+ customer.kind === 'company'
228
+ ? t('staff.support.tickets.customerKind.company', 'Company')
229
+ : t('staff.support.tickets.customerKind.person', 'Person'),
230
+ }))
231
+ },
232
+ [t],
233
+ )
234
+
235
+ const loadProjectOptions = React.useCallback(async (query?: string) => {
236
+ const params = new URLSearchParams({ page: '1', pageSize: String(PROJECT_SEARCH_PAGE_SIZE) })
237
+ const trimmed = (query ?? '').trim()
238
+ if (trimmed) params.set('q', trimmed)
239
+ const projects = await loadOptions(`/api/staff/timesheets/time-projects?${params.toString()}`, (row) =>
240
+ readString(row, 'name'),
241
+ )
242
+ setProjectLabels((current) => ({
243
+ ...current,
244
+ ...Object.fromEntries(projects.map((project) => [project.value, project.label])),
245
+ }))
246
+ return projects
247
+ }, [])
248
+
249
+ const filters = React.useMemo<FilterDef[]>(
250
+ () => [
251
+ {
252
+ id: 'portalStatus',
253
+ label: t('staff.support.tickets.filters.portalStatus', 'Status the customer sees'),
254
+ type: 'select',
255
+ options: [
256
+ { value: 'received', label: supportPortalStatusLabel(t, 'received') },
257
+ { value: 'in_progress', label: supportPortalStatusLabel(t, 'in_progress') },
258
+ { value: 'resolved', label: supportPortalStatusLabel(t, 'resolved') },
259
+ ],
260
+ },
261
+ {
262
+ id: 'supportCategory',
263
+ label: t('staff.support.tickets.filters.category', 'Category'),
264
+ type: 'select',
265
+ options: categoryOptions,
266
+ },
267
+ {
268
+ id: 'timeProjectId',
269
+ label: t('staff.support.tickets.filters.project', 'Project'),
270
+ type: 'combobox',
271
+ placeholder: t('staff.support.tickets.filters.projectPlaceholder', 'Search projects'),
272
+ loadOptions: loadProjectOptions,
273
+ formatValue: (value: string) => projectLabels[value] ?? value,
274
+ },
275
+ {
276
+ id: 'customerId',
277
+ label: t('staff.support.tickets.filters.customer', 'Customer'),
278
+ type: 'combobox',
279
+ placeholder: t('staff.support.tickets.filters.customerPlaceholder', 'Search customers'),
280
+ loadOptions: loadCustomerOptions,
281
+ formatValue: (value: string) => customerLabels[value] ?? value,
282
+ },
283
+ {
284
+ id: 'assignee',
285
+ label: t('staff.support.tickets.filters.assignee', 'Assignee'),
286
+ type: 'select',
287
+ options: [{ value: UNASSIGNED_VALUE, label: t('staff.support.tickets.unassigned', 'Unassigned') }, ...assigneeOptions],
288
+ },
289
+ {
290
+ id: 'supportUnnotified',
291
+ label: t('staff.support.tickets.filters.nobodyNotified', 'Nobody notified'),
292
+ type: 'checkbox',
293
+ },
294
+ ],
295
+ [assigneeOptions, categoryOptions, customerLabels, loadCustomerOptions, loadProjectOptions, projectLabels, t],
296
+ )
297
+
298
+ const columns = React.useMemo<ColumnDef<SupportRow>[]>(
299
+ () => [
300
+ {
301
+ accessorKey: 'reference',
302
+ header: t('staff.support.tickets.columns.reference', 'Ticket'),
303
+ cell: ({ row }) => (
304
+ <div className="flex flex-col gap-1">
305
+ <span className="font-medium text-foreground">{row.original.ticket.supportReference}</span>
306
+ {row.original.ticket.unnotifiedAt ? (
307
+ <Badge variant="warning" size="sm" data-testid="support-ticket-unnotified">
308
+ {t('staff.support.tickets.nobodyNotified', 'Nobody notified')}
309
+ </Badge>
310
+ ) : null}
311
+ </div>
312
+ ),
313
+ },
314
+ {
315
+ accessorKey: 'subject',
316
+ header: t('staff.support.tickets.columns.subject', 'Subject'),
317
+ cell: ({ row }) => row.original.ticket.requestSubject ?? '',
318
+ },
319
+ {
320
+ accessorKey: 'customer',
321
+ header: t('staff.support.tickets.columns.customer', 'Customer'),
322
+ cell: ({ row }) => row.original.ticket.customerName ?? '',
323
+ },
324
+ {
325
+ accessorKey: 'category',
326
+ header: t('staff.support.tickets.columns.category', 'Category'),
327
+ cell: ({ row }) => row.original.ticket.categoryLabel,
328
+ },
329
+ {
330
+ accessorKey: 'portalStatus',
331
+ header: t('staff.support.tickets.columns.portalStatus', 'Status'),
332
+ cell: ({ row }) => (
333
+ <StatusBadge variant={SUPPORT_PORTAL_STATUS_VARIANT[row.original.ticket.portalStatus]} dot>
334
+ {supportPortalStatusLabel(t, row.original.ticket.portalStatus)}
335
+ </StatusBadge>
336
+ ),
337
+ },
338
+ {
339
+ accessorKey: 'project',
340
+ header: t('staff.support.tickets.columns.project', 'Project'),
341
+ cell: ({ row }) => row.original.projectName ?? '',
342
+ },
343
+ {
344
+ accessorKey: 'assignee',
345
+ header: t('staff.support.tickets.columns.assignee', 'Assignee'),
346
+ cell: ({ row }) =>
347
+ row.original.assigneeName ?? (
348
+ <span className="text-muted-foreground">{t('staff.support.tickets.unassigned', 'Unassigned')}</span>
349
+ ),
350
+ },
351
+ {
352
+ accessorKey: 'updatedAt',
353
+ header: t('staff.support.tickets.columns.lastActivity', 'Last activity'),
354
+ cell: ({ row }) => (row.original.updatedAt ? formatDateTime(row.original.updatedAt) ?? '' : ''),
355
+ },
356
+ ],
357
+ [t],
358
+ )
359
+
360
+ const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
361
+
362
+ return (
363
+ <Page>
364
+ <PageBody>
365
+ {loadError ? (
366
+ <ErrorMessage label={t('staff.support.tickets.loadError', 'Could not load the support tickets.')} />
367
+ ) : null}
368
+ {optionsError ? (
369
+ <ErrorMessage
370
+ label={t('staff.support.tickets.optionsLoadError', 'Could not load the filter options.')}
371
+ />
372
+ ) : null}
373
+ <DataTable<SupportRow>
374
+ title={t('staff.support.tickets.title', 'Support Inbox')}
375
+ titleHeadingLevel={1}
376
+ data={rows}
377
+ columns={columns}
378
+ isLoading={isLoading}
379
+ searchValue={search}
380
+ onSearchChange={(value) => {
381
+ setSearch(value)
382
+ setPage(1)
383
+ }}
384
+ searchPlaceholder={t('staff.support.tickets.searchPlaceholder', 'Search by ticket reference')}
385
+ filters={filters}
386
+ filterValues={filterValues}
387
+ onFiltersApply={(values) => {
388
+ setFilterValues(values)
389
+ setPage(1)
390
+ }}
391
+ onFiltersClear={() => {
392
+ setFilterValues({})
393
+ setPage(1)
394
+ }}
395
+ emptyState={
396
+ <div className="py-12 text-center text-sm text-muted-foreground">
397
+ {t('staff.support.tickets.empty', 'No support tickets match these filters.')}
398
+ </div>
399
+ }
400
+ refreshButton={{
401
+ label: t('staff.support.tickets.refresh', 'Refresh'),
402
+ onRefresh: refresh,
403
+ isRefreshing: isLoading,
404
+ }}
405
+ pagination={{
406
+ page,
407
+ pageSize: PAGE_SIZE,
408
+ total,
409
+ totalPages,
410
+ totalIsCapped,
411
+ onPageChange: setPage,
412
+ }}
413
+ onRowClick={(row) => openTask(row.id)}
414
+ />
415
+ {drawerTaskId ? (
416
+ <TaskDrawer
417
+ taskId={drawerTaskId}
418
+ open
419
+ onOpenChange={(next) => {
420
+ if (!next) closeTask()
421
+ }}
422
+ onChanged={refresh}
423
+ />
424
+ ) : null}
425
+ </PageBody>
426
+ </Page>
427
+ )
428
+ }
@@ -5,6 +5,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
5
5
  import { seedStaffActivityTypes, seedStaffAddressTypes, seedStaffTeamExamples, type StaffSeedScope } from './lib/seeds'
6
6
  import { seedStaffTimeTrackingExamples } from './lib/timeTrackingSeeds'
7
7
  import { migrateProjectCodes } from './lib/time-tracking/migrateProjectCodes'
8
+ import { resolveContainerProjectCodeGenerator, seedSupportDefaults } from './lib/support/seedDefaults'
8
9
  import { appendWidgetsToRoles } from '@open-mercato/core/modules/dashboards/lib/role-widgets'
9
10
  import type { ProgressService } from '@open-mercato/core/modules/progress/lib/progressService'
10
11
  import {
@@ -202,6 +203,32 @@ const seedAddressTypesCommand: ModuleCli = {
202
203
  },
203
204
  }
204
205
 
206
+ const seedSupportDefaultsCommand: ModuleCli = {
207
+ command: 'seed-support-defaults',
208
+ async run(rest) {
209
+ const args = parseArgs(rest)
210
+ const tenantId = String(args.tenantId ?? args.tenant ?? '')
211
+ const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')
212
+ if (!tenantId || !organizationId) {
213
+ console.error('Usage: mercato staff seed-support-defaults --tenant <tenantId> --org <organizationId>')
214
+ return
215
+ }
216
+ const container = await createRequestContainer()
217
+ const scope: StaffSeedScope = { tenantId, organizationId }
218
+ try {
219
+ const em = container.resolve<EntityManager>('em')
220
+ const { translate } = await resolveTranslations()
221
+ await seedSupportDefaults(em.fork(), scope, { translate, generateCode: resolveContainerProjectCodeGenerator(container) })
222
+ console.log('🎫 Support ticket defaults seeded for organization', organizationId)
223
+ } finally {
224
+ const disposable = container as unknown as { dispose?: () => Promise<void> }
225
+ if (typeof disposable.dispose === 'function') {
226
+ await disposable.dispose()
227
+ }
228
+ }
229
+ },
230
+ }
231
+
205
232
  const seedTimesheetsWidgetsCommand: ModuleCli = {
206
233
  command: 'seed-timesheets-widgets',
207
234
  async run(rest) {
@@ -344,6 +371,7 @@ export default [
344
371
  seedExamplesCommand,
345
372
  seedTimeTrackingExamplesCommand,
346
373
  seedTimesheetsWidgetsCommand,
374
+ seedSupportDefaultsCommand,
347
375
  migrateProjectCodesCommand,
348
376
  recalculateCommand,
349
377
  ]
@@ -14,3 +14,6 @@ import './timesheets-tasks'
14
14
  import './timesheets-tags'
15
15
  import './timesheets-task-comments'
16
16
  import './timesheets-reports'
17
+ import './support-routing-rules'
18
+ import './timesheets-task-assignment'
19
+ import './support-tickets'
@@ -3,7 +3,8 @@ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
3
  import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
4
4
  import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
5
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
6
- import { StaffTeamMember } from '../data/entities'
6
+ import { LockMode } from '@mikro-orm/core'
7
+ import { StaffTeamMember, StaffTimeProject } from '../data/entities'
7
8
 
8
9
  export { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }
9
10
 
@@ -125,3 +126,23 @@ export async function requireTeamMember(
125
126
  if (!member) throw new CrudHttpError(404, { error: message })
126
127
  return member
127
128
  }
129
+
130
+ /**
131
+ * Shared row lock every writer takes before it makes a task live on a project (create,
132
+ * portal intake, restore) or points a support routing rule at one. The fallback-project
133
+ * undo holds the exclusive lock across its checks and the project's soft-delete, so a
134
+ * writer either lands first and the undo sees it, or waits and then sees the project
135
+ * gone. Lock order is always project before rule. MUST run inside the writer's
136
+ * transaction; `null` means the project is not live once the lock is granted.
137
+ */
138
+ export async function lockLiveProjectForWrite(
139
+ em: EntityManager,
140
+ timeProjectId: string,
141
+ scope: StaffCommandScope,
142
+ ): Promise<StaffTimeProject | null> {
143
+ return em.findOne(
144
+ StaffTimeProject,
145
+ applyScopeToWhere<StaffTimeProject>({ id: timeProjectId, deletedAt: null }, scope),
146
+ { lockMode: LockMode.PESSIMISTIC_READ },
147
+ )
148
+ }