@open-mercato/core 0.4.5-develop-636d33c995 → 0.4.5-develop-3d8e759e45

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 (211) hide show
  1. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +17 -2
  2. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  3. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +15 -0
  4. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  5. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +30 -0
  6. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  7. package/dist/modules/catalog/inbox-actions.js +51 -0
  8. package/dist/modules/catalog/inbox-actions.js.map +7 -0
  9. package/dist/modules/catalog/lib/messageObjectPreviews.js +146 -0
  10. package/dist/modules/catalog/lib/messageObjectPreviews.js.map +7 -0
  11. package/dist/modules/catalog/message-objects.js +95 -0
  12. package/dist/modules/catalog/message-objects.js.map +7 -0
  13. package/dist/modules/currencies/backend/currencies/[id]/page.js +21 -0
  14. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  15. package/dist/modules/currencies/lib/messageObjectPreviews.js +51 -0
  16. package/dist/modules/currencies/lib/messageObjectPreviews.js.map +7 -0
  17. package/dist/modules/currencies/message-objects.js +41 -0
  18. package/dist/modules/currencies/message-objects.js.map +7 -0
  19. package/dist/modules/customers/backend/customers/companies/[id]/page.js +20 -0
  20. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  21. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -1
  22. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  23. package/dist/modules/customers/backend/customers/people/[id]/page.js +20 -0
  24. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  25. package/dist/modules/customers/components/detail/CompanyHighlights.js +18 -14
  26. package/dist/modules/customers/components/detail/CompanyHighlights.js.map +2 -2
  27. package/dist/modules/customers/components/detail/PersonHighlights.js +18 -14
  28. package/dist/modules/customers/components/detail/PersonHighlights.js.map +2 -2
  29. package/dist/modules/customers/inbox-actions.js +230 -0
  30. package/dist/modules/customers/inbox-actions.js.map +7 -0
  31. package/dist/modules/customers/lib/messageObjectPreviews.js +41 -5
  32. package/dist/modules/customers/lib/messageObjectPreviews.js.map +2 -2
  33. package/dist/modules/customers/message-objects.js +31 -11
  34. package/dist/modules/customers/message-objects.js.map +2 -2
  35. package/dist/modules/inbox_ops/api/emails/[id]/route.js +40 -1
  36. package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
  37. package/dist/modules/inbox_ops/api/extract/route.js +87 -0
  38. package/dist/modules/inbox_ops/api/extract/route.js.map +7 -0
  39. package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js +6 -1
  40. package/dist/modules/inbox_ops/api/proposals/[id]/translate/route.js.map +2 -2
  41. package/dist/modules/inbox_ops/api/proposals/counts/route.js.map +2 -2
  42. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +40 -14
  43. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
  44. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js +2 -2
  45. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.meta.js.map +2 -2
  46. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +161 -79
  47. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  48. package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js +2 -2
  49. package/dist/modules/inbox_ops/backend/inbox-ops/page.meta.js.map +2 -2
  50. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js +109 -62
  51. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.js.map +3 -3
  52. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js +2 -2
  53. package/dist/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.js.map +2 -2
  54. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +36 -14
  55. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  56. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js +2 -2
  57. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.meta.js.map +2 -2
  58. package/dist/modules/inbox_ops/components/proposals/ActionCard.js +65 -10
  59. package/dist/modules/inbox_ops/components/proposals/ActionCard.js.map +2 -2
  60. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +58 -10
  61. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  62. package/dist/modules/inbox_ops/lib/constants.js.map +2 -2
  63. package/dist/modules/inbox_ops/lib/contactValidation.js +40 -0
  64. package/dist/modules/inbox_ops/lib/contactValidation.js.map +7 -0
  65. package/dist/modules/inbox_ops/lib/executionEngine.js +31 -826
  66. package/dist/modules/inbox_ops/lib/executionEngine.js.map +3 -3
  67. package/dist/modules/inbox_ops/lib/executionHelpers.js +368 -0
  68. package/dist/modules/inbox_ops/lib/executionHelpers.js.map +7 -0
  69. package/dist/modules/inbox_ops/lib/extractionPrompt.js +28 -35
  70. package/dist/modules/inbox_ops/lib/extractionPrompt.js.map +3 -3
  71. package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js +1 -0
  72. package/dist/modules/inbox_ops/lib/inbox-actions-generated.d.js.map +7 -0
  73. package/dist/modules/inbox_ops/lib/translationProvider.js +15 -10
  74. package/dist/modules/inbox_ops/lib/translationProvider.js.map +2 -2
  75. package/dist/modules/inbox_ops/subscribers/extractionWorker.js +16 -16
  76. package/dist/modules/inbox_ops/subscribers/extractionWorker.js.map +2 -2
  77. package/dist/modules/messages/commands/messages.js +3 -0
  78. package/dist/modules/messages/commands/messages.js.map +2 -2
  79. package/dist/modules/messages/components/message-detail/panels/objects-panel.js +6 -1
  80. package/dist/modules/messages/components/message-detail/panels/objects-panel.js.map +2 -2
  81. package/dist/modules/messages/components/message-detail/panels/thread-panel.js +4 -1
  82. package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
  83. package/dist/modules/messages/frontend/messages/view/[token]/page.js +1 -0
  84. package/dist/modules/messages/frontend/messages/view/[token]/page.js.map +2 -2
  85. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -7
  86. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  87. package/dist/modules/resources/lib/messageObjectPreviews.js +43 -0
  88. package/dist/modules/resources/lib/messageObjectPreviews.js.map +7 -0
  89. package/dist/modules/resources/message-objects.js +37 -0
  90. package/dist/modules/resources/message-objects.js.map +7 -0
  91. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +19 -0
  92. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  93. package/dist/modules/sales/backend/sales/documents/[id]/page.js +23 -2
  94. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  95. package/dist/modules/sales/backend/sales/quotes/[id]/page.js +1 -1
  96. package/dist/modules/sales/backend/sales/quotes/[id]/page.js.map +2 -2
  97. package/dist/modules/sales/inbox-actions.js +278 -0
  98. package/dist/modules/sales/inbox-actions.js.map +7 -0
  99. package/dist/modules/sales/lib/messageObjectPreviews.js +49 -4
  100. package/dist/modules/sales/lib/messageObjectPreviews.js.map +2 -2
  101. package/dist/modules/sales/message-objects.js +44 -2
  102. package/dist/modules/sales/message-objects.js.map +2 -2
  103. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js +59 -30
  104. package/dist/modules/sales/widgets/messages/SalesDocumentMessageDetail.js.map +2 -2
  105. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js +1 -1
  106. package/dist/modules/sales/widgets/messages/SalesDocumentMessagePreview.js.map +1 -1
  107. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +8 -30
  108. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  109. package/dist/modules/staff/backend/staff/my-availability/page.js +13 -0
  110. package/dist/modules/staff/backend/staff/my-availability/page.js.map +2 -2
  111. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +8 -31
  112. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  113. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +32 -10
  114. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  115. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +14 -1
  116. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  117. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +14 -1
  118. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  119. package/dist/modules/staff/components/TeamForm.js +4 -2
  120. package/dist/modules/staff/components/TeamForm.js.map +2 -2
  121. package/dist/modules/staff/components/TeamRoleForm.js +4 -2
  122. package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
  123. package/dist/modules/staff/lib/messageObjectPreviews.js +111 -2
  124. package/dist/modules/staff/lib/messageObjectPreviews.js.map +2 -2
  125. package/dist/modules/staff/message-objects.js +79 -8
  126. package/dist/modules/staff/message-objects.js.map +2 -2
  127. package/jest.config.cjs +1 -0
  128. package/jest.mocks/inbox-actions.generated.js +5 -0
  129. package/package.json +2 -2
  130. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +19 -5
  131. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +14 -0
  132. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +40 -0
  133. package/src/modules/catalog/inbox-actions.ts +60 -0
  134. package/src/modules/catalog/lib/messageObjectPreviews.ts +176 -0
  135. package/src/modules/catalog/message-objects.ts +102 -0
  136. package/src/modules/currencies/backend/currencies/[id]/page.tsx +20 -0
  137. package/src/modules/currencies/lib/messageObjectPreviews.ts +65 -0
  138. package/src/modules/currencies/message-objects.ts +40 -0
  139. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +19 -0
  140. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +13 -0
  141. package/src/modules/customers/backend/customers/people/[id]/page.tsx +19 -0
  142. package/src/modules/customers/components/detail/CompanyHighlights.tsx +14 -9
  143. package/src/modules/customers/components/detail/PersonHighlights.tsx +14 -9
  144. package/src/modules/customers/inbox-actions.ts +285 -0
  145. package/src/modules/customers/lib/messageObjectPreviews.ts +43 -3
  146. package/src/modules/customers/message-objects.ts +31 -11
  147. package/src/modules/inbox_ops/api/emails/[id]/route.ts +44 -0
  148. package/src/modules/inbox_ops/api/extract/route.ts +94 -0
  149. package/src/modules/inbox_ops/api/proposals/[id]/translate/route.ts +6 -1
  150. package/src/modules/inbox_ops/api/proposals/counts/route.ts +2 -0
  151. package/src/modules/inbox_ops/backend/inbox-ops/log/page.meta.ts +2 -2
  152. package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +43 -13
  153. package/src/modules/inbox_ops/backend/inbox-ops/page.meta.ts +2 -2
  154. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +176 -81
  155. package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.meta.ts +2 -2
  156. package/src/modules/inbox_ops/backend/inbox-ops/proposals/[id]/page.tsx +122 -68
  157. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.meta.ts +2 -2
  158. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +36 -14
  159. package/src/modules/inbox_ops/components/proposals/ActionCard.tsx +91 -7
  160. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +64 -12
  161. package/src/modules/inbox_ops/lib/constants.ts +9 -0
  162. package/src/modules/inbox_ops/lib/contactValidation.ts +54 -0
  163. package/src/modules/inbox_ops/lib/executionEngine.ts +47 -1060
  164. package/src/modules/inbox_ops/lib/executionHelpers.ts +527 -0
  165. package/src/modules/inbox_ops/lib/extractionPrompt.ts +45 -34
  166. package/src/modules/inbox_ops/lib/inbox-actions-generated.d.ts +11 -0
  167. package/src/modules/inbox_ops/lib/translationProvider.ts +16 -10
  168. package/src/modules/inbox_ops/subscribers/extractionWorker.ts +16 -18
  169. package/src/modules/messages/commands/messages.ts +4 -0
  170. package/src/modules/messages/components/message-detail/panels/objects-panel.tsx +8 -1
  171. package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +3 -0
  172. package/src/modules/messages/frontend/messages/view/[token]/page.tsx +1 -0
  173. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +20 -4
  174. package/src/modules/resources/lib/messageObjectPreviews.ts +55 -0
  175. package/src/modules/resources/message-objects.ts +36 -0
  176. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +18 -0
  177. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +23 -0
  178. package/src/modules/sales/backend/sales/quotes/[id]/page.tsx +1 -1
  179. package/src/modules/sales/inbox-actions.ts +359 -0
  180. package/src/modules/sales/lib/messageObjectPreviews.ts +54 -4
  181. package/src/modules/sales/message-objects.ts +44 -2
  182. package/src/modules/sales/widgets/messages/SalesDocumentMessageDetail.tsx +72 -34
  183. package/src/modules/sales/widgets/messages/SalesDocumentMessagePreview.tsx +1 -1
  184. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +7 -29
  185. package/src/modules/staff/backend/staff/my-availability/page.tsx +14 -0
  186. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +8 -30
  187. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +28 -7
  188. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +12 -0
  189. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +12 -0
  190. package/src/modules/staff/components/TeamForm.tsx +3 -0
  191. package/src/modules/staff/components/TeamRoleForm.tsx +3 -0
  192. package/src/modules/staff/lib/messageObjectPreviews.ts +133 -2
  193. package/src/modules/staff/message-objects.ts +79 -8
  194. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js +0 -51
  195. package/dist/modules/customers/widgets/messages/CustomerMessageObjectDetail.js.map +0 -7
  196. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js +0 -35
  197. package/dist/modules/customers/widgets/messages/CustomerMessageObjectPreview.js.map +0 -7
  198. package/dist/modules/customers/widgets/messages/index.js +0 -7
  199. package/dist/modules/customers/widgets/messages/index.js.map +0 -7
  200. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js +0 -51
  201. package/dist/modules/staff/widgets/messages/StaffMessageObjectDetail.js.map +0 -7
  202. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js +0 -34
  203. package/dist/modules/staff/widgets/messages/StaffMessageObjectPreview.js.map +0 -7
  204. package/dist/modules/staff/widgets/messages/index.js +0 -7
  205. package/dist/modules/staff/widgets/messages/index.js.map +0 -7
  206. package/src/modules/customers/widgets/messages/CustomerMessageObjectDetail.tsx +0 -57
  207. package/src/modules/customers/widgets/messages/CustomerMessageObjectPreview.tsx +0 -49
  208. package/src/modules/customers/widgets/messages/index.ts +0 -2
  209. package/src/modules/staff/widgets/messages/StaffMessageObjectDetail.tsx +0 -57
  210. package/src/modules/staff/widgets/messages/StaffMessageObjectPreview.tsx +0 -44
  211. package/src/modules/staff/widgets/messages/index.ts +0 -2
