@open-mercato/core 0.6.7-develop.6726.1.983ae8a07e → 0.6.7-develop.6749.1.6b54c56dfe

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 (191) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/resources_resource/index.js +2 -0
  3. package/dist/generated/entities/resources_resource/index.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +1 -0
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/helpers/integration/salesFixtures.js +31 -2
  7. package/dist/helpers/integration/salesFixtures.js.map +2 -2
  8. package/dist/helpers/integration/salesUi.js +5 -0
  9. package/dist/helpers/integration/salesUi.js.map +2 -2
  10. package/dist/modules/auth/api/profile/route.js +8 -2
  11. package/dist/modules/auth/api/profile/route.js.map +2 -2
  12. package/dist/modules/auth/backend/auth/profile/page.js.map +2 -2
  13. package/dist/modules/auth/backend/profile/change-password/page.js.map +2 -2
  14. package/dist/modules/communication_channels/data/enrichers.js +4 -4
  15. package/dist/modules/communication_channels/data/enrichers.js.map +2 -2
  16. package/dist/modules/customers/analytics.js +1 -0
  17. package/dist/modules/customers/analytics.js.map +2 -2
  18. package/dist/modules/customers/components/detail/DealForm.js +2 -1
  19. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  20. package/dist/modules/customers/components/detail/DealsSection.js +1 -1
  21. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  22. package/dist/modules/customers/data/validators.js +3 -1
  23. package/dist/modules/customers/data/validators.js.map +2 -2
  24. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js +52 -2
  25. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js.map +2 -2
  26. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +42 -26
  27. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +2 -2
  28. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js +2 -2
  29. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js.map +2 -2
  30. package/dist/modules/data_sync/lib/queue-policy.js +15 -0
  31. package/dist/modules/data_sync/lib/queue-policy.js.map +7 -0
  32. package/dist/modules/data_sync/lib/queue.js +16 -1
  33. package/dist/modules/data_sync/lib/queue.js.map +2 -2
  34. package/dist/modules/data_sync/lib/start-run.js +2 -1
  35. package/dist/modules/data_sync/lib/start-run.js.map +2 -2
  36. package/dist/modules/data_sync/lib/sync-engine.js +35 -2
  37. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  38. package/dist/modules/data_sync/lib/sync-run-service.js +70 -2
  39. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  40. package/dist/modules/data_sync/workers/sync-export.js +9 -2
  41. package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
  42. package/dist/modules/data_sync/workers/sync-import.js +9 -2
  43. package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
  44. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +34 -12
  45. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  46. package/dist/modules/dictionaries/api/openapi.js +15 -2
  47. package/dist/modules/dictionaries/api/openapi.js.map +2 -2
  48. package/dist/modules/dictionaries/components/hooks/useDictionaryEntries.js +30 -29
  49. package/dist/modules/dictionaries/components/hooks/useDictionaryEntries.js.map +2 -2
  50. package/dist/modules/dictionaries/data/validators.js +7 -0
  51. package/dist/modules/dictionaries/data/validators.js.map +2 -2
  52. package/dist/modules/dictionaries/lib/clientEntries.js +4 -3
  53. package/dist/modules/dictionaries/lib/clientEntries.js.map +2 -2
  54. package/dist/modules/dictionaries/lib/fetchAllEntries.js +43 -0
  55. package/dist/modules/dictionaries/lib/fetchAllEntries.js.map +7 -0
  56. package/dist/modules/entities/api/definitions.js +51 -3
  57. package/dist/modules/entities/api/definitions.js.map +2 -2
  58. package/dist/modules/entities/api/entities.js +21 -2
  59. package/dist/modules/entities/api/entities.js.map +2 -2
  60. package/dist/modules/entities/api/records.js +3 -24
  61. package/dist/modules/entities/api/records.js.map +2 -2
  62. package/dist/modules/entities/api/sidebar-entities.js +34 -12
  63. package/dist/modules/entities/api/sidebar-entities.js.map +2 -2
  64. package/dist/modules/entities/lib/entityAcl.js +46 -0
  65. package/dist/modules/entities/lib/entityAcl.js.map +2 -2
  66. package/dist/modules/messages/api/route.js +2 -5
  67. package/dist/modules/messages/api/route.js.map +2 -2
  68. package/dist/modules/messages/lib/participantScope.js +15 -0
  69. package/dist/modules/messages/lib/participantScope.js.map +7 -0
  70. package/dist/modules/notifications/api/route.js +3 -1
  71. package/dist/modules/notifications/api/route.js.map +2 -2
  72. package/dist/modules/notifications/api/unread-count/route.js +25 -6
  73. package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
  74. package/dist/modules/notifications/lib/notificationScope.js +26 -0
  75. package/dist/modules/notifications/lib/notificationScope.js.map +7 -0
  76. package/dist/modules/notifications/lib/notificationService.js +7 -3
  77. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  78. package/dist/modules/notifications/lib/routeHelpers.js +16 -2
  79. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  80. package/dist/modules/planner/components/unavailabilityReasons.js +3 -3
  81. package/dist/modules/planner/components/unavailabilityReasons.js.map +2 -2
  82. package/dist/modules/progress/lib/progressService.js +2 -0
  83. package/dist/modules/progress/lib/progressService.js.map +2 -2
  84. package/dist/modules/progress/lib/progressServiceImpl.js +322 -104
  85. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  86. package/dist/modules/resources/api/resources.js +2 -0
  87. package/dist/modules/resources/api/resources.js.map +2 -2
  88. package/dist/modules/resources/backend/resources/resources/[id]/page.js +3 -2
  89. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  90. package/dist/modules/resources/commands/resources.js +9 -0
  91. package/dist/modules/resources/commands/resources.js.map +2 -2
  92. package/dist/modules/resources/components/detail/dictionaries.js +3 -3
  93. package/dist/modules/resources/components/detail/dictionaries.js.map +2 -2
  94. package/dist/modules/resources/data/entities.js +3 -0
  95. package/dist/modules/resources/data/entities.js.map +2 -2
  96. package/dist/modules/resources/data/validators.js +6 -2
  97. package/dist/modules/resources/data/validators.js.map +2 -2
  98. package/dist/modules/resources/lib/seeds.js +16 -9
  99. package/dist/modules/resources/lib/seeds.js.map +2 -2
  100. package/dist/modules/resources/migrations/Migration20260608231000.js +13 -0
  101. package/dist/modules/resources/migrations/Migration20260608231000.js.map +7 -0
  102. package/dist/modules/sales/backend/sales/documents/create/page.js +11 -14
  103. package/dist/modules/sales/backend/sales/documents/create/page.js.map +2 -2
  104. package/dist/modules/sales/commands/documents.js +8 -0
  105. package/dist/modules/sales/commands/documents.js.map +2 -2
  106. package/dist/modules/sales/components/documents/ItemsSection.js +18 -9
  107. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  108. package/dist/modules/sales/components/documents/LineItemDialog.js +12 -5
  109. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  110. package/dist/modules/sales/components/documents/SalesDocumentForm.js +48 -2
  111. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  112. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +167 -0
  113. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +7 -0
  114. package/dist/modules/sales/data/validators.js +5 -1
  115. package/dist/modules/sales/data/validators.js.map +2 -2
  116. package/dist/modules/staff/components/detail/dictionaries.js +3 -3
  117. package/dist/modules/staff/components/detail/dictionaries.js.map +2 -2
  118. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js +11 -4
  119. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js.map +2 -2
  120. package/generated/entities/resources_resource/index.ts +1 -0
  121. package/generated/entity-fields-registry.ts +1 -0
  122. package/package.json +7 -7
  123. package/src/helpers/integration/salesFixtures.ts +34 -2
  124. package/src/helpers/integration/salesUi.ts +8 -0
  125. package/src/modules/auth/api/profile/route.ts +8 -2
  126. package/src/modules/auth/backend/auth/profile/page.tsx +1 -0
  127. package/src/modules/auth/backend/profile/change-password/page.tsx +1 -0
  128. package/src/modules/communication_channels/data/enrichers.ts +6 -26
  129. package/src/modules/customers/analytics.ts +1 -0
  130. package/src/modules/customers/components/detail/DealForm.tsx +5 -1
  131. package/src/modules/customers/components/detail/DealsSection.tsx +1 -1
  132. package/src/modules/customers/data/validators.ts +5 -1
  133. package/src/modules/dashboards/i18n/de.json +3 -0
  134. package/src/modules/dashboards/i18n/en.json +3 -0
  135. package/src/modules/dashboards/i18n/es.json +3 -0
  136. package/src/modules/dashboards/i18n/pl.json +3 -0
  137. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts +86 -0
  138. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +33 -18
  139. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.ts +2 -2
  140. package/src/modules/data_sync/lib/adapter.ts +18 -0
  141. package/src/modules/data_sync/lib/queue-policy.ts +30 -0
  142. package/src/modules/data_sync/lib/queue.ts +19 -1
  143. package/src/modules/data_sync/lib/start-run.ts +2 -1
  144. package/src/modules/data_sync/lib/sync-engine.ts +46 -0
  145. package/src/modules/data_sync/lib/sync-run-service.ts +82 -1
  146. package/src/modules/data_sync/workers/sync-export.ts +8 -1
  147. package/src/modules/data_sync/workers/sync-import.ts +8 -1
  148. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +39 -11
  149. package/src/modules/dictionaries/api/openapi.ts +19 -0
  150. package/src/modules/dictionaries/components/hooks/useDictionaryEntries.ts +35 -36
  151. package/src/modules/dictionaries/data/validators.ts +14 -0
  152. package/src/modules/dictionaries/lib/clientEntries.ts +4 -3
  153. package/src/modules/dictionaries/lib/fetchAllEntries.ts +72 -0
  154. package/src/modules/entities/api/definitions.ts +51 -3
  155. package/src/modules/entities/api/entities.ts +30 -2
  156. package/src/modules/entities/api/records.ts +3 -27
  157. package/src/modules/entities/api/sidebar-entities.ts +43 -14
  158. package/src/modules/entities/lib/entityAcl.ts +58 -0
  159. package/src/modules/messages/api/route.ts +4 -5
  160. package/src/modules/messages/lib/participantScope.ts +64 -0
  161. package/src/modules/notifications/api/route.ts +2 -0
  162. package/src/modules/notifications/api/unread-count/route.ts +42 -5
  163. package/src/modules/notifications/lib/notificationScope.ts +40 -0
  164. package/src/modules/notifications/lib/notificationService.ts +5 -0
  165. package/src/modules/notifications/lib/routeHelpers.ts +19 -2
  166. package/src/modules/planner/components/unavailabilityReasons.ts +3 -3
  167. package/src/modules/progress/AGENTS.md +11 -1
  168. package/src/modules/progress/lib/progressService.ts +1 -0
  169. package/src/modules/progress/lib/progressServiceImpl.ts +385 -117
  170. package/src/modules/resources/api/resources.ts +2 -0
  171. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +9 -3
  172. package/src/modules/resources/commands/resources.ts +10 -0
  173. package/src/modules/resources/components/detail/dictionaries.ts +3 -3
  174. package/src/modules/resources/data/entities.ts +3 -0
  175. package/src/modules/resources/data/validators.ts +5 -0
  176. package/src/modules/resources/lib/seeds.ts +16 -9
  177. package/src/modules/resources/migrations/.snapshot-open-mercato.json +17 -1
  178. package/src/modules/resources/migrations/Migration20260608231000.ts +13 -0
  179. package/src/modules/sales/backend/sales/documents/create/page.tsx +2 -4
  180. package/src/modules/sales/commands/documents.ts +8 -0
  181. package/src/modules/sales/components/documents/ItemsSection.tsx +19 -10
  182. package/src/modules/sales/components/documents/LineItemDialog.tsx +13 -5
  183. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +48 -6
  184. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +198 -0
  185. package/src/modules/sales/data/validators.ts +7 -1
  186. package/src/modules/sales/i18n/de.json +2 -0
  187. package/src/modules/sales/i18n/en.json +2 -0
  188. package/src/modules/sales/i18n/es.json +2 -0
  189. package/src/modules/sales/i18n/pl.json +2 -0
  190. package/src/modules/staff/components/detail/dictionaries.ts +3 -3
  191. package/src/modules/workflows/widgets/injection/order-approval/widget.client.tsx +12 -5
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/staff/components/detail/dictionaries.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\n\nexport type DictionaryEntryOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\ntype DictionarySummary = {\n id: string\n key: string\n name: string\n}\n\nconst STAFF_DICTIONARY_KEYS = {\n activityTypes: 'staff-activity-types',\n addressTypes: 'staff-address-types',\n} as const\n\nasync function ensureDictionary(key: string, name: string): Promise<DictionarySummary | null> {\n const listCall = await apiCall<{ items?: DictionarySummary[] }>('/api/dictionaries')\n const items = Array.isArray(listCall.result?.items) ? listCall.result?.items ?? [] : []\n const existing = items.find((item) => item && item.key === key)\n if (existing) return existing\n if (!listCall.ok) return null\n const created = await apiCallOrThrow<DictionarySummary>(\n '/api/dictionaries',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ key, name }),\n },\n )\n const result = created.result as DictionarySummary | undefined\n if (result && result.id && result.key) return result\n return null\n}\n\nexport async function loadStaffDictionaryEntries(kind: keyof typeof STAFF_DICTIONARY_KEYS): Promise<DictionaryEntryOption[]> {\n const { entries } = await loadStaffDictionary(kind)\n return entries\n}\n\nexport async function loadStaffDictionary(\n kind: keyof typeof STAFF_DICTIONARY_KEYS,\n): Promise<{ dictionary: DictionarySummary | null; entries: DictionaryEntryOption[] }> {\n const key = STAFF_DICTIONARY_KEYS[kind]\n const name = kind === 'activityTypes' ? 'Staff activity types' : 'Staff address types'\n const dictionary = await ensureDictionary(key, name)\n if (!dictionary) return { dictionary: null, entries: [] }\n const entriesCall = await apiCall<{ items?: Record<string, unknown>[] }>(`/api/dictionaries/${dictionary.id}/entries`)\n if (!entriesCall.ok) return { dictionary, entries: [] }\n const items = Array.isArray(entriesCall.result?.items) ? entriesCall.result?.items ?? [] : []\n const entries = items\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const value = typeof record.value === 'string' ? record.value : null\n if (!value) return null\n const label = typeof record.label === 'string' && record.label.trim().length ? record.label : value\n const color = typeof record.color === 'string' ? record.color : null\n const icon = typeof record.icon === 'string' ? record.icon : null\n return { value, label, color, icon }\n })\n .filter((entry): entry is DictionaryEntryOption => !!entry)\n return { dictionary, entries }\n}\n\nexport async function createStaffDictionaryEntry(\n kind: keyof typeof STAFF_DICTIONARY_KEYS,\n input: { value: string; label?: string; color?: string | null; icon?: string | null },\n): Promise<DictionaryEntryOption | null> {\n const key = STAFF_DICTIONARY_KEYS[kind]\n const name = kind === 'activityTypes' ? 'Staff activity types' : 'Staff address types'\n const dictionary = await ensureDictionary(key, name)\n if (!dictionary) return null\n const response = await apiCallOrThrow<Record<string, unknown>>(\n `/api/dictionaries/${dictionary.id}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n value: input.value,\n label: input.label ?? undefined,\n color: input.color ?? undefined,\n icon: input.icon ?? undefined,\n }),\n },\n )\n const payload = response.result ?? {}\n const value = typeof payload.value === 'string' && payload.value.trim().length ? payload.value : input.value\n const label = typeof payload.label === 'string' && payload.label.trim().length ? payload.label : value\n const color = typeof payload.color === 'string' ? payload.color : input.color ?? null\n const icon = typeof payload.icon === 'string' ? payload.icon : input.icon ?? null\n return { value, label, color, icon }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { fetchAllDictionaryEntries } from '@open-mercato/core/modules/dictionaries/lib/fetchAllEntries'\n\nexport type DictionaryEntryOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\ntype DictionarySummary = {\n id: string\n key: string\n name: string\n}\n\nconst STAFF_DICTIONARY_KEYS = {\n activityTypes: 'staff-activity-types',\n addressTypes: 'staff-address-types',\n} as const\n\nasync function ensureDictionary(key: string, name: string): Promise<DictionarySummary | null> {\n const listCall = await apiCall<{ items?: DictionarySummary[] }>('/api/dictionaries')\n const items = Array.isArray(listCall.result?.items) ? listCall.result?.items ?? [] : []\n const existing = items.find((item) => item && item.key === key)\n if (existing) return existing\n if (!listCall.ok) return null\n const created = await apiCallOrThrow<DictionarySummary>(\n '/api/dictionaries',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ key, name }),\n },\n )\n const result = created.result as DictionarySummary | undefined\n if (result && result.id && result.key) return result\n return null\n}\n\nexport async function loadStaffDictionaryEntries(kind: keyof typeof STAFF_DICTIONARY_KEYS): Promise<DictionaryEntryOption[]> {\n const { entries } = await loadStaffDictionary(kind)\n return entries\n}\n\nexport async function loadStaffDictionary(\n kind: keyof typeof STAFF_DICTIONARY_KEYS,\n): Promise<{ dictionary: DictionarySummary | null; entries: DictionaryEntryOption[] }> {\n const key = STAFF_DICTIONARY_KEYS[kind]\n const name = kind === 'activityTypes' ? 'Staff activity types' : 'Staff address types'\n const dictionary = await ensureDictionary(key, name)\n if (!dictionary) return { dictionary: null, entries: [] }\n const entriesCall = await fetchAllDictionaryEntries(dictionary.id)\n if (!entriesCall.ok) return { dictionary, entries: [] }\n const entries = entriesCall.items\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const value = typeof record.value === 'string' ? record.value : null\n if (!value) return null\n const label = typeof record.label === 'string' && record.label.trim().length ? record.label : value\n const color = typeof record.color === 'string' ? record.color : null\n const icon = typeof record.icon === 'string' ? record.icon : null\n return { value, label, color, icon }\n })\n .filter((entry): entry is DictionaryEntryOption => !!entry)\n return { dictionary, entries }\n}\n\nexport async function createStaffDictionaryEntry(\n kind: keyof typeof STAFF_DICTIONARY_KEYS,\n input: { value: string; label?: string; color?: string | null; icon?: string | null },\n): Promise<DictionaryEntryOption | null> {\n const key = STAFF_DICTIONARY_KEYS[kind]\n const name = kind === 'activityTypes' ? 'Staff activity types' : 'Staff address types'\n const dictionary = await ensureDictionary(key, name)\n if (!dictionary) return null\n const response = await apiCallOrThrow<Record<string, unknown>>(\n `/api/dictionaries/${dictionary.id}/entries`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n value: input.value,\n label: input.label ?? undefined,\n color: input.color ?? undefined,\n icon: input.icon ?? undefined,\n }),\n },\n )\n const payload = response.result ?? {}\n const value = typeof payload.value === 'string' && payload.value.trim().length ? payload.value : input.value\n const label = typeof payload.label === 'string' && payload.label.trim().length ? payload.label : value\n const color = typeof payload.color === 'string' ? payload.color : input.color ?? null\n const icon = typeof payload.icon === 'string' ? payload.icon : input.icon ?? null\n return { value, label, color, icon }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -9,6 +9,7 @@ import { Spinner } from "@open-mercato/ui/primitives/spinner";
9
9
  import { Textarea } from "@open-mercato/ui/primitives/textarea";
10
10
  import { Badge } from "@open-mercato/ui/primitives/badge";
11
11
  import { emitSalesDocumentDataRefresh } from "@open-mercato/core/modules/sales/lib/frontend/documentDataEvents";
12
+ import { fetchAllDictionaryEntries } from "@open-mercato/core/modules/dictionaries/lib/fetchAllEntries";
12
13
  const WORKFLOW_ID = "sales.order-approval";
13
14
  function OrderApprovalWidget({ data }) {
14
15
  const t = useT();
@@ -102,10 +103,16 @@ function OrderApprovalWidget({ data }) {
102
103
  queryKey: ["dictionary-entries", orderStatusDictionaryId],
103
104
  queryFn: async () => {
104
105
  if (!orderStatusDictionaryId) return { items: [] };
105
- const result = await apiCall(
106
- `/api/dictionaries/${orderStatusDictionaryId}/entries`
107
- );
108
- return result.ok ? result.result : { items: [] };
106
+ const result = await fetchAllDictionaryEntries(orderStatusDictionaryId);
107
+ if (!result.ok) return { items: [] };
108
+ const items = result.items.flatMap((entry) => {
109
+ const id = typeof entry.id === "string" ? entry.id : null;
110
+ const value = typeof entry.value === "string" ? entry.value : null;
111
+ if (!id || !value) return [];
112
+ const label = typeof entry.label === "string" && entry.label.trim().length ? entry.label : value;
113
+ return [{ id, value, label }];
114
+ });
115
+ return { items };
109
116
  },
110
117
  enabled: Boolean(orderStatusDictionaryId),
111
118
  staleTime: 6e4
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/workflows/widgets/injection/order-approval/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { emitSalesDocumentDataRefresh } from '@open-mercato/core/modules/sales/lib/frontend/documentDataEvents'\n\ntype OrderRecord = {\n id: string\n orderNumber?: string\n status?: string\n statusEntryId?: string\n}\n\ntype WorkflowInstance = {\n id: string\n workflowId: string\n status: 'RUNNING' | 'PAUSED' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'WAITING_FOR_ACTIVITIES'\n currentStepId: string\n context: Record<string, any>\n}\n\ntype UserTask = {\n id: string\n taskName: string\n status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'\n workflowInstanceId: string\n claimedBy: string | null\n}\n\ntype DictionaryEntry = {\n id: string\n value: string\n label: string\n color?: string\n icon?: string\n}\n\nconst WORKFLOW_ID = 'sales.order-approval'\n\nexport default function OrderApprovalWidget({ data }: InjectionWidgetComponentProps<unknown, OrderRecord>) {\n const t = useT()\n const queryClient = useQueryClient()\n const orderId = data?.id\n\n const [decision, setDecision] = React.useState<'approve' | 'reject' | ''>('')\n const [comments, setComments] = React.useState('')\n const [error, setError] = React.useState<string | null>(null)\n\n // Track if we're waiting for workflow to process (for polling)\n const [isWaitingForProcessing, setIsWaitingForProcessing] = React.useState(false)\n\n // Fetch current order status directly (to detect when workflow updates it)\n // Use list endpoint with ID filter since single-item endpoint doesn't exist\n const { data: orderData } = useQuery({\n queryKey: ['order-status', orderId],\n queryFn: async () => {\n if (!orderId) return null\n const result = await apiCall<{ items: Array<{ id: string; status?: string | null }> }>(\n `/api/sales/orders?id=${orderId}&pageSize=1`\n )\n return result.ok && result.result?.items?.[0] ? result.result.items[0] : null\n },\n enabled: Boolean(orderId),\n staleTime: 5_000,\n // Poll when waiting for processing to detect status change\n refetchInterval: isWaitingForProcessing ? 2_000 : false,\n })\n\n // Use fresh order status from query, fallback to prop data\n // The API returns status as a string value (e.g., \"approved\", \"pending_approval\")\n const currentOrderStatus = orderData?.status || data?.status\n\n // Fetch active workflow instances for this order\n const { data: instancesData, isLoading: instancesLoading } = useQuery({\n queryKey: ['workflow-instances', orderId],\n queryFn: async () => {\n if (!orderId) return { data: [] }\n const result = await apiCall<{ data: WorkflowInstance[] }>(\n `/api/workflows/instances?entityId=${orderId}&status=RUNNING,PAUSED,WAITING_FOR_ACTIVITIES`\n )\n return result.ok ? result.result : { data: [] }\n },\n enabled: Boolean(orderId),\n staleTime: 5_000,\n // Poll every 2 seconds when waiting for processing\n refetchInterval: isWaitingForProcessing ? 2_000 : false,\n })\n\n const activeInstance = instancesData?.data?.find(\n (inst) => inst.workflowId === WORKFLOW_ID && !['COMPLETED', 'FAILED', 'CANCELLED'].includes(inst.status)\n )\n\n // Fetch pending user tasks for active instance\n const { data: tasksData, isLoading: tasksLoading } = useQuery({\n queryKey: ['workflow-tasks', activeInstance?.id],\n queryFn: async () => {\n if (!activeInstance?.id) return { data: [] }\n const result = await apiCall<{ data: UserTask[] }>(\n `/api/workflows/tasks?workflowInstanceId=${activeInstance.id}&status=PENDING,IN_PROGRESS`\n )\n return result.ok ? result.result : { data: [] }\n },\n enabled: Boolean(activeInstance?.id),\n staleTime: 5_000,\n // Poll every 2 seconds when waiting for processing\n refetchInterval: isWaitingForProcessing ? 2_000 : false,\n })\n\n const pendingTask = tasksData?.data?.[0]\n\n // Auto-detect when workflow is processing (active instance but no pending task)\n const isProcessing = activeInstance && !pendingTask && !['COMPLETED', 'FAILED', 'CANCELLED'].includes(activeInstance.status)\n\n // Track previous status to detect changes\n const prevStatusRef = React.useRef<string | undefined>(data?.status)\n\n // Update polling state when processing state changes\n React.useEffect(() => {\n if (isProcessing) {\n setIsWaitingForProcessing(true)\n } else if (!activeInstance || activeInstance.status === 'COMPLETED') {\n setIsWaitingForProcessing(false)\n }\n }, [isProcessing, activeInstance])\n\n // When order status changes, emit refresh event to update the page\n React.useEffect(() => {\n const newStatus = orderData?.status\n const oldStatus = prevStatusRef.current\n\n if (newStatus && oldStatus && newStatus !== oldStatus) {\n // Status changed - emit document refresh event to reload the page data\n if (orderId) {\n emitSalesDocumentDataRefresh({ documentId: orderId, kind: 'order' })\n }\n }\n\n prevStatusRef.current = newStatus || oldStatus\n }, [orderData?.status, orderId])\n\n // First fetch dictionaries to find the order_status dictionary ID\n const { data: dictionariesData } = useQuery({\n queryKey: ['dictionaries'],\n queryFn: async () => {\n const result = await apiCall<{ items: Array<{ id: string; key: string }> }>(\n '/api/dictionaries'\n )\n return result.ok ? result.result : { items: [] }\n },\n staleTime: 60_000,\n })\n\n const orderStatusDictionaryId = React.useMemo(() => {\n const dict = dictionariesData?.items?.find(d => d.key === 'sales.order_status')\n return dict?.id\n }, [dictionariesData])\n\n // Fetch order status dictionary entries using the dictionary ID\n const { data: statusEntriesData } = useQuery({\n queryKey: ['dictionary-entries', orderStatusDictionaryId],\n queryFn: async () => {\n if (!orderStatusDictionaryId) return { items: [] }\n const result = await apiCall<{ items: DictionaryEntry[] }>(\n `/api/dictionaries/${orderStatusDictionaryId}/entries`\n )\n return result.ok ? result.result : { items: [] }\n },\n enabled: Boolean(orderStatusDictionaryId),\n staleTime: 60_000,\n })\n\n const findStatusId = React.useCallback((code: string) => {\n const entries = statusEntriesData?.items || []\n // The API returns 'value' as the code/key for dictionary entries\n const entry = entries.find(\n (e) => e.value === code || e.label?.toLowerCase() === code.toLowerCase()\n )\n return entry?.id\n }, [statusEntriesData])\n\n // Start workflow mutation\n const startWorkflowMutation = useMutation({\n mutationFn: async () => {\n const pendingApprovalStatusId = findStatusId('pending_approval')\n const approvedStatusId = findStatusId('approved')\n const rejectedStatusId = findStatusId('rejected')\n\n if (!pendingApprovalStatusId || !approvedStatusId || !rejectedStatusId) {\n throw new Error(t('workflows.orderApproval.missingStatuses', 'Missing order status entries. Please ensure pending_approval, approved, and rejected statuses exist in the sales.order_status dictionary.'))\n }\n\n const result = await apiCall<{ data: WorkflowInstance }>('/api/workflows/instances', {\n method: 'POST',\n body: JSON.stringify({\n workflowId: WORKFLOW_ID,\n initialContext: {\n orderId,\n pendingApprovalStatusId,\n approvedStatusId,\n rejectedStatusId,\n },\n metadata: {\n entityType: 'SalesOrder',\n entityId: orderId,\n },\n }),\n })\n\n if (!result.ok) {\n const errorResult = result.result as { error?: string } | null\n throw new Error(errorResult?.error || t('workflows.orderApproval.startError', 'Failed to start approval workflow'))\n }\n\n return result.result\n },\n onSuccess: () => {\n setError(null)\n queryClient.invalidateQueries({ queryKey: ['workflow-instances', orderId] })\n },\n onError: (err: Error) => {\n setError(err.message)\n },\n })\n\n // Complete task mutation\n const completeTaskMutation = useMutation({\n mutationFn: async ({ taskId, formData }: { taskId: string; formData: { decision: string; comments?: string } }) => {\n const result = await apiCall(`/api/workflows/tasks/${taskId}/complete`, {\n method: 'POST',\n body: JSON.stringify({ formData }),\n })\n\n if (!result.ok) {\n const errorResult = result.result as { error?: string } | null\n throw new Error(errorResult?.error || t('workflows.orderApproval.completeError', 'Failed to complete approval task'))\n }\n\n return result.result\n },\n onSuccess: () => {\n setError(null)\n setDecision('')\n setComments('')\n // Start polling immediately after submitting decision\n setIsWaitingForProcessing(true)\n queryClient.invalidateQueries({ queryKey: ['workflow-instances', orderId] })\n queryClient.invalidateQueries({ queryKey: ['workflow-tasks', activeInstance?.id] })\n // Also refresh the order data\n queryClient.invalidateQueries({ queryKey: ['sales-order', orderId] })\n },\n onError: (err: Error) => {\n setError(err.message)\n },\n })\n\n const handleStartWorkflow = () => {\n // Start polling after starting workflow\n setIsWaitingForProcessing(true)\n startWorkflowMutation.mutate()\n }\n\n const handleCompleteTask = () => {\n if (!pendingTask || !decision) return\n completeTaskMutation.mutate({\n taskId: pendingTask.id,\n formData: { decision, comments },\n })\n }\n\n // Handle keyboard shortcuts (Cmd/Ctrl+Enter to submit)\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n if (pendingTask && decision && !isSubmitting) {\n handleCompleteTask()\n }\n }\n }\n\n const isLoading = instancesLoading || tasksLoading\n const isSubmitting = startWorkflowMutation.isPending || completeTaskMutation.isPending\n\n // Don't render if no orderId\n if (!orderId) return null\n\n // Only show widget when order status is pending_approval\n const orderStatus = currentOrderStatus?.toLowerCase()\n if (orderStatus !== 'pending_approval') {\n return null\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center p-4\">\n <Spinner size=\"sm\" />\n <span className=\"ml-2 text-sm text-muted-foreground\">{t('common.loading', 'Loading...')}</span>\n </div>\n )\n }\n\n // Show workflow status badge\n const getStatusBadge = () => {\n if (!activeInstance) return null\n\n const statusVariants: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {\n RUNNING: 'default',\n PAUSED: 'secondary',\n WAITING_FOR_ACTIVITIES: 'secondary',\n COMPLETED: 'default',\n FAILED: 'destructive',\n CANCELLED: 'outline',\n }\n\n return (\n <Badge variant={statusVariants[activeInstance.status] || 'outline'}>\n {t(`workflows.instances.statuses.${activeInstance.status}`, activeInstance.status)}\n </Badge>\n )\n }\n\n return (\n <div className=\"space-y-3 rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <div className=\"text-sm font-semibold text-foreground\">\n {t('workflows.orderApproval.groupLabel', 'Order Approval')}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t('workflows.orderApproval.groupDescription', 'Review and approve or reject this order')}\n </p>\n </div>\n {getStatusBadge()}\n </div>\n\n {error && (\n <div className=\"rounded-md border border-red-200 bg-red-50 p-3 text-xs text-red-800\">\n {error}\n </div>\n )}\n\n {/* No active workflow - show request approval button */}\n {!activeInstance && (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {t('workflows.orderApproval.noWorkflowActive', 'No approval workflow is active for this order.')}\n </p>\n <Button\n onClick={handleStartWorkflow}\n disabled={isSubmitting}\n variant=\"default\"\n size=\"sm\"\n >\n {isSubmitting && <Spinner size=\"sm\" className=\"mr-2\" />}\n {t('workflows.orderApproval.requestApproval', 'Request Approval')}\n </Button>\n </div>\n )}\n\n {/* Active workflow with pending task - show approve/reject UI */}\n {activeInstance && pendingTask && (\n <div className=\"space-y-3\" onKeyDown={handleKeyDown}>\n <div className=\"rounded-md border border-amber-200 bg-amber-50 p-3\">\n <p className=\"text-sm font-medium text-amber-800\">\n {t('workflows.orderApproval.pendingTitle', 'Pending Approval')}\n </p>\n <p className=\"text-xs text-amber-700 mt-1\">\n {t('workflows.orderApproval.pendingDescription', 'This order requires approval before processing.')}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('workflows.orderApproval.decisionLabel', 'Decision')}\n </label>\n <div className=\"flex gap-2\">\n <Button\n variant={decision === 'approve' ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setDecision('approve')}\n className={decision === 'approve' ? 'bg-emerald-600 hover:bg-emerald-700' : ''}\n >\n {t('workflows.orderApproval.approveButton', 'Approve')}\n </Button>\n <Button\n variant={decision === 'reject' ? 'destructive' : 'outline'}\n size=\"sm\"\n onClick={() => setDecision('reject')}\n >\n {t('workflows.orderApproval.rejectButton', 'Reject')}\n </Button>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('workflows.orderApproval.commentsLabel', 'Comments')} <span className=\"text-muted-foreground font-normal\">({t('common.optional', 'optional')})</span>\n </label>\n <Textarea\n value={comments}\n onChange={(e) => setComments(e.target.value)}\n placeholder={t('workflows.orderApproval.commentsPlaceholder', 'Add optional comments...')}\n rows={2}\n />\n </div>\n\n <Button\n onClick={handleCompleteTask}\n disabled={!decision || isSubmitting}\n variant=\"default\"\n size=\"sm\"\n className=\"w-full\"\n >\n {isSubmitting && <Spinner size=\"sm\" className=\"mr-2\" />}\n {t('workflows.orderApproval.submitDecision', 'Submit Decision')}\n </Button>\n </div>\n )}\n\n {/* Active workflow but no pending task (processing) */}\n {activeInstance && !pendingTask && activeInstance.status !== 'COMPLETED' && (\n <div className=\"rounded-md border border-blue-200 bg-blue-50 p-3\">\n <p className=\"text-sm text-blue-800\">\n {t('workflows.orderApproval.processing', 'Workflow is processing...')}\n </p>\n </div>\n )}\n\n {/* Completed workflow */}\n {activeInstance && activeInstance.status === 'COMPLETED' && (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 p-3\">\n <p className=\"text-sm text-emerald-800\">\n {t('workflows.orderApproval.completed', 'Approval workflow completed.')}\n </p>\n </div>\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { emitSalesDocumentDataRefresh } from '@open-mercato/core/modules/sales/lib/frontend/documentDataEvents'\nimport { fetchAllDictionaryEntries } from '@open-mercato/core/modules/dictionaries/lib/fetchAllEntries'\n\ntype OrderRecord = {\n id: string\n orderNumber?: string\n status?: string\n statusEntryId?: string\n}\n\ntype WorkflowInstance = {\n id: string\n workflowId: string\n status: 'RUNNING' | 'PAUSED' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'WAITING_FOR_ACTIVITIES'\n currentStepId: string\n context: Record<string, any>\n}\n\ntype UserTask = {\n id: string\n taskName: string\n status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'\n workflowInstanceId: string\n claimedBy: string | null\n}\n\ntype DictionaryEntry = {\n id: string\n value: string\n label: string\n color?: string\n icon?: string\n}\n\nconst WORKFLOW_ID = 'sales.order-approval'\n\nexport default function OrderApprovalWidget({ data }: InjectionWidgetComponentProps<unknown, OrderRecord>) {\n const t = useT()\n const queryClient = useQueryClient()\n const orderId = data?.id\n\n const [decision, setDecision] = React.useState<'approve' | 'reject' | ''>('')\n const [comments, setComments] = React.useState('')\n const [error, setError] = React.useState<string | null>(null)\n\n // Track if we're waiting for workflow to process (for polling)\n const [isWaitingForProcessing, setIsWaitingForProcessing] = React.useState(false)\n\n // Fetch current order status directly (to detect when workflow updates it)\n // Use list endpoint with ID filter since single-item endpoint doesn't exist\n const { data: orderData } = useQuery({\n queryKey: ['order-status', orderId],\n queryFn: async () => {\n if (!orderId) return null\n const result = await apiCall<{ items: Array<{ id: string; status?: string | null }> }>(\n `/api/sales/orders?id=${orderId}&pageSize=1`\n )\n return result.ok && result.result?.items?.[0] ? result.result.items[0] : null\n },\n enabled: Boolean(orderId),\n staleTime: 5_000,\n // Poll when waiting for processing to detect status change\n refetchInterval: isWaitingForProcessing ? 2_000 : false,\n })\n\n // Use fresh order status from query, fallback to prop data\n // The API returns status as a string value (e.g., \"approved\", \"pending_approval\")\n const currentOrderStatus = orderData?.status || data?.status\n\n // Fetch active workflow instances for this order\n const { data: instancesData, isLoading: instancesLoading } = useQuery({\n queryKey: ['workflow-instances', orderId],\n queryFn: async () => {\n if (!orderId) return { data: [] }\n const result = await apiCall<{ data: WorkflowInstance[] }>(\n `/api/workflows/instances?entityId=${orderId}&status=RUNNING,PAUSED,WAITING_FOR_ACTIVITIES`\n )\n return result.ok ? result.result : { data: [] }\n },\n enabled: Boolean(orderId),\n staleTime: 5_000,\n // Poll every 2 seconds when waiting for processing\n refetchInterval: isWaitingForProcessing ? 2_000 : false,\n })\n\n const activeInstance = instancesData?.data?.find(\n (inst) => inst.workflowId === WORKFLOW_ID && !['COMPLETED', 'FAILED', 'CANCELLED'].includes(inst.status)\n )\n\n // Fetch pending user tasks for active instance\n const { data: tasksData, isLoading: tasksLoading } = useQuery({\n queryKey: ['workflow-tasks', activeInstance?.id],\n queryFn: async () => {\n if (!activeInstance?.id) return { data: [] }\n const result = await apiCall<{ data: UserTask[] }>(\n `/api/workflows/tasks?workflowInstanceId=${activeInstance.id}&status=PENDING,IN_PROGRESS`\n )\n return result.ok ? result.result : { data: [] }\n },\n enabled: Boolean(activeInstance?.id),\n staleTime: 5_000,\n // Poll every 2 seconds when waiting for processing\n refetchInterval: isWaitingForProcessing ? 2_000 : false,\n })\n\n const pendingTask = tasksData?.data?.[0]\n\n // Auto-detect when workflow is processing (active instance but no pending task)\n const isProcessing = activeInstance && !pendingTask && !['COMPLETED', 'FAILED', 'CANCELLED'].includes(activeInstance.status)\n\n // Track previous status to detect changes\n const prevStatusRef = React.useRef<string | undefined>(data?.status)\n\n // Update polling state when processing state changes\n React.useEffect(() => {\n if (isProcessing) {\n setIsWaitingForProcessing(true)\n } else if (!activeInstance || activeInstance.status === 'COMPLETED') {\n setIsWaitingForProcessing(false)\n }\n }, [isProcessing, activeInstance])\n\n // When order status changes, emit refresh event to update the page\n React.useEffect(() => {\n const newStatus = orderData?.status\n const oldStatus = prevStatusRef.current\n\n if (newStatus && oldStatus && newStatus !== oldStatus) {\n // Status changed - emit document refresh event to reload the page data\n if (orderId) {\n emitSalesDocumentDataRefresh({ documentId: orderId, kind: 'order' })\n }\n }\n\n prevStatusRef.current = newStatus || oldStatus\n }, [orderData?.status, orderId])\n\n // First fetch dictionaries to find the order_status dictionary ID\n const { data: dictionariesData } = useQuery({\n queryKey: ['dictionaries'],\n queryFn: async () => {\n const result = await apiCall<{ items: Array<{ id: string; key: string }> }>(\n '/api/dictionaries'\n )\n return result.ok ? result.result : { items: [] }\n },\n staleTime: 60_000,\n })\n\n const orderStatusDictionaryId = React.useMemo(() => {\n const dict = dictionariesData?.items?.find(d => d.key === 'sales.order_status')\n return dict?.id\n }, [dictionariesData])\n\n // Fetch order status dictionary entries using the dictionary ID\n const { data: statusEntriesData } = useQuery({\n queryKey: ['dictionary-entries', orderStatusDictionaryId],\n queryFn: async (): Promise<{ items: DictionaryEntry[] }> => {\n if (!orderStatusDictionaryId) return { items: [] }\n const result = await fetchAllDictionaryEntries(orderStatusDictionaryId)\n if (!result.ok) return { items: [] }\n const items = result.items.flatMap((entry) => {\n const id = typeof entry.id === 'string' ? entry.id : null\n const value = typeof entry.value === 'string' ? entry.value : null\n if (!id || !value) return []\n const label = typeof entry.label === 'string' && entry.label.trim().length ? entry.label : value\n return [{ id, value, label }]\n })\n return { items }\n },\n enabled: Boolean(orderStatusDictionaryId),\n staleTime: 60_000,\n })\n\n const findStatusId = React.useCallback((code: string) => {\n const entries = statusEntriesData?.items || []\n // The API returns 'value' as the code/key for dictionary entries\n const entry = entries.find(\n (e) => e.value === code || e.label?.toLowerCase() === code.toLowerCase()\n )\n return entry?.id\n }, [statusEntriesData])\n\n // Start workflow mutation\n const startWorkflowMutation = useMutation({\n mutationFn: async () => {\n const pendingApprovalStatusId = findStatusId('pending_approval')\n const approvedStatusId = findStatusId('approved')\n const rejectedStatusId = findStatusId('rejected')\n\n if (!pendingApprovalStatusId || !approvedStatusId || !rejectedStatusId) {\n throw new Error(t('workflows.orderApproval.missingStatuses', 'Missing order status entries. Please ensure pending_approval, approved, and rejected statuses exist in the sales.order_status dictionary.'))\n }\n\n const result = await apiCall<{ data: WorkflowInstance }>('/api/workflows/instances', {\n method: 'POST',\n body: JSON.stringify({\n workflowId: WORKFLOW_ID,\n initialContext: {\n orderId,\n pendingApprovalStatusId,\n approvedStatusId,\n rejectedStatusId,\n },\n metadata: {\n entityType: 'SalesOrder',\n entityId: orderId,\n },\n }),\n })\n\n if (!result.ok) {\n const errorResult = result.result as { error?: string } | null\n throw new Error(errorResult?.error || t('workflows.orderApproval.startError', 'Failed to start approval workflow'))\n }\n\n return result.result\n },\n onSuccess: () => {\n setError(null)\n queryClient.invalidateQueries({ queryKey: ['workflow-instances', orderId] })\n },\n onError: (err: Error) => {\n setError(err.message)\n },\n })\n\n // Complete task mutation\n const completeTaskMutation = useMutation({\n mutationFn: async ({ taskId, formData }: { taskId: string; formData: { decision: string; comments?: string } }) => {\n const result = await apiCall(`/api/workflows/tasks/${taskId}/complete`, {\n method: 'POST',\n body: JSON.stringify({ formData }),\n })\n\n if (!result.ok) {\n const errorResult = result.result as { error?: string } | null\n throw new Error(errorResult?.error || t('workflows.orderApproval.completeError', 'Failed to complete approval task'))\n }\n\n return result.result\n },\n onSuccess: () => {\n setError(null)\n setDecision('')\n setComments('')\n // Start polling immediately after submitting decision\n setIsWaitingForProcessing(true)\n queryClient.invalidateQueries({ queryKey: ['workflow-instances', orderId] })\n queryClient.invalidateQueries({ queryKey: ['workflow-tasks', activeInstance?.id] })\n // Also refresh the order data\n queryClient.invalidateQueries({ queryKey: ['sales-order', orderId] })\n },\n onError: (err: Error) => {\n setError(err.message)\n },\n })\n\n const handleStartWorkflow = () => {\n // Start polling after starting workflow\n setIsWaitingForProcessing(true)\n startWorkflowMutation.mutate()\n }\n\n const handleCompleteTask = () => {\n if (!pendingTask || !decision) return\n completeTaskMutation.mutate({\n taskId: pendingTask.id,\n formData: { decision, comments },\n })\n }\n\n // Handle keyboard shortcuts (Cmd/Ctrl+Enter to submit)\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n if (pendingTask && decision && !isSubmitting) {\n handleCompleteTask()\n }\n }\n }\n\n const isLoading = instancesLoading || tasksLoading\n const isSubmitting = startWorkflowMutation.isPending || completeTaskMutation.isPending\n\n // Don't render if no orderId\n if (!orderId) return null\n\n // Only show widget when order status is pending_approval\n const orderStatus = currentOrderStatus?.toLowerCase()\n if (orderStatus !== 'pending_approval') {\n return null\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center p-4\">\n <Spinner size=\"sm\" />\n <span className=\"ml-2 text-sm text-muted-foreground\">{t('common.loading', 'Loading...')}</span>\n </div>\n )\n }\n\n // Show workflow status badge\n const getStatusBadge = () => {\n if (!activeInstance) return null\n\n const statusVariants: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {\n RUNNING: 'default',\n PAUSED: 'secondary',\n WAITING_FOR_ACTIVITIES: 'secondary',\n COMPLETED: 'default',\n FAILED: 'destructive',\n CANCELLED: 'outline',\n }\n\n return (\n <Badge variant={statusVariants[activeInstance.status] || 'outline'}>\n {t(`workflows.instances.statuses.${activeInstance.status}`, activeInstance.status)}\n </Badge>\n )\n }\n\n return (\n <div className=\"space-y-3 rounded-lg border bg-card p-4 shadow-sm\">\n <div className=\"flex items-start justify-between gap-3\">\n <div>\n <div className=\"text-sm font-semibold text-foreground\">\n {t('workflows.orderApproval.groupLabel', 'Order Approval')}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {t('workflows.orderApproval.groupDescription', 'Review and approve or reject this order')}\n </p>\n </div>\n {getStatusBadge()}\n </div>\n\n {error && (\n <div className=\"rounded-md border border-red-200 bg-red-50 p-3 text-xs text-red-800\">\n {error}\n </div>\n )}\n\n {/* No active workflow - show request approval button */}\n {!activeInstance && (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {t('workflows.orderApproval.noWorkflowActive', 'No approval workflow is active for this order.')}\n </p>\n <Button\n onClick={handleStartWorkflow}\n disabled={isSubmitting}\n variant=\"default\"\n size=\"sm\"\n >\n {isSubmitting && <Spinner size=\"sm\" className=\"mr-2\" />}\n {t('workflows.orderApproval.requestApproval', 'Request Approval')}\n </Button>\n </div>\n )}\n\n {/* Active workflow with pending task - show approve/reject UI */}\n {activeInstance && pendingTask && (\n <div className=\"space-y-3\" onKeyDown={handleKeyDown}>\n <div className=\"rounded-md border border-amber-200 bg-amber-50 p-3\">\n <p className=\"text-sm font-medium text-amber-800\">\n {t('workflows.orderApproval.pendingTitle', 'Pending Approval')}\n </p>\n <p className=\"text-xs text-amber-700 mt-1\">\n {t('workflows.orderApproval.pendingDescription', 'This order requires approval before processing.')}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('workflows.orderApproval.decisionLabel', 'Decision')}\n </label>\n <div className=\"flex gap-2\">\n <Button\n variant={decision === 'approve' ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setDecision('approve')}\n className={decision === 'approve' ? 'bg-emerald-600 hover:bg-emerald-700' : ''}\n >\n {t('workflows.orderApproval.approveButton', 'Approve')}\n </Button>\n <Button\n variant={decision === 'reject' ? 'destructive' : 'outline'}\n size=\"sm\"\n onClick={() => setDecision('reject')}\n >\n {t('workflows.orderApproval.rejectButton', 'Reject')}\n </Button>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('workflows.orderApproval.commentsLabel', 'Comments')} <span className=\"text-muted-foreground font-normal\">({t('common.optional', 'optional')})</span>\n </label>\n <Textarea\n value={comments}\n onChange={(e) => setComments(e.target.value)}\n placeholder={t('workflows.orderApproval.commentsPlaceholder', 'Add optional comments...')}\n rows={2}\n />\n </div>\n\n <Button\n onClick={handleCompleteTask}\n disabled={!decision || isSubmitting}\n variant=\"default\"\n size=\"sm\"\n className=\"w-full\"\n >\n {isSubmitting && <Spinner size=\"sm\" className=\"mr-2\" />}\n {t('workflows.orderApproval.submitDecision', 'Submit Decision')}\n </Button>\n </div>\n )}\n\n {/* Active workflow but no pending task (processing) */}\n {activeInstance && !pendingTask && activeInstance.status !== 'COMPLETED' && (\n <div className=\"rounded-md border border-blue-200 bg-blue-50 p-3\">\n <p className=\"text-sm text-blue-800\">\n {t('workflows.orderApproval.processing', 'Workflow is processing...')}\n </p>\n </div>\n )}\n\n {/* Completed workflow */}\n {activeInstance && activeInstance.status === 'COMPLETED' && (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 p-3\">\n <p className=\"text-sm text-emerald-800\">\n {t('workflows.orderApproval.completed', 'Approval workflow completed.')}\n </p>\n </div>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -13,6 +13,7 @@ export const appearance_icon = "appearance_icon";
13
13
  export const appearance_color = "appearance_color";
14
14
  export const is_active = "is_active";
15
15
  export const availability_rule_set_id = "availability_rule_set_id";
16
+ export const custom_fieldset_code = "custom_fieldset_code";
16
17
  export const created_at = "created_at";
17
18
  export const updated_at = "updated_at";
18
19
  export const deleted_at = "deleted_at";
@@ -1783,6 +1783,7 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
1783
1783
  "appearance_color": "appearance_color",
1784
1784
  "is_active": "is_active",
1785
1785
  "availability_rule_set_id": "availability_rule_set_id",
1786
+ "custom_fieldset_code": "custom_fieldset_code",
1786
1787
  "created_at": "created_at",
1787
1788
  "updated_at": "updated_at",
1788
1789
  "deleted_at": "deleted_at"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.7-develop.6726.1.983ae8a07e",
3
+ "version": "0.6.7-develop.6749.1.6b54c56dfe",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -254,16 +254,16 @@
254
254
  "zod": "^4.4.3"
255
255
  },
256
256
  "peerDependencies": {
257
- "@open-mercato/ai-assistant": "0.6.7-develop.6726.1.983ae8a07e",
258
- "@open-mercato/shared": "0.6.7-develop.6726.1.983ae8a07e",
259
- "@open-mercato/ui": "0.6.7-develop.6726.1.983ae8a07e",
257
+ "@open-mercato/ai-assistant": "0.6.7-develop.6749.1.6b54c56dfe",
258
+ "@open-mercato/shared": "0.6.7-develop.6749.1.6b54c56dfe",
259
+ "@open-mercato/ui": "0.6.7-develop.6749.1.6b54c56dfe",
260
260
  "react": "^19.0.0",
261
261
  "react-dom": "^19.0.0"
262
262
  },
263
263
  "devDependencies": {
264
- "@open-mercato/ai-assistant": "0.6.7-develop.6726.1.983ae8a07e",
265
- "@open-mercato/shared": "0.6.7-develop.6726.1.983ae8a07e",
266
- "@open-mercato/ui": "0.6.7-develop.6726.1.983ae8a07e",
264
+ "@open-mercato/ai-assistant": "0.6.7-develop.6749.1.6b54c56dfe",
265
+ "@open-mercato/shared": "0.6.7-develop.6749.1.6b54c56dfe",
266
+ "@open-mercato/ui": "0.6.7-develop.6749.1.6b54c56dfe",
267
267
  "@testing-library/dom": "^10.4.1",
268
268
  "@testing-library/jest-dom": "^6.9.1",
269
269
  "@testing-library/react": "^16.3.1",
@@ -47,7 +47,27 @@ export async function createSalesOrderFixture(
47
47
  token: string,
48
48
  currencyCode = 'USD',
49
49
  ): Promise<string> {
50
- return createEntity(request, token, '/api/sales/orders', { currencyCode }, ['id', 'orderId']);
50
+ // A sales order must contain at least one line (see issue #4021). Seed a
51
+ // zero-priced placeholder line so the order is valid on creation without
52
+ // perturbing totals in specs that add their own priced lines afterwards.
53
+ return createEntity(
54
+ request,
55
+ token,
56
+ '/api/sales/orders',
57
+ {
58
+ currencyCode,
59
+ lines: [
60
+ {
61
+ currencyCode,
62
+ quantity: 1,
63
+ name: `QA seed line ${Date.now()}`,
64
+ unitPriceNet: 0,
65
+ unitPriceGross: 0,
66
+ },
67
+ ],
68
+ },
69
+ ['id', 'orderId'],
70
+ );
51
71
  }
52
72
 
53
73
  export async function createOrderLineFixture(
@@ -108,7 +128,19 @@ export async function canManageSalesOrders(
108
128
  ): Promise<boolean> {
109
129
  const response = await apiRequest(request, 'POST', '/api/sales/orders', {
110
130
  token,
111
- data: { currencyCode: 'USD' },
131
+ // A sales order must contain at least one line (issue #4021).
132
+ data: {
133
+ currencyCode: 'USD',
134
+ lines: [
135
+ {
136
+ currencyCode: 'USD',
137
+ quantity: 1,
138
+ name: 'QA probe line',
139
+ unitPriceNet: 0,
140
+ unitPriceGross: 0,
141
+ },
142
+ ],
143
+ },
112
144
  });
113
145
  if (response.status() === 403) return false;
114
146
  if (!response.ok()) return false;
@@ -652,6 +652,14 @@ async function createSalesDocumentFixture(
652
652
  };
653
653
  if (customerEntityId) payload.customerEntityId = customerEntityId;
654
654
  if (channelId) payload.channelId = channelId;
655
+ if (kind === 'order') {
656
+ // A sales order must contain at least one line (issue #4021). Seed a
657
+ // zero-priced placeholder line so the order is valid on creation without
658
+ // perturbing totals in specs that add their own priced lines afterwards.
659
+ payload.lines = [
660
+ { currencyCode: 'USD', quantity: 1, name: 'QA seed line', unitPriceNet: 0, unitPriceGross: 0 },
661
+ ];
662
+ }
655
663
 
656
664
  const response = await apiRequest(page.request, 'POST', kind === 'quote' ? '/api/sales/quotes' : '/api/sales/orders', {
657
665
  token,
@@ -18,6 +18,7 @@ const logger = createLogger('auth').child({ component: 'profile' })
18
18
 
19
19
  const profileResponseSchema = z.object({
20
20
  email: z.string().email(),
21
+ name: z.string().nullable().optional(),
21
22
  roles: z.array(z.string()),
22
23
  })
23
24
 
@@ -106,7 +107,12 @@ export async function GET(req: Request) {
106
107
  if (!user) {
107
108
  return NextResponse.json({ error: translate('auth.users.form.errors.notFound', 'User not found') }, { status: 404 })
108
109
  }
109
- return NextResponse.json({ email: String(user.email), roles: auth.roles ?? [] })
110
+ const displayName = typeof user.name === 'string' ? user.name.trim() : ''
111
+ return NextResponse.json({
112
+ email: String(user.email),
113
+ name: displayName.length > 0 ? displayName : null,
114
+ roles: auth.roles ?? [],
115
+ })
110
116
  } catch (err) {
111
117
  logger.error('Profile load failed', { err })
112
118
  return NextResponse.json({ error: translate('auth.profile.form.errors.load', 'Failed to load profile.') }, { status: 400 })
@@ -207,7 +213,7 @@ export const openApi: OpenApiRouteDoc = {
207
213
  methods: {
208
214
  GET: {
209
215
  summary: 'Get current profile',
210
- description: 'Returns the email address for the signed-in user.',
216
+ description: 'Returns the email address, display name, and roles for the signed-in user. The display name is null when unset.',
211
217
  responses: [
212
218
  { status: 200, description: 'Profile payload', schema: profileResponseSchema },
213
219
  { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
@@ -18,6 +18,7 @@ const logger = createLogger('auth').child({ component: 'profile-page' })
18
18
 
19
19
  type ProfileResponse = {
20
20
  email?: string | null
21
+ name?: string | null
21
22
  }
22
23
 
23
24
  type ProfileUpdateResponse = {
@@ -17,6 +17,7 @@ const logger = createLogger('auth').child({ component: 'change-password-page' })
17
17
 
18
18
  type ProfileResponse = {
19
19
  email?: string | null
20
+ name?: string | null
20
21
  }
21
22
 
22
23
  type ProfileUpdateResponse = {
@@ -1,6 +1,10 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import type { EnricherContext, ResponseEnricher } from '@open-mercato/shared/lib/crud/response-enricher'
3
3
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
+ import {
5
+ applyMessageParticipantScope,
6
+ type MessagesParticipantScopeDatabase,
7
+ } from '../../messages/lib/participantScope'
4
8
  import { sanitizeChannelHtml } from '../lib/sanitize-channel-html'
5
9
  import {
6
10
  CommunicationChannel,
@@ -43,21 +47,6 @@ type MessageRecord = Record<string, unknown> & {
43
47
  threadId?: string | null
44
48
  }
45
49
 
46
- type MessageParticipantDatabase = {
47
- messages: {
48
- id: string
49
- tenant_id: string
50
- organization_id: string | null
51
- sender_user_id: string
52
- deleted_at: Date | null
53
- }
54
- message_recipients: {
55
- message_id: string
56
- recipient_user_id: string
57
- deleted_at: Date | null
58
- }
59
- }
60
-
61
50
  type ResolvedCtx = EnricherContext & {
62
51
  em: EntityManager
63
52
  }
@@ -151,22 +140,13 @@ const messageChannelEnricher: ResponseEnricher<
151
140
  }))
152
141
  }
153
142
 
154
- const db = em.getKysely<MessageParticipantDatabase>()
155
- let participantQuery = db
156
- .selectFrom('messages as m')
157
- .leftJoin('message_recipients as r', (join) => join
158
- .onRef('m.id', '=', 'r.message_id')
159
- .on('r.recipient_user_id', '=', userId)
160
- .on('r.deleted_at', 'is', null))
143
+ const db = em.getKysely<MessagesParticipantScopeDatabase>()
144
+ let participantQuery = applyMessageParticipantScope(db.selectFrom('messages as m'), userId)
161
145
  .select('m.id')
162
146
  .distinct()
163
147
  .where('m.id', 'in', messageIds)
164
148
  .where('m.tenant_id', '=', tenantId)
165
149
  .where('m.deleted_at', 'is', null)
166
- .where((eb) => eb.or([
167
- eb('m.sender_user_id', '=', userId),
168
- eb('r.message_id', 'is not', null),
169
- ]))
170
150
 
171
151
  participantQuery = organizationId !== null
172
152
  ? participantQuery.where('m.organization_id', '=', organizationId)
@@ -31,6 +31,7 @@ export const analyticsConfig: AnalyticsModuleConfig = {
31
31
  id: { dbColumn: 'id', type: 'uuid' },
32
32
  valueAmount: { dbColumn: 'value_amount', type: 'numeric' },
33
33
  status: { dbColumn: 'status', type: 'text' },
34
+ closureOutcome: { dbColumn: 'closure_outcome', type: 'text' },
34
35
  pipelineStage: { dbColumn: 'pipeline_stage', type: 'text' },
35
36
  probability: { dbColumn: 'probability', type: 'numeric' },
36
37
  createdAt: { dbColumn: 'created_at', type: 'timestamp' },
@@ -15,6 +15,7 @@ import {
15
15
  } from '@open-mercato/ui/primitives/select'
16
16
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
17
17
  import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
18
+ import { DEAL_DESCRIPTION_MAX_LENGTH } from '../../data/validators'
18
19
  import { DictionarySelectField } from '../formConfig'
19
20
  import { createDictionarySelectLabels } from './utils'
20
21
  import { E } from '#generated/entities.ids.generated'
@@ -263,7 +264,10 @@ const schema = z.object({
263
264
  'customers.people.detail.deals.expectedCloseInvalid',
264
265
  )
265
266
  .optional(),
266
- description: z.string().max(4000, 'customers.people.detail.deals.descriptionTooLong').optional(),
267
+ description: z
268
+ .string()
269
+ .max(DEAL_DESCRIPTION_MAX_LENGTH, 'customers.people.detail.deals.descriptionTooLong')
270
+ .optional(),
267
271
  personIds: z.array(z.string().trim().min(1)).optional(),
268
272
  companyIds: z.array(z.string().trim().min(1)).optional(),
269
273
  }).passthrough()
@@ -988,7 +988,7 @@ export function DealsSection({
988
988
  {deal.title || emptyLabel}
989
989
  </Link>
990
990
  {deal.description ? (
991
- <p className="mt-1 text-sm text-muted-foreground whitespace-pre-wrap">{deal.description}</p>
991
+ <p className="mt-1 line-clamp-3 text-sm text-muted-foreground whitespace-pre-wrap">{deal.description}</p>
992
992
  ) : null}
993
993
  </div>
994
994
  <div className="flex items-center gap-2">
@@ -11,6 +11,10 @@ export const ACTIVITY_TIME_REQUIRED_MESSAGE_KEY = 'customers.activities.errors.t
11
11
  export const ACTIVITY_PHONE_REQUIRED_MESSAGE_KEY = 'customers.activities.errors.phoneRequired'
12
12
  export const ACTIVITY_PHONE_INVALID_MESSAGE_KEY = 'customers.activities.errors.phoneInvalid'
13
13
 
14
+ // customer_deals.description is an unbounded `text` column; this cap only exists to keep
15
+ // request bodies, fulltext search documents and query-index documents from growing without limit.
16
+ export const DEAL_DESCRIPTION_MAX_LENGTH = 50_000
17
+
14
18
  const emptyStringToNull = (value: unknown): unknown => {
15
19
  if (typeof value !== 'string') return value
16
20
  const trimmed = value.trim()
@@ -161,7 +165,7 @@ export const companyUpdateSchema = z
161
165
 
162
166
  export const dealCreateSchema = scopedSchema.extend({
163
167
  title: z.string().min(1).max(200),
164
- description: z.string().max(4000).optional(),
168
+ description: z.string().max(DEAL_DESCRIPTION_MAX_LENGTH).optional(),
165
169
  status: z.string().max(50).optional(),
166
170
  pipelineStage: z.string().max(100).optional(),
167
171
  pipelineId: uuid().optional(),
@@ -52,6 +52,9 @@
52
52
  "dashboards.analytics.settings.chartLayout": "Diagramm-Layout",
53
53
  "dashboards.analytics.settings.chartVariant": "Diagrammstil",
54
54
  "dashboards.analytics.settings.dateRange": "Zeitraum",
55
+ "dashboards.analytics.settings.dealStatusScope": "Berücksichtigte Deals",
56
+ "dashboards.analytics.settings.dealStatusScopeAll": "Alle Deals, einschließlich gewonnener und verlorener",
57
+ "dashboards.analytics.settings.dealStatusScopeOpen": "Nur offene Deals",
55
58
  "dashboards.analytics.settings.granularity": "Granularität",
56
59
  "dashboards.analytics.settings.horizontal": "Horizontal",
57
60
  "dashboards.analytics.settings.limit": "Anzahl der Elemente",
@@ -52,6 +52,9 @@
52
52
  "dashboards.analytics.settings.chartLayout": "Chart Layout",
53
53
  "dashboards.analytics.settings.chartVariant": "Chart Style",
54
54
  "dashboards.analytics.settings.dateRange": "Date Range",
55
+ "dashboards.analytics.settings.dealStatusScope": "Deals included",
56
+ "dashboards.analytics.settings.dealStatusScopeAll": "All deals, including won and lost",
57
+ "dashboards.analytics.settings.dealStatusScopeOpen": "Open deals only",
55
58
  "dashboards.analytics.settings.granularity": "Granularity",
56
59
  "dashboards.analytics.settings.horizontal": "Horizontal",
57
60
  "dashboards.analytics.settings.limit": "Number of items",
@@ -52,6 +52,9 @@
52
52
  "dashboards.analytics.settings.chartLayout": "Disposición del gráfico",
53
53
  "dashboards.analytics.settings.chartVariant": "Estilo de gráfico",
54
54
  "dashboards.analytics.settings.dateRange": "Rango de fechas",
55
+ "dashboards.analytics.settings.dealStatusScope": "Oportunidades incluidas",
56
+ "dashboards.analytics.settings.dealStatusScopeAll": "Todas las oportunidades, incluidas ganadas y perdidas",
57
+ "dashboards.analytics.settings.dealStatusScopeOpen": "Solo oportunidades abiertas",
55
58
  "dashboards.analytics.settings.granularity": "Granularidad",
56
59
  "dashboards.analytics.settings.horizontal": "Horizontal",
57
60
  "dashboards.analytics.settings.limit": "Número de elementos",
@@ -52,6 +52,9 @@
52
52
  "dashboards.analytics.settings.chartLayout": "Układ wykresu",
53
53
  "dashboards.analytics.settings.chartVariant": "Styl wykresu",
54
54
  "dashboards.analytics.settings.dateRange": "Zakres dat",
55
+ "dashboards.analytics.settings.dealStatusScope": "Uwzględnione szanse",
56
+ "dashboards.analytics.settings.dealStatusScopeAll": "Wszystkie szanse, w tym wygrane i przegrane",
57
+ "dashboards.analytics.settings.dealStatusScopeOpen": "Tylko otwarte szanse",
55
58
  "dashboards.analytics.settings.granularity": "Szczegółowość",
56
59
  "dashboards.analytics.settings.horizontal": "Poziomy",
57
60
  "dashboards.analytics.settings.limit": "Liczba elementów",