@open-mercato/core 0.6.6-develop.6256.1.9fc16aedc4 → 0.6.6-develop.6286.1.d985c5c374

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 (234) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/carrier_shipment_idempotency_key/index.js +19 -0
  3. package/dist/generated/entities/carrier_shipment_idempotency_key/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +2 -1
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +10 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/lib/productResolution.js +45 -0
  9. package/dist/modules/catalog/lib/productResolution.js.map +7 -0
  10. package/dist/modules/communication_channels/workers/channel-import-history.js +1 -1
  11. package/dist/modules/communication_channels/workers/channel-import-history.js.map +2 -2
  12. package/dist/modules/currencies/api/fetch-configs/route.js +34 -0
  13. package/dist/modules/currencies/api/fetch-configs/route.js.map +2 -2
  14. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +17 -15
  15. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  16. package/dist/modules/customers/api/companies/[id]/route.js +176 -161
  17. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  18. package/dist/modules/customers/api/people/[id]/route.js +75 -59
  19. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  20. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +17 -10
  21. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  22. package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js +6 -4
  23. package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js.map +2 -2
  24. package/dist/modules/customers/components/detail/AiActionChips.js +5 -2
  25. package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
  26. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +19 -21
  27. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  28. package/dist/modules/data_sync/api/runs/[id]/cancel.js +1 -1
  29. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  30. package/dist/modules/data_sync/backend/data-sync/page.js +9 -45
  31. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  32. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +11 -17
  33. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  34. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +12 -10
  35. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  36. package/dist/modules/data_sync/lib/id-mapping.js +46 -1
  37. package/dist/modules/data_sync/lib/id-mapping.js.map +2 -2
  38. package/dist/modules/data_sync/lib/sync-engine.js +2 -2
  39. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  40. package/dist/modules/data_sync/lib/syncRunStatus.js +30 -0
  41. package/dist/modules/data_sync/lib/syncRunStatus.js.map +7 -0
  42. package/dist/modules/entities/api/records.js +6 -0
  43. package/dist/modules/entities/api/records.js.map +2 -2
  44. package/dist/modules/feature_toggles/api/global/route.js +17 -15
  45. package/dist/modules/feature_toggles/api/global/route.js.map +2 -2
  46. package/dist/modules/feature_toggles/api/openapi.js +2 -2
  47. package/dist/modules/feature_toggles/api/openapi.js.map +2 -2
  48. package/dist/modules/feature_toggles/commands/global.js +14 -0
  49. package/dist/modules/feature_toggles/commands/global.js.map +2 -2
  50. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +6 -0
  51. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  52. package/dist/modules/messages/api/[id]/conversation/archive/route.js +25 -7
  53. package/dist/modules/messages/api/[id]/conversation/archive/route.js.map +2 -2
  54. package/dist/modules/messages/api/[id]/conversation/read/route.js +24 -6
  55. package/dist/modules/messages/api/[id]/conversation/read/route.js.map +2 -2
  56. package/dist/modules/messages/api/[id]/route.js +13 -1
  57. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  58. package/dist/modules/messages/api/openapi.js +3 -1
  59. package/dist/modules/messages/api/openapi.js.map +2 -2
  60. package/dist/modules/messages/commands/conversation.js +77 -0
  61. package/dist/modules/messages/commands/conversation.js.map +2 -2
  62. package/dist/modules/messages/components/MessageDetailPageClient.js +16 -4
  63. package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
  64. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js +3 -1
  65. package/dist/modules/messages/components/message-detail/hooks/useMessageDetails.js.map +2 -2
  66. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +29 -4
  67. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
  68. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js +10 -1
  69. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.js.map +2 -2
  70. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js +13 -11
  71. package/dist/modules/messages/components/message-detail/panels/MainMessageHeader.js.map +2 -2
  72. package/dist/modules/payment_gateways/api/cancel/route.js +5 -0
  73. package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
  74. package/dist/modules/payment_gateways/api/capture/route.js +5 -0
  75. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  76. package/dist/modules/payment_gateways/api/refund/route.js +5 -0
  77. package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
  78. package/dist/modules/payment_gateways/lib/gateway-service.js +52 -25
  79. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  80. package/dist/modules/payment_gateways/lib/status-machine.js +13 -0
  81. package/dist/modules/payment_gateways/lib/status-machine.js.map +2 -2
  82. package/dist/modules/perspectives/api/[tableId]/route.js +8 -2
  83. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  84. package/dist/modules/perspectives/data/entities.js +33 -3
  85. package/dist/modules/perspectives/data/entities.js.map +2 -2
  86. package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js +63 -0
  87. package/dist/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.js.map +7 -0
  88. package/dist/modules/perspectives/services/perspectiveService.js +17 -0
  89. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  90. package/dist/modules/planner/commands/availability-weekly.js +33 -1
  91. package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
  92. package/dist/modules/planner/components/AvailabilityRulesEditor.js +14 -2
  93. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  94. package/dist/modules/progress/api/active/route.js +1 -1
  95. package/dist/modules/progress/api/active/route.js.map +2 -2
  96. package/dist/modules/progress/lib/progressService.js.map +1 -1
  97. package/dist/modules/progress/lib/progressServiceImpl.js +33 -8
  98. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  99. package/dist/modules/query_index/api/status.js +19 -24
  100. package/dist/modules/query_index/api/status.js.map +3 -3
  101. package/dist/modules/query_index/lib/coverage.js +35 -0
  102. package/dist/modules/query_index/lib/coverage.js.map +2 -2
  103. package/dist/modules/query_index/lib/engine.js +11 -6
  104. package/dist/modules/query_index/lib/engine.js.map +2 -2
  105. package/dist/modules/resources/api/resource-types.js +5 -37
  106. package/dist/modules/resources/api/resource-types.js.map +2 -2
  107. package/dist/modules/resources/api/resourceTypeCounts.js +35 -0
  108. package/dist/modules/resources/api/resourceTypeCounts.js.map +7 -0
  109. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -1
  110. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +1 -1
  111. package/dist/modules/resources/backend/resources/resource-types/page.js +2 -1
  112. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  113. package/dist/modules/sales/components/documents/ReturnDialog.js +4 -1
  114. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  115. package/dist/modules/sales/components/documents/ShipmentDialog.js +17 -2
  116. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  117. package/dist/modules/sales/di.js +4 -0
  118. package/dist/modules/sales/di.js.map +2 -2
  119. package/dist/modules/sales/services/salesOrderService.js +26 -0
  120. package/dist/modules/sales/services/salesOrderService.js.map +7 -0
  121. package/dist/modules/shipping_carriers/api/shipments/route.js +8 -0
  122. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  123. package/dist/modules/shipping_carriers/data/entities.js +38 -0
  124. package/dist/modules/shipping_carriers/data/entities.js.map +2 -2
  125. package/dist/modules/shipping_carriers/data/validators.js +2 -1
  126. package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
  127. package/dist/modules/shipping_carriers/lib/shipment-idempotency.js +76 -0
  128. package/dist/modules/shipping_carriers/lib/shipment-idempotency.js.map +7 -0
  129. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js +2 -1
  130. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.js.map +2 -2
  131. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +20 -2
  132. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
  133. package/dist/modules/shipping_carriers/lib/shipping-service.js +82 -43
  134. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
  135. package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js +14 -0
  136. package/dist/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.js.map +7 -0
  137. package/dist/modules/staff/lib/timesheets-ui/ListView.js +15 -6
  138. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +2 -2
  139. package/dist/modules/translations/components/TranslationManager.js +9 -18
  140. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  141. package/generated/entities/carrier_shipment_idempotency_key/index.ts +8 -0
  142. package/generated/entities.ids.generated.ts +2 -1
  143. package/generated/entity-fields-registry.ts +10 -0
  144. package/package.json +9 -8
  145. package/src/modules/catalog/lib/productResolution.ts +75 -0
  146. package/src/modules/communication_channels/workers/channel-import-history.ts +1 -1
  147. package/src/modules/currencies/api/fetch-configs/route.ts +36 -0
  148. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +20 -17
  149. package/src/modules/currencies/i18n/de.json +2 -1
  150. package/src/modules/currencies/i18n/en.json +2 -1
  151. package/src/modules/currencies/i18n/es.json +2 -1
  152. package/src/modules/currencies/i18n/pl.json +2 -1
  153. package/src/modules/customers/api/companies/[id]/route.ts +214 -189
  154. package/src/modules/customers/api/people/[id]/route.ts +106 -80
  155. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +19 -12
  156. package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +8 -6
  157. package/src/modules/customers/components/detail/AiActionChips.tsx +6 -3
  158. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +16 -18
  159. package/src/modules/data_sync/api/runs/[id]/cancel.ts +1 -1
  160. package/src/modules/data_sync/backend/data-sync/page.tsx +11 -58
  161. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +13 -20
  162. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +10 -8
  163. package/src/modules/data_sync/lib/id-mapping.ts +58 -1
  164. package/src/modules/data_sync/lib/sync-engine.ts +2 -2
  165. package/src/modules/data_sync/lib/syncRunStatus.ts +39 -0
  166. package/src/modules/entities/api/records.ts +14 -0
  167. package/src/modules/feature_toggles/api/global/route.ts +16 -14
  168. package/src/modules/feature_toggles/api/openapi.ts +2 -2
  169. package/src/modules/feature_toggles/commands/global.ts +15 -0
  170. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +7 -0
  171. package/src/modules/messages/api/[id]/conversation/archive/route.ts +29 -6
  172. package/src/modules/messages/api/[id]/conversation/read/route.ts +29 -6
  173. package/src/modules/messages/api/[id]/route.ts +15 -0
  174. package/src/modules/messages/api/openapi.ts +2 -0
  175. package/src/modules/messages/commands/conversation.ts +87 -0
  176. package/src/modules/messages/components/MessageDetailPageClient.tsx +16 -4
  177. package/src/modules/messages/components/message-detail/hooks/useMessageDetails.ts +2 -0
  178. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +39 -4
  179. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsQueries.ts +15 -1
  180. package/src/modules/messages/components/message-detail/panels/MainMessageHeader.tsx +29 -14
  181. package/src/modules/messages/components/message-detail/types.ts +2 -0
  182. package/src/modules/messages/i18n/de.json +4 -0
  183. package/src/modules/messages/i18n/en.json +4 -0
  184. package/src/modules/messages/i18n/es.json +4 -0
  185. package/src/modules/messages/i18n/pl.json +4 -0
  186. package/src/modules/payment_gateways/api/cancel/route.ts +5 -0
  187. package/src/modules/payment_gateways/api/capture/route.ts +5 -0
  188. package/src/modules/payment_gateways/api/refund/route.ts +5 -0
  189. package/src/modules/payment_gateways/lib/gateway-service.ts +58 -25
  190. package/src/modules/payment_gateways/lib/status-machine.ts +16 -0
  191. package/src/modules/perspectives/api/[tableId]/route.ts +8 -2
  192. package/src/modules/perspectives/data/entities.ts +43 -3
  193. package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +69 -25
  194. package/src/modules/perspectives/migrations/Migration20260619120000_perspectives_live_row_uniqueness.ts +65 -0
  195. package/src/modules/perspectives/services/perspectiveService.ts +17 -0
  196. package/src/modules/planner/commands/availability-weekly.ts +48 -1
  197. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +31 -10
  198. package/src/modules/progress/api/active/route.ts +1 -1
  199. package/src/modules/progress/lib/progressService.ts +2 -2
  200. package/src/modules/progress/lib/progressServiceImpl.ts +33 -8
  201. package/src/modules/query_index/api/status.ts +28 -32
  202. package/src/modules/query_index/lib/coverage.ts +63 -0
  203. package/src/modules/query_index/lib/engine.ts +37 -24
  204. package/src/modules/resources/api/resource-types.ts +4 -44
  205. package/src/modules/resources/api/resourceTypeCounts.ts +74 -0
  206. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +1 -1
  207. package/src/modules/resources/backend/resources/resource-types/page.tsx +1 -0
  208. package/src/modules/sales/components/documents/ReturnDialog.tsx +4 -1
  209. package/src/modules/sales/components/documents/ShipmentDialog.tsx +20 -2
  210. package/src/modules/sales/di.ts +6 -0
  211. package/src/modules/sales/services/salesOrderService.ts +38 -0
  212. package/src/modules/shipping_carriers/api/shipments/route.ts +8 -0
  213. package/src/modules/shipping_carriers/data/entities.ts +33 -0
  214. package/src/modules/shipping_carriers/data/validators.ts +1 -0
  215. package/src/modules/shipping_carriers/i18n/de.json +1 -0
  216. package/src/modules/shipping_carriers/i18n/en.json +1 -0
  217. package/src/modules/shipping_carriers/i18n/es.json +1 -0
  218. package/src/modules/shipping_carriers/i18n/pl.json +1 -0
  219. package/src/modules/shipping_carriers/lib/shipment-idempotency.ts +119 -0
  220. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/shipmentApi.ts +3 -1
  221. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +24 -2
  222. package/src/modules/shipping_carriers/lib/shipping-service.ts +87 -43
  223. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +197 -0
  224. package/src/modules/shipping_carriers/migrations/Migration20260618173616_shipping_carriers.ts +14 -0
  225. package/src/modules/staff/i18n/de.json +1 -0
  226. package/src/modules/staff/i18n/en.json +1 -0
  227. package/src/modules/staff/i18n/es.json +1 -0
  228. package/src/modules/staff/i18n/pl.json +1 -0
  229. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +15 -7
  230. package/src/modules/translations/components/TranslationManager.tsx +17 -24
  231. package/src/modules/translations/i18n/de.json +1 -0
  232. package/src/modules/translations/i18n/en.json +1 -0
  233. package/src/modules/translations/i18n/es.json +1 -0
  234. package/src/modules/translations/i18n/pl.json +1 -0
