@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
@@ -5,11 +5,13 @@ import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacS
5
5
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
6
6
  import { CustomerEntity } from '@open-mercato/core/modules/customers/data/entities'
7
7
  import {
8
+ StaffSupportTicket,
8
9
  StaffTeamMember,
9
10
  StaffTimeProject,
10
11
  StaffTimeReport,
11
12
  StaffTimeReportEntry,
12
13
  StaffTimeReportEvent,
14
+ StaffTimeProjectMember,
13
15
  StaffTimeTask,
14
16
  StaffTimeTaskStatus,
15
17
  } from './entities'
@@ -22,8 +24,15 @@ import {
22
24
  type MemberPreview,
23
25
  } from '../lib/timesheets-projects/listProjectMembersPreview'
24
26
  import { computeTaskRollups, EMPTY_TASK_ROLLUP } from '../lib/timesheets-tasks/computeTaskRollups'
27
+ import { derivePortalStatus } from '../lib/support/portalStatus'
28
+ import {
29
+ isActiveAssignment,
30
+ normalizeAssignmentGraceDays,
31
+ resolveAssignmentGraceDaysFromContainer,
32
+ } from '../lib/time-tracking/projectAssignment'
25
33
 
26
34
  const MANAGE_FEATURE = 'staff.timesheets.projects.manage'
35
+ const TASKS_VIEW_FEATURE = 'staff.timesheets.tasks.view'
27
36
  const RATES_FEATURE = 'staff.timesheets.rates.view'
28
37
 
