@open-mercato/core 0.4.5-develop-5191db4ef3 → 0.4.5-develop-033a719bf2

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 (385) hide show
  1. package/dist/generated/entities/message/index.js +65 -0
  2. package/dist/generated/entities/message/index.js.map +7 -0
  3. package/dist/generated/entities/message_access_token/index.js +19 -0
  4. package/dist/generated/entities/message_access_token/index.js.map +7 -0
  5. package/dist/generated/entities/message_confirmation/index.js +21 -0
  6. package/dist/generated/entities/message_confirmation/index.js.map +7 -0
  7. package/dist/generated/entities/message_object/index.js +23 -0
  8. package/dist/generated/entities/message_object/index.js.map +7 -0
  9. package/dist/generated/entities/message_recipient/index.js +31 -0
  10. package/dist/generated/entities/message_recipient/index.js.map +7 -0
  11. package/dist/generated/entities.ids.generated.js +8 -0
  12. package/dist/generated/entities.ids.generated.js.map +2 -2
  13. package/dist/generated/entity-fields-registry.js +10 -0
  14. package/dist/generated/entity-fields-registry.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/deals/[id]/page.js +27 -8
  16. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  17. package/dist/modules/customers/lib/messageObjectPreviews.js +131 -0
  18. package/dist/modules/customers/lib/messageObjectPreviews.js.map +7 -0
  19. package/dist/modules/customers/message-objects.js +71 -0
  20. package/dist/modules/customers/message-objects.js.map +7 -0
  21. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js +51 -0
  22. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js.map +7 -0
  23. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js +35 -0
  24. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js.map +7 -0
  25. package/dist/modules/customers/widgets/messages/index.js +7 -0
  26. package/dist/modules/customers/widgets/messages/index.js.map +7 -0
  27. package/dist/modules/messages/acl.js +15 -0
  28. package/dist/modules/messages/acl.js.map +7 -0
  29. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js +92 -0
  30. package/dist/modules/messages/api/[id]/actions/[actionId]/route.js.map +7 -0
  31. package/dist/modules/messages/api/[id]/archive/route.js +120 -0
  32. package/dist/modules/messages/api/[id]/archive/route.js.map +7 -0
  33. package/dist/modules/messages/api/[id]/attachments/route.js +195 -0
  34. package/dist/modules/messages/api/[id]/attachments/route.js.map +7 -0
  35. package/dist/modules/messages/api/[id]/confirmation/route.js +67 -0
  36. package/dist/modules/messages/api/[id]/confirmation/route.js.map +7 -0
  37. package/dist/modules/messages/api/[id]/conversation/archive/route.js +68 -0
  38. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +7 -0
  39. package/dist/modules/messages/api/[id]/conversation/read/route.js +68 -0
  40. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +7 -0
  41. package/dist/modules/messages/api/[id]/conversation/route.js +68 -0
  42. package/dist/modules/messages/api/[id]/conversation/route.js.map +7 -0
  43. package/dist/modules/messages/api/[id]/forward/route.js +85 -0
  44. package/dist/modules/messages/api/[id]/forward/route.js.map +7 -0
  45. package/dist/modules/messages/api/[id]/forward-preview/route.js +70 -0
  46. package/dist/modules/messages/api/[id]/forward-preview/route.js.map +7 -0
  47. package/dist/modules/messages/api/[id]/read/route.js +120 -0
  48. package/dist/modules/messages/api/[id]/read/route.js.map +7 -0
  49. package/dist/modules/messages/api/[id]/reply/route.js +87 -0
  50. package/dist/modules/messages/api/[id]/reply/route.js.map +7 -0
  51. package/dist/modules/messages/api/[id]/route.js +350 -0
  52. package/dist/modules/messages/api/[id]/route.js.map +7 -0
  53. package/dist/modules/messages/api/object-types/route.js +54 -0
  54. package/dist/modules/messages/api/object-types/route.js.map +7 -0
  55. package/dist/modules/messages/api/openapi.js +261 -0
  56. package/dist/modules/messages/api/openapi.js.map +7 -0
  57. package/dist/modules/messages/api/route.js +262 -0
  58. package/dist/modules/messages/api/route.js.map +7 -0
  59. package/dist/modules/messages/api/token/[token]/route.js +99 -0
  60. package/dist/modules/messages/api/token/[token]/route.js.map +7 -0
  61. package/dist/modules/messages/api/types/route.js +40 -0
  62. package/dist/modules/messages/api/types/route.js.map +7 -0
  63. package/dist/modules/messages/api/unread-count/route.js +43 -0
  64. package/dist/modules/messages/api/unread-count/route.js.map +7 -0
  65. package/dist/modules/messages/backend/messages/[id]/page.js +10 -0
  66. package/dist/modules/messages/backend/messages/[id]/page.js.map +7 -0
  67. package/dist/modules/messages/backend/messages/[id]/page.meta.js +16 -0
  68. package/dist/modules/messages/backend/messages/[id]/page.meta.js.map +7 -0
  69. package/dist/modules/messages/backend/messages/compose/page.js +10 -0
  70. package/dist/modules/messages/backend/messages/compose/page.js.map +7 -0
  71. package/dist/modules/messages/backend/messages/compose/page.meta.js +17 -0
  72. package/dist/modules/messages/backend/messages/compose/page.meta.js.map +7 -0
  73. package/dist/modules/messages/backend/page.js +10 -0
  74. package/dist/modules/messages/backend/page.js.map +7 -0
  75. package/dist/modules/messages/backend/page.meta.js +33 -0
  76. package/dist/modules/messages/backend/page.meta.js.map +7 -0
  77. package/dist/modules/messages/commands/actions.js +265 -0
  78. package/dist/modules/messages/commands/actions.js.map +7 -0
  79. package/dist/modules/messages/commands/attachments.js +217 -0
  80. package/dist/modules/messages/commands/attachments.js.map +7 -0
  81. package/dist/modules/messages/commands/confirmations.js +151 -0
  82. package/dist/modules/messages/commands/confirmations.js.map +7 -0
  83. package/dist/modules/messages/commands/conversation.js +240 -0
  84. package/dist/modules/messages/commands/conversation.js.map +7 -0
  85. package/dist/modules/messages/commands/messages.js +748 -0
  86. package/dist/modules/messages/commands/messages.js.map +7 -0
  87. package/dist/modules/messages/commands/recipients.js +259 -0
  88. package/dist/modules/messages/commands/recipients.js.map +7 -0
  89. package/dist/modules/messages/commands/shared.js +258 -0
  90. package/dist/modules/messages/commands/shared.js.map +7 -0
  91. package/dist/modules/messages/commands/tokens.js +69 -0
  92. package/dist/modules/messages/commands/tokens.js.map +7 -0
  93. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -0
  94. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +7 -0
  95. package/dist/modules/messages/components/MessageDetailPageClient.js +261 -0
  96. package/dist/modules/messages/components/MessageDetailPageClient.js.map +7 -0
  97. package/dist/modules/messages/components/MessagesInboxPageClient.js +390 -0
  98. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +7 -0
  99. package/dist/modules/messages/components/confirmation/MessageConfirmationActions.js +31 -0
  100. package/dist/modules/messages/components/confirmation/MessageConfirmationActions.js.map +7 -0
  101. package/dist/modules/messages/components/confirmation/MessageConfirmationContent.js +69 -0
  102. package/dist/modules/messages/components/confirmation/MessageConfirmationContent.js.map +7 -0
  103. package/dist/modules/messages/components/defaults/DefaultMessageActions.js +31 -0
  104. package/dist/modules/messages/components/defaults/DefaultMessageActions.js.map +7 -0
  105. package/dist/modules/messages/components/defaults/DefaultMessageContent.js +19 -0
  106. package/dist/modules/messages/components/defaults/DefaultMessageContent.js.map +7 -0
  107. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +90 -0
  108. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +7 -0
  109. package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js +86 -0
  110. package/dist/modules/messages/components/defaults/MessageRecordObjectDetail.js.map +7 -0
  111. package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js +61 -0
  112. package/dist/modules/messages/components/defaults/MessageRecordObjectPreview.js.map +7 -0
  113. package/dist/modules/messages/components/message-detail/detail-panels.js +27 -0
  114. package/dist/modules/messages/components/message-detail/detail-panels.js.map +7 -0
  115. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +52 -0
  116. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +7 -0
  117. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +289 -0
  118. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +7 -0
  119. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +103 -0
  120. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +7 -0
  121. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +78 -0
  122. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +7 -0
  123. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js +94 -0
  124. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js.map +7 -0
  125. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +110 -0
  126. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +7 -0
  127. package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js +58 -0
  128. package/dist/modules/messages/components/message-detail/panels/MessageListComponent.js.map +7 -0
  129. package/dist/modules/messages/components/message-detail/panels/actions-panel.js +51 -0
  130. package/dist/modules/messages/components/message-detail/panels/actions-panel.js.map +7 -0
  131. package/dist/modules/messages/components/message-detail/panels/attachments-panel.js +66 -0
  132. package/dist/modules/messages/components/message-detail/panels/attachments-panel.js.map +7 -0
  133. package/dist/modules/messages/components/message-detail/panels/body-panel.js +20 -0
  134. package/dist/modules/messages/components/message-detail/panels/body-panel.js.map +7 -0
  135. package/dist/modules/messages/components/message-detail/panels/composer-dialogs.js +36 -0
  136. package/dist/modules/messages/components/message-detail/panels/composer-dialogs.js.map +7 -0
  137. package/dist/modules/messages/components/message-detail/panels/dialogs.js +96 -0
  138. package/dist/modules/messages/components/message-detail/panels/dialogs.js.map +7 -0
  139. package/dist/modules/messages/components/message-detail/panels/index.js +25 -0
  140. package/dist/modules/messages/components/message-detail/panels/index.js.map +7 -0
  141. package/dist/modules/messages/components/message-detail/panels/meta-panel.js +14 -0
  142. package/dist/modules/messages/components/message-detail/panels/meta-panel.js.map +7 -0
  143. package/dist/modules/messages/components/message-detail/panels/objects-panel.js +51 -0
  144. package/dist/modules/messages/components/message-detail/panels/objects-panel.js.map +7 -0
  145. package/dist/modules/messages/components/message-detail/panels/thread-panel.js +54 -0
  146. package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +7 -0
  147. package/dist/modules/messages/components/message-detail/types.js +1 -0
  148. package/dist/modules/messages/components/message-detail/types.js.map +7 -0
  149. package/dist/modules/messages/components/message-detail/utils.js +54 -0
  150. package/dist/modules/messages/components/message-detail/utils.js.map +7 -0
  151. package/dist/modules/messages/components/utils/PriorityBadge.js +52 -0
  152. package/dist/modules/messages/components/utils/PriorityBadge.js.map +7 -0
  153. package/dist/modules/messages/components/utils/typeUiRegistry.js +77 -0
  154. package/dist/modules/messages/components/utils/typeUiRegistry.js.map +7 -0
  155. package/dist/modules/messages/data/entities.js +309 -0
  156. package/dist/modules/messages/data/entities.js.map +7 -0
  157. package/dist/modules/messages/data/validators.js +272 -0
  158. package/dist/modules/messages/data/validators.js.map +7 -0
  159. package/dist/modules/messages/emails/MessageEmail.js +108 -0
  160. package/dist/modules/messages/emails/MessageEmail.js.map +7 -0
  161. package/dist/modules/messages/events.js +24 -0
  162. package/dist/modules/messages/events.js.map +7 -0
  163. package/dist/modules/messages/frontend/messages/view/[token]/page.js +247 -0
  164. package/dist/modules/messages/frontend/messages/view/[token]/page.js.map +7 -0
  165. package/dist/modules/messages/frontend/messages/view/[token]/page.meta.js +9 -0
  166. package/dist/modules/messages/frontend/messages/view/[token]/page.meta.js.map +7 -0
  167. package/dist/modules/messages/index.js +21 -0
  168. package/dist/modules/messages/index.js.map +7 -0
  169. package/dist/modules/messages/lib/actions.js +141 -0
  170. package/dist/modules/messages/lib/actions.js.map +7 -0
  171. package/dist/modules/messages/lib/attachments.js +131 -0
  172. package/dist/modules/messages/lib/attachments.js.map +7 -0
  173. package/dist/modules/messages/lib/constants.js +7 -0
  174. package/dist/modules/messages/lib/constants.js.map +7 -0
  175. package/dist/modules/messages/lib/email-sender.js +201 -0
  176. package/dist/modules/messages/lib/email-sender.js.map +7 -0
  177. package/dist/modules/messages/lib/forwarding.js +179 -0
  178. package/dist/modules/messages/lib/forwarding.js.map +7 -0
  179. package/dist/modules/messages/lib/message-objects-registry.js +49 -0
  180. package/dist/modules/messages/lib/message-objects-registry.js.map +7 -0
  181. package/dist/modules/messages/lib/message-types-registry.js +41 -0
  182. package/dist/modules/messages/lib/message-types-registry.js.map +7 -0
  183. package/dist/modules/messages/lib/object-validation.js +20 -0
  184. package/dist/modules/messages/lib/object-validation.js.map +7 -0
  185. package/dist/modules/messages/lib/operationMetadata.js +21 -0
  186. package/dist/modules/messages/lib/operationMetadata.js.map +7 -0
  187. package/dist/modules/messages/lib/priorityUtils.js +61 -0
  188. package/dist/modules/messages/lib/priorityUtils.js.map +7 -0
  189. package/dist/modules/messages/lib/routeHelpers.js +44 -0
  190. package/dist/modules/messages/lib/routeHelpers.js.map +7 -0
  191. package/dist/modules/messages/message-objects.js +7 -0
  192. package/dist/modules/messages/message-objects.js.map +7 -0
  193. package/dist/modules/messages/message-types.js +67 -0
  194. package/dist/modules/messages/message-types.js.map +7 -0
  195. package/dist/modules/messages/migrations/Migration20260213181243.js +31 -0
  196. package/dist/modules/messages/migrations/Migration20260213181243.js.map +7 -0
  197. package/dist/modules/messages/migrations/Migration20260215165126.js +16 -0
  198. package/dist/modules/messages/migrations/Migration20260215165126.js.map +7 -0
  199. package/dist/modules/messages/notifications.js +27 -0
  200. package/dist/modules/messages/notifications.js.map +7 -0
  201. package/dist/modules/messages/setup.js +21 -0
  202. package/dist/modules/messages/setup.js.map +7 -0
  203. package/dist/modules/messages/subscribers/message-notification.js +108 -0
  204. package/dist/modules/messages/subscribers/message-notification.js.map +7 -0
  205. package/dist/modules/messages/workers/send-email.worker.js +253 -0
  206. package/dist/modules/messages/workers/send-email.worker.js.map +7 -0
  207. package/dist/modules/sales/backend/sales/documents/[id]/page.js +30 -11
  208. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  209. package/dist/modules/sales/commands/payments.js +12 -6
  210. package/dist/modules/sales/commands/payments.js.map +2 -2
  211. package/dist/modules/sales/lib/messageObjectPreviews.js +114 -0
  212. package/dist/modules/sales/lib/messageObjectPreviews.js.map +7 -0
  213. package/dist/modules/sales/message-objects.js +57 -0
  214. package/dist/modules/sales/message-objects.js.map +7 -0
  215. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js +51 -0
  216. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js.map +7 -0
  217. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js +36 -0
  218. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js.map +7 -0
  219. package/dist/modules/sales/widgets/messages/index.js +7 -0
  220. package/dist/modules/sales/widgets/messages/index.js.map +7 -0
  221. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +55 -1
  222. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  223. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +60 -1
  224. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  225. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +2 -19
  226. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  227. package/dist/modules/staff/components/LeaveRequestDetail.js +112 -0
  228. package/dist/modules/staff/components/LeaveRequestDetail.js.map +7 -0
  229. package/dist/modules/staff/components/LeaveRequestForm.js +3 -1
  230. package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
  231. package/dist/modules/staff/components/LeaveRequestPreview.js +43 -0
  232. package/dist/modules/staff/components/LeaveRequestPreview.js.map +7 -0
  233. package/dist/modules/staff/lib/messageObjectPreviews.js +148 -0
  234. package/dist/modules/staff/lib/messageObjectPreviews.js.map +7 -0
  235. package/dist/modules/staff/message-objects.js +104 -0
  236. package/dist/modules/staff/message-objects.js.map +7 -0
  237. package/dist/modules/staff/message-types.js +23 -0
  238. package/dist/modules/staff/message-types.js.map +7 -0
  239. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js +51 -0
  240. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js.map +7 -0
  241. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js +34 -0
  242. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js.map +7 -0
  243. package/dist/modules/staff/widgets/messages/index.js +7 -0
  244. package/dist/modules/staff/widgets/messages/index.js.map +7 -0
  245. package/generated/entities/message/index.ts +31 -0
  246. package/generated/entities/message_access_token/index.ts +8 -0
  247. package/generated/entities/message_confirmation/index.ts +9 -0
  248. package/generated/entities/message_object/index.ts +10 -0
  249. package/generated/entities/message_recipient/index.ts +14 -0
  250. package/generated/entities.ids.generated.ts +8 -0
  251. package/generated/entity-fields-registry.ts +10 -0
  252. package/jest.setup.ts +5 -0
  253. package/package.json +2 -2
  254. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +20 -4
  255. package/src/modules/customers/i18n/de.json +4 -0
  256. package/src/modules/customers/i18n/en.json +4 -0
  257. package/src/modules/customers/i18n/es.json +4 -0
  258. package/src/modules/customers/i18n/pl.json +4 -0
  259. package/src/modules/customers/lib/messageObjectPreviews.ts +154 -0
  260. package/src/modules/customers/message-objects.ts +70 -0
  261. package/src/modules/customers/widgets/messages/CustomerMessageObjectDetail.tsx +57 -0
  262. package/src/modules/customers/widgets/messages/CustomerMessageObjectPreview.tsx +49 -0
  263. package/src/modules/customers/widgets/messages/index.ts +2 -0
  264. package/src/modules/messages/acl.ts +11 -0
  265. package/src/modules/messages/api/[id]/actions/[actionId]/route.ts +103 -0
  266. package/src/modules/messages/api/[id]/archive/route.ts +138 -0
  267. package/src/modules/messages/api/[id]/attachments/route.ts +217 -0
  268. package/src/modules/messages/api/[id]/confirmation/route.ts +73 -0
  269. package/src/modules/messages/api/[id]/conversation/archive/route.ts +69 -0
  270. package/src/modules/messages/api/[id]/conversation/read/route.ts +69 -0
  271. package/src/modules/messages/api/[id]/conversation/route.ts +69 -0
  272. package/src/modules/messages/api/[id]/forward/route.ts +87 -0
  273. package/src/modules/messages/api/[id]/forward-preview/route.ts +75 -0
  274. package/src/modules/messages/api/[id]/read/route.ts +138 -0
  275. package/src/modules/messages/api/[id]/reply/route.ts +89 -0
  276. package/src/modules/messages/api/[id]/route.ts +401 -0
  277. package/src/modules/messages/api/object-types/route.ts +54 -0
  278. package/src/modules/messages/api/openapi.ts +261 -0
  279. package/src/modules/messages/api/route.ts +374 -0
  280. package/src/modules/messages/api/token/[token]/route.ts +103 -0
  281. package/src/modules/messages/api/types/route.ts +39 -0
  282. package/src/modules/messages/api/unread-count/route.ts +55 -0
  283. package/src/modules/messages/backend/messages/[id]/page.meta.ts +12 -0
  284. package/src/modules/messages/backend/messages/[id]/page.tsx +12 -0
  285. package/src/modules/messages/backend/messages/compose/page.meta.ts +13 -0
  286. package/src/modules/messages/backend/messages/compose/page.tsx +12 -0
  287. package/src/modules/messages/backend/page.meta.ts +31 -0
  288. package/src/modules/messages/backend/page.tsx +12 -0
  289. package/src/modules/messages/commands/actions.ts +307 -0
  290. package/src/modules/messages/commands/attachments.ts +227 -0
  291. package/src/modules/messages/commands/confirmations.ts +183 -0
  292. package/src/modules/messages/commands/conversation.ts +292 -0
  293. package/src/modules/messages/commands/messages.ts +845 -0
  294. package/src/modules/messages/commands/recipients.ts +281 -0
  295. package/src/modules/messages/commands/shared.ts +350 -0
  296. package/src/modules/messages/commands/tokens.ts +80 -0
  297. package/src/modules/messages/components/ComposeMessagePageClient.tsx +23 -0
  298. package/src/modules/messages/components/MessageDetailPageClient.tsx +287 -0
  299. package/src/modules/messages/components/MessagesInboxPageClient.tsx +469 -0
  300. package/src/modules/messages/components/confirmation/MessageConfirmationActions.tsx +35 -0
  301. package/src/modules/messages/components/confirmation/MessageConfirmationContent.tsx +88 -0
  302. package/src/modules/messages/components/defaults/DefaultMessageActions.tsx +37 -0
  303. package/src/modules/messages/components/defaults/DefaultMessageContent.tsx +21 -0
  304. package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +102 -0
  305. package/src/modules/messages/components/defaults/MessageRecordObjectDetail.tsx +114 -0
  306. package/src/modules/messages/components/defaults/MessageRecordObjectPreview.tsx +74 -0
  307. package/src/modules/messages/components/message-detail/detail-panels.ts +13 -0
  308. package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +56 -0
  309. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +367 -0
  310. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +134 -0
  311. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +102 -0
  312. package/src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx +108 -0
  313. package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +144 -0
  314. package/src/modules/messages/components/message-detail/panels/MessageListComponent.tsx +63 -0
  315. package/src/modules/messages/components/message-detail/panels/actions-panel.tsx +66 -0
  316. package/src/modules/messages/components/message-detail/panels/attachments-panel.tsx +86 -0
  317. package/src/modules/messages/components/message-detail/panels/body-panel.tsx +32 -0
  318. package/src/modules/messages/components/message-detail/panels/composer-dialogs.tsx +42 -0
  319. package/src/modules/messages/components/message-detail/panels/dialogs.tsx +107 -0
  320. package/src/modules/messages/components/message-detail/panels/index.ts +11 -0
  321. package/src/modules/messages/components/message-detail/panels/meta-panel.tsx +19 -0
  322. package/src/modules/messages/components/message-detail/panels/objects-panel.tsx +65 -0
  323. package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +65 -0
  324. package/src/modules/messages/components/message-detail/types.ts +114 -0
  325. package/src/modules/messages/components/message-detail/utils.ts +62 -0
  326. package/src/modules/messages/components/utils/PriorityBadge.tsx +63 -0
  327. package/src/modules/messages/components/utils/typeUiRegistry.ts +106 -0
  328. package/src/modules/messages/data/entities.ts +284 -0
  329. package/src/modules/messages/data/validators.ts +297 -0
  330. package/src/modules/messages/emails/MessageEmail.tsx +143 -0
  331. package/src/modules/messages/events.ts +24 -0
  332. package/src/modules/messages/frontend/messages/view/[token]/page.meta.ts +5 -0
  333. package/src/modules/messages/frontend/messages/view/[token]/page.tsx +389 -0
  334. package/src/modules/messages/i18n/de.json +240 -0
  335. package/src/modules/messages/i18n/en.json +240 -0
  336. package/src/modules/messages/i18n/es.json +240 -0
  337. package/src/modules/messages/i18n/pl.json +240 -0
  338. package/src/modules/messages/index.ts +19 -0
  339. package/src/modules/messages/lib/actions.ts +204 -0
  340. package/src/modules/messages/lib/attachments.ts +197 -0
  341. package/src/modules/messages/lib/constants.ts +2 -0
  342. package/src/modules/messages/lib/email-sender.ts +255 -0
  343. package/src/modules/messages/lib/forwarding.ts +240 -0
  344. package/src/modules/messages/lib/message-objects-registry.ts +60 -0
  345. package/src/modules/messages/lib/message-types-registry.ts +48 -0
  346. package/src/modules/messages/lib/object-validation.ts +26 -0
  347. package/src/modules/messages/lib/operationMetadata.ts +43 -0
  348. package/src/modules/messages/lib/priorityUtils.ts +76 -0
  349. package/src/modules/messages/lib/routeHelpers.ts +65 -0
  350. package/src/modules/messages/message-objects.ts +5 -0
  351. package/src/modules/messages/message-types.ts +65 -0
  352. package/src/modules/messages/migrations/.snapshot-open-mercato.json +957 -0
  353. package/src/modules/messages/migrations/Migration20260213181243.ts +34 -0
  354. package/src/modules/messages/migrations/Migration20260215165126.ts +16 -0
  355. package/src/modules/messages/notifications.ts +25 -0
  356. package/src/modules/messages/setup.ts +19 -0
  357. package/src/modules/messages/subscribers/message-notification.ts +138 -0
  358. package/src/modules/messages/workers/send-email.worker.ts +321 -0
  359. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +23 -7
  360. package/src/modules/sales/commands/payments.ts +12 -6
  361. package/src/modules/sales/i18n/de.json +3 -0
  362. package/src/modules/sales/i18n/en.json +3 -0
  363. package/src/modules/sales/i18n/es.json +3 -0
  364. package/src/modules/sales/i18n/pl.json +3 -0
  365. package/src/modules/sales/lib/messageObjectPreviews.ts +150 -0
  366. package/src/modules/sales/message-objects.ts +56 -0
  367. package/src/modules/sales/widgets/messages/SalesDocumentMessageDetail.tsx +57 -0
  368. package/src/modules/sales/widgets/messages/SalesDocumentMessagePreview.tsx +46 -0
  369. package/src/modules/sales/widgets/messages/index.ts +2 -0
  370. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +54 -0
  371. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +58 -0
  372. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +2 -32
  373. package/src/modules/staff/components/LeaveRequestDetail.tsx +135 -0
  374. package/src/modules/staff/components/LeaveRequestForm.tsx +3 -0
  375. package/src/modules/staff/components/LeaveRequestPreview.tsx +74 -0
  376. package/src/modules/staff/i18n/de.json +8 -0
  377. package/src/modules/staff/i18n/en.json +8 -0
  378. package/src/modules/staff/i18n/es.json +8 -0
  379. package/src/modules/staff/i18n/pl.json +8 -0
  380. package/src/modules/staff/lib/messageObjectPreviews.ts +182 -0
  381. package/src/modules/staff/message-objects.ts +102 -0
  382. package/src/modules/staff/message-types.ts +21 -0
  383. package/src/modules/staff/widgets/messages/StaffMessageObjectDetail.tsx +57 -0
  384. package/src/modules/staff/widgets/messages/StaffMessageObjectPreview.tsx +44 -0
  385. package/src/modules/staff/widgets/messages/index.ts +2 -0