@@ -228,6 +228,91 @@ const markConversationUnreadForActorCommand: CommandHandler<unknown, { ok: true;
228
228
  },
229
229
  }
230
230
 
231
+ const unarchiveConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {
232
+ id: 'messages.conversation.unarchive_for_actor',
233
+ async execute(rawInput, ctx) {
234
+ const input = conversationCommandSchema.parse(rawInput)
235
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
236
+ const scope = await resolveConversationScope(em, input)
237
+ const messageIdsToUnarchive = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))
238
+
239
+ if (messageIdsToUnarchive.length === 0) {
240
+ return { ok: true, affectedCount: 0 }
241
+ }
242
+
243
+ const unarchivedIds: string[] = []
244
+ await em.transactional(async (trx) => {
245
+ const recipients = await trx.find(MessageRecipient, {
246
+ messageId: { $in: messageIdsToUnarchive },
247
+ recipientUserId: input.userId,
248
+ deletedAt: null,
249
+ })
250
+ for (const recipient of recipients) {
251
+ if (recipient.status === 'archived' || recipient.archivedAt !== null) {
252
+ recipient.archivedAt = null
253
+ recipient.status = recipient.readAt ? 'read' : 'unread'
254
+ unarchivedIds.push(recipient.messageId)
255
+ }
256
+ }
257
+ })
258
+
259
+ for (const messageId of unarchivedIds) {
260
+ await emitMessagesEvent('messages.message.unarchived', {
261
+ messageId,
262
+ recipientUserId: input.userId,
263
+ userId: input.userId,
264
+ tenantId: input.tenantId,
265
+ organizationId: input.organizationId,
266
+ }, { persistent: true })
267
+ }
268
+
269
+ return { ok: true, affectedCount: unarchivedIds.length }
270
+ },
271
+ }
272
+
273
+ const markConversationReadForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {
274
+ id: 'messages.conversation.mark_read_for_actor',
275
+ async execute(rawInput, ctx) {
276
+ const input = conversationCommandSchema.parse(rawInput)
277
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
278
+ const scope = await resolveConversationScope(em, input)
279
+ const messageIdsToMarkRead = scope.messageIds.filter((messageId) => scope.recipientMessageIds.has(messageId))
280
+
281
+ if (messageIdsToMarkRead.length === 0) {
282
+ return { ok: true, affectedCount: 0 }
283
+ }
284
+
285
+ const readAt = new Date()
286
+ const markedIds: string[] = []
287
+ await em.transactional(async (trx) => {
288
+ const recipients = await trx.find(MessageRecipient, {
289
+ messageId: { $in: messageIdsToMarkRead },
290
+ recipientUserId: input.userId,
291
+ deletedAt: null,
292
+ })
293
+ for (const recipient of recipients) {
294
+ if (recipient.status === 'unread') {
295
+ recipient.status = 'read'
296
+ recipient.readAt = readAt
297
+ markedIds.push(recipient.messageId)
298
+ }
299
+ }
300
+ })
301
+
302
+ for (const messageId of markedIds) {
303
+ await emitMessagesEvent('messages.message.read', {
304
+ messageId,
305
+ recipientUserId: input.userId,
306
+ userId: input.userId,
307
+ tenantId: input.tenantId,
308
+ organizationId: input.organizationId,
309
+ }, { persistent: true })
310
+ }
311
+
312
+ return { ok: true, affectedCount: markedIds.length }
313
+ },
314
+ }
315
+
231
316
  type DeletedTarget = { messageId: string; target: 'sender' | 'recipient' }
