@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,150 @@
1
+ import { z } from 'zod'
2
+ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
4
+ import { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
5
+ import { StaffSupportRoutingRule } from '../../../data/entities'
6
+ import {
7
+ staffSupportRoutingRuleCreateSchema,
8
+ staffSupportRoutingRuleUpdateSchema,
9
+ } from '../../../data/validators'
10
+ import { staffSupportRoutingRuleCommandIds } from '../../../commands/support-routing-rules'
11
+ import { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from '../../openapi'
12
+
13
+ const SETTINGS_FEATURE = 'staff.timesheets.settings.manage'
14
+
15
+ const F = {
16
+ id: 'id',
17
+ tenant_id: 'tenant_id',
18
+ organization_id: 'organization_id',
19
+ category_value: 'category_value',
20
+ time_project_id: 'time_project_id',
21
+ is_active: 'is_active',
22
+ created_at: 'created_at',
23
+ updated_at: 'updated_at',
24
+ } as const
25
+
26
+ const routeMetadata = {
27
+ GET: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },
28
+ POST: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },
29
+ PUT: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },
30
+ DELETE: { requireAuth: true, requireFeatures: [SETTINGS_FEATURE] },
31
+ }
32
+
33
+ export const metadata = routeMetadata
34
+
35
+ const rawBodySchema = z.object({}).passthrough()
36
+
37
+ const listSchema = z
38
+ .object({
39
+ page: z.coerce.number().min(1).default(1),
40
+ pageSize: z.coerce.number().min(1).max(100).default(100),
41
+ ids: z.string().optional(),
42
+ sortField: z.string().optional(),
43
+ sortDir: z.enum(['asc', 'desc']).optional(),
44
+ })
45
+ .passthrough()
46
+
47
+ const crud = makeCrudRoute({
48
+ metadata: routeMetadata,
49
+ orm: {
50
+ entity: StaffSupportRoutingRule,
51
+ idField: 'id',
52
+ orgField: 'organizationId',
53
+ tenantField: 'tenantId',
54
+ softDeleteField: 'deletedAt',
55
+ },
56
+ indexer: { entityType: 'staff:staff_support_routing_rule' },
57
+ list: {
58
+ schema: listSchema,
59
+ entityId: 'staff:staff_support_routing_rule',
60
+ fields: [
61
+ F.id,
62
+ F.organization_id,
63
+ F.tenant_id,
64
+ F.category_value,
65
+ F.time_project_id,
66
+ F.is_active,
67
+ F.created_at,
68
+ F.updated_at,
69
+ ],
70
+ defaultSort: { field: F.created_at, dir: 'asc' },
71
+ sortFieldMap: { createdAt: F.created_at, updatedAt: F.updated_at },
72
+ buildFilters: async (query) => {
73
+ const filters: Record<string, unknown> = {}
74
+ if (typeof query.ids === 'string' && query.ids.trim().length > 0) {
75
+ const ids = query.ids.split(',').map((value) => value.trim()).filter((value) => value.length > 0)
76
+ if (ids.length > 0) filters[F.id] = { $in: ids }
77
+ }
78
+ return filters
79
+ },
80
+ },
81
+ actions: {
82
+ create: {
83
+ commandId: staffSupportRoutingRuleCommandIds.create,
84
+ schema: rawBodySchema,
85
+ mapInput: async ({ raw, ctx }) => {
86
+ const { translate } = await resolveTranslations()
87
+ return parseScopedCommandInput(staffSupportRoutingRuleCreateSchema, raw ?? {}, ctx, translate)
88
+ },
89
+ response: ({ result }) => ({ id: result?.ruleId ?? null }),
90
+ status: 201,
91
+ },
92
+ update: {
93
+ commandId: staffSupportRoutingRuleCommandIds.update,
94
+ schema: rawBodySchema,
95
+ mapInput: async ({ raw, ctx }) => {
96
+ const { translate } = await resolveTranslations()
97
+ return parseScopedCommandInput(staffSupportRoutingRuleUpdateSchema, raw ?? {}, ctx, translate)
98
+ },
99
+ response: () => ({ ok: true }),
100
+ },
101
+ delete: {
102
+ commandId: staffSupportRoutingRuleCommandIds.delete,
103
+ schema: rawBodySchema,
104
+ mapInput: async ({ parsed, ctx }) => {
105
+ const { translate } = await resolveTranslations()
106
+ return { id: resolveCrudRecordId(parsed, ctx, translate) }
107
+ },
108
+ response: () => ({ ok: true }),
109
+ },
110
+ },
111
+ })
112
+
113
+ export const GET = crud.GET
114
+ export const POST = crud.POST
115
+ export const PUT = crud.PUT
116
+ export const DELETE = crud.DELETE
117
+
118
+ export const supportRoutingRuleListItemSchema = z.object({
119
+ id: z.string().uuid().nullable().optional(),
120
+ organization_id: z.string().uuid().nullable().optional(),
121
+ tenant_id: z.string().uuid().nullable().optional(),
122
+ category_value: z.string().nullable().optional(),
123
+ time_project_id: z.string().uuid().nullable().optional(),
124
+ is_active: z.boolean().nullable().optional(),
125
+ created_at: z.string().nullable().optional(),
126
+ updated_at: z.string().nullable().optional(),
127
+ })
128
+
129
+ export const openApi = createStaffCrudOpenApi({
130
+ resourceName: 'Support routing rule',
131
+ pluralName: 'Support routing rules',
132
+ querySchema: listSchema,
133
+ listResponseSchema: createPagedListResponseSchema(supportRoutingRuleListItemSchema),
134
+ create: {
135
+ schema: staffSupportRoutingRuleCreateSchema.omit({ tenantId: true, organizationId: true }),
136
+ responseSchema: z.object({ id: z.string().uuid().nullable() }),
137
+ description:
138
+ 'Creates a routing rule. A null `categoryValue` is the organization fallback rule (exactly one; it can be repointed but not deleted or paused). A rule with a category sends that category to its own project.',
139
+ },
140
+ update: {
141
+ schema: staffSupportRoutingRuleUpdateSchema,
142
+ responseSchema: defaultOkResponseSchema,
143
+ description: 'Repoints a rule at another project or pauses/resumes a per-category rule. Pausing the fallback rule answers 422.',
144
+ },
145
+ del: {
146
+ schema: z.object({ id: z.string().uuid() }),
147
+ responseSchema: defaultOkResponseSchema,
148
+ description: 'Deletes a per-category rule so the category falls back to the fallback project. Deleting the fallback rule answers 422.',
149
+ },
150
+ })
@@ -0,0 +1,181 @@
1
+ /**
2
+ * `GET /api/staff/timesheets/support-tickets/options` — the filter options behind
3
+ * the Support Inbox.
4
+ *
5
+ * The inbox is open to every holder of `staff.timesheets.tasks.view`, so its option
6
+ * lists cannot come from routes that need more: the routing preview needs
7
+ * `staff.timesheets.settings.manage` and the team directory needs `staff.view`,
8
+ * which an agent with the `employee` role holds neither of.
9
+ *
10
+ * * `categories` is the organization's support category dictionary — the exact
11
+ * organization, never an inherited one, the same lookup the portal form and the
12
+ * ticket command use.
13
+ * * `assignees` is limited to the people the caller can already see through their
14
+ * project access: everyone actively assigned to one of the caller's projects, or,
15
+ * for a holder of `staff.timesheets.projects.manage`, every active staff member of
16
+ * the organization.
17
+ */
18
+
19
+ import { NextResponse } from 'next/server'
20
+ import { z } from 'zod'
21
+ import type { EntityManager } from '@mikro-orm/postgresql'
22
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
23
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
24
+ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
25
+ import { resolveSingleOrganizationIdOrDeny } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
26
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
27
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
28
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
29
+ import { createLogger } from '@open-mercato/shared/lib/logger'
30
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
31
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
32
+ import { StaffTeamMember, StaffTimeProjectMember } from '../../../../data/entities'
33
+ import { loadSupportTicketCategories } from '../../../../lib/support/dictionaries'
34
+ import { resolveFeatureAccess } from '../../../../lib/time-tracking/featureAccess'
35
+ import { MANAGE_PROJECTS_FEATURE, resolveProjectAccess } from '../../../../lib/time-tracking/access'
36
+ import {
37
+ isActiveAssignment,
38
+ normalizeAssignmentGraceDays,
39
+ resolveAssignmentGraceDaysFromContainer,
40
+ } from '../../../../lib/time-tracking/projectAssignment'
41
+ import { readSearchParamsRecord, runTimesheetInterceptors } from '../../_shared/withTimesheetInterceptors'
42
+
43
+ const logger = createLogger('staff').child({ component: 'api/timesheets/support-tickets/options' })
44
+
45
+ const VIEW_FEATURE = 'staff.timesheets.tasks.view'
46
+ const MAX_ASSIGNEES = 500
47
+
48
+ export const metadata = {
49
+ GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },
50
+ }
51
+
52
+ const optionsResponseSchema = z.object({
53
+ categories: z.array(z.object({ value: z.string(), label: z.string() })),
54
+ assignees: z.array(z.object({ id: z.string().uuid(), displayName: z.string() })),
55
+ })
56
+
57
+ type SupportInboxOptions = z.infer<typeof optionsResponseSchema>
58
+
59
+ type Scope = { tenantId: string; organizationId: string }
60
+
61
+ async function loadAssignableStaffMemberIds(
62
+ em: EntityManager,
63
+ scope: Scope,
64
+ projectIds: string[],
65
+ graceDays: number | null,
66
+ ): Promise<string[]> {
67
+ if (projectIds.length === 0) return []
68
+ const memberships = await em.find(StaffTimeProjectMember, {
69
+ timeProjectId: { $in: projectIds },
70
+ tenantId: scope.tenantId,
71
+ organizationId: scope.organizationId,
72
+ status: 'active',
73
+ deletedAt: null,
74
+ })
75
+ const now = new Date()
76
+ return Array.from(
77
+ new Set(
78
+ memberships
79
+ .filter((membership) => isActiveAssignment(membership, now, normalizeAssignmentGraceDays(graceDays)))
80
+ .map((membership) => membership.staffMemberId),
81
+ ),
82
+ )
83
+ }
84
+
85
+ async function loadAssignees(
86
+ em: EntityManager,
87
+ scope: Scope,
88
+ staffMemberIds: string[] | null,
89
+ ): Promise<SupportInboxOptions['assignees']> {
90
+ if (staffMemberIds !== null && staffMemberIds.length === 0) return []
91
+ const where: Record<string, unknown> = {
92
+ tenantId: scope.tenantId,
93
+ organizationId: scope.organizationId,
94
+ isActive: true,
95
+ deletedAt: null,
96
+ }
97
+ if (staffMemberIds !== null) where.id = { $in: staffMemberIds }
98
+ const members = await findWithDecryption(em, StaffTeamMember, where, { limit: MAX_ASSIGNEES }, scope)
99
+ return members
100
+ .map((member) => ({ id: member.id, displayName: member.displayName || member.id }))
101
+ .sort((left, right) => left.displayName.localeCompare(right.displayName))
102
+ }
103
+
104
+ export async function GET(req: Request) {
105
+ try {
106
+ const container = await createRequestContainer()
107
+ const auth = await getAuthFromRequest(req)
108
+ const { translate } = await resolveTranslations()
109
+ if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })
110
+
111
+ const orgScope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
112
+ const tenantId = orgScope?.tenantId ?? auth.tenantId ?? null
113
+ const organizationId = resolveSingleOrganizationIdOrDeny(orgScope, auth) ?? null
114
+ if (!tenantId || !organizationId) {
115
+ throw new CrudHttpError(400, {
116
+ error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),
117
+ })
118
+ }
119
+
120
+ const interceptors = await runTimesheetInterceptors({
121
+ request: req,
122
+ method: 'GET',
123
+ scope: { container, userId: auth.sub, tenantId, organizationId },
124
+ query: readSearchParamsRecord(req.url),
125
+ })
126
+ if (!interceptors.ok) return interceptors.response
127
+ const { session } = interceptors
128
+
129
+ const scope: Scope = { tenantId, organizationId }
130
+ const em = (container.resolve('em') as EntityManager).fork()
131
+ const manageAccess = await resolveFeatureAccess(container, auth.sub ?? null, [MANAGE_PROJECTS_FEATURE], scope)
132
+ const graceDays = await resolveAssignmentGraceDaysFromContainer(container, tenantId)
133
+ const access = await resolveProjectAccess({
134
+ em,
135
+ userId: auth.sub ?? null,
136
+ tenantId,
137
+ organizationId,
138
+ canManageAll: manageAccess.allowed,
139
+ userFeatures: manageAccess.grantedFeatures,
140
+ assignmentGraceDays: graceDays,
141
+ })
142
+
143
+ const assignableIds = access.canManageAll
144
+ ? null
145
+ : await loadAssignableStaffMemberIds(em, scope, access.projectIds, graceDays)
146
+
147
+ const [categories, assignees] = await Promise.all([
148
+ loadSupportTicketCategories(em, scope),
149
+ loadAssignees(em.fork(), scope, assignableIds),
150
+ ])
151
+
152
+ const payload: SupportInboxOptions = optionsResponseSchema.parse({ categories, assignees })
153
+ return session.respond(200, payload)
154
+ } catch (err) {
155
+ if (err instanceof CrudHttpError) {
156
+ return NextResponse.json(err.body, { status: err.status })
157
+ }
158
+ logger.error('staff.support.inbox options failed', { err })
159
+ getTelemetryRuntime()?.reportError(err, { module: 'staff', code: 'staff.support_inbox_options_failed' })
160
+ const { translate } = await resolveTranslations()
161
+ return NextResponse.json({ error: translate('staff.errors.internal', 'Internal server error') }, { status: 500 })
162
+ }
163
+ }
164
+
165
+ export const openApi: OpenApiRouteDoc = {
166
+ tag: 'Staff',
167
+ summary: 'Support Inbox filter options',
168
+ methods: {
169
+ GET: {
170
+ summary: 'Support Inbox filter options',
171
+ description:
172
+ "The organization's support ticket categories and the staff members a ticket can be filtered by assignee. Assignees are limited to the people actively assigned to the caller's projects; a holder of `staff.timesheets.projects.manage` gets every active staff member of the organization.",
173
+ responses: [{ status: 200, description: 'Filter options', schema: optionsResponseSchema }],
174
+ errors: [
175
+ { status: 400, description: 'Missing tenant or organization scope' },
176
+ { status: 401, description: 'Unauthorized' },
177
+ { status: 403, description: 'Missing staff.timesheets.tasks.view' },
178
+ ],
179
+ },
180
+ },
181
+ }
@@ -0,0 +1,31 @@
1
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
+ import {
3
+ TASK_ASSIGNMENT_VIEW_FEATURE,
4
+ createTaskAssignmentPostHandler,
5
+ taskAssignmentResponseSchema,
6
+ } from '../../../../../lib/time-tracking/taskAssignmentRoute'
7
+
8
+ export const metadata = {
9
+ POST: { requireAuth: true, requireFeatures: [TASK_ASSIGNMENT_VIEW_FEATURE] },
10
+ }
11
+
12
+ export const POST = createTaskAssignmentPostHandler('claim')
13
+
14
+ export const openApi: OpenApiRouteDoc = {
15
+ tag: 'Staff',
16
+ summary: 'Claim an unassigned task',
17
+ methods: {
18
+ POST: {
19
+ summary: 'Claim an unassigned task',
20
+ description: 'Sets the caller as the assignee of an unassigned, not-done task. Only active assigners of the task project (a project member flagged as assigner, inside the assignment window) may claim; `staff.timesheets.tasks.manage` is not required. The update is conditional on the task still being unassigned, so of two concurrent claims exactly one wins and the other answers 409. Accepts `x-om-ext-optimistic-lock-expected-updated-at`.',
21
+ responses: [{ status: 200, description: 'Claim an unassigned task', schema: taskAssignmentResponseSchema }],
22
+ errors: [
23
+ { status: 401, description: 'Unauthorized' },
24
+ { status: 403, description: 'The caller is not an active assigner of the project' },
25
+ { status: 404, description: 'Task not found, or not on a project the caller can see' },
26
+ { status: 409, description: 'The task was already claimed, or changed since the version the caller sent' },
27
+ { status: 422, description: 'The task is in a done status' },
28
+ ],
29
+ },
30
+ },
31
+ }
@@ -50,7 +50,7 @@ import { User } from '@open-mercato/core/modules/auth/data/entities'
50
50
  import { StaffTimeTaskComment } from '../../../../../data/entities'