@@ -0,0 +1,845 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { z } from 'zod'
3
+ import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
4
+ import { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'
5
+ import { Message, MessageObject, MessageRecipient, type MessageActionData } from '../data/entities'
6
+ import { emitMessagesEvent } from '../events'
7
+ import {
8
+ composeMessageSchema,
9
+ forwardMessageSchema,
10
+ replyMessageSchema,
11
+ updateDraftSchema,
12
+ } from '../data/validators'
13
+ import { linkAttachmentsToMessage, linkLibraryAttachmentsToMessage, copyAttachmentsForForwardMessages } from '../lib/attachments'
14
+ import { MESSAGE_ATTACHMENT_ENTITY_ID } from '../lib/constants'
15
+ import { getMessageTypeOrDefault } from '../lib/message-types-registry'
16
+ import { validateMessageObjectsForType } from '../lib/object-validation'
17
+ import { buildForwardBodyFromLegacyInput, buildForwardPreviewFromThreadSlice, buildForwardThreadSlice } from '../lib/forwarding'
18
+ import {
19
+ assertOrganizationAccess,
20
+ loadMessageAggregateSnapshot,
21
+ restoreMessageAggregateSnapshot,
22
+ type MessageAggregateSnapshot,
23
+ type MessageScopeInput,
24
+ } from './shared'
25
+
26
+ type MessageSentEventPayload = {
27
+ messageId: string
28
+ senderUserId: string
29
+ recipientUserIds: string[]
30
+ sendViaEmail: boolean
31
+ externalEmail?: string | null
32
+ forwardedFrom?: string
33
+ replyTo?: string
34
+ tenantId: string
35
+ organizationId?: string | null
36
+ }
37
+
38
+ type ContainerWithResolve = {
39
+ resolve: (name: string) => unknown
40
+ }
41
+
42
+ async function emitMessageSentEvent(_container: ContainerWithResolve, payload: MessageSentEventPayload) {
43
+ await emitMessagesEvent('messages.message.sent', payload, { persistent: true })
44
+ }
45
+
46
+ async function emitMessageDeletedEvent(_container: ContainerWithResolve, payload: {
47
+ messageId: string
48
+ actorUserId: string
49
+ target: 'sender' | 'recipient'
50
+ tenantId: string
51
+ organizationId: string | null
52
+ }) {
53
+ await emitMessagesEvent('messages.message.deleted', payload, { persistent: true })
54
+ }
55
+
56
+ const scopeSchema = z.object({
57
+ tenantId: z.string().uuid(),
58
+ organizationId: z.string().uuid().nullable(),
59
+ userId: z.string().uuid(),
60
+ })
61
+
62
+ const composeCommandSchema = composeMessageSchema.safeExtend({
63
+ tenantId: scopeSchema.shape.tenantId,
64
+ organizationId: scopeSchema.shape.organizationId,
65
+ userId: scopeSchema.shape.userId,
66
+ })
67
+
68
+ const updateDraftCommandSchema = updateDraftSchema.safeExtend({
69
+ messageId: z.string().uuid(),
70
+ tenantId: scopeSchema.shape.tenantId,
71
+ organizationId: scopeSchema.shape.organizationId,
72
+ userId: scopeSchema.shape.userId,
73
+ })
74
+
75
+ const replyCommandSchema = replyMessageSchema.safeExtend({
76
+ messageId: z.string().uuid(),
77
+ tenantId: scopeSchema.shape.tenantId,
78
+ organizationId: scopeSchema.shape.organizationId,
79
+ userId: scopeSchema.shape.userId,
80
+ })
81
+
82
+ const forwardCommandSchema = forwardMessageSchema.safeExtend({
83
+ messageId: z.string().uuid(),
84
+ tenantId: scopeSchema.shape.tenantId,
85
+ organizationId: scopeSchema.shape.organizationId,
86
+ userId: scopeSchema.shape.userId,
87
+ })
88
+
89
+ const deleteForActorCommandSchema = z.object({
90
+ messageId: z.string().uuid(),
91
+ tenantId: scopeSchema.shape.tenantId,
92
+ organizationId: scopeSchema.shape.organizationId,
93
+ userId: scopeSchema.shape.userId,
94
+ })
95
+
96
+ type ComposeCommandInput = z.infer<typeof composeCommandSchema>
97
+ type UpdateDraftCommandInput = z.infer<typeof updateDraftCommandSchema>
98
+ type ReplyCommandInput = z.infer<typeof replyCommandSchema>
99
+ type ForwardCommandInput = z.infer<typeof forwardCommandSchema>
100
+ type DeleteForActorCommandInput = z.infer<typeof deleteForActorCommandSchema>
101
+
102
+ type MessageDeleteUndoState = {
103
+ messageId: string
104
+ messageDeletedAt: string | null
105
+ recipientId: string | null
106
+ recipientStatus: 'unread' | 'read' | 'archived' | 'deleted' | null
107
+ recipientDeletedAt: string | null
108
+ }
109
+
110
+ function toIso(value: Date | null | undefined): string | null {
111
+ return value ? value.toISOString() : null
112
+ }
113
+
114
+ function toDate(value: string | null | undefined): Date | null {
115
+ if (!value) return null
116
+ return new Date(value)
117
+ }
118
+
119
+ function buildReplySubject(subject: string): string {
120
+ if (/^re:\s*/i.test(subject)) return subject
121
+ return `Re: ${subject}`
122
+ }
123
+
124
+ async function requireMessageById(
125
+ em: EntityManager,
126
+ scope: MessageScopeInput,
127
+ messageId: string,
128
+ ) {
129
+ const message = await em.findOne(Message, {
130
+ id: messageId,
131
+ tenantId: scope.tenantId,
132
+ deletedAt: null,
133
+ })
134
+ if (!message) throw new Error('Message not found')
135
+ assertOrganizationAccess(scope, message)
136
+ return message
137
+ }
138
+
139
+ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: string | null; externalEmail: string | null; isDraft: boolean; recipientUserIds: string[] }> = {
140
+ id: 'messages.messages.compose',
141
+ async execute(rawInput, ctx) {
142
+ const input = composeCommandSchema.parse(rawInput)
143
+ if (input.objects?.length) {
144
+ const objectValidationError = validateMessageObjectsForType(input.type, input.objects)
145
+ if (objectValidationError) throw new Error(objectValidationError)
146
+ }
147
+
148
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
149
+ let messageId = ''
150
+ let responseThreadId: string | null = null
151
+ let responseExternalEmail: string | null = null
152
+
153
+ await em.transactional(async (trx) => {
154
+ const threadId = input.parentMessageId
155
+ ? (
156
+ await trx.findOne(Message, {
157
+ id: input.parentMessageId,
158
+ tenantId: input.tenantId,
159
+ organizationId: input.organizationId,
160
+ deletedAt: null,
161
+ })
162
+ )?.threadId ?? input.parentMessageId
163
+ : undefined
164
+
165
+ const isPublicVisibility = input.visibility === 'public'
166
+ const sendViaEmail = isPublicVisibility ? true : input.sendViaEmail
167
+ const message = trx.create(Message, {
168
+ type: input.type,
169
+ visibility: input.visibility ?? null,
170
+ sourceEntityType: input.sourceEntityType,
171
+ sourceEntityId: input.sourceEntityId,
172
+ externalEmail: input.externalEmail,
173
+ externalName: input.externalName,
174
+ threadId: threadId ?? undefined,
175
+ parentMessageId: input.parentMessageId,
176
+ senderUserId: input.userId,
177
+ subject: input.subject,
178
+ body: input.body,
179
+ bodyFormat: input.bodyFormat,
180
+ priority: input.priority,
181
+ status: input.isDraft ? 'draft' : 'sent',
182
+ isDraft: input.isDraft ?? false,
183
+ sentAt: input.isDraft ? null : new Date(),
184
+ actionData: input.actionData as MessageActionData | undefined,
185
+ sendViaEmail,
186
+ tenantId: input.tenantId,
187
+ organizationId: input.organizationId,
188
+ })
189
+
190
+ await trx.persistAndFlush(message)
191
+ if (!threadId && !input.isDraft && !message.threadId) {
192
+ message.threadId = message.id
193
+ await trx.flush()
194
+ }
195
+
196
+ for (const recipient of input.recipients) {
197
+ trx.persist(trx.create(MessageRecipient, {
198
+ messageId: message.id,
199
+ recipientUserId: recipient.userId,
200
+ recipientType: recipient.type,
201
+ status: 'unread',
202
+ }))
203
+ }
204
+
205
+ if (input.objects) {
206
+ for (const obj of input.objects) {
207
+ trx.persist(trx.create(MessageObject, {
208
+ messageId: message.id,
209
+ entityModule: obj.entityModule,
210
+ entityType: obj.entityType,
211
+ entityId: obj.entityId,
212
+ actionRequired: obj.actionRequired,
213
+ actionType: obj.actionType,
214
+ actionLabel: obj.actionLabel,
215
+ }))
216
+ }
217
+ }
218
+
219
+ await trx.flush()
220
+
221
+ if (input.attachmentIds?.length) {
222
+ await linkAttachmentsToMessage(
223
+ trx,
224
+ message.id,
225
+ input.attachmentIds,
226
+ input.organizationId,
227
+ input.tenantId,
228
+ )
229
+ }
230
+
231
+ if (input.attachmentRecordId) {
232
+ await linkLibraryAttachmentsToMessage(
233
+ trx,
234
+ message.id,
235
+ input.attachmentRecordId,
236
+ input.organizationId,
237
+ input.tenantId,
238
+ )
239
+ }
240
+
241
+ messageId = message.id
242
+ responseThreadId = message.threadId ?? null
243
+ responseExternalEmail = message.externalEmail ?? null
244
+ })
245
+
246
+ if (!input.isDraft) {
247
+ await emitMessageSentEvent(ctx.container, {
248
+ messageId,
249
+ senderUserId: input.userId,
250
+ recipientUserIds: input.recipients.map((recipient) => recipient.userId),
251
+ sendViaEmail: input.visibility === 'public' ? true : input.sendViaEmail,
252
+ externalEmail: responseExternalEmail,
253
+ tenantId: input.tenantId,
254
+ organizationId: input.organizationId,
255
+ })
256
+ }
257
+
258
+ return {
259
+ id: messageId,
260
+ threadId: responseThreadId,
261
+ externalEmail: responseExternalEmail,
262
+ isDraft: input.isDraft,
263
+ recipientUserIds: input.recipients.map((recipient) => recipient.userId),
264
+ }
265
+ },
266
+ async captureAfter(_input, result, ctx) {
267
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
268
+ return loadMessageAggregateSnapshot(em, result.id)
269
+ },
270
+ buildLog: async ({ input, result, snapshots }) => {
271
+ const parsed = composeCommandSchema.parse(input)
272
+ return {
273
+ actionLabel: parsed.isDraft ? 'Create draft message' : 'Compose message',
274
+ resourceKind: 'messages.message',
275
+ resourceId: result.id,
276
+ tenantId: parsed.tenantId,
277
+ organizationId: parsed.organizationId,
278
+ payload: {
279
+ undo: {
280
+ after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,
281
+ } satisfies UndoPayload<MessageAggregateSnapshot>,
282
+ },
283
+ snapshotAfter: snapshots.after ?? null,
284
+ }
285
+ },
286
+ undo: async ({ logEntry, ctx }) => {
287
+ const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)
288
+ const after = undo?.after
289
+ if (!after) return
290
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
291
+ const message = await em.findOne(Message, { id: after.message.id })
292
+ if (!message) return
293
+ message.deletedAt = new Date()
294
+ await em.flush()
295
+ },
296
+ }
297
+
298
+ const updateDraftCommand: CommandHandler<unknown, { ok: true; id: string }> = {
299
+ id: 'messages.messages.update_draft',
300
+ async prepare(rawInput, ctx) {
301
+ const input = updateDraftCommandSchema.parse(rawInput)
302
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
303
+ const snapshot = await loadMessageAggregateSnapshot(em, input.messageId, {
304
+ tenantId: input.tenantId,
305
+ organizationId: input.organizationId,
306
+ })
307
+ return snapshot ? { before: snapshot } : {}
308
+ },
309
+ async execute(rawInput, ctx) {
310
+ const input = updateDraftCommandSchema.parse(rawInput)
311
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
312
+ const message = await requireMessageById(em, input, input.messageId)
313
+
314
+ if (message.senderUserId !== input.userId) throw new Error('Access denied')
315
+ if (!message.isDraft) throw new Error('Only draft messages can be edited')
316
+
317
+ const nextMessageType = input.type ?? message.type
318
+ if (input.objects) {
319
+ const objectValidationError = validateMessageObjectsForType(nextMessageType, input.objects)
320
+ if (objectValidationError) throw new Error(objectValidationError)
321
+ } else if (input.type !== undefined) {
322
+ const existingObjects = await em.find(MessageObject, { messageId: message.id })
323
+ if (existingObjects.length > 0) {
324
+ const objectValidationError = validateMessageObjectsForType(
325
+ nextMessageType,
326
+ existingObjects.map((item) => ({
327
+ entityModule: item.entityModule,
328
+ entityType: item.entityType,
329
+ entityId: item.entityId,
330
+ })),
331
+ )
332
+ if (objectValidationError) throw new Error(objectValidationError)
333
+ }
334
+ }
335
+
336
+ if (input.type !== undefined) message.type = input.type
337
+ if (input.visibility !== undefined) message.visibility = input.visibility
338
+ if (input.sourceEntityType !== undefined) message.sourceEntityType = input.sourceEntityType
339
+ if (input.sourceEntityId !== undefined) message.sourceEntityId = input.sourceEntityId
340
+ if (input.externalEmail !== undefined) message.externalEmail = input.externalEmail
341
+ if (input.externalName !== undefined) message.externalName = input.externalName
342
+ if (input.subject !== undefined) message.subject = input.subject
343
+ if (input.body !== undefined) message.body = input.body
344
+ if (input.bodyFormat !== undefined) message.bodyFormat = input.bodyFormat
345
+ if (input.priority !== undefined) message.priority = input.priority
346
+ if (input.actionData !== undefined) message.actionData = input.actionData
347
+ if (input.sendViaEmail !== undefined) message.sendViaEmail = input.sendViaEmail
348
+
349
+ if (input.recipients) {
350
+ await em.nativeDelete(MessageRecipient, { messageId: message.id })
351
+ for (const recipient of input.recipients) {
352
+ em.persist(em.create(MessageRecipient, {
353
+ messageId: message.id,
354
+ recipientUserId: recipient.userId,
355
+ recipientType: recipient.type,
356
+ status: 'unread',
357
+ }))
358
+ }
359
+ }
360
+
361
+ if (input.objects) {
362
+ await em.nativeDelete(MessageObject, { messageId: message.id })
363
+ for (const object of input.objects) {
364
+ em.persist(em.create(MessageObject, {
365
+ messageId: message.id,
366
+ entityModule: object.entityModule,
367
+ entityType: object.entityType,
368
+ entityId: object.entityId,
369
+ actionRequired: object.actionRequired,
370
+ actionType: object.actionType,
371
+ actionLabel: object.actionLabel,
372
+ }))
373
+ }
374
+ }
375
+
376
+ if (input.attachmentIds) {
377
+ const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')
378
+ if (input.attachmentIds.length === 0) {
379
+ await em.nativeDelete(Attachment, {
380
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
381
+ recordId: message.id,
382
+ tenantId: input.tenantId,
383
+ organizationId: input.organizationId,
384
+ })
385
+ } else {
386
+ await em.nativeDelete(Attachment, {
387
+ entityId: MESSAGE_ATTACHMENT_ENTITY_ID,
388
+ recordId: message.id,
389
+ tenantId: input.tenantId,
390
+ organizationId: input.organizationId,
391
+ id: { $nin: input.attachmentIds },
392
+ })
393
+ }
394
+ await linkAttachmentsToMessage(
395
+ em,
396
+ message.id,
397
+ input.attachmentIds,
398
+ input.organizationId,
399
+ input.tenantId,
400
+ )
401
+ }
402
+
403
+ await em.flush()
404
+ return { ok: true, id: message.id }
405
+ },
406
+ async captureAfter(rawInput, _result, ctx) {
407
+ const input = updateDraftCommandSchema.parse(rawInput)
408
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
409
+ return loadMessageAggregateSnapshot(em, input.messageId, {
410
+ tenantId: input.tenantId,
411
+ organizationId: input.organizationId,
412
+ })
413
+ },
414
+ buildLog: async ({ input, snapshots }) => {
415
+ const parsed = updateDraftCommandSchema.parse(input)
416
+ return {
417
+ actionLabel: 'Update draft message',
418
+ resourceKind: 'messages.message',
419
+ resourceId: parsed.messageId,
420
+ tenantId: parsed.tenantId,
421
+ organizationId: parsed.organizationId,
422
+ payload: {
423
+ undo: {
424
+ before: (snapshots.before as MessageAggregateSnapshot | undefined) ?? null,
425
+ after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,
426
+ } satisfies UndoPayload<MessageAggregateSnapshot>,
427
+ },
428
+ snapshotBefore: snapshots.before ?? null,
429
+ snapshotAfter: snapshots.after ?? null,
430
+ }
431
+ },
432
+ undo: async ({ logEntry, ctx }) => {
433
+ const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)
434
+ const before = undo?.before
435
+ if (!before) return
436
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
437
+ await restoreMessageAggregateSnapshot(em, before)
438
+ },
439
+ }
440
+
441
+ const replyMessageCommand: CommandHandler<unknown, { id: string; externalEmail: string | null; recipientUserIds: string[] }> = {
442
+ id: 'messages.messages.reply',
443
+ async execute(rawInput, ctx) {
444
+ const input = replyCommandSchema.parse(rawInput)
445
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
446
+ const original = await requireMessageById(em, input, input.messageId)
447
+ const ownRecipient = await em.findOne(MessageRecipient, {
448
+ messageId: original.id,
449
+ recipientUserId: input.userId,
450
+ deletedAt: null,
451
+ })
452
+ if (original.senderUserId !== input.userId && !ownRecipient) throw new Error('Access denied')
453
+
454
+ const messageType = getMessageTypeOrDefault(original.type)
455
+ if (messageType.allowReply === false) throw new Error('Reply is not allowed for this message type')
456
+
457
+ const recipientIds = new Set(
458
+ (input.recipients ?? [])
459
+ .map((recipient) => recipient.userId)
460
+ .filter((recipientUserId) => recipientUserId !== input.userId),
461
+ )
462
+
463
+ if (recipientIds.size === 0) {
464
+ const originalRecipients = await em.find(MessageRecipient, { messageId: original.id, deletedAt: null })
465
+ if (input.replyAll) {
466
+ if (original.senderUserId !== input.userId) recipientIds.add(original.senderUserId)
467
+ for (const recipient of originalRecipients) {
468
+ if (recipient.recipientUserId !== input.userId) recipientIds.add(recipient.recipientUserId)
469
+ }
470
+ } else if (original.senderUserId !== input.userId) {
471
+ recipientIds.add(original.senderUserId)
472
+ } else {
473
+ for (const recipient of originalRecipients) {
474
+ if (recipient.recipientUserId !== input.userId) {
475
+ recipientIds.add(recipient.recipientUserId)
476
+ break
477
+ }
478
+ }
479
+ }
480
+ }
481
+ if (recipientIds.size === 0) throw new Error('No recipients available for reply')
482
+
483
+ let messageId = ''
484
+ let responseExternalEmail: string | null = null
485
+ await em.transactional(async (trx) => {
486
+ const message = trx.create(Message, {
487
+ type: original.type,
488
+ visibility: original.visibility ?? null,
489
+ sourceEntityType: original.sourceEntityType,
490
+ sourceEntityId: original.sourceEntityId,
491
+ externalEmail: original.externalEmail,
492
+ externalName: original.externalName,
493
+ threadId: original.threadId ?? original.id,
494
+ parentMessageId: original.id,
495
+ senderUserId: input.userId,
496
+ subject: buildReplySubject(original.subject),
497
+ body: input.body,
498
+ bodyFormat: input.bodyFormat,
499
+ priority: original.priority,
500
+ status: 'sent',
501
+ isDraft: false,
502
+ sentAt: new Date(),
503
+ sendViaEmail: input.sendViaEmail,
504
+ tenantId: input.tenantId,
505
+ organizationId: input.organizationId,
506
+ })
507
+ await trx.persistAndFlush(message)
508
+ for (const recipientUserId of recipientIds) {
509
+ trx.persist(trx.create(MessageRecipient, {
510
+ messageId: message.id,
511
+ recipientUserId,
512
+ recipientType: 'to',
513
+ status: 'unread',
514
+ }))
515
+ }
516
+ await trx.flush()
517
+ if (input.attachmentIds?.length) {
518
+ await linkAttachmentsToMessage(
519
+ trx,
520
+ message.id,
521
+ input.attachmentIds,
522
+ input.organizationId,
523
+ input.tenantId,
524
+ )
525
+ }
526
+ if (input.attachmentRecordId) {
527
+ await linkLibraryAttachmentsToMessage(
528
+ trx,
529
+ message.id,
530
+ input.attachmentRecordId,
531
+ input.organizationId,
532
+ input.tenantId,
533
+ )
534
+ }
535
+ messageId = message.id
536
+ responseExternalEmail = message.externalEmail ?? null
537
+ })
538
+
539
+ await emitMessageSentEvent(ctx.container, {
540
+ messageId,
541
+ senderUserId: input.userId,
542
+ recipientUserIds: Array.from(recipientIds),
543
+ sendViaEmail: input.sendViaEmail,
544
+ externalEmail: responseExternalEmail,
545
+ replyTo: original.id,
546
+ tenantId: input.tenantId,
547
+ organizationId: input.organizationId,
548
+ })
549
+
550
+ return {
551
+ id: messageId,
552
+ externalEmail: responseExternalEmail,
553
+ recipientUserIds: Array.from(recipientIds),
554
+ }
555
+ },
556
+ async captureAfter(_input, result, ctx) {
557
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
558
+ return loadMessageAggregateSnapshot(em, result.id)
559
+ },
560
+ buildLog: async ({ input, result, snapshots }) => {
561
+ const parsed = replyCommandSchema.parse(input)
562
+ return {
563
+ actionLabel: 'Reply to message',
564
+ resourceKind: 'messages.message',
565
+ resourceId: result.id,
566
+ parentResourceKind: 'messages.message',
567
+ parentResourceId: parsed.messageId,
568
+ tenantId: parsed.tenantId,
569
+ organizationId: parsed.organizationId,
570
+ payload: {
571
+ undo: {
572
+ after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,
573
+ } satisfies UndoPayload<MessageAggregateSnapshot>,
574
+ },
575
+ snapshotAfter: snapshots.after ?? null,
576
+ }
577
+ },
578
+ undo: async ({ logEntry, ctx }) => {
579
+ const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)
580
+ const after = undo?.after
581
+ if (!after) return
582
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
583
+ const message = await em.findOne(Message, { id: after.message.id })
584
+ if (!message) return
585
+ message.deletedAt = new Date()
586
+ await em.flush()
587
+ },
588
+ }
589
+
590
+ const forwardMessageCommand: CommandHandler<unknown, { id: string; externalEmail: string | null; recipientUserIds: string[] }> = {
591
+ id: 'messages.messages.forward',
592
+ async execute(rawInput, ctx) {
593
+ const input = forwardCommandSchema.parse(rawInput)
594
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
595
+ const original = await requireMessageById(em, input, input.messageId)
596
+ const isRecipient = await em.findOne(MessageRecipient, {
597
+ messageId: input.messageId,
598
+ recipientUserId: input.userId,
599
+ deletedAt: null,
600
+ })
601
+ if (original.senderUserId !== input.userId && !isRecipient) throw new Error('Access denied')
602
+
603
+ const messageType = getMessageTypeOrDefault(original.type)
604
+ if (messageType.allowForward === false) throw new Error('Forward is not allowed for this message type')
605
+
606
+ const originalObjects = await em.find(MessageObject, { messageId: input.messageId })
607
+ const forwardThreadSlice = await buildForwardThreadSlice(em, {
608
+ tenantId: input.tenantId,
609
+ organizationId: input.organizationId,
610
+ userId: input.userId,
611
+ }, original)
612
+ const generatedPreview = await buildForwardPreviewFromThreadSlice(em, {
613
+ tenantId: input.tenantId,
614
+ organizationId: input.organizationId,
615
+ userId: input.userId,
616
+ }, original, forwardThreadSlice)
617
+ const forwardedBody = typeof input.body === 'string'
618
+ ? input.body
619
+ : buildForwardBodyFromLegacyInput(generatedPreview.body, input.additionalBody)
620
+ let newMessageId = ''
621
+ let responseExternalEmail: string | null = null
622
+ await em.transactional(async (trx) => {
623
+ const newMessage = trx.create(Message, {
624
+ type: original.type,
625
+ visibility: original.visibility ?? null,
626
+ sourceEntityType: original.sourceEntityType,
627
+ sourceEntityId: original.sourceEntityId,
628
+ externalEmail: original.externalEmail,
629
+ externalName: original.externalName,
630
+ threadId: original.threadId ?? original.id,
631
+ parentMessageId: original.id,
632
+ senderUserId: input.userId,
633
+ subject: `Fwd: ${original.subject}`,
634
+ body: forwardedBody,
635
+ bodyFormat: original.bodyFormat,
636
+ priority: original.priority,
637
+ status: 'sent',
638
+ isDraft: false,
639
+ sentAt: new Date(),
640
+ sendViaEmail: input.sendViaEmail,
641
+ tenantId: input.tenantId,
642
+ organizationId: input.organizationId,
643
+ })
644
+ await trx.persistAndFlush(newMessage)
645
+ for (const recipient of input.recipients) {
646
+ trx.persist(trx.create(MessageRecipient, {
647
+ messageId: newMessage.id,
648
+ recipientUserId: recipient.userId,
649
+ recipientType: recipient.type,
650
+ status: 'unread',
651
+ }))
652
+ }
653
+ for (const obj of originalObjects) {
654
+ trx.persist(trx.create(MessageObject, {
655
+ messageId: newMessage.id,
656
+ entityModule: obj.entityModule,
657
+ entityType: obj.entityType,
658
+ entityId: obj.entityId,
659
+ actionRequired: obj.actionRequired,
660
+ actionType: obj.actionType,
661
+ actionLabel: obj.actionLabel,
662
+ entitySnapshot: obj.entitySnapshot,
663
+ }))
664
+ }
665
+ await trx.flush()
666
+ if (input.includeAttachments !== false) {
667
+ await copyAttachmentsForForwardMessages(
668
+ trx,
669
+ forwardThreadSlice.map((item) => item.id),
670
+ newMessage.id,
671
+ input.organizationId,
672
+ input.tenantId,
673
+ )
674
+ }
675
+ newMessageId = newMessage.id
676
+ responseExternalEmail = newMessage.externalEmail ?? null
677
+ })
678
+
679
+ await emitMessageSentEvent(ctx.container, {
680
+ messageId: newMessageId,
681
+ senderUserId: input.userId,
682
+ recipientUserIds: input.recipients.map((item) => item.userId),
683
+ sendViaEmail: input.sendViaEmail,
684
+ externalEmail: responseExternalEmail,
685
+ forwardedFrom: original.id,
686
+ tenantId: input.tenantId,
687
+ organizationId: input.organizationId,
688
+ })
689
+
690
+ return {
691
+ id: newMessageId,
692
+ externalEmail: responseExternalEmail,
693
+ recipientUserIds: input.recipients.map((item) => item.userId),
694
+ }
695
+ },
696
+ async captureAfter(_input, result, ctx) {
697
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
698
+ return loadMessageAggregateSnapshot(em, result.id)
699
+ },
700
+ buildLog: async ({ input, result, snapshots }) => {
701
+ const parsed = forwardCommandSchema.parse(input)
702
+ return {
703
+ actionLabel: 'Forward message',
704
+ resourceKind: 'messages.message',
705
+ resourceId: result.id,
706
+ parentResourceKind: 'messages.message',
707
+ parentResourceId: parsed.messageId,
708
+ tenantId: parsed.tenantId,
709
+ organizationId: parsed.organizationId,
710
+ payload: {
711
+ undo: {
712
+ after: (snapshots.after as MessageAggregateSnapshot | undefined) ?? null,
713
+ } satisfies UndoPayload<MessageAggregateSnapshot>,
714
+ },
715
+ snapshotAfter: snapshots.after ?? null,
716
+ }
717
+ },
718
+ undo: async ({ logEntry, ctx }) => {
719
+ const undo = extractUndoPayload<UndoPayload<MessageAggregateSnapshot>>(logEntry)
720
+ const after = undo?.after
721
+ if (!after) return
722
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
723
+ const message = await em.findOne(Message, { id: after.message.id })
724
+ if (!message) return
725
+ message.deletedAt = new Date()
726
+ await em.flush()
727
+ },
728
+ }
729
+
730
+ const deleteForActorCommand: CommandHandler<unknown, { ok: true }> = {
731
+ id: 'messages.messages.delete_for_actor',
732
+ async prepare(rawInput, ctx) {
733
+ const input = deleteForActorCommandSchema.parse(rawInput)
734
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
735
+ const message = await requireMessageById(em, input, input.messageId)
736
+ const recipient = await em.findOne(MessageRecipient, {
737
+ messageId: input.messageId,
738
+ recipientUserId: input.userId,
739
+ deletedAt: null,
740
+ })
741
+ return {
742
+ before: {
743
+ messageId: message.id,
744
+ messageDeletedAt: toIso(message.deletedAt),
745
+ recipientId: recipient?.id ?? null,
746
+ recipientStatus: recipient?.status ?? null,
747
+ recipientDeletedAt: toIso(recipient?.deletedAt),
748
+ } satisfies MessageDeleteUndoState,
749
+ }
750
+ },
751
+ async execute(rawInput, ctx) {
752
+ const input = deleteForActorCommandSchema.parse(rawInput)
753
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
754
+ const message = await requireMessageById(em, input, input.messageId)
755
+ const recipient = await em.findOne(MessageRecipient, {
756
+ messageId: input.messageId,
757
+ recipientUserId: input.userId,
758
+ deletedAt: null,
759
+ })
760
+ if (recipient) {
761
+ recipient.status = 'deleted'
762
+ recipient.deletedAt = new Date()
763
+ await em.flush()
764
+ await emitMessageDeletedEvent(ctx.container, {
765
+ messageId: input.messageId,
766
+ actorUserId: input.userId,
767
+ target: 'recipient',
768
+ tenantId: input.tenantId,
769
+ organizationId: input.organizationId,
770
+ })
771
+ return { ok: true }
772
+ }
773
+ if (message.senderUserId === input.userId) {
774
+ message.deletedAt = new Date()
775
+ await em.flush()
776
+ await emitMessageDeletedEvent(ctx.container, {
777
+ messageId: input.messageId,
778
+ actorUserId: input.userId,
779
+ target: 'sender',
780
+ tenantId: input.tenantId,
781
+ organizationId: input.organizationId,
782
+ })
783
+ return { ok: true }
784
+ }
785
+ throw new Error('Access denied')
786
+ },
787
+ async captureAfter(rawInput, _result, ctx) {
788
+ const input = deleteForActorCommandSchema.parse(rawInput)
789
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
790
+ const message = await em.findOne(Message, { id: input.messageId, tenantId: input.tenantId })
791
+ const recipient = await em.findOne(MessageRecipient, {
792
+ messageId: input.messageId,
793
+ recipientUserId: input.userId,
794
+ })
795
+ return {
796
+ messageId: input.messageId,
797
+ messageDeletedAt: toIso(message?.deletedAt),
798
+ recipientId: recipient?.id ?? null,
799
+ recipientStatus: recipient?.status ?? null,
800
+ recipientDeletedAt: toIso(recipient?.deletedAt),
801
+ } satisfies MessageDeleteUndoState
802
+ },
803
+ buildLog: async ({ input, snapshots }) => {
804
+ const parsed = deleteForActorCommandSchema.parse(input)
805
+ return {
806
+ actionLabel: 'Delete message for actor',
807
+ resourceKind: 'messages.message',
808
+ resourceId: parsed.messageId,
809
+ tenantId: parsed.tenantId,
810
+ organizationId: parsed.organizationId,
811
+ payload: {
812
+ undo: {
813
+ before: (snapshots.before as MessageDeleteUndoState | undefined) ?? null,
814
+ after: (snapshots.after as MessageDeleteUndoState | undefined) ?? null,
815
+ } satisfies UndoPayload<MessageDeleteUndoState>,
816
+ },
817
+ snapshotBefore: snapshots.before ?? null,
818
+ snapshotAfter: snapshots.after ?? null,
819
+ }
820
+ },
821
+ undo: async ({ logEntry, ctx }) => {
822
+ const undo = extractUndoPayload<UndoPayload<MessageDeleteUndoState>>(logEntry)
823
+ const before = undo?.before
824
+ if (!before) return
825
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
826
+ const message = await em.findOne(Message, { id: before.messageId })
827
+ if (message) {
828
+ message.deletedAt = toDate(before.messageDeletedAt)
829
+ }
830
+ if (before.recipientId) {
831
+ const recipient = await em.findOne(MessageRecipient, { id: before.recipientId })
832
+ if (recipient) {
833
+ recipient.status = (before.recipientStatus ?? 'unread') as MessageRecipient['status']
834
+ recipient.deletedAt = toDate(before.recipientDeletedAt)
835
+ }
836
+ }
837
+ await em.flush()
838
+ },
839
+ }
840
+
841
+ registerCommand(composeMessageCommand)
842
+ registerCommand(updateDraftCommand)
843
+ registerCommand(replyMessageCommand)
844
+ registerCommand(forwardMessageCommand)
845
+ registerCommand(deleteForActorCommand)