232
317
 
233
318
  const deleteConversationForActorCommand: CommandHandler<unknown, { ok: true; affectedCount: number }> = {
@@ -289,5 +374,7 @@ const deleteConversationForActorCommand: CommandHandler<unknown, { ok: true; aff
289
374
  }
290
375
 
291
376
  registerCommand(archiveConversationForActorCommand)
377
+ registerCommand(unarchiveConversationForActorCommand)
292
378
  registerCommand(markConversationUnreadForActorCommand)
379
+ registerCommand(markConversationReadForActorCommand)
293
380
  registerCommand(deleteConversationForActorCommand)
@@ -220,6 +220,8 @@ export function MessageDetailPageClient({ id }: { id: string }) {
220
220
  const firstConversationMessageId = state.conversationItems[0]?.id ?? null
221
221
  const latestConversationMessageId = state.conversationItems[state.conversationItems.length - 1]?.id ?? null
222
222
  const canRunConversationActions = Boolean(firstConversationMessageId)
223
+ const conversationArchived = Boolean(detail.conversationArchived)
224
+ const conversationAllUnread = Boolean(detail.conversationAllUnread)
223
225
 
224
226
  return (
225
227
  <div className="space-y-3">
@@ -228,6 +230,8 @@ export function MessageDetailPageClient({ id }: { id: string }) {
228
230
  priority={(detail.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal'}
229
231
  canReply={!detail.isDraft && detail.typeDefinition.allowReply && Boolean(firstConversationMessageId)}
230
232
  canForwardAll={!detail.isDraft && detail.typeDefinition.allowForward && Boolean(latestConversationMessageId)}
233
+ conversationArchived={conversationArchived}
234
+ conversationAllUnread={conversationAllUnread}
231
235
  actionsDisabled={Boolean(state.activeConversationAction)}
232
236
  activeActionId={state.activeConversationAction}
233
237
  onReply={() => {
@@ -244,13 +248,21 @@ export function MessageDetailPageClient({ id }: { id: string }) {
244
248
  messageId: latestConversationMessageId,
245
249
  })
246
250
  }}
247
- onArchiveConversation={() => {
251
+ onToggleArchiveConversation={() => {
248
252
  if (!canRunConversationActions) return
249
- void state.archiveConversation(firstConversationMessageId ?? undefined)
253
+ if (conversationArchived) {
254
+ void state.unarchiveConversation(firstConversationMessageId ?? undefined)
255
+ } else {
256
+ void state.archiveConversation(firstConversationMessageId ?? undefined)
257
+ }
250
258
  }}
251
- onMarkAllUnread={() => {
259
+ onToggleReadConversation={() => {
252
260
  if (!canRunConversationActions) return
253
- void state.markConversationUnread(firstConversationMessageId ?? undefined)
261
+ if (conversationAllUnread) {
262
+ void state.markConversationRead(firstConversationMessageId ?? undefined)
263
+ } else {
264
+ void state.markConversationUnread(firstConversationMessageId ?? undefined)
265
+ }
254
266
  }}
255
267
  onDeleteConversation={() => {
256
268
  if (!canRunConversationActions || state.activeConversationAction) return
@@ -61,7 +61,9 @@ export function useMessageDetails(id: string) {
61
61
  attachments: queryState.attachments,
62
62
  isArchived,
63
63
  onDeleted: () => router.push('/backend/messages'),
64
+ onMarkedUnread: () => router.push('/backend/messages'),
64
65
  refreshDetailWithoutAutoMarkRead: queryState.refreshDetailWithoutAutoMarkRead,
66
+ suppressAutoMarkRead: queryState.suppressAutoMarkRead,
65
67
  })
66
68
 
67
69
  const conversationState = useMessageDetailsConversation({
@@ -18,9 +18,15 @@ import { isSafeNavigationHref, parseObjectActionId, toErrorMessage } from '../ut
18
18
 
19
19
  type RequestAndRefreshOptions = {
20
20
  skipDetailAutoMarkRead?: boolean
21
+ onSuccess?: () => void
21
22
  }
22
23
 
23
- type ConversationActionKind = 'archiveConversation' | 'deleteConversation' | 'markAllUnread'
24
+ type ConversationActionKind =
25
+ | 'archiveConversation'
26
+ | 'unarchiveConversation'
27
+ | 'deleteConversation'
28
+ | 'markAllUnread'
29
+ | 'markAllRead'
24
30
 
25
31
  type MessageDetailMutationContext = {
26
32
  resourceKind: 'message' | 'conversation'
@@ -37,7 +43,9 @@ type UseMessageDetailsActionsInput = {
37
43
  attachments: MessageAttachment[] | undefined
38
44
  isArchived: boolean
39
45
  onDeleted: () => void
46
+ onMarkedUnread: () => void
40
47
  refreshDetailWithoutAutoMarkRead: () => Promise<MessageDetail | null>
48
+ suppressAutoMarkRead: () => void
41
49
  }
42
50
 
43
51
  export function useMessageDetailsActions({
@@ -48,7 +56,9 @@ export function useMessageDetailsActions({
48
56
  attachments,
49
57
  isArchived,
50
58
  onDeleted,
59
+ onMarkedUnread,
51
60
  refreshDetailWithoutAutoMarkRead,
61
+ suppressAutoMarkRead,
52
62
  }: UseMessageDetailsActionsInput) {
53
63
  const [replyOpen, setReplyOpen] = React.useState(false)
54
64
  const [forwardOpen, setForwardOpen] = React.useState(false)
@@ -88,6 +98,7 @@ export function useMessageDetailsActions({
88
98
  context: { resourceKind: 'message', messageId: id, action: 'state-change', retryLastMutation },
89
99
  mutationPayload: { messageId: id, action: 'state-change', url, method },
90
100
  })
101
+ options?.onSuccess?.()
91
102
  } catch (err) {
92
103
  flash(
93
104
  err instanceof Error
@@ -188,13 +199,32 @@ export function useMessageDetailsActions({
188
199
  })
189
200
  }, [id, runConversationAction, t])
190
201
 
202
+ const unarchiveConversation = React.useCallback(async (messageId?: string) => {
203
+ const targetMessageId = messageId ?? id
204
+ await runConversationAction('unarchiveConversation', {
205
+ url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/archive`,
206
+ method: 'DELETE',
207
+ successMessage: t('messages.flash.conversationUnarchived', 'Conversation restored from archive.'),
208
+ })
209
+ }, [id, runConversationAction, t])
210
+
191
211
  const markConversationUnread = React.useCallback(async (messageId?: string) => {
212
+ suppressAutoMarkRead()
192
213
  const targetMessageId = messageId ?? id
193
214
  await runConversationAction('markAllUnread', {
194
215
  url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/read`,
195
216
  method: 'DELETE',
196
217
  successMessage: t('messages.flash.conversationMarkedUnread', 'Conversation marked unread.'),
197
- skipDetailAutoMarkRead: true,
218
+ onSuccess: onMarkedUnread,
219
+ })
220
+ }, [id, onMarkedUnread, runConversationAction, suppressAutoMarkRead, t])
221
+
222
+ const markConversationRead = React.useCallback(async (messageId?: string) => {
223
+ const targetMessageId = messageId ?? id
224
+ await runConversationAction('markAllRead', {
225
+ url: `/api/messages/${encodeURIComponent(targetMessageId)}/conversation/read`,
226
+ method: 'PUT',
227
+ successMessage: t('messages.flash.conversationMarkedRead', 'Conversation marked read.'),
198
228
  })
199
229
  }, [id, runConversationAction, t])
200
230
 
@@ -362,12 +392,15 @@ export function useMessageDetailsActions({
362
392
  }, [detail?.actionData?.actions])
363
393
 
364
394
  const toggleRead = React.useCallback(async () => {
395
+ if (detail?.isRead) {
396
+ suppressAutoMarkRead()
397
+ }
365
398
  await requestAndRefresh(
366
399
  `/api/messages/${encodeURIComponent(id)}/read`,
367
400
  detail?.isRead ? 'DELETE' : 'PUT',
368
- detail?.isRead ? { skipDetailAutoMarkRead: true } : undefined,
401
+ detail?.isRead ? { skipDetailAutoMarkRead: true, onSuccess: onMarkedUnread } : undefined,
369
402
  )
370
- }, [detail?.isRead, id, requestAndRefresh])
403
+ }, [detail?.isRead, id, onMarkedUnread, requestAndRefresh, suppressAutoMarkRead])
371
404
 
372
405
  const toggleArchive = React.useCallback(async () => {
373
406
  await requestAndRefresh(
@@ -393,8 +426,10 @@ export function useMessageDetailsActions({
393
426
  toggleRead,
394
427
  toggleArchive,
395
428
  archiveConversation,
429
+ unarchiveConversation,
396
430
  deleteConversation,
397
431
  markConversationUnread,
432
+ markConversationRead,
398
433
  requestAndRefresh,
399
434
  handleDelete,
400
435
  handleDeleteDialogKeyDown,
@@ -26,10 +26,19 @@ export function useMessageDetailsQueries({
26
26
  [id, scopeVersion],
27
27
  )
28
28
 
29
+ const suppressAutoMarkReadRef = React.useRef(false)
30
+
31
+ React.useEffect(() => {
32
+ suppressAutoMarkReadRef.current = false
33
+ }, [id])
34
+
29
35
  const detailQuery = useQuery<MessageDetail | null>({
30
36
  queryKey: detailQueryKey,
31
37
  queryFn: async () => {
32
- const call = await apiCall<MessageDetail>(`/api/messages/${encodeURIComponent(id)}`)
38
+ const detailUrl = suppressAutoMarkReadRef.current
39
+ ? `/api/messages/${encodeURIComponent(id)}?skipMarkRead=1`
40
+ : `/api/messages/${encodeURIComponent(id)}`
41
+ const call = await apiCall<MessageDetail>(detailUrl)
33
42
  if (call.status === 404) {
34
43
  return null
35
44
  }
@@ -97,6 +106,10 @@ export function useMessageDetailsQueries({
97
106
  return call.result
98
107
  }, [detailQueryKey, id, queryClient, t])
99
108
 
109
+ const suppressAutoMarkRead = React.useCallback(() => {
110
+ suppressAutoMarkReadRef.current = true
111
+ }, [])
112
+
100
113
  const listItemComponentKey = detail?.typeDefinition.ui?.listItemComponent ?? null
101
114
  const contentComponentKey = detail?.typeDefinition.ui?.contentComponent ?? null
102
115
  const actionsComponentKey = detail?.typeDefinition.ui?.actionsComponent ?? null
@@ -111,6 +124,7 @@ export function useMessageDetailsQueries({
111
124
  attachmentsQuery,
112
125
  attachments,
113
126
  refreshDetailWithoutAutoMarkRead,
127
+ suppressAutoMarkRead,
114
128
  listItemComponentKey,
115
129
  contentComponentKey,
116
130
  actionsComponentKey,
@@ -4,7 +4,7 @@ import * as React from 'react'
4
4
  import { useT } from '@open-mercato/shared/lib/i18n/context'
5
5
  import { FormHeader, type ActionItem } from '@open-mercato/ui/backend/forms'
6
6
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
7
- import { Archive, Forward, MailMinus, Reply, Trash2 } from 'lucide-react'
7
+ import { Archive, ArchiveRestore, Forward, MailMinus, MailOpen, Reply, Trash2 } from 'lucide-react'
8
8
  import { PriorityBadge } from '../../utils/PriorityBadge'
9
9
 
10
10
  type MainMessageHeaderProps = {
@@ -12,12 +12,21 @@ type MainMessageHeaderProps = {
12
12
  priority: 'low' | 'normal' | 'high' | 'urgent'
13
13
  canReply: boolean
14
14
  canForwardAll: boolean
15
+ conversationArchived: boolean
16
+ conversationAllUnread: boolean
15
17
  actionsDisabled?: boolean
16
- activeActionId?: 'forwardAll' | 'archiveConversation' | 'markAllUnread' | 'deleteConversation' | null
18
+ activeActionId?:
19
+ | 'forwardAll'
20
+ | 'archiveConversation'
21
+ | 'unarchiveConversation'
22
+ | 'markAllUnread'
23
+ | 'markAllRead'
24
+ | 'deleteConversation'
25
+ | null
17
26
  onReply: () => void
18
27
  onForwardAll: () => void
19
- onArchiveConversation: () => void
20
- onMarkAllUnread: () => void
28
+ onToggleArchiveConversation: () => void
29
+ onToggleReadConversation: () => void
21
30
  onDeleteConversation: () => void
22
31
  }
23
32
 
@@ -42,19 +51,23 @@ export function MainMessageHeader(props: MainMessageHeaderProps) {
42
51
  },
43
52
  {
44
53
  id: 'archive-conversation',
45
- label: t('messages.actions.archiveConversation', 'Archive conversation'),
46
- icon: Archive,
47
- onSelect: props.onArchiveConversation,
54
+ label: props.conversationArchived
55
+ ? t('messages.actions.unarchiveConversation', 'Unarchive conversation')
56
+ : t('messages.actions.archiveConversation', 'Archive conversation'),
57
+ icon: props.conversationArchived ? ArchiveRestore : Archive,
58
+ onSelect: props.onToggleArchiveConversation,
48
59
  disabled: props.actionsDisabled,
49
- loading: props.activeActionId === 'archiveConversation',
60
+ loading: props.activeActionId === 'archiveConversation' || props.activeActionId === 'unarchiveConversation',
50
61
  },
51
62
  {
52
63
  id: 'mark-all-unread',
53
- label: t('messages.actions.markAllUnread', 'Mark all unread'),
54
- icon: MailMinus,
55
- onSelect: props.onMarkAllUnread,
64
+ label: props.conversationAllUnread
65
+ ? t('messages.actions.markAllRead', 'Mark all read')
66
+ : t('messages.actions.markAllUnread', 'Mark all unread'),
67
+ icon: props.conversationAllUnread ? MailOpen : MailMinus,
68
+ onSelect: props.onToggleReadConversation,
56
69
  disabled: props.actionsDisabled,
57
- loading: props.activeActionId === 'markAllUnread',
70
+ loading: props.activeActionId === 'markAllUnread' || props.activeActionId === 'markAllRead',
58
71
  },
59
72
  {
60
73
  id: 'delete-conversation',
@@ -68,10 +81,12 @@ export function MainMessageHeader(props: MainMessageHeaderProps) {
68
81
  props.actionsDisabled,
69
82
  props.canForwardAll,
70
83
  props.canReply,
71
- props.onArchiveConversation,
84
+ props.conversationArchived,
85
+ props.conversationAllUnread,
86
+ props.onToggleArchiveConversation,
72
87
  props.onDeleteConversation,
73
88
  props.onForwardAll,
74
- props.onMarkAllUnread,
89
+ props.onToggleReadConversation,
75
90
  props.onReply,
76
91
  props.activeActionId,
77
92
  t,
@@ -97,6 +97,8 @@ export type MessageDetail = {
97
97
  objects?: MessageDetailObject[]
98
98
  }>
99
99
  isRead: boolean
100
+ conversationArchived?: boolean
101
+ conversationAllUnread?: boolean
100
102
  }
101
103
 
102
104
  export type ActionResult = {
@@ -9,6 +9,7 @@
9
9
  "messages.actions.edit": "Bearbeiten",
10
10
  "messages.actions.executing": "Wird ausgeführt...",
11
11
  "messages.actions.forwardAll": "Alle weiterleiten",
12
+ "messages.actions.markAllRead": "Alle als gelesen markieren",
12
13
  "messages.actions.markAllUnread": "Alle als ungelesen markieren",
13
14
  "messages.actions.markRead": "Als gelesen markieren",
14
15
  "messages.actions.markUnread": "Als ungelesen markieren",
@@ -16,6 +17,7 @@
16
17
  "messages.actions.taken": "Aktion ausgeführt",
17
18
  "messages.actions.title": "Aktionen",
18
19
  "messages.actions.unarchive": "Wiederherstellen",
20
+ "messages.actions.unarchiveConversation": "Konversation wiederherstellen",
19
21
  "messages.attachFiles": "Dateien anhängen",
20
22
  "messages.attachObject": "Objekt anhängen",
21
23
  "messages.attachedFiles": "Anhänge",
@@ -146,7 +148,9 @@
146
148
  "messages.flash.actionSuccess": "Aktion abgeschlossen.",
147
149
  "messages.flash.conversationArchived": "Konversation archiviert.",
148
150
  "messages.flash.conversationDeleted": "Konversation gelöscht.",
151
+ "messages.flash.conversationMarkedRead": "Konversation als gelesen markiert.",
149
152
  "messages.flash.conversationMarkedUnread": "Konversation als ungelesen markiert.",
153
+ "messages.flash.conversationUnarchived": "Konversation wiederhergestellt.",
150
154
  "messages.flash.deleted": "Nachricht gelöscht.",
151
155
  "messages.flash.draftSaved": "Entwurf gespeichert.",
152
156
  "messages.flash.forwardSuccess": "Nachricht weitergeleitet.",
@@ -9,6 +9,7 @@
9
9
  "messages.actions.edit": "Edit",
10
10
  "messages.actions.executing": "Executing...",
11
11
  "messages.actions.forwardAll": "Forward all",
12
+ "messages.actions.markAllRead": "Mark all read",
12
13
  "messages.actions.markAllUnread": "Mark all unread",
13
14
  "messages.actions.markRead": "Mark read",
14
15
  "messages.actions.markUnread": "Mark unread",
@@ -16,6 +17,7 @@
16
17
  "messages.actions.taken": "Action taken",
17
18
  "messages.actions.title": "Actions",
18
19
  "messages.actions.unarchive": "Unarchive",
20
+ "messages.actions.unarchiveConversation": "Unarchive conversation",
19
21
  "messages.attachFiles": "Attach files",
20
22
  "messages.attachObject": "Attach object",
21
23
  "messages.attachedFiles": "Attachments",
@@ -146,7 +148,9 @@
146
148
  "messages.flash.actionSuccess": "Action completed.",
147
149
  "messages.flash.conversationArchived": "Conversation archived.",
148
150
  "messages.flash.conversationDeleted": "Conversation deleted.",
151
+ "messages.flash.conversationMarkedRead": "Conversation marked read.",
149
152
  "messages.flash.conversationMarkedUnread": "Conversation marked unread.",
153
+ "messages.flash.conversationUnarchived": "Conversation restored from archive.",
150
154
  "messages.flash.deleted": "Message deleted.",
151
155
  "messages.flash.draftSaved": "Draft saved.",
152
156
  "messages.flash.forwardSuccess": "Message forwarded.",
@@ -9,6 +9,7 @@
9
9
  "messages.actions.edit": "Editar",
10
10
  "messages.actions.executing": "Ejecutando...",
11
11
  "messages.actions.forwardAll": "Reenviar todo",
12
+ "messages.actions.markAllRead": "Marcar todo como leído",
12
13
  "messages.actions.markAllUnread": "Marcar todo como no leído",
13
14
  "messages.actions.markRead": "Marcar como leído",
14
15
  "messages.actions.markUnread": "Marcar como no leído",
@@ -16,6 +17,7 @@
16
17
  "messages.actions.taken": "Acción realizada",
17
18
  "messages.actions.title": "Acciones",
18
19
  "messages.actions.unarchive": "Desarchivar",
20
+ "messages.actions.unarchiveConversation": "Desarchivar conversación",
19
21
  "messages.attachFiles": "Adjuntar archivos",
20
22
  "messages.attachObject": "Adjuntar objeto",
21
23
  "messages.attachedFiles": "Adjuntos",
@@ -146,7 +148,9 @@
146
148
  "messages.flash.actionSuccess": "Acción completada.",
147
149
  "messages.flash.conversationArchived": "Conversación archivada.",
148
150
  "messages.flash.conversationDeleted": "Conversación eliminada.",
151
+ "messages.flash.conversationMarkedRead": "Conversación marcada como leída.",
149
152
  "messages.flash.conversationMarkedUnread": "Conversación marcada como no leída.",
153
+ "messages.flash.conversationUnarchived": "Conversación desarchivada.",
150
154
  "messages.flash.deleted": "Mensaje eliminado.",
151
155
  "messages.flash.draftSaved": "Borrador guardado.",
152
156
  "messages.flash.forwardSuccess": "Mensaje reenviado.",
@@ -9,6 +9,7 @@
9
9
  "messages.actions.edit": "Edytuj",
10
10
  "messages.actions.executing": "Wykonywanie...",
11
11
  "messages.actions.forwardAll": "Przekaż cały wątek",
12
+ "messages.actions.markAllRead": "Oznacz wszystko jako przeczytane",
12
13
  "messages.actions.markAllUnread": "Oznacz wszystko jako nieprzeczytane",
13
14
  "messages.actions.markRead": "Oznacz jako przeczytane",
14
15
  "messages.actions.markUnread": "Oznacz jako nieprzeczytane",
@@ -16,6 +17,7 @@
16
17
  "messages.actions.taken": "Wykonana akcja",
17
18
  "messages.actions.title": "Akcje",
18
19
  "messages.actions.unarchive": "Przywróć z archiwum",
20
+ "messages.actions.unarchiveConversation": "Przywróć konwersację z archiwum",
19
21
  "messages.attachFiles": "Dołącz pliki",
20
22
  "messages.attachObject": "Dołącz obiekt",
21
23
  "messages.attachedFiles": "Załączniki",
@@ -146,7 +148,9 @@
146
148
  "messages.flash.actionSuccess": "Akcja wykonana.",
147
149
  "messages.flash.conversationArchived": "Konwersacja zarchiwizowana.",
148
150
  "messages.flash.conversationDeleted": "Konwersacja usunięta.",
151
+ "messages.flash.conversationMarkedRead": "Konwersacja oznaczona jako przeczytana.",
149
152
  "messages.flash.conversationMarkedUnread": "Konwersacja oznaczona jako nieprzeczytana.",
153
+ "messages.flash.conversationUnarchived": "Konwersacja przywrócona z archiwum.",
150
154
  "messages.flash.deleted": "Wiadomość usunięta.",
151
155
  "messages.flash.draftSaved": "Szkic zapisany.",
152
156
  "messages.flash.forwardSuccess": "Wiadomość przekazana.",
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
6
  import { cancelSchema } from '../../data/validators'
6
7
  import type { PaymentGatewayService } from '../../lib/gateway-service'
7
8
  import { paymentGatewaysTag } from '../openapi'
@@ -73,6 +74,9 @@ export async function POST(req: Request) {
73
74
  })
74
75
  return NextResponse.json(result)
75
76
  } catch (err: unknown) {
77
+ if (isCrudHttpError(err)) {
78
+ return NextResponse.json(err.body, { status: err.status })
79
+ }
76
80
  const message = err instanceof Error ? err.message : 'Cancel failed'
77
81
  return NextResponse.json({ error: message }, { status: 502 })
78
82
  }
@@ -87,6 +91,7 @@ export const openApi = {
87
91
  tags: [paymentGatewaysTag],
88
92
  responses: [
89
93
  { status: 200, description: 'Payment cancelled' },
94
+ { status: 409, description: 'Invalid payment status transition' },
90
95
  { status: 422, description: 'Invalid payload' },
91
96
  { status: 502, description: 'Gateway provider error' },
92
97
  ],
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
6
  import { captureSchema } from '../../data/validators'
6
7
  import type { PaymentGatewayService } from '../../lib/gateway-service'
7
8
  import { paymentGatewaysTag } from '../openapi'
@@ -73,6 +74,9 @@ export async function POST(req: Request) {
73
74
  })
74
75
  return NextResponse.json(result)
75
76
  } catch (err: unknown) {
77
+ if (isCrudHttpError(err)) {
78
+ return NextResponse.json(err.body, { status: err.status })
79
+ }
76
80
  const message = err instanceof Error ? err.message : 'Capture failed'
77
81
  return NextResponse.json({ error: message }, { status: 502 })
78
82
  }
@@ -87,6 +91,7 @@ export const openApi = {
87
91
  tags: [paymentGatewaysTag],
88
92
  responses: [
89
93
  { status: 200, description: 'Payment captured' },
94
+ { status: 409, description: 'Invalid payment status transition' },
90
95
  { status: 422, description: 'Invalid payload' },
91
96
  { status: 502, description: 'Gateway provider error' },
92
97
  ],
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
3
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
4
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
6
  import { refundSchema } from '../../data/validators'
6
7
  import type { PaymentGatewayService } from '../../lib/gateway-service'
7
8
  import { paymentGatewaysTag } from '../openapi'
@@ -74,6 +75,9 @@ export async function POST(req: Request) {
74
75
  })
75
76
  return NextResponse.json(result)
76
77
  } catch (err: unknown) {
78
+ if (isCrudHttpError(err)) {
79
+ return NextResponse.json(err.body, { status: err.status })
80
+ }
77
81
  const message = err instanceof Error ? err.message : 'Refund failed'
78
82
  return NextResponse.json({ error: message }, { status: 502 })
79
83
  }
@@ -88,6 +92,7 @@ export const openApi = {
88
92
  tags: [paymentGatewaysTag],
89
93
  responses: [
90
94
  { status: 200, description: 'Payment refunded' },
95
+ { status: 409, description: 'Invalid payment status transition' },
91
96
  { status: 422, description: 'Invalid payload' },
92
97
  { status: 502, description: 'Gateway provider error' },
93
98
  ],