51
51
  import {
52
52
  staffTimeTaskCommentCreateSchema,
53
- staffTimeTaskCommentUpdateSchema,
53
+ staffTimeTaskCommentUpdateRequestSchema,
54
54
  } from '../../../../../data/validators'
55
55
  import { staffTimeTaskCommentCommandIds } from '../../../../../commands/timesheets-task-comments'
56
56
  import { STAFF_TIME_TRACKING_RESOURCE_KINDS } from '../../../../guards'
@@ -86,6 +86,8 @@ const commentItemSchema = z.object({
86
86
  authorUserId: z.string().uuid().nullable(),
87
87
  authorName: z.string().nullable(),
88
88
  authorEmail: z.string().nullable(),
89
+ visibility: z.enum(['internal', 'customer']),
90
+ authorKind: z.enum(['staff', 'customer']),
89
91
  createdAt: z.string().nullable(),
90
92
  updatedAt: z.string().nullable(),
91
93
  })
@@ -106,8 +108,16 @@ const createResponseSchema = z.object({
106
108
 
107
109
  const okResponseSchema = z.object({ ok: z.boolean() })
108
110
 
109
- const createBodySchema = z.object({ body: z.string().trim().min(1).max(5000) })
110
- const updateBodySchema = z.object({ id: z.string().uuid(), body: z.string().trim().min(1).max(5000) })
111
+ const visibilitySchema = z.enum(['internal', 'customer'])
112
+ const createBodySchema = z.object({
113
+ body: z.string().trim().min(1).max(5000),
114
+ visibility: visibilitySchema.optional(),
115
+ })
116
+ const updateBodySchema = z.object({
117
+ id: z.string().uuid(),
118
+ body: z.string().trim().min(1).max(5000).optional(),
119
+ visibility: visibilitySchema.optional(),
120
+ })
111
121
 
112
122
  type RouteContext = {
113
123
  ctx: CommandRuntimeContext
@@ -233,13 +243,17 @@ async function resolveAuthorMeta(
233
243
  function serializeComment(comment: StaffTimeTaskComment, authors: Map<string, AuthorMeta>) {
234
244
  const authorUserId = comment.authorUserId ?? null
235
245
  const meta = authorUserId ? authors.get(authorUserId) ?? null : null
246
+ const authorKind = comment.authorKind === 'customer' ? 'customer' : 'staff'
247
+ const customerName = comment.authorDisplaySnapshot?.displayName ?? null
236
248
  return {
237
249
  id: comment.id,
238
250
  taskId: comment.taskId,
239
251
  body: comment.body,
240
252
  authorUserId,
241
- authorName: meta?.name ?? null,
242
- authorEmail: meta?.email ?? null,
253
+ authorName: authorKind === 'customer' ? customerName : meta?.name ?? null,
254
+ authorEmail: authorKind === 'customer' ? null : meta?.email ?? null,
255
+ visibility: comment.visibility === 'customer' ? 'customer' : 'internal',
256
+ authorKind,
243
257
  createdAt: comment.createdAt ? comment.createdAt.toISOString() : null,
244
258
  updatedAt: comment.updatedAt ? comment.updatedAt.toISOString() : null,
245
259
  }
@@ -373,7 +387,7 @@ export async function POST(req: Request) {
373
387
  // here and the command stamps the authenticated caller instead.
374
388
  const input = parseScopedCommandInput(
375
389
  staffTimeTaskCommentCreateSchema,
376
- { taskId: route.taskId, body: parsedBody.body },
390
+ { taskId: route.taskId, body: parsedBody.body, visibility: parsedBody.visibility ?? 'internal' },
377
391
  route.ctx,
378
392
  route.translate,
379
393
  )
@@ -416,7 +430,11 @@ export async function PUT(req: Request) {
416
430
  const parsedBody = updateBodySchema.parse(raw ?? {})
417
431
  await requireCommentOnTask(route, parsedBody.id)
418
432
 
419
- const input = staffTimeTaskCommentUpdateSchema.parse({ id: parsedBody.id, body: parsedBody.body })
433
+ const input = staffTimeTaskCommentUpdateRequestSchema.parse({
434
+ id: parsedBody.id,
435
+ body: parsedBody.body,
436
+ visibility: parsedBody.visibility,
437
+ })
420
438
 
421
439
  const guardResult = await runGuards(route, 'update', input.id, input as unknown as Record<string, unknown>)
422
440
  if (!guardResult.ok) return guardResult.response
@@ -0,0 +1,30 @@
1
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
+ import {
3
+ TASK_ASSIGNMENT_VIEW_FEATURE,
4
+ createTaskAssignmentPostHandler,
5
+ taskAssignmentResponseSchema,
6
+ } from '../../../../../lib/time-tracking/taskAssignmentRoute'
7
+
8
+ export const metadata = {
9
+ POST: { requireAuth: true, requireFeatures: [TASK_ASSIGNMENT_VIEW_FEATURE] },
10
+ }
11
+
12
+ export const POST = createTaskAssignmentPostHandler('release')
13
+
14
+ export const openApi: OpenApiRouteDoc = {
15
+ tag: 'Staff',
16
+ summary: 'Release a task you hold',
17
+ methods: {
18
+ POST: {
19
+ summary: 'Release a task you hold',
20
+ description: 'Clears the assignee of a task the caller is assigned to, making it claimable again; the project assigners are notified. Only the current assignee may release (403 otherwise); managers keep using PUT /api/staff/timesheets/tasks.',
21
+ responses: [{ status: 200, description: 'Release a task you hold', schema: taskAssignmentResponseSchema }],
22
+ errors: [
23
+ { status: 401, description: 'Unauthorized' },
24
+ { status: 403, description: 'The caller is not the current assignee' },
25
+ { status: 404, description: 'Task not found, or not on a project the caller can see' },
26
+ { status: 409, description: 'The task changed since the version the caller sent, or it is no longer assigned to the caller (`task_assignment_changed`)' },
27
+ ],
28
+ },
29
+ },
30
+ }