29
38
  /**
@@ -635,6 +644,198 @@ const taskContextEnricher: ResponseEnricher<EntityRecord, TaskContextEnrichment>
635
644
  },
636
645
  }
637
646
 
647
+ export type SupportTicketEnrichment = {
648
+ _staffSupport: {
649
+ ticket: {
650
+ id: string
651
+ supportReference: string
652
+ category: { value: string; label: string }
653
+ routedVia: string
654
+ portalStatus: string
655
+ customerSnapshot: Record<string, unknown> | null
656
+ reportedByCustomerUserId: string | null
657
+ unnotifiedAt: string | null
658
+ customerRequest: { subject: string; body: string } | null
659
+ } | null
660
+ }
661
+ }
662
+
663
+ const EMPTY_SUPPORT_TICKET: SupportTicketEnrichment = { _staffSupport: { ticket: null } }
664
+
665
+ /**
666
+ * The customer request behind a portal ticket. Internal tasks get `ticket: null`.
667
+ * `customerRequest` is the encrypted original the portal shows, read here so the
668
+ * drawer can show staff exactly what the customer sees.
669
+ */
670
+ const supportTicketEnricher: ResponseEnricher<EntityRecord, SupportTicketEnrichment> = {
671
+ id: 'staff.timesheets-tasks-support-ticket',
672
+ targetEntity: 'staff:staff_time_task',
673
+ priority: 4,
674
+ timeout: 3000,
675
+ critical: false,
676
+ fallback: { ...EMPTY_SUPPORT_TICKET },
677
+ cacheableOnListHit: false,
678
+
679
+ async enrichOne(record, context) {
680
+ const enriched = await this.enrichMany!([record], context)
681
+ return enriched[0]
682
+ },
683
+
684
+ async enrichMany(records, context) {
685
+ if (records.length === 0) return records as (EntityRecord & SupportTicketEnrichment)[]
686
+ const ctx = context as InternalContext
687
+ if (!ctx.tenantId || !ctx.organizationId) {
688
+ return records.map((record) => ({ ...record, ...EMPTY_SUPPORT_TICKET }))
689
+ }
690
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
691
+ const portalRecordIds = records
692
+ .filter((record) => record.source === 'portal' || record.source === undefined)
693
+ .map((record) => record.id)
694
+ .filter((id): id is string => typeof id === 'string')
695
+ if (portalRecordIds.length === 0) {
696
+ return records.map((record) => ({ ...record, ...EMPTY_SUPPORT_TICKET }))
697
+ }
698
+ const tickets = await findWithDecryption(
699
+ ctx.em.fork(),
700
+ StaffSupportTicket,
701
+ { taskId: { $in: portalRecordIds }, ...scope, deletedAt: null },
702
+ undefined,
703
+ scope,
704
+ )
705
+ if (tickets.length === 0) {
706
+ return records.map((record) => ({ ...record, ...EMPTY_SUPPORT_TICKET }))
707
+ }
708
+ const ticketByTask = new Map(tickets.map((ticket) => [ticket.taskId, ticket]))
709
+ const tasks = await ctx.em.fork().find(
710
+ StaffTimeTask,
711
+ { id: { $in: tickets.map((ticket) => ticket.taskId) }, ...scope },
712
+ { fields: ['id', 'taskStatusId'] },
713
+ )
714
+ const statusIds = [...new Set(tasks.map((task) => task.taskStatusId))]
715
+ const statuses = statusIds.length
716
+ ? await ctx.em.fork().find(StaffTimeTaskStatus, { id: { $in: statusIds }, ...scope }, { fields: ['id', 'isDefault', 'isDone'] })
717
+ : []
718
+ const statusById = new Map(statuses.map((status) => [status.id, status]))
719
+ const statusByTask = new Map(tasks.map((task) => [task.id, statusById.get(task.taskStatusId) ?? null]))
720
+
721
+ return records.map((record) => {
722
+ const ticket = typeof record.id === 'string' ? ticketByTask.get(record.id) : undefined
723
+ if (!ticket) return { ...record, ...EMPTY_SUPPORT_TICKET }
724
+ const request = ticket.customerRequestSnapshot
725
+ return {
726
+ ...record,
727
+ _staffSupport: {
728
+ ticket: {
729
+ id: ticket.id,
730
+ supportReference: ticket.supportReference,
731
+ category: ticket.categorySnapshot,
732
+ routedVia: ticket.routedVia,
733
+ portalStatus: derivePortalStatus(statusByTask.get(ticket.taskId) ?? null),
734
+ customerSnapshot: ticket.customerSnapshot ?? null,
735
+ reportedByCustomerUserId: ticket.reportedByCustomerUserId ?? null,
736
+ unnotifiedAt: ticket.unnotifiedAt ? ticket.unnotifiedAt.toISOString() : null,
737
+ customerRequest:
738
+ request && typeof request === 'object' && typeof request.subject === 'string'
739
+ ? { subject: request.subject, body: typeof request.body === 'string' ? request.body : '' }
740
+ : null,
741
+ },
742
+ },
743
+ }
744
+ })
745
+ },
746
+ }
747
+
748
+ /**
749
+ * Whether the CALLER may claim or release each task right now, decided by the same
750
+ * rule the claim and release commands enforce: claiming needs an active assigner
751
+ * membership of the task's project inside its assignment window (plus the tenant's
752
+ * grace days), an unassigned task and a column that is not done; releasing needs the
753
+ * caller to be the current assignee. The drawer reads these instead of guessing from
754
+ * a page of project members, so it never offers an action the server refuses.
755
+ */
756
+ export type TaskAssignmentEnrichment = {
757
+ _staffAssignment: {
758
+ canClaim: boolean
759
+ canRelease: boolean
760
+ }
761
+ }
762
+
763
+ const NO_TASK_ASSIGNMENT: TaskAssignmentEnrichment = { _staffAssignment: { canClaim: false, canRelease: false } }
764
+
765
+ const taskAssignmentEnricher: ResponseEnricher<EntityRecord, TaskAssignmentEnrichment> = {
766
+ id: 'staff.timesheets-tasks-assignment',
767
+ targetEntity: 'staff:staff_time_task',
768
+ features: [TASKS_VIEW_FEATURE],
769
+ priority: 3,
770
+ timeout: 3000,
771
+ critical: false,
772
+ fallback: { ...NO_TASK_ASSIGNMENT },
773
+ cacheableOnListHit: false,
774
+
775
+ async enrichOne(record, context) {
776
+ const enriched = await this.enrichMany!([record], context)
777
+ return enriched[0]
778
+ },
779
+
780
+ async enrichMany(records, context) {
781
+ if (records.length === 0) return records as (EntityRecord & TaskAssignmentEnrichment)[]
782
+ const ctx = context as InternalContext
783
+ if (!ctx.tenantId || !ctx.organizationId || !ctx.userId) {
784
+ return records.map((record) => ({ ...record, ...NO_TASK_ASSIGNMENT }))
785
+ }
786
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
787
+ const callerStaffMemberId = await resolveCallerStaffMemberId(ctx)
788
+ if (!callerStaffMemberId) return records.map((record) => ({ ...record, ...NO_TASK_ASSIGNMENT }))
789
+
790
+ const taskIds = records.map((record) => record.id).filter((id): id is string => typeof id === 'string')
791
+ const tasks = await ctx.em.fork().find(
792
+ StaffTimeTask,
793
+ { id: { $in: taskIds }, ...scope, deletedAt: null },
794
+ { fields: ['id', 'timeProjectId', 'taskStatusId', 'assigneeStaffMemberId'] },
795
+ )
796
+ const taskById = new Map(tasks.map((task) => [task.id, task]))
797
+ const projectIds = [...new Set(tasks.map((task) => task.timeProjectId).filter(Boolean))]
798
+ const statusIds = [...new Set(tasks.map((task) => task.taskStatusId).filter(Boolean))]
799
+
800
+ const [statuses, memberships, graceDays] = await Promise.all([
801
+ statusIds.length
802
+ ? ctx.em.fork().find(StaffTimeTaskStatus, { id: { $in: statusIds }, ...scope }, { fields: ['id', 'isDone'] })
803
+ : Promise.resolve([]),
804
+ projectIds.length
805
+ ? ctx.em.fork().find(StaffTimeProjectMember, {
806
+ timeProjectId: { $in: projectIds },
807
+ staffMemberId: callerStaffMemberId,
808
+ isAssigner: true,
809
+ status: 'active',
810
+ deletedAt: null,
811
+ ...scope,
812
+ })
813
+ : Promise.resolve([]),
814
+ resolveAssignmentGraceDaysFromContainer(ctx.container, ctx.tenantId),
815
+ ])
816
+ const doneStatusIds = new Set(statuses.filter((status) => status.isDone).map((status) => status.id))
817
+ const now = new Date()
818
+ const assignerProjectIds = new Set(
819
+ memberships
820
+ .filter((membership) => isActiveAssignment(membership, now, normalizeAssignmentGraceDays(graceDays)))
821
+ .map((membership) => membership.timeProjectId),
822
+ )
823
+
824
+ return records.map((record) => {
825
+ const task = taskById.get(record.id)
826
+ if (!task) return { ...record, ...NO_TASK_ASSIGNMENT }
827
+ const assignee = task.assigneeStaffMemberId ?? null
828
+ return {
829
+ ...record,
830
+ _staffAssignment: {
831
+ canClaim: !assignee && assignerProjectIds.has(task.timeProjectId) && !doneStatusIds.has(task.taskStatusId),
832
+ canRelease: assignee === callerStaffMemberId,
833
+ },
834
+ }
835
+ })
836
+ },
837
+ }
838
+
638
839
  /**
639
840
  * What a report row cannot say about itself: whether it is frozen, how many entries
640
841
  * the freeze holds, and whether anyone has taken a file out of it.
@@ -787,6 +988,8 @@ const apiEnrichers: ResponseEnricher[] = [
787
988
  taskRollupEnricher,
788
989
  taskTagEnricher,
789
990
  taskContextEnricher,
991
+ supportTicketEnricher,
992
+ taskAssignmentEnricher,
790
993
  timeEntryEnricher,
791
994
  reportEnricher,
792
995
  ]
@@ -376,6 +376,11 @@ export class StaffTeamMemberAddress {
376
376
  export type StaffTimeEntrySource = TimeEntrySource
377
377
  export type StaffTimeProjectStatus = 'active' | 'on_hold' | 'completed'
378
378
  export type StaffTimeProjectMemberStatus = 'active' | 'inactive'
379
+ export type StaffTimeTaskSource = 'internal' | 'portal'
380
+ export type StaffTimeTaskCommentVisibility = 'internal' | 'customer'
381
+ export type StaffTimeTaskCommentAuthorKind = 'staff' | 'customer'
382
+ export type StaffSupportRoutedVia = 'category' | 'fallback'
383
+ export type StaffSupportPortalStatus = 'received' | 'in_progress' | 'resolved'
379
384
  export type StaffTimeEntrySegmentType = 'work' | 'break'
380
385
  export type StaffTimeProjectBudgetKind = 'none' | 'hours' | 'amount'
381
386
  export type StaffTimeReportPeriodKind = 'week' | 'month' | 'year' | 'custom'
@@ -572,6 +577,9 @@ export class StaffTimeProject {
572
577
  @Property({ name: 'budget_alerted_at_percent', type: 'integer', nullable: true })
573
578
  budgetAlertedAtPercent?: number | null
574
579
 
580
+ @Property({ name: 'default_assignee_staff_member_id', type: 'uuid', nullable: true })
581
+ defaultAssigneeStaffMemberId?: string | null
582
+
575
583
  @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
576
584
  createdAt: Date = new Date()
577
585
 
@@ -587,7 +595,10 @@ export class StaffTimeProject {
587
595
  @Index({ name: 'staff_time_project_members_project_idx', properties: ['organizationId', 'timeProjectId'] })
588
596
  @Index({ name: 'staff_time_project_members_member_idx', properties: ['organizationId', 'staffMemberId'] })
589
597
  @Index({ name: 'staff_time_project_members_unique_idx', properties: ['organizationId', 'tenantId', 'timeProjectId', 'staffMemberId'], options: { unique: true, where: 'deleted_at IS NULL' } })
598
+ @Index({ name: 'staff_time_project_members_assigner_idx', properties: ['tenantId', 'organizationId', 'timeProjectId'], options: { where: "is_assigner = true AND status = 'active' AND deleted_at IS NULL" } })
590
599
  export class StaffTimeProjectMember {
600
+ [OptionalProps]?: 'status' | 'showInGrid' | 'isAssigner' | 'createdAt' | 'updatedAt'
601
+
591
602
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
592
603
  id!: string
593
604
 
@@ -612,6 +623,9 @@ export class StaffTimeProjectMember {
612
623
  @Property({ name: 'show_in_grid', type: 'boolean', default: false })
613
624
  showInGrid: boolean = false
614
625
 
626
+ @Property({ name: 'is_assigner', type: 'boolean', default: false })
627
+ isAssigner: boolean = false
628
+
615
629
  @Property({ name: 'assigned_start_date', type: 'date' })
616
630
  assignedStartDate!: Date
617
631
 
@@ -684,8 +698,9 @@ export class StaffTimeTaskStatus {
684
698
  @Index({ name: 'staff_time_tasks_assignee_idx', properties: ['organizationId', 'assigneeStaffMemberId'] })
685
699
  @Index({ name: 'staff_time_tasks_sequence_unique_idx', properties: ['organizationId', 'tenantId', 'timeProjectId', 'sequenceNumber'], options: { unique: true, where: 'deleted_at IS NULL' } })
686
700
  @Index({ name: 'staff_time_tasks_reference_unique_idx', properties: ['organizationId', 'tenantId', 'reference'], options: { unique: true, where: 'deleted_at IS NULL' } })
701
+ @Index({ name: 'staff_time_tasks_portal_customer_idx', properties: ['tenantId', 'organizationId', 'customerId', 'updatedAt'], options: { where: "source = 'portal' AND deleted_at IS NULL" } })
687
702
  export class StaffTimeTask {
688
- [OptionalProps]?: 'sequenceNumber' | 'position' | 'createdAt' | 'updatedAt'
703
+ [OptionalProps]?: 'sequenceNumber' | 'position' | 'source' | 'createdAt' | 'updatedAt'
689
704
 
690
705
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
691
706
  id!: string
@@ -729,6 +744,110 @@ export class StaffTimeTask {
729
744
  @Property({ name: 'closed_at', type: Date, nullable: true })
730
745
  closedAt?: Date | null
731
746
 
747
+ @Property({ type: 'text', default: 'internal' })
748
+ source: StaffTimeTaskSource = 'internal'
749
+
750
+ @Property({ name: 'customer_id', type: 'uuid', nullable: true })
751
+ customerId?: string | null
752
+
753
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
754
+ createdAt: Date = new Date()
755
+
756
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
757
+ updatedAt: Date = new Date()
758
+
759
+ @Property({ name: 'deleted_at', type: Date, nullable: true })
760
+ deletedAt?: Date | null
761
+ }
762
+
763
+ @Entity({ tableName: 'staff_support_tickets' })
764
+ @Index({ name: 'staff_support_tickets_tenant_org_idx', properties: ['tenantId', 'organizationId'] })
765
+ @Index({ name: 'staff_support_tickets_task_unique_idx', properties: ['tenantId', 'organizationId', 'taskId'], options: { unique: true, where: 'deleted_at IS NULL' } })
766
+ @Index({ name: 'staff_support_tickets_reference_unique_idx', properties: ['tenantId', 'organizationId', 'supportReference'], options: { unique: true, where: 'deleted_at IS NULL' } })
767
+ @Index({ name: 'staff_support_tickets_sequence_idx', properties: ['tenantId', 'organizationId', 'supportSequenceNumber'] })
768
+ export class StaffSupportTicket {
769
+ [OptionalProps]?: 'createdAt' | 'updatedAt'
770
+
771
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
772
+ id!: string
773
+
774
+ @Property({ name: 'tenant_id', type: 'uuid' })
775
+ tenantId!: string
776
+
777
+ @Property({ name: 'organization_id', type: 'uuid' })
778
+ organizationId!: string
779
+
780
+ @Property({ name: 'task_id', type: 'uuid' })
781
+ taskId!: string
782
+
783
+ @Property({ name: 'customer_snapshot', type: 'jsonb', nullable: true })
784
+ customerSnapshot?: Record<string, unknown> | null
785
+
786
+ @Property({ name: 'reported_by_customer_user_id', type: 'uuid', nullable: true })
787
+ reportedByCustomerUserId?: string | null
788
+
789
+ @Property({ name: 'support_reference', type: 'text' })
790
+ supportReference!: string
791
+
792
+ @Property({ name: 'support_sequence_number', type: 'integer' })
793
+ supportSequenceNumber!: number
794
+
795
+ @Property({ name: 'category_value', type: 'text' })
796
+ categoryValue!: string
797
+
798
+ @Property({ name: 'category_snapshot', type: 'jsonb' })
799
+ categorySnapshot!: { value: string; label: string }
800
+
801
+ @Property({ name: 'routed_via', type: 'text' })
802
+ routedVia!: StaffSupportRoutedVia
803
+
804
+ @Property({ name: 'customer_request_snapshot', type: 'jsonb' })
805
+ customerRequestSnapshot!: { subject: string; body: string }
806
+
807
+ @Property({ name: 'unnotified_at', type: Date, nullable: true })
808
+ unnotifiedAt?: Date | null
809
+
810
+ @Property({ name: 'portal_status_published', type: 'text', nullable: true })
811
+ portalStatusPublished?: StaffSupportPortalStatus | null
812
+
813
+ @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
814
+ createdAt: Date = new Date()
815
+
816
+ @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
817
+ updatedAt: Date = new Date()
818
+
819
+ @Property({ name: 'deleted_at', type: Date, nullable: true })
820
+ deletedAt?: Date | null
821
+ }
822
+
823
+ @Entity({ tableName: 'staff_support_routing_rules' })
824
+ @Index({ name: 'staff_support_routing_rules_tenant_org_idx', properties: ['tenantId', 'organizationId'] })
825
+ @Index({
826
+ name: 'staff_support_routing_rules_category_unique_idx',
827
+ expression:
828
+ 'create unique index "staff_support_routing_rules_category_unique_idx" on "staff_support_routing_rules" ("tenant_id", "organization_id", coalesce("category_value", \'\')) where "deleted_at" is null',
829
+ })
830
+ export class StaffSupportRoutingRule {
831
+ [OptionalProps]?: 'isActive' | 'createdAt' | 'updatedAt'
832
+
833
+ @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
834
+ id!: string
835
+
836
+ @Property({ name: 'tenant_id', type: 'uuid' })
837
+ tenantId!: string
838
+
839
+ @Property({ name: 'organization_id', type: 'uuid' })
840
+ organizationId!: string
841
+
842
+ @Property({ name: 'category_value', type: 'text', nullable: true })
843
+ categoryValue?: string | null
844
+
845
+ @Property({ name: 'time_project_id', type: 'uuid' })
846
+ timeProjectId!: string
847
+
848
+ @Property({ name: 'is_active', type: 'boolean', default: true })
849
+ isActive: boolean = true
850
+
732
851
  @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
733
852
  createdAt: Date = new Date()
734
853
 
@@ -743,7 +862,7 @@ export class StaffTimeTask {
743
862
  @Index({ name: 'staff_time_task_comments_tenant_org_idx', properties: ['tenantId', 'organizationId'] })
744
863
  @Index({ name: 'staff_time_task_comments_task_idx', properties: ['organizationId', 'taskId', 'createdAt'] })
745
864
  export class StaffTimeTaskComment {
746
- [OptionalProps]?: 'createdAt' | 'updatedAt'
865
+ [OptionalProps]?: 'visibility' | 'authorKind' | 'createdAt' | 'updatedAt'
747
866
 
748
867
  @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
749
868
  id!: string
@@ -763,6 +882,18 @@ export class StaffTimeTaskComment {
763
882
  @Property({ name: 'author_user_id', type: 'uuid', nullable: true })
764
883
  authorUserId?: string | null
765
884
 
885
+ @Property({ type: 'text', default: 'internal' })
886
+ visibility: StaffTimeTaskCommentVisibility = 'internal'
887
+
888
+ @Property({ name: 'author_kind', type: 'text', default: 'staff' })
889
+ authorKind: StaffTimeTaskCommentAuthorKind = 'staff'
890
+
891
+ @Property({ name: 'author_customer_user_id', type: 'uuid', nullable: true })
892
+ authorCustomerUserId?: string | null
893
+
894
+ @Property({ name: 'author_display_snapshot', type: 'jsonb', nullable: true })
895
+ authorDisplaySnapshot?: { displayName: string } | null
896
+
766
897
  @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
767
898
  createdAt: Date = new Date()
768
899
 
@@ -8,7 +8,7 @@ import type { EntityExtension } from '@open-mercato/shared/modules/entities'
8
8
  * `staff_time_projects.customer_id` are plain uuid columns; the declarations below are
9
9
  * the traversal contract over them and nothing more. They contain **string ids only** —
10
10
  * no import of a customers or sales symbol — so they add no load-time dependency and
11
- * staff's `requires: ['planner', 'resources']` in `index.ts` does not grow. That matters:
11
+ * staff's `requires` in `index.ts` never gains `customers` or `sales`. That matters:
12
12
  * staff is slated for extraction into a standalone package, and a link that pulled in
13
13
  * `@open-mercato/core/modules/customers` would break the extraction.
14
14
  *
@@ -62,6 +62,14 @@ const entityExtensions: EntityExtension[] = [
62
62
  cardinality: 'one-to-many',
63
63
  description: 'Time projects owned by a customer record (D-9: the customers supertype)',
64
64
  },
65
+ {
66
+ base: 'customers:customer_entity',
67
+ extension: 'staff:staff_time_task',
68
+ join: { baseKey: 'id', extensionKey: 'customer_id' },
69
+ table: 'staff_time_tasks',
70
+ cardinality: 'one-to-many',
71
+ description: 'Tasks filed for a customer record, such as customer portal support tickets',
72
+ },
65
73
  ]
66
74
 
67
75
  export const extensions = entityExtensions
@@ -476,6 +476,7 @@ export const staffTimeProjectUpdateSchema = z.object({
476
476
  budgetKind: timeProjectBudgetKindSchema.optional(),
477
477
  budgetValue: numericAmountSchema.optional().nullable(),
478
478
  budgetWarnAtPercent: z.number().int().min(1).max(100).optional(),
479
+ defaultAssigneeStaffMemberId: z.string().uuid().optional().nullable(),
479
480
  })
480
481
 
481
482
  export const staffTimeProjectMemberAssignSchema = z.object({
@@ -486,6 +487,7 @@ export const staffTimeProjectMemberAssignSchema = z.object({
486
487
  status: timeProjectMemberStatusSchema.optional().default('active'),
487
488
  assignedStartDate: z.coerce.date(),
488
489
  assignedEndDate: z.coerce.date().optional().nullable(),
490
+ isAssigner: z.boolean().optional(),
489
491
  })
490
492
 
491
493
  export const staffTimeProjectMemberUpdateSchema = z.object({
@@ -493,6 +495,7 @@ export const staffTimeProjectMemberUpdateSchema = z.object({
493
495
  role: z.string().max(100).optional().nullable(),
494
496
  status: timeProjectMemberStatusSchema.optional(),
495
497
  assignedEndDate: z.coerce.date().optional().nullable(),
498
+ isAssigner: z.boolean().optional(),
496
499
  })
497
500
 
498
501
  export const staffMyProjectVisibilityUpdateSchema = z.object({
@@ -596,13 +599,24 @@ export const staffTimeTaskCommentCreateSchema = z.object({
596
599
  taskId: z.string().uuid(),
597
600
  body: z.string().trim().min(1).max(5000),
598
601
  authorUserId: z.string().uuid().optional().nullable(),
602
+ visibility: z.enum(['internal', 'customer']).optional(),
599
603
  })
600
604
 
601
605
  export const staffTimeTaskCommentUpdateSchema = z.object({
602
606
  ...scopedUpdateFields,
603
607
  body: z.string().trim().min(1).max(5000),
608
+ visibility: z.enum(['internal', 'customer']).optional(),
604
609
  })
605
610
 
611
+ export const staffTimeTaskCommentUpdateRequestSchema = staffTimeTaskCommentUpdateSchema
612
+ .extend({
613
+ body: z.string().trim().min(1).max(5000).optional(),
614
+ })
615
+ .refine((value) => value.body !== undefined || value.visibility !== undefined, {
616
+ message: 'body or visibility is required',
617
+ path: ['body'],
618
+ })
619
+
606
620
  export const staffTimeTagCreateSchema = z.object({
607
621
  ...scopedCreateFields,
608
622
  slug: timeSlugSchema,
@@ -721,6 +735,7 @@ export type StaffTimeTaskUpdateInput = z.infer<typeof staffTimeTaskUpdateSchema>
721
735
  export type StaffTimeTaskStatusChangeInput = z.infer<typeof staffTimeTaskStatusChangeSchema>
722
736
  export type StaffTimeTaskCommentCreateInput = z.infer<typeof staffTimeTaskCommentCreateSchema>
723
737
  export type StaffTimeTaskCommentUpdateInput = z.infer<typeof staffTimeTaskCommentUpdateSchema>
738
+ export type StaffTimeTaskCommentUpdateRequestInput = z.infer<typeof staffTimeTaskCommentUpdateRequestSchema>
724
739
  export type StaffTimeTagCreateInput = z.infer<typeof staffTimeTagCreateSchema>
725
740
  export type StaffTimeTaskTagAssignmentInput = z.infer<typeof staffTimeTaskTagAssignmentSchema>
726
741
  export type StaffTimeEntryTagAssignmentInput = z.infer<typeof staffTimeEntryTagAssignmentSchema>
@@ -731,3 +746,24 @@ export type StaffTimeReportCloseInput = z.infer<typeof staffTimeReportCloseSchem
731
746
  export type StaffTimeReportUnlockInput = z.infer<typeof staffTimeReportUnlockSchema>
732
747
  export type StaffTimeProjectChangeCurrencyInput = z.infer<typeof staffTimeProjectChangeCurrencySchema>
733
748
  export type StaffTimeTrackingSettingsInput = z.infer<typeof staffTimeTrackingSettingsSchema>
749
+
750
+ export const staffSupportRoutingRuleCreateSchema = z.object({
751
+ ...scopedCreateFields,
752
+ categoryValue: z.string().trim().min(1).max(150).optional().nullable(),
753
+ timeProjectId: z.string().uuid(),
754
+ isActive: z.boolean().optional().default(true),
755
+ })
756
+
757
+ export const staffSupportRoutingRuleUpdateSchema = z.object({
758
+ ...scopedUpdateFields,
759
+ timeProjectId: z.string().uuid().optional(),
760
+ isActive: z.boolean().optional(),
761
+ })
762
+
763
+ export const staffSupportFallbackProjectCreateSchema = z.object({
764
+ ...scopedCreateFields,
765
+ })
766
+
767
+ export type StaffSupportRoutingRuleCreateInput = z.infer<typeof staffSupportRoutingRuleCreateSchema>
768
+ export type StaffSupportRoutingRuleUpdateInput = z.infer<typeof staffSupportRoutingRuleUpdateSchema>
769
+ export type StaffSupportFallbackProjectCreateInput = z.infer<typeof staffSupportFallbackProjectCreateSchema>
@@ -9,6 +9,12 @@ export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
9
9
  { field: 'unavailability_reason_value' },
10
10
  ],
11
11
  },
12
+ {
13
+ entityId: 'staff:staff_support_ticket',
14
+ fields: [
15
+ { field: 'customer_request_snapshot' },
16
+ ],
17
+ },
12
18
  ]
13
19
 
14
20
  export default defaultEncryptionMaps
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Payload contracts for the `staff.timesheets.*` event family.
2
+ * Payload contracts for the `staff.timesheets.*` and `staff.support_ticket.*` event families.
3
3
  *
4
4
  * **Why this file exists.** Outbound webhooks in this platform are not registered
5
5
  * per event: `packages/webhooks` subscribes once with `event: '*'`
@@ -109,6 +109,18 @@ const taskStatusChanged = scope
109
109
  })
110
110
  .passthrough()
111
111
 
112
+ /** Ids only: the event is `clientBroadcast: true`, so no names, no customer and no text. */
113
+ const taskAssigneeChanged = scope
114
+ .extend({
115
+ id: z.string(),
116
+ timeProjectId: z.string(),
117
+ previousAssigneeStaffMemberId: z.string().nullable(),
118
+ assigneeStaffMemberId: z.string().nullable(),
119
+ changeKind: z.enum(['default', 'claim', 'release', 'manual']),
120
+ actorUserId: z.string().nullable().optional(),
121
+ })
122
+ .passthrough()
123
+
112
124
  /**
113
125
  * No `customerId`: the event is `clientBroadcast: true` and the DOM Event Bridge
114
126
  * applies no feature check, so the one field linking a report to a named client is
@@ -241,6 +253,7 @@ export const staffTimeTrackingEventPayloadSchemas = {
241
253
  'staff.timesheets.time_task.updated': crudPayload,
242
254
  'staff.timesheets.time_task.deleted': crudPayload,
243
255
  'staff.timesheets.time_task.status_changed': taskStatusChanged,
256
+ 'staff.timesheets.time_task.assignee_changed': taskAssigneeChanged,
244
257
  'staff.timesheets.time_task_status.created': crudPayload,
245
258
  'staff.timesheets.time_task_status.updated': crudPayload,
246
259
  'staff.timesheets.time_task_status.deleted': crudPayload,
@@ -266,3 +279,76 @@ export type StaffTimeTrackingEventId = keyof typeof staffTimeTrackingEventPayloa
266
279
  export type StaffTimeTrackingEventPayload<TEventId extends StaffTimeTrackingEventId> = z.infer<
267
280
  typeof staffTimeTrackingEventPayloadSchemas[TEventId]
268
281
  >
282
+
283
+ /**
284
+ * Names the customer and the ticket reference, so the event stays server-side
285
+ * (no `clientBroadcast`); webhooks and the notification subscriber read it.
286
+ */
287
+ const supportTicketCreated = scope
288
+ .extend({
289
+ id: z.string(),
290
+ taskId: z.string(),
291
+ projectId: z.string(),
292
+ customerId: z.string(),
293
+ supportReference: z.string(),
294
+ categoryValue: z.string(),
295
+ })
296
+ .passthrough()
297
+
298
+ const supportTicketCustomerReplied = scope
299
+ .extend({
300
+ id: z.string(),
301
+ taskId: z.string(),
302
+ projectId: z.string(),
303
+ commentId: z.string(),
304
+ supportReference: z.string(),
305
+ })
306
+ .passthrough()
307
+
308
+ /** Ids only: the event is `clientBroadcast: true`, so no customer, no reference and no text. */
309
+ const supportTicketActivityRecorded = scope
310
+ .extend({
311
+ id: z.string(),
312
+ taskId: z.string(),
313
+ projectId: z.string(),
314
+ kind: z.enum(['created', 'customer_replied', 'portal_status', 'attachment_added', 'attachment_removed']),
315
+ /** The changed attachment for `attachment_added` / `attachment_removed`, so distinct changes stay distinct for the 500 ms duplicate filter. */
316
+ attachmentId: z.string().optional(),
317
+ })
318
+ .passthrough()
319
+
320
+ const supportTicketPortalUpdated = scope
321
+ .extend({
322
+ id: z.string(),
323
+ ticketId: z.string(),
324
+ supportReference: z.string(),
325
+ portalStatus: z.enum(['received', 'in_progress', 'resolved']),
326
+ kind: z.enum(['status', 'reply']),
327
+ /** The change announced: the task version for `status`, the comment id for `reply`. Keeps distinct changes distinct for the portal bridge's 500 ms duplicate filter. */
328
+ version: z.string().optional(),
329
+ /**
330
+ * The portal SSE stream narrows a broadcast to these customer-user ids. The
331
+ * emitter never publishes this event with an empty list, so a ticket update can
332
+ * only ever reach the portal users of the ticket's own customer.
333
+ */
334
+ recipientUserIds: z.array(z.string()).min(1),
335
+ })
336
+ .passthrough()
337
+
338
+ /**
339
+ * Every declared `staff.support_ticket.*` event id and the payload a webhook endpoint
340
+ * subscribed to it receives. Same rules as the time-tracking map above: `tenantId` is
341
+ * required and every schema is `passthrough()`.
342
+ */
343
+ export const staffSupportTicketEventPayloadSchemas = {
344
+ 'staff.support_ticket.created': supportTicketCreated,
345
+ 'staff.support_ticket.customer_replied': supportTicketCustomerReplied,
346
+ 'staff.support_ticket.activity_recorded': supportTicketActivityRecorded,
347
+ 'staff.support_ticket.portal_updated': supportTicketPortalUpdated,
348
+ } as const
349
+
350
+ export type StaffSupportTicketEventId = keyof typeof staffSupportTicketEventPayloadSchemas
351
+
352
+ export type StaffSupportTicketEventPayload<TEventId extends StaffSupportTicketEventId> = z.infer<
353
+ typeof staffSupportTicketEventPayloadSchemas[TEventId]
354
+ >