@@ -1,4 +1,4 @@
1
- import { generateObject } from 'ai'
1
+ import { generateText } from 'ai'
2
2
  import { z } from 'zod'
3
3
  import {
4
4
  resolveOpenCodeModel,
@@ -8,7 +8,7 @@ import { createStructuredModel, resolveExtractionProviderId, withTimeout } from
8
8
 
9
9
  const LANGUAGE_NAMES: Record<string, string> = { en: 'English', de: 'German', es: 'Spanish', pl: 'Polish' }
10
10
 
11
- const translationOutputSchema = z.object({
11
+ const translationResultSchema = z.object({
12
12
  summary: z.string(),
13
13
  actions: z.record(z.string(), z.string()),
14
14
  })
@@ -35,20 +35,26 @@ export async function translateProposalContent(input: {
35
35
 
36
36
  const timeoutMs = parseInt(process.env.INBOX_OPS_TRANSLATION_TIMEOUT_MS || '30000', 10)
37
37
 
38
+ const actionIds = Object.keys(input.actionDescriptions)
39
+
38
40
  const result = await withTimeout(
39
- generateObject({
41
+ generateText({
40
42
  model,
41
- schema: translationOutputSchema,
42
- system: `You are a professional translator. Translate the provided content from ${sourceLang} to ${targetLang}. Preserve proper nouns, numbers, dates, currencies, product names, and company names exactly as they appear. Maintain the same tone and meaning.`,
43
- prompt: JSON.stringify({
44
- summary: input.summary,
45
- actions: input.actionDescriptions,
46
- }),
43
+ system: `You are a professional translator. Translate the provided content from ${sourceLang} to ${targetLang}. Preserve proper nouns, numbers, dates, currencies, product names, and company names exactly as they appear. Maintain the same tone and meaning. Respond ONLY with valid JSON, no markdown fences.`,
44
+ prompt: `Translate and return JSON with this exact shape:
45
+ {"summary": "translated summary", "actions": {"action-id-1": "translated description", ...}}
46
+
47
+ Content to translate:
48
+ ${JSON.stringify({ summary: input.summary, actions: input.actionDescriptions })}
49
+
50
+ Action IDs to preserve exactly: ${JSON.stringify(actionIds)}`,
47
51
  temperature: 0,
48
52
  }),
49
53
  timeoutMs,
50
54
  `Translation timed out after ${timeoutMs}ms`,
51
55
  )
52
56
 
53
- return result.object
57
+ const text = result.text.replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, '').trim()
58
+ const parsed = translationResultSchema.parse(JSON.parse(text))
59
+ return parsed
54
60
  }
@@ -160,7 +160,7 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
160
160
  const maxTextSize = parseInt(process.env.INBOX_OPS_MAX_TEXT_SIZE || '204800', 10)
161
161
  const truncatedText = fullText.slice(0, maxTextSize)
162
162
 
163
- const systemPrompt = buildExtractionSystemPrompt(contactMatches, catalogProducts, undefined, workingLanguage)
163
+ const systemPrompt = await buildExtractionSystemPrompt(contactMatches, catalogProducts, undefined, workingLanguage)
164
164
  const userPrompt = buildExtractionUserPrompt(truncatedText)
165
165
 
166
166
  let extractionResult: ReturnType<typeof extractionOutputSchema.parse>
@@ -269,22 +269,20 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
269
269
 
270
270
  action.payloadJson = JSON.stringify(enriched)
271
271
 
272
- // Discrepancy descriptions are stored in the DB and rendered on the proposal review page.
273
- // Not i18n keys — the proposal UI displays them as-is for operator guidance.
274
272
  for (const warning of warnings) {
275
273
  if (warning === 'no_channel_resolved') {
276
274
  enrichmentDiscrepancies.push({
277
275
  actionIndex,
278
276
  type: 'other',
279
277
  severity: 'error',
280
- description: 'No sales channel available. Create a channel in Sales settings before accepting this order.',
278
+ description: 'inbox_ops.discrepancy.desc.no_channel',
281
279
  })
282
280
  } else if (warning === 'no_currency_resolved') {
283
281
  enrichmentDiscrepancies.push({
284
282
  actionIndex,
285
283
  type: 'currency_mismatch',
286
284
  severity: 'warning',
287
- description: 'No currency could be resolved for this order. Set a currency code or configure a sales channel with a default currency.',
285
+ description: 'inbox_ops.discrepancy.desc.no_currency',
288
286
  })
289
287
  }
290
288
  }
@@ -317,7 +315,7 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
317
315
  actionIndex,
318
316
  type: 'product_not_found',
319
317
  severity: 'error',
320
- description: `Product "${productName}" could not be matched to any catalog product`,
318
+ description: 'inbox_ops.discrepancy.desc.product_not_matched',
321
319
  foundValue: productName,
322
320
  })
323
321
  const nameKey = productName.toLowerCase().trim()
@@ -328,7 +326,7 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
328
326
  const currencyCode = typeof parsedPayload.currencyCode === 'string' ? parsedPayload.currencyCode : undefined
329
327
  autoProductActions.push({
330
328
  actionType: 'create_product',
331
- description: `Create catalog product "${productName}"`,
329
+ description: 'inbox_ops.action.desc.create_product',
332
330
  confidence: 0.9,
333
331
  requiredFeature: REQUIRED_FEATURES_MAP.create_product,
334
332
  payloadJson: JSON.stringify({
@@ -412,7 +410,7 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
412
410
  proposalId: proposalId,
413
411
  sortOrder: combinedProposedActions.length + index,
414
412
  actionType: 'draft_reply',
415
- description: `Draft reply to ${reply.toName || reply.to}: ${reply.subject}`,
413
+ description: 'inbox_ops.action.desc.draft_reply',
416
414
  payload: {
417
415
  to: reply.to,
418
416
  toName: reply.toName,
@@ -468,8 +466,8 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
468
466
  createDiscrepancy(em, proposalId, allActions, {
469
467
  type: 'unknown_contact',
470
468
  severity: 'warning',
471
- description: `No matching contact found for ${match.participant.name} (${match.participant.email})`,
472
- foundValue: match.participant.email,
469
+ description: 'inbox_ops.discrepancy.desc.no_matching_contact',
470
+ foundValue: `${match.participant.name} (${match.participant.email})`,
473
471
  }, scope),
474
472
  )
475
473
  }
@@ -483,8 +481,8 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
483
481
  createDiscrepancy(em, proposalId, allActions, {
484
482
  type: 'unknown_contact',
485
483
  severity: 'warning',
486
- description: `No matching contact found for ${participant.name} (${participant.email})`,
487
- foundValue: participant.email,
484
+ description: 'inbox_ops.discrepancy.desc.no_matching_contact',
485
+ foundValue: `${participant.name} (${participant.email})`,
488
486
  }, scope),
489
487
  )
490
488
  }
@@ -505,7 +503,7 @@ export default async function handle(payload: EmailReceivedPayload, ctx: Resolve
505
503
  actionIndex,
506
504
  type: 'unknown_contact',
507
505
  severity: 'error',
508
- description: `Draft reply target "${toEmail}" has no matching contact. Create the contact first.`,
506
+ description: 'inbox_ops.discrepancy.desc.draft_reply_no_contact',
509
507
  foundValue: toEmail,
510
508
  }, scope),
511
509
  )
@@ -606,7 +604,7 @@ function buildContactActionsForUnmatchedParticipants(
606
604
  })
607
605
  .map((m) => ({
608
606
  actionType: 'create_contact' as const,
609
- description: `Create contact for ${m.participant.name} (${m.participant.email})`,
607
+ description: 'inbox_ops.action.desc.create_contact',
610
608
  confidence: 0.9,
611
609
  requiredFeature: REQUIRED_FEATURES_MAP.create_contact,
612
610
  payloadJson: JSON.stringify({
@@ -647,7 +645,7 @@ function buildLinkContactActionsForMatchedParticipants(
647
645
  })
648
646
  .map((m) => ({
649
647
  actionType: 'link_contact' as const,
650
- description: `Link ${m.participant.name} (${m.participant.email}) to existing contact`,
648
+ description: 'inbox_ops.action.desc.link_contact',
651
649
  confidence: 0.95,
652
650
  requiredFeature: REQUIRED_FEATURES_MAP.link_contact,
653
651
  payloadJson: JSON.stringify({
@@ -701,7 +699,7 @@ function buildContactActionsForUnmatchedLlmParticipants(
701
699
  })
702
700
  .map((p) => ({
703
701
  actionType: 'create_contact' as const,
704
- description: `Create contact for ${p.name} (${p.email})`,
702
+ description: 'inbox_ops.action.desc.create_contact',
705
703
  confidence: 0.85,
706
704
  requiredFeature: REQUIRED_FEATURES_MAP.create_contact,
707
705
  payloadJson: JSON.stringify({
@@ -749,8 +747,8 @@ async function detectDuplicateOrders(
749
747
  discrepancies.push({
750
748
  type: 'duplicate_order',
751
749
  severity: 'error',
752
- description: `An order with customer reference "${customerReference}" already exists (${existing.orderNumber || existing.id})`,
753
- expectedValue: null,
750
+ description: 'inbox_ops.discrepancy.desc.duplicate_order_reference',
751
+ expectedValue: existing.orderNumber || existing.id,
754
752
  foundValue: customerReference,
755
753
  actionIndex: action.index,
756
754
  })
@@ -477,6 +477,10 @@ const replyMessageCommand: CommandHandler<unknown, { id: string; externalEmail:
477
477
  }
478
478
  }
479
479
  }
480
+
481
+ if (recipientIds.size === 0 && original.senderUserId === input.userId) {
482
+ recipientIds.add(input.userId)
483
+ }
480
484
  }
481
485
  if (recipientIds.size === 0) throw new Error('No recipients available for reply')
482
486
 
@@ -5,6 +5,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
5
5
  import {
6
6
  getMessageUiComponentRegistry,
7
7
  } from '../../utils/typeUiRegistry'
8
+ import { getMessageObjectType } from '../../../lib/message-objects-registry'
8
9
  import type { MessageAction, MessageDetail } from '../types'
9
10
  import { toObjectAction } from '../utils'
10
11
 
@@ -28,11 +29,16 @@ export function MessageDetailObjectsSection(props: ObjectsPanelProps) {
28
29
  const componentKey = `${item.entityModule}:${item.entityType}`
29
30
  const DetailComponent = messageUiRegistry.objectDetailComponents[componentKey] ?? null
30
31
  const objectActions = props.objectActionsByObjectId.get(item.id)
32
+ const objectType = getMessageObjectType(item.entityModule, item.entityType)
31
33
 
32
34
  if (DetailComponent) {
33
- const actions: MessageObjectAction[] = objectActions
35
+ const storedActions: MessageObjectAction[] = objectActions
34
36
  ? Array.from(objectActions.entries()).map(([actionId, action]) => toObjectAction(actionId, action))
35
37
  : []
38
+ const typeDefinitionActions: MessageObjectAction[] = storedActions.length === 0
39
+ ? (objectType?.actions ?? [])
40
+ : []
41
+ const actions = storedActions.length > 0 ? storedActions : typeDefinitionActions
36
42
 
37
43
  return (
38
44
  <DetailComponent
@@ -49,6 +55,7 @@ export function MessageDetailObjectsSection(props: ObjectsPanelProps) {
49
55
  actionTakenAt={props.detail.actionTakenAt ? new Date(props.detail.actionTakenAt) : null}
50
56
  actionTakenByUserId={props.detail.actionTakenByUserId ?? null}
51
57
  actions={actions}
58
+ icon={objectType?.icon}
52
59
  onAction={async (actionId, payload) => {
53
60
  const action = objectActions?.get(actionId)
54
61
  if (!action) return
@@ -3,6 +3,7 @@
3
3
  import { useT } from '@open-mercato/shared/lib/i18n/context'
4
4
  import { MarkdownContent } from '@open-mercato/ui/backend/markdown/MarkdownContent'
5
5
  import { getMessageUiComponentRegistry } from '../../utils/typeUiRegistry'
6
+ import { getMessageObjectType } from '../../../lib/message-objects-registry'
6
7
  import type { MessageDetail, MessageDetailObject } from '../types'
7
8
  import { formatDateTime } from '../utils'
8
9
 
@@ -38,6 +39,7 @@ export function MessageDetailThreadSection({ detail }: { detail: MessageDetail }
38
39
  const componentKey = `${obj.entityModule}:${obj.entityType}`
39
40
  const PreviewComponent = messageUiRegistry.objectPreviewComponents[componentKey]
40
41
  ?? messageUiRegistry.objectPreviewComponents['messages:default']
42
+ const objectType = getMessageObjectType(obj.entityModule, obj.entityType)
41
43
  if (!PreviewComponent) return null
42
44
 
43
45
  return (
@@ -51,6 +53,7 @@ export function MessageDetailThreadSection({ detail }: { detail: MessageDetail }
51
53
  actionRequired={obj.actionRequired}
52
54
  actionType={obj.actionType ?? undefined}
53
55
  actionLabel={obj.actionLabel ?? undefined}
56
+ icon={objectType?.icon}
54
57
  />
55
58
  </div>
56
59
  )
@@ -375,6 +375,7 @@ export default function MessageTokenPage({ params }: { params: { token: string }
375
375
  actionTaken={data.actionTaken ?? null}
376
376
  actionTakenAt={data.actionTakenAt ? new Date(data.actionTakenAt) : null}
377
377
  actionTakenByUserId={data.actionTakenByUserId ?? null}
378
+ icon={objectType?.icon}
378
379
  onAction={async () => {
379
380
  return
380
381
  }}
@@ -12,6 +12,7 @@ import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
12
12
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
13
13
  import { ActivitiesSection, NotesSection, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'
14
14
  import { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'
15
+ import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
15
16
  import { useT } from '@open-mercato/shared/lib/i18n/context'
16
17
  import { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'
17
18
  import { buildResourceScheduleItems } from '@open-mercato/core/modules/resources/lib/resourceSchedule'
@@ -490,10 +491,25 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
490
491
  backHref="/backend/resources/resources"
491
492
  backLabel={t('resources.resources.detail.back', 'Back to resources')}
492
493
  utilityActions={(
493
- <VersionHistoryAction
494
- config={resourceId ? { resourceKind: 'resources.resource', resourceId, includeRelated: true } : null}
495
- t={t}
496
- />
494
+ <>
495
+ {resourceId ? (
496
+ <SendObjectMessageDialog
497
+ object={{
498
+ entityModule: 'resources',
499
+ entityType: 'resource',
500
+ entityId: resourceId,
501
+ previewData: {
502
+ title: resourceTitle,
503
+ },
504
+ }}
505
+ viewHref={`/backend/resources/resources/${resourceId}`}
506
+ />
507
+ ) : null}
508
+ <VersionHistoryAction
509
+ config={resourceId ? { resourceKind: 'resources.resource', resourceId, includeRelated: true } : null}
510
+ t={t}
511
+ />
512
+ </>
497
513
  )}
498
514
  title={resourceTitle}
499
515
  subtitle={t('resources.resources.detail.subtitle', 'Resource profile and activity')}
@@ -0,0 +1,55 @@
1
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
2
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
4
+ import type { ObjectPreviewData } from '@open-mercato/shared/modules/messages/types'
5
+ import type { EntityManager } from '@mikro-orm/postgresql'
6
+ import { ResourcesResource } from '../data/entities'
7
+
8
+ type PreviewContext = {
9
+ tenantId: string
10
+ organizationId?: string | null
11
+ }
12
+
13
+ async function resolveEm() {
14
+ const { resolve } = await createRequestContainer()
15
+ return resolve('em') as EntityManager
16
+ }
17
+
18
+ export async function loadResourcePreview(
19
+ entityId: string,
20
+ ctx: PreviewContext,
21
+ ): Promise<ObjectPreviewData> {
22
+ const { t } = await resolveTranslations()
23
+ const defaultTitle = t('resources.messageObjects.resource.title')
24
+
25
+ if (!ctx.organizationId) {
26
+ return { title: defaultTitle, subtitle: entityId }
27
+ }
28
+
29
+ const em = await resolveEm()
30
+ const entity = await findOneWithDecryption(
31
+ em,
32
+ ResourcesResource,
33
+ {
34
+ id: entityId,
35
+ tenantId: ctx.tenantId,
36
+ organizationId: ctx.organizationId,
37
+ deletedAt: null,
38
+ },
39
+ undefined,
40
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
41
+ )
42
+
43
+ if (!entity) {
44
+ return {
45
+ title: defaultTitle,
46
+ subtitle: entityId,
47
+ status: t('customers.messageObjects.notFound'),
48
+ statusColor: 'gray',
49
+ }
50
+ }
51
+
52
+ return {
53
+ title: entity.name,
54
+ }
55
+ }
@@ -0,0 +1,36 @@
1
+ import type { MessageObjectTypeDefinition } from '@open-mercato/shared/modules/messages/types'
2
+ import { MessageObjectDetail, MessageObjectPreview } from '@open-mercato/ui/backend/messages'
3
+
4
+ const objectMessageTypes = ['default', 'messages.defaultWithObjects']
5
+
6
+ export const messageObjectTypes: MessageObjectTypeDefinition[] = [
7
+ {
8
+ module: 'resources',
9
+ entityType: 'resource',
10
+ messageTypes: objectMessageTypes,
11
+ entityId: 'resources:resources_resource',
12
+ optionLabelField: 'name',
13
+ optionSubtitleField: 'description',
14
+ labelKey: 'resources.messageObjects.resource.title',
15
+ icon: 'package',
16
+ PreviewComponent: MessageObjectPreview,
17
+ DetailComponent: MessageObjectDetail,
18
+ actions: [
19
+ {
20
+ id: 'view',
21
+ labelKey: 'common.view',
22
+ variant: 'outline',
23
+ href: '/backend/resources/resources/{entityId}',
24
+ },
25
+ ],
26
+ loadPreview: async (entityId, ctx) => {
27
+ if (typeof window !== 'undefined') {
28
+ return { title: 'Resource', subtitle: entityId }
29
+ }
30
+ const { loadResourcePreview } = await import('./lib/messageObjectPreviews')
31
+ return loadResourcePreview(entityId, ctx)
32
+ },
33
+ },
34
+ ]
35
+
36
+ export default messageObjectTypes
@@ -12,6 +12,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
12
12
  import { useChannelFields, buildChannelPayload, type ChannelFormValues } from '@open-mercato/core/modules/sales/components/channels/channelFormFields'
13
13
  import { E } from '#generated/entities.ids.generated'
14
14
  import { SalesChannelOffersPanel } from '@open-mercato/core/modules/sales/components/channels/SalesChannelOffersPanel'
15
+ import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
15
16
 
16
17
  type ChannelApiResponse = {
17
18
  items?: Array<Record<string, unknown>>
@@ -126,6 +127,23 @@ export default function EditChannelPage({ params }: { params?: { channelId?: str
126
127
  <CrudForm<ChannelFormValues>
127
128
  title={t('sales.channels.form.editTitle', 'Edit channel')}
128
129
  versionHistory={{ resourceKind: 'sales.channel', resourceId: channelId ? String(channelId) : '' }}
130
+ extraActions={channelId ? (
131
+ <SendObjectMessageDialog
132
+ object={{
133
+ entityModule: 'sales',
134
+ entityType: 'channel',
135
+ entityId: channelId,
136
+ previewData: {
137
+ title: initialValues?.name ?? '',
138
+ metadata: {
139
+ [t('sales.channels.form.contactEmail')]: initialValues?.contactEmail ?? '-',
140
+ [t('sales.channels.form.websiteUrl')]: initialValues?.websiteUrl ?? '-',
141
+ },
142
+ },
143
+ }}
144
+ viewHref={`/backend/sales/channels/${channelId}/edit`}
145
+ />
146
+ ) : undefined}
129
147
  entityId={E.sales.sales_channel}
130
148
  fields={fields}
131
149
  groups={[
@@ -63,6 +63,16 @@ import { readMarkdownPreferenceCookie, writeMarkdownPreferenceCookie } from '@op
63
63
  import { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'
64
64
  import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
65
65
 
66
+ function formatMessageAmount(amount: number | null | undefined, currency: string | null | undefined): string | null {
67
+ if (typeof amount !== 'number' || !Number.isFinite(amount)) return null
68
+ if (!currency) return amount.toLocaleString()
69
+ try {
70
+ return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(amount)
71
+ } catch {
72
+ return `${amount.toLocaleString()} ${currency}`
73
+ }
74
+ }
75
+
66
76
  function CurrencyInlineEditor({
67
77
  label,
68
78
  value,
@@ -1844,9 +1854,11 @@ function StatusInlineEditor({
1844
1854
  export default function SalesDocumentDetailPage({
1845
1855
  params,
1846
1856
  initialKind,
1857
+ includeAmountInMessageMetadata,
1847
1858
  }: {
1848
1859
  params: { id: string }
1849
1860
  initialKind?: 'order' | 'quote'
1861
+ includeAmountInMessageMetadata?: boolean
1850
1862
  }) {
1851
1863
  const t = useT()
1852
1864
  const router = useRouter()
@@ -2729,6 +2741,11 @@ export default function SalesDocumentDetailPage({
2729
2741
  : null
2730
2742
  const contactEmail = resolveCustomerEmail(customerSnapshot) ?? metadataEmail ?? record?.contactEmail ?? null
2731
2743
  const statusDisplay = record?.status ? statusDictionaryMap[record.status] ?? null : null
2744
+ const previewAmount = formatMessageAmount(record?.grandTotalGrossAmount ?? null, record?.currencyCode ?? null)
2745
+ const messagePreviewMetadata: Record<string, string> = {}
2746
+ if (includeAmountInMessageMetadata && previewAmount) {
2747
+ messagePreviewMetadata[t('sales.documents.detail.totals.grandTotalGross')] = previewAmount
2748
+ }
2732
2749
  const contactRecordId = customerSnapshot?.contact?.id ?? customerSnapshot?.customer?.id ?? record?.customerEntityId ?? null
2733
2750
  const resolveAdjustmentLabel = React.useCallback(
2734
2751
  (row: AdjustmentRowData) => {
@@ -4434,7 +4451,13 @@ export default function SalesDocumentDetailPage({
4434
4451
  entityId: record.id,
4435
4452
  sourceEntityType: kind === 'order' ? 'sales.order' : 'sales.quote',
4436
4453
  sourceEntityId: record.id,
4454
+ previewData: {
4455
+ title: number,
4456
+ status: statusDisplay?.label ?? record?.status ?? undefined,
4457
+ metadata: Object.keys(messagePreviewMetadata).length > 0 ? messagePreviewMetadata : undefined,
4458
+ },
4437
4459
  }}
4460
+ viewHref={`/backend/sales/${kind === 'order' ? 'orders' : 'quotes'}/${record.id}`}
4438
4461
  defaultValues={{
4439
4462
  sourceEntityType: kind === 'order' ? 'sales.order' : 'sales.quote',
4440
4463
  sourceEntityId: record.id,
@@ -3,5 +3,5 @@
3
3
  import SalesDocumentDetailPage from '../../documents/[id]/page'
4
4
 
5
5
  export default function SalesQuoteDetailPage(props: { params: { id: string } }) {
6
- return <SalesDocumentDetailPage {...props} initialKind="quote" />
6
+ return <SalesDocumentDetailPage {...props} initialKind="quote" includeAmountInMessageMetadata />
7
7
  }