@open-mercato/core 0.6.5-develop.5337.1.534b781eac → 0.6.5

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 (350) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -1
  3. package/dist/bootstrap.js +46 -6
  4. package/dist/bootstrap.js.map +2 -2
  5. package/dist/generated/entities/organization/index.js +2 -0
  6. package/dist/generated/entities/organization/index.js.map +2 -2
  7. package/dist/generated/entity-fields-registry.js +1 -0
  8. package/dist/generated/entity-fields-registry.js.map +2 -2
  9. package/dist/helpers/integration/crmFixtures.js +4 -0
  10. package/dist/helpers/integration/crmFixtures.js.map +2 -2
  11. package/dist/modules/attachments/api/library/route.js +2 -2
  12. package/dist/modules/attachments/api/library/route.js.map +2 -2
  13. package/dist/modules/attachments/api/route.js +2 -0
  14. package/dist/modules/attachments/api/route.js.map +2 -2
  15. package/dist/modules/attachments/components/AttachmentContentPreview.js +9 -5
  16. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  17. package/dist/modules/attachments/lib/access.js +18 -0
  18. package/dist/modules/attachments/lib/access.js.map +2 -2
  19. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +3 -2
  20. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
  21. package/dist/modules/audit_logs/data/entities.js +2 -1
  22. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  23. package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
  24. package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
  25. package/dist/modules/audit_logs/services/accessLogService.js +10 -0
  26. package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
  27. package/dist/modules/auth/api/admin/nav.js +9 -0
  28. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  29. package/dist/modules/auth/api/login.js +4 -13
  30. package/dist/modules/auth/api/login.js.map +2 -2
  31. package/dist/modules/auth/commands/users.js +20 -14
  32. package/dist/modules/auth/commands/users.js.map +2 -2
  33. package/dist/modules/auth/data/entities.js +4 -2
  34. package/dist/modules/auth/data/entities.js.map +2 -2
  35. package/dist/modules/auth/lib/backendChrome.js +35 -2
  36. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  37. package/dist/modules/auth/lib/consentIntegrity.js +3 -3
  38. package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
  39. package/dist/modules/auth/migrations/Migration20260610120000.js +30 -0
  40. package/dist/modules/auth/migrations/Migration20260610120000.js.map +7 -0
  41. package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
  42. package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
  43. package/dist/modules/auth/services/authService.js +5 -3
  44. package/dist/modules/auth/services/authService.js.map +2 -2
  45. package/dist/modules/auth/services/rbacService.js +3 -2
  46. package/dist/modules/auth/services/rbacService.js.map +2 -2
  47. package/dist/modules/catalog/ai-tools/configuration-pack.js.map +1 -1
  48. package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +1 -1
  49. package/dist/modules/catalog/ai-tools/products-pack.js.map +1 -1
  50. package/dist/modules/catalog/ai-tools/variants-pack.js.map +1 -1
  51. package/dist/modules/communication_channels/data/entities.js.map +1 -1
  52. package/dist/modules/communication_channels/encryption.js.map +1 -1
  53. package/dist/modules/communication_channels/lib/thread-matcher.js.map +1 -1
  54. package/dist/modules/communication_channels/lib/thread-token.js.map +1 -1
  55. package/dist/modules/currencies/api/currencies/route.js +4 -3
  56. package/dist/modules/currencies/api/currencies/route.js.map +2 -2
  57. package/dist/modules/customer_accounts/api/admin/roles.js +2 -1
  58. package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
  59. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
  60. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
  61. package/dist/modules/customer_accounts/events.js +1 -1
  62. package/dist/modules/customer_accounts/events.js.map +1 -1
  63. package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +1 -1
  64. package/dist/modules/customers/acl.js +1 -1
  65. package/dist/modules/customers/acl.js.map +1 -1
  66. package/dist/modules/customers/ai-tools/companies-pack.js.map +1 -1
  67. package/dist/modules/customers/ai-tools/deals-pack.js.map +1 -1
  68. package/dist/modules/customers/ai-tools/people-pack.js.map +1 -1
  69. package/dist/modules/customers/api/companies/route.js +4 -4
  70. package/dist/modules/customers/api/companies/route.js.map +2 -2
  71. package/dist/modules/customers/api/deals/route.js +43 -2
  72. package/dist/modules/customers/api/deals/route.js.map +2 -2
  73. package/dist/modules/customers/api/deals/summary/route.js +402 -0
  74. package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
  75. package/dist/modules/customers/api/people/route.js +4 -4
  76. package/dist/modules/customers/api/people/route.js.map +2 -2
  77. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
  78. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
  79. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
  80. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
  81. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
  82. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  83. package/dist/modules/customers/backend/customers/deals/page.js +221 -56
  84. package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
  85. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
  86. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  87. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
  88. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  89. package/dist/modules/customers/cli.js +15 -9
  90. package/dist/modules/customers/cli.js.map +2 -2
  91. package/dist/modules/customers/commands/addresses.js +5 -5
  92. package/dist/modules/customers/commands/addresses.js.map +2 -2
  93. package/dist/modules/customers/commands/comments.js +5 -5
  94. package/dist/modules/customers/commands/comments.js.map +2 -2
  95. package/dist/modules/customers/commands/deals.js +2 -2
  96. package/dist/modules/customers/commands/deals.js.map +2 -2
  97. package/dist/modules/customers/commands/entity-roles.js +2 -1
  98. package/dist/modules/customers/commands/entity-roles.js.map +2 -2
  99. package/dist/modules/customers/commands/interactions.js +8 -5
  100. package/dist/modules/customers/commands/interactions.js.map +2 -2
  101. package/dist/modules/customers/commands/shared.js +21 -6
  102. package/dist/modules/customers/commands/shared.js.map +2 -2
  103. package/dist/modules/customers/commands/tags.js +3 -3
  104. package/dist/modules/customers/commands/tags.js.map +2 -2
  105. package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
  106. package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
  107. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
  108. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
  109. package/dist/modules/customers/components/detail/DealForm.js +100 -17
  110. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  111. package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
  112. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  113. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
  114. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  115. package/dist/modules/customers/components/detail/assignableStaff.js +21 -8
  116. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  117. package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
  118. package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
  119. package/dist/modules/customers/lib/dealsMetrics.js +82 -0
  120. package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
  121. package/dist/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.js.map +1 -1
  122. package/dist/modules/data_sync/api/run.js +1 -1
  123. package/dist/modules/data_sync/api/run.js.map +2 -2
  124. package/dist/modules/directory/api/organization-branding/route.js +214 -0
  125. package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
  126. package/dist/modules/directory/api/organizations/route.js +7 -0
  127. package/dist/modules/directory/api/organizations/route.js.map +3 -3
  128. package/dist/modules/directory/backend/directory/branding/page.js +214 -0
  129. package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
  130. package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
  131. package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
  132. package/dist/modules/directory/commands/organizations.js +8 -1
  133. package/dist/modules/directory/commands/organizations.js.map +2 -2
  134. package/dist/modules/directory/data/entities.js +3 -0
  135. package/dist/modules/directory/data/entities.js.map +2 -2
  136. package/dist/modules/directory/data/validators.js +9 -0
  137. package/dist/modules/directory/data/validators.js.map +2 -2
  138. package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
  139. package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
  140. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
  141. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
  142. package/dist/modules/directory/utils/organizationScope.js +59 -27
  143. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  144. package/dist/modules/entities/api/definitions.batch.js +2 -1
  145. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  146. package/dist/modules/entities/api/entities.js +7 -0
  147. package/dist/modules/entities/api/entities.js.map +2 -2
  148. package/dist/modules/entities/api/records.js +26 -15
  149. package/dist/modules/entities/api/records.js.map +2 -2
  150. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
  151. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  152. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
  153. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
  154. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
  155. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  156. package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
  157. package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
  158. package/dist/modules/payment_gateways/api/transactions/route.js +2 -4
  159. package/dist/modules/payment_gateways/api/transactions/route.js.map +2 -2
  160. package/dist/modules/progress/api/jobs/[id]/route.js +7 -2
  161. package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
  162. package/dist/modules/progress/api/jobs/route.js +1 -1
  163. package/dist/modules/progress/api/jobs/route.js.map +2 -2
  164. package/dist/modules/progress/lib/progressServiceImpl.js +8 -2
  165. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  166. package/dist/modules/query_index/data/entities.js +2 -1
  167. package/dist/modules/query_index/data/entities.js.map +2 -2
  168. package/dist/modules/query_index/lib/engine.js +4 -2
  169. package/dist/modules/query_index/lib/engine.js.map +2 -2
  170. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
  171. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
  172. package/dist/modules/resources/api/resources.js +2 -3
  173. package/dist/modules/resources/api/resources.js.map +2 -2
  174. package/dist/modules/sales/api/documents/factory.js +2 -2
  175. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  176. package/dist/modules/sales/commands/documents.js +7 -5
  177. package/dist/modules/sales/commands/documents.js.map +2 -2
  178. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
  179. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  180. package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
  181. package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
  182. package/dist/modules/staff/api/team-members.js +9 -2
  183. package/dist/modules/staff/api/team-members.js.map +2 -2
  184. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
  185. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  186. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
  187. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  188. package/dist/modules/staff/commands/team-members.js +1 -1
  189. package/dist/modules/staff/commands/team-members.js.map +2 -2
  190. package/dist/modules/staff/components/TeamMemberForm.js +1 -1
  191. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  192. package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
  193. package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
  194. package/dist/modules/sync_excel/api/import/route.js +1 -1
  195. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  196. package/dist/modules/workflows/api/definitions/route.js +3 -2
  197. package/dist/modules/workflows/api/definitions/route.js.map +2 -2
  198. package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
  199. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  200. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
  201. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  202. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
  203. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  204. package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
  205. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  206. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
  207. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
  208. package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
  209. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  210. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
  211. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
  212. package/generated/entities/organization/index.ts +1 -0
  213. package/generated/entity-fields-registry.ts +1 -0
  214. package/package.json +11 -12
  215. package/src/bootstrap.ts +65 -7
  216. package/src/helpers/integration/crmFixtures.ts +21 -1
  217. package/src/modules/attachments/AGENTS.md +79 -0
  218. package/src/modules/attachments/api/library/route.ts +2 -2
  219. package/src/modules/attachments/api/route.ts +2 -0
  220. package/src/modules/attachments/components/AttachmentContentPreview.tsx +6 -6
  221. package/src/modules/attachments/lib/access.ts +36 -0
  222. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +14 -2
  223. package/src/modules/audit_logs/data/entities.ts +1 -0
  224. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
  225. package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
  226. package/src/modules/audit_logs/services/accessLogService.ts +15 -0
  227. package/src/modules/auth/api/admin/nav.ts +9 -0
  228. package/src/modules/auth/api/login.ts +13 -13
  229. package/src/modules/auth/commands/users.ts +32 -15
  230. package/src/modules/auth/data/entities.ts +13 -1
  231. package/src/modules/auth/i18n/de.json +0 -1
  232. package/src/modules/auth/i18n/en.json +0 -1
  233. package/src/modules/auth/i18n/es.json +0 -1
  234. package/src/modules/auth/i18n/pl.json +0 -1
  235. package/src/modules/auth/lib/backendChrome.tsx +37 -1
  236. package/src/modules/auth/lib/consentIntegrity.ts +6 -3
  237. package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -10
  238. package/src/modules/auth/migrations/Migration20260610120000.ts +53 -0
  239. package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
  240. package/src/modules/auth/services/authService.ts +24 -4
  241. package/src/modules/auth/services/rbacService.ts +11 -2
  242. package/src/modules/catalog/ai-tools/configuration-pack.ts +1 -1
  243. package/src/modules/catalog/ai-tools/prices-offers-pack.ts +1 -1
  244. package/src/modules/catalog/ai-tools/products-pack.ts +1 -1
  245. package/src/modules/catalog/ai-tools/variants-pack.ts +1 -1
  246. package/src/modules/communication_channels/data/entities.ts +2 -2
  247. package/src/modules/communication_channels/encryption.ts +1 -1
  248. package/src/modules/communication_channels/lib/adapter.ts +1 -1
  249. package/src/modules/communication_channels/lib/thread-matcher.ts +1 -1
  250. package/src/modules/communication_channels/lib/thread-token.ts +1 -1
  251. package/src/modules/currencies/api/currencies/route.ts +4 -3
  252. package/src/modules/customer_accounts/api/admin/roles.ts +2 -1
  253. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
  254. package/src/modules/customer_accounts/events.ts +1 -1
  255. package/src/modules/customer_accounts/lib/resolveTenantContext.ts +2 -2
  256. package/src/modules/customers/acl.ts +1 -1
  257. package/src/modules/customers/ai-tools/companies-pack.ts +1 -1
  258. package/src/modules/customers/ai-tools/deals-pack.ts +1 -1
  259. package/src/modules/customers/ai-tools/people-pack.ts +1 -1
  260. package/src/modules/customers/api/companies/route.ts +4 -4
  261. package/src/modules/customers/api/deals/route.ts +51 -2
  262. package/src/modules/customers/api/deals/summary/route.ts +496 -0
  263. package/src/modules/customers/api/people/route.ts +4 -4
  264. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
  265. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
  266. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
  267. package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
  268. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
  269. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
  270. package/src/modules/customers/cli.ts +15 -15
  271. package/src/modules/customers/commands/addresses.ts +5 -5
  272. package/src/modules/customers/commands/comments.ts +5 -5
  273. package/src/modules/customers/commands/deals.ts +2 -2
  274. package/src/modules/customers/commands/entity-roles.ts +2 -1
  275. package/src/modules/customers/commands/interactions.ts +8 -5
  276. package/src/modules/customers/commands/shared.ts +26 -4
  277. package/src/modules/customers/commands/tags.ts +3 -3
  278. package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
  279. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
  280. package/src/modules/customers/components/detail/DealForm.tsx +121 -19
  281. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
  282. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
  283. package/src/modules/customers/components/detail/assignableStaff.ts +32 -8
  284. package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
  285. package/src/modules/customers/i18n/de.json +43 -0
  286. package/src/modules/customers/i18n/en.json +43 -0
  287. package/src/modules/customers/i18n/es.json +43 -0
  288. package/src/modules/customers/i18n/pl.json +43 -0
  289. package/src/modules/customers/lib/dealsMetrics.ts +159 -0
  290. package/src/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.ts +1 -1
  291. package/src/modules/data_sync/api/run.ts +1 -1
  292. package/src/modules/directory/api/organization-branding/route.ts +238 -0
  293. package/src/modules/directory/api/organizations/route.ts +7 -0
  294. package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
  295. package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
  296. package/src/modules/directory/commands/organizations.ts +9 -1
  297. package/src/modules/directory/data/entities.ts +3 -0
  298. package/src/modules/directory/data/validators.ts +12 -0
  299. package/src/modules/directory/i18n/de.json +21 -0
  300. package/src/modules/directory/i18n/en.json +21 -0
  301. package/src/modules/directory/i18n/es.json +21 -0
  302. package/src/modules/directory/i18n/pl.json +21 -0
  303. package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
  304. package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
  305. package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
  306. package/src/modules/directory/utils/organizationScope.ts +85 -30
  307. package/src/modules/entities/api/definitions.batch.ts +11 -7
  308. package/src/modules/entities/api/entities.ts +11 -0
  309. package/src/modules/entities/api/records.ts +46 -25
  310. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
  311. package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
  312. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
  313. package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
  314. package/src/modules/entities/i18n/de.json +1 -0
  315. package/src/modules/entities/i18n/en.json +1 -0
  316. package/src/modules/entities/i18n/es.json +1 -0
  317. package/src/modules/entities/i18n/pl.json +1 -0
  318. package/src/modules/payment_gateways/api/transactions/route.ts +2 -5
  319. package/src/modules/progress/api/jobs/[id]/route.ts +6 -1
  320. package/src/modules/progress/api/jobs/route.ts +1 -1
  321. package/src/modules/progress/lib/progressServiceImpl.ts +7 -1
  322. package/src/modules/query_index/data/entities.ts +1 -0
  323. package/src/modules/query_index/lib/engine.ts +11 -5
  324. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
  325. package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
  326. package/src/modules/resources/api/resources.ts +2 -3
  327. package/src/modules/sales/api/documents/factory.ts +2 -2
  328. package/src/modules/sales/commands/documents.ts +7 -5
  329. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
  330. package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
  331. package/src/modules/staff/AGENTS.md +1 -1
  332. package/src/modules/staff/api/team-members.ts +9 -2
  333. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
  334. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
  335. package/src/modules/staff/commands/team-members.ts +5 -2
  336. package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
  337. package/src/modules/staff/i18n/de.json +1 -0
  338. package/src/modules/staff/i18n/en.json +1 -0
  339. package/src/modules/staff/i18n/es.json +1 -0
  340. package/src/modules/staff/i18n/pl.json +1 -0
  341. package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
  342. package/src/modules/sync_excel/api/import/route.ts +1 -1
  343. package/src/modules/workflows/api/definitions/route.ts +3 -2
  344. package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
  345. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
  346. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
  347. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
  348. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
  349. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
  350. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
@@ -23,22 +23,46 @@ export type AssignableStaffMembersPage = {
23
23
  pageSize: number
24
24
  }
25
25
 
26
+ // The assignable-staff roster is owned by the optional, ejectable `staff` module.
27
+ // When that module is disabled, its `/api/staff/team-members/assignable` endpoint is
28
+ // absent and the request resolves to 404. Customers UI (deals / people / companies
29
+ // owner filters, role-assignment dialogs) is core and always enabled, so it must not
30
+ // break in that case — a missing staff module simply means there is no roster to
31
+ // offer. Treat the 404 as an empty page and let any other failure propagate.
32
+ function isAssignableEndpointMissing(error: unknown): boolean {
33
+ return (
34
+ typeof error === 'object' &&
35
+ error !== null &&
36
+ (error as { status?: unknown }).status === 404
37
+ )
38
+ }
39
+
26
40
  export async function fetchAssignableStaffMembersPage(
27
41
  query: string,
28
42
  options?: { page?: number; pageSize?: number; signal?: AbortSignal },
29
43
  ): Promise<AssignableStaffMembersPage> {
44
+ const page = options?.page ?? 1
45
+ const pageSize = options?.pageSize ?? 24
30
46
  const params = new URLSearchParams()
31
- params.set('page', String(options?.page ?? 1))
32
- params.set('pageSize', String(options?.pageSize ?? 24))
47
+ params.set('page', String(page))
48
+ params.set('pageSize', String(pageSize))
33
49
  const normalizedQuery = query.trim()
34
50
  if (normalizedQuery.length > 0) {
35
51
  params.set('search', normalizedQuery)
36
52
  }
37
53
 
38
- const data = await readApiResultOrThrow<AssignableStaffResponse>(
39
- `/api/staff/team-members/assignable?${params.toString()}`,
40
- options?.signal ? { signal: options.signal } : undefined,
41
- )
54
+ let data: AssignableStaffResponse
55
+ try {
56
+ data = await readApiResultOrThrow<AssignableStaffResponse>(
57
+ `/api/staff/team-members/assignable?${params.toString()}`,
58
+ options?.signal ? { signal: options.signal } : undefined,
59
+ )
60
+ } catch (error) {
61
+ if (isAssignableEndpointMissing(error)) {
62
+ return { items: [], total: 0, page, pageSize }
63
+ }
64
+ throw error
65
+ }
42
66
 
43
67
  const rawItems = Array.isArray(data?.items) ? data.items : []
44
68
  const deduped = new Map<string, AssignableStaffMember>()
@@ -108,11 +132,11 @@ export async function fetchAssignableStaffMembersPage(
108
132
  page:
109
133
  typeof data?.page === 'number' && Number.isFinite(data.page)
110
134
  ? data.page
111
- : options?.page ?? 1,
135
+ : page,
112
136
  pageSize:
113
137
  typeof data?.pageSize === 'number' && Number.isFinite(data.pageSize)
114
138
  ? data.pageSize
115
- : options?.pageSize ?? 24,
139
+ : pageSize,
116
140
  }
117
141
  }
118
142
 
@@ -0,0 +1,77 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '@open-mercato/shared/lib/utils'
5
+ import type { DictionaryMap } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
6
+
7
+ export type PipelineStageDatum = {
8
+ stage: string | null
9
+ count: number
10
+ value: number
11
+ }
12
+
13
+ export type PipelineStageBarProps = {
14
+ stages: PipelineStageDatum[]
15
+ stageDictionary: DictionaryMap
16
+ unassignedLabel: string
17
+ }
18
+
19
+ function resolveStageColor(stage: string | null, stageDictionary: DictionaryMap): string | null {
20
+ if (!stage) return null
21
+ const entry = stageDictionary[stage]
22
+ return entry?.color ?? null
23
+ }
24
+
25
+ function resolveStageLabel(stage: string | null, stageDictionary: DictionaryMap, unassignedLabel: string): string {
26
+ if (!stage) return unassignedLabel
27
+ return stageDictionary[stage]?.label ?? unassignedLabel
28
+ }
29
+
30
+ export function PipelineStageBar({ stages, stageDictionary, unassignedLabel }: PipelineStageBarProps) {
31
+ const segments = React.useMemo(
32
+ () => stages.filter((entry) => entry.count > 0),
33
+ [stages],
34
+ )
35
+
36
+ if (segments.length === 0) return null
37
+
38
+ return (
39
+ <div className="space-y-2">
40
+ <div className="flex h-2 w-full gap-1 overflow-hidden rounded-full">
41
+ {segments.map((entry, index) => {
42
+ const color = resolveStageColor(entry.stage, stageDictionary)
43
+ return (
44
+ <span
45
+ key={entry.stage ?? `unassigned-${index}`}
46
+ className={cn('h-full rounded-full', color ? null : 'bg-muted')}
47
+ style={{ flexGrow: entry.count, ...(color ? { backgroundColor: color } : {}) }}
48
+ aria-hidden
49
+ />
50
+ )
51
+ })}
52
+ </div>
53
+ <ul className="flex flex-wrap gap-x-3 gap-y-1">
54
+ {segments.map((entry, index) => {
55
+ const color = resolveStageColor(entry.stage, stageDictionary)
56
+ const label = resolveStageLabel(entry.stage, stageDictionary, unassignedLabel)
57
+ return (
58
+ <li
59
+ key={entry.stage ?? `unassigned-legend-${index}`}
60
+ className="inline-flex items-center gap-1.5 text-xs text-muted-foreground"
61
+ >
62
+ <span
63
+ className={cn('inline-block h-2 w-2 rounded-full border border-border', color ? null : 'bg-muted')}
64
+ style={color ? { backgroundColor: color } : undefined}
65
+ aria-hidden
66
+ />
67
+ <span>{label}</span>
68
+ <span className="font-medium text-foreground">{entry.count}</span>
69
+ </li>
70
+ )
71
+ })}
72
+ </ul>
73
+ </div>
74
+ )
75
+ }
76
+
77
+ export default PipelineStageBar
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Ausgewählte Deals löschen",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Verloren",
1158
+ "customers.deals.list.close.overdue": "Überfällig",
1159
+ "customers.deals.list.close.won": "Gewonnen",
1157
1160
  "customers.deals.list.columns.companies": "Unternehmen",
1158
1161
  "customers.deals.list.columns.expectedClose": "Voraussichtlicher Abschluss",
1159
1162
  "customers.deals.list.columns.owner": "Verantwortlich",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "Geschäftsliste konnte nicht geladen werden",
1172
1175
  "customers.deals.list.filters.companies": "Unternehmen",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Nach Unternehmen filtern",
1177
+ "customers.deals.list.filters.needsAttention": "Benötigt Aufmerksamkeit",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Filter \"Benötigt Aufmerksamkeit\" entfernen",
1174
1179
  "customers.deals.list.filters.people": "Personen",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Nach Personen filtern",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} in {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Aktive Deals",
1183
+ "customers.deals.list.kpi.avgDeal": "Ø Deal {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Näherungswert; Deals in {currencies} ohne Wechselkurs sind ausgeschlossen.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Näherungswert; keine Basiswährung ist konfiguriert.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "Im Vergleich zum Vorzeitraum",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "Keine vergleichbare Änderung",
1188
+ "customers.deals.list.kpi.error": "Metriken konnten nicht geladen werden",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} aktive Deals",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} aktive Deals",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} aktiver Deal",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} aktive Deals",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} Deals in diesem Quartal abgeschlossen",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} Deals in diesem Quartal abgeschlossen",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} Deal in diesem Quartal abgeschlossen",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} Deals in diesem Quartal abgeschlossen",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} benötigen Aufmerksamkeit",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} benötigen Aufmerksamkeit",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} benötigt Aufmerksamkeit",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} benötigen Aufmerksamkeit",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} Inhaber",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} Inhaber",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} Inhaber",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} Inhaber",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} Pipelines",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} Pipelines",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} Pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} Pipelines",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "von {value}% im letzten Quartal",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Pipeline-Wert",
1212
+ "customers.deals.list.kpi.retry": "Erneut versuchen",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "Alle Pipelines · dieses Quartal",
1214
+ "customers.deals.list.kpi.unassignedStage": "Nicht zugewiesen",
1215
+ "customers.deals.list.kpi.updating": "Metriken werden aktualisiert",
1216
+ "customers.deals.list.kpi.winRate": "Gewinnrate",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Gewonnen dieses Quartal",
1176
1218
  "customers.deals.list.noValue": "Nicht gesetzt",
1177
1219
  "customers.deals.list.refresh": "Aktualisieren",
1178
1220
  "customers.deals.list.searchPlaceholder": "Geschäfte suchen…",
1179
1221
  "customers.deals.list.title": "Geschäfte",
1222
+ "customers.deals.list.unknownOwner": "Unbekannter Inhaber",
1180
1223
  "customers.deals.list.unnamedCompany": "Unbenanntes Unternehmen",
1181
1224
  "customers.deals.list.unnamedPerson": "Unbenannte Person",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Deal öffnen",
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Delete selected deals",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Lost",
1158
+ "customers.deals.list.close.overdue": "Overdue",
1159
+ "customers.deals.list.close.won": "Won",
1157
1160
  "customers.deals.list.columns.companies": "Companies",
1158
1161
  "customers.deals.list.columns.expectedClose": "Expected close",
1159
1162
  "customers.deals.list.columns.owner": "Owner",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "Unable to load deals list",
1172
1175
  "customers.deals.list.filters.companies": "Companies",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Filter by companies",
1177
+ "customers.deals.list.filters.needsAttention": "Needs attention",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Remove needs attention filter",
1174
1179
  "customers.deals.list.filters.people": "People",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Filter by people",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} across {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Active deals",
1183
+ "customers.deals.list.kpi.avgDeal": "avg deal {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Approximate; deals in {currencies} without a rate are excluded.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Approximate; no base currency is configured.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "Compared to previous period",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "No comparable change",
1188
+ "customers.deals.list.kpi.error": "Couldn't load deal metrics",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} active deals",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} active deals",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} active deal",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} active deals",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} deals closed this quarter",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} deals closed this quarter",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} deal closed this quarter",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} deals closed this quarter",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} need attention",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} need attention",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} needs attention",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} need attention",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} owners",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} owners",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} owner",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} owners",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} pipelines",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} pipelines",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} pipelines",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "from {value}% last quarter",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Pipeline value",
1212
+ "customers.deals.list.kpi.retry": "Retry",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "All pipelines · this quarter",
1214
+ "customers.deals.list.kpi.unassignedStage": "Unassigned",
1215
+ "customers.deals.list.kpi.updating": "Updating metrics",
1216
+ "customers.deals.list.kpi.winRate": "Win rate",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Won this quarter",
1176
1218
  "customers.deals.list.noValue": "Not set",
1177
1219
  "customers.deals.list.refresh": "Refresh",
1178
1220
  "customers.deals.list.searchPlaceholder": "Search by title, description…",
1179
1221
  "customers.deals.list.title": "Deals",
1222
+ "customers.deals.list.unknownOwner": "Unknown owner",
1180
1223
  "customers.deals.list.unnamedCompany": "Unnamed company",
1181
1224
  "customers.deals.list.unnamedPerson": "Unnamed person",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Open deal",
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Eliminar negocios seleccionados",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Perdido",
1158
+ "customers.deals.list.close.overdue": "Vencido",
1159
+ "customers.deals.list.close.won": "Ganado",
1157
1160
  "customers.deals.list.columns.companies": "Empresas",
1158
1161
  "customers.deals.list.columns.expectedClose": "Cierre esperado",
1159
1162
  "customers.deals.list.columns.owner": "Responsable",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "No se pudo cargar la lista de negocios",
1172
1175
  "customers.deals.list.filters.companies": "Empresas",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Filtrar por empresas",
1177
+ "customers.deals.list.filters.needsAttention": "Requiere atención",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Quitar filtro de requiere atención",
1174
1179
  "customers.deals.list.filters.people": "Personas",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Filtrar por personas",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} en {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Negocios activos",
1183
+ "customers.deals.list.kpi.avgDeal": "negocio medio {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Aproximado; se excluyen los negocios en {currencies} sin tipo de cambio.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Aproximado; no hay moneda base configurada.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "Comparado con el período anterior",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "Sin cambio comparable",
1188
+ "customers.deals.list.kpi.error": "No se pudieron cargar las métricas",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} negocios activos",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} negocios activos",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} negocio activo",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} negocios activos",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} negocios cerrados este trimestre",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} negocios cerrados este trimestre",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} negocio cerrado este trimestre",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} negocios cerrados este trimestre",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} requieren atención",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} requieren atención",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} requiere atención",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} requieren atención",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} responsables",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} responsables",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} responsable",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} responsables",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} pipelines",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} pipelines",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} pipelines",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "desde {value}% el trimestre pasado",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Valor del pipeline",
1212
+ "customers.deals.list.kpi.retry": "Reintentar",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "Todos los pipelines · este trimestre",
1214
+ "customers.deals.list.kpi.unassignedStage": "Sin asignar",
1215
+ "customers.deals.list.kpi.updating": "Actualizando métricas",
1216
+ "customers.deals.list.kpi.winRate": "Tasa de éxito",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Ganados este trimestre",
1176
1218
  "customers.deals.list.noValue": "Sin definir",
1177
1219
  "customers.deals.list.refresh": "Actualizar",
1178
1220
  "customers.deals.list.searchPlaceholder": "Buscar negocios…",
1179
1221
  "customers.deals.list.title": "Negocios",
1222
+ "customers.deals.list.unknownOwner": "Responsable desconocido",
1180
1223
  "customers.deals.list.unnamedCompany": "Empresa sin nombre",
1181
1224
  "customers.deals.list.unnamedPerson": "Persona sin nombre",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Abrir oportunidad",
@@ -1154,6 +1154,9 @@
1154
1154
  "customers.deals.list.bulkDelete.progressName": "Usuń zaznaczone transakcje",
1155
1155
  "customers.deals.list.bulkDelete.success": "{count} deals deleted",
1156
1156
  "customers.deals.list.bulkDelete.title": "Delete {count} deals?",
1157
+ "customers.deals.list.close.lost": "Przegrana",
1158
+ "customers.deals.list.close.overdue": "Po terminie",
1159
+ "customers.deals.list.close.won": "Wygrana",
1157
1160
  "customers.deals.list.columns.companies": "Firmy",
1158
1161
  "customers.deals.list.columns.expectedClose": "Planowana finalizacja",
1159
1162
  "customers.deals.list.columns.owner": "Właściciel",
@@ -1171,12 +1174,52 @@
1171
1174
  "customers.deals.list.error.load": "Nie udało się wczytać listy szans",
1172
1175
  "customers.deals.list.filters.companies": "Firmy",
1173
1176
  "customers.deals.list.filters.companiesPlaceholder": "Filtruj po firmach",
1177
+ "customers.deals.list.filters.needsAttention": "Wymaga uwagi",
1178
+ "customers.deals.list.filters.needsAttentionRemove": "Usuń filtr wymaga uwagi",
1174
1179
  "customers.deals.list.filters.people": "Osoby",
1175
1180
  "customers.deals.list.filters.peoplePlaceholder": "Filtruj po osobach",
1181
+ "customers.deals.list.kpi.activeAcrossPipelines": "{deals} w {pipelines}",
1182
+ "customers.deals.list.kpi.activeDeals": "Aktywne szanse",
1183
+ "customers.deals.list.kpi.avgDeal": "śr. szansa {value}",
1184
+ "customers.deals.list.kpi.currencyApproxMissing": "Wartość przybliżona; szanse w {currencies} bez kursu są pomijane.",
1185
+ "customers.deals.list.kpi.currencyApproxNoBase": "Wartość przybliżona; nie skonfigurowano waluty bazowej.",
1186
+ "customers.deals.list.kpi.deltaTooltip": "W porównaniu z poprzednim okresem",
1187
+ "customers.deals.list.kpi.deltaUnavailable": "Brak porównywalnej zmiany",
1188
+ "customers.deals.list.kpi.error": "Nie udało się załadować metryk",
1189
+ "customers.deals.list.kpi.frag.activeDeals.few": "{count} aktywne szanse",
1190
+ "customers.deals.list.kpi.frag.activeDeals.many": "{count} aktywnych szans",
1191
+ "customers.deals.list.kpi.frag.activeDeals.one": "{count} aktywna szansa",
1192
+ "customers.deals.list.kpi.frag.activeDeals.other": "{count} aktywnych szans",
1193
+ "customers.deals.list.kpi.frag.dealsClosed.few": "{count} szanse zamknięte w tym kwartale",
1194
+ "customers.deals.list.kpi.frag.dealsClosed.many": "{count} szans zamkniętych w tym kwartale",
1195
+ "customers.deals.list.kpi.frag.dealsClosed.one": "{count} szansa zamknięta w tym kwartale",
1196
+ "customers.deals.list.kpi.frag.dealsClosed.other": "{count} szans zamkniętych w tym kwartale",
1197
+ "customers.deals.list.kpi.frag.needAttention.few": "{count} wymagają uwagi",
1198
+ "customers.deals.list.kpi.frag.needAttention.many": "{count} wymaga uwagi",
1199
+ "customers.deals.list.kpi.frag.needAttention.one": "{count} wymaga uwagi",
1200
+ "customers.deals.list.kpi.frag.needAttention.other": "{count} wymaga uwagi",
1201
+ "customers.deals.list.kpi.frag.owners.few": "{count} właścicieli",
1202
+ "customers.deals.list.kpi.frag.owners.many": "{count} właścicieli",
1203
+ "customers.deals.list.kpi.frag.owners.one": "{count} właściciel",
1204
+ "customers.deals.list.kpi.frag.owners.other": "{count} właścicieli",
1205
+ "customers.deals.list.kpi.frag.pipelines.few": "{count} pipeline'y",
1206
+ "customers.deals.list.kpi.frag.pipelines.many": "{count} pipeline'ów",
1207
+ "customers.deals.list.kpi.frag.pipelines.one": "{count} pipeline",
1208
+ "customers.deals.list.kpi.frag.pipelines.other": "{count} pipeline'ów",
1209
+ "customers.deals.list.kpi.fromLastQuarter": "z {value}% w zeszłym kwartale",
1210
+ "customers.deals.list.kpi.ownersNeedAttention": "{owners} · {attention}",
1211
+ "customers.deals.list.kpi.pipelineValue": "Wartość pipeline'u",
1212
+ "customers.deals.list.kpi.retry": "Ponów",
1213
+ "customers.deals.list.kpi.scopeAllPipelinesThisQuarter": "Wszystkie pipeline'y · ten kwartał",
1214
+ "customers.deals.list.kpi.unassignedStage": "Nieprzypisany",
1215
+ "customers.deals.list.kpi.updating": "Aktualizowanie metryk",
1216
+ "customers.deals.list.kpi.winRate": "Skuteczność",
1217
+ "customers.deals.list.kpi.wonThisQuarter": "Wygrane w tym kwartale",
1176
1218
  "customers.deals.list.noValue": "Brak danych",
1177
1219
  "customers.deals.list.refresh": "Odśwież",
1178
1220
  "customers.deals.list.searchPlaceholder": "Szukaj szans…",
1179
1221
  "customers.deals.list.title": "Szanse",
1222
+ "customers.deals.list.unknownOwner": "Nieznany właściciel",
1180
1223
  "customers.deals.list.unnamedCompany": "Firma bez nazwy",
1181
1224
  "customers.deals.list.unnamedPerson": "Osoba bez nazwy",
1182
1225
  "customers.deals.pipeline.actions.openDeal": "Otwórz szansę",
@@ -0,0 +1,159 @@
1
+ import type { RateResult } from '@open-mercato/core/modules/currencies/services/exchangeRateService'
2
+
3
+ /**
4
+ * Quarter / period helpers for the deals KPI summary. Computed in **UTC** so the
5
+ * window boundaries are stable regardless of the server timezone — `expected_close_at`
6
+ * is a bare `Date` (date-only) while `created_at` / `updated_at` are timestamptz, and
7
+ * mixing local-time boundaries would misbucket deals near a quarter edge.
8
+ */
9
+
10
+ export type PeriodWindow = {
11
+ /** Inclusive lower bound (UTC). */
12
+ start: Date
13
+ /** Exclusive upper bound (UTC). */
14
+ end: Date
15
+ }
16
+
17
+ export type TrailingMonth = {
18
+ /** Inclusive lower bound (UTC) of the month bucket. */
19
+ start: Date
20
+ /** 'YYYY-MM' label for the bucket. */
21
+ label: string
22
+ }
23
+
24
+ export type DeltaDirection = 'up' | 'down' | 'unchanged'
25
+
26
+ export type Delta = {
27
+ value: number
28
+ direction: DeltaDirection
29
+ }
30
+
31
+ export type CurrencySum = {
32
+ currency: string
33
+ total: number
34
+ }
35
+
36
+ export type ConvertedSums = {
37
+ total: number
38
+ convertedAll: boolean
39
+ missingRateCurrencies: string[]
40
+ }
41
+
42
+ function startOfQuarterUtc(year: number, quarterStartMonth: number): Date {
43
+ return new Date(Date.UTC(year, quarterStartMonth, 1, 0, 0, 0, 0))
44
+ }
45
+
46
+ /**
47
+ * Returns the [start, end) window of the calendar quarter that contains `now`,
48
+ * in UTC. Quarters are fixed 3-month blocks: Jan–Mar, Apr–Jun, Jul–Sep, Oct–Dec.
49
+ * `end` is exclusive (the start of the next quarter).
50
+ */
51
+ export function getQuarterWindow(now: Date): PeriodWindow {
52
+ const year = now.getUTCFullYear()
53
+ const quarterIndex = Math.floor(now.getUTCMonth() / 3)
54
+ const startMonth = quarterIndex * 3
55
+ const start = startOfQuarterUtc(year, startMonth)
56
+ const end = startOfQuarterUtc(year, startMonth + 3)
57
+ return { start, end }
58
+ }
59
+
60
+ /**
61
+ * Returns the [start, end) window of the quarter immediately preceding the one
62
+ * that contains `now`, in UTC. `end` is exclusive and equals the current quarter's start.
63
+ */
64
+ export function getPreviousQuarterWindow(now: Date): PeriodWindow {
65
+ const current = getQuarterWindow(now)
66
+ const start = startOfQuarterUtc(current.start.getUTCFullYear(), current.start.getUTCMonth() - 3)
67
+ return { start, end: current.start }
68
+ }
69
+
70
+ function monthLabel(year: number, monthIndex: number): string {
71
+ const month = monthIndex + 1
72
+ return `${year}-${month < 10 ? `0${month}` : month}`
73
+ }
74
+
75
+ /**
76
+ * Returns `count` trailing month buckets ending with the month that contains `now`,
77
+ * ordered oldest → newest. Each bucket exposes its UTC start and a 'YYYY-MM' label.
78
+ * Used to drive the win-rate sparkline series.
79
+ */
80
+ export function getTrailingMonths(now: Date, count: number): TrailingMonth[] {
81
+ const buckets: TrailingMonth[] = []
82
+ const baseYear = now.getUTCFullYear()
83
+ const baseMonth = now.getUTCMonth()
84
+ for (let offset = count - 1; offset >= 0; offset -= 1) {
85
+ const start = new Date(Date.UTC(baseYear, baseMonth - offset, 1, 0, 0, 0, 0))
86
+ buckets.push({ start, label: monthLabel(start.getUTCFullYear(), start.getUTCMonth()) })
87
+ }
88
+ return buckets
89
+ }
90
+
91
+ /**
92
+ * Percentage change of `current` relative to `previous`, rounded to whole percent.
93
+ * When there is no previous-period baseline, avoid reporting artificial growth.
94
+ */
95
+ export function computeDelta(current: number, previous: number): Delta {
96
+ if (previous === 0) {
97
+ return { value: 0, direction: 'unchanged' }
98
+ }
99
+ const change = ((current - previous) / Math.abs(previous)) * 100
100
+ const value = Math.round(change)
101
+ if (value > 0) return { value, direction: 'up' }
102
+ if (value < 0) return { value, direction: 'down' }
103
+ return { value: 0, direction: 'unchanged' }
104
+ }
105
+
106
+ function extractRate(result: RateResult | undefined): number | null {
107
+ if (!result || result.rates.length === 0) return null
108
+ const rate = Number(result.rates[0].rate)
109
+ if (!Number.isFinite(rate) || rate <= 0) return null
110
+ return rate
111
+ }
112
+
113
+ /**
114
+ * Converts per-currency sums to the tenant base currency, mirroring the conversion
115
+ * logic in `api/deals/aggregate/route.ts`:
116
+ * - the base currency stays 1:1,
117
+ * - other currencies multiply by the rate from `rates` (keyed `"FROM/BASE"`),
118
+ * - a currency with no usable rate is excluded from `total` and flagged in
119
+ * `missingRateCurrencies` (with `convertedAll: false`).
120
+ *
121
+ * When `baseCode` is null there is no base currency configured, so nothing can be
122
+ * converted: every present currency is reported as missing and `convertedAll` is false.
123
+ *
124
+ * `rates` accepts the `Map<string, RateResult>` shape returned by
125
+ * `exchangeRateService.getRates` so callers can pass its output directly.
126
+ */
127
+ export function convertSumsToBase(
128
+ perCurrency: CurrencySum[],
129
+ baseCode: string | null,
130
+ rates: Map<string, RateResult>,
131
+ ): ConvertedSums {
132
+ if (!baseCode) {
133
+ const missing = Array.from(
134
+ new Set(perCurrency.map((entry) => entry.currency).filter((code): code is string => Boolean(code))),
135
+ )
136
+ return { total: 0, convertedAll: missing.length === 0, missingRateCurrencies: missing }
137
+ }
138
+
139
+ let total = 0
140
+ let convertedAll = true
141
+ const missingRateCurrencies: string[] = []
142
+ for (const entry of perCurrency) {
143
+ if (!entry.currency) continue
144
+ if (entry.currency === baseCode) {
145
+ total += entry.total
146
+ continue
147
+ }
148
+ const rate = extractRate(rates.get(`${entry.currency}/${baseCode}`))
149
+ if (rate !== null) {
150
+ total += entry.total * rate
151
+ } else {
152
+ convertedAll = false
153
+ if (!missingRateCurrencies.includes(entry.currency)) {
154
+ missingRateCurrencies.push(entry.currency)
155
+ }
156
+ }
157
+ }
158
+ return { total: Math.round(total), convertedAll, missingRateCurrencies }
159
+ }
@@ -15,7 +15,7 @@ import { Migration } from '@mikro-orm/migrations';
15
15
  * 'pink', '', or NULL. Lane.tsx now reads the value directly as a tone identifier.
16
16
  *
17
17
  * Unknown / unmappable hex values collapse to 'neutral'. Forward-only — reverting tones to
18
- * the original hex is lossy and not supported. See `.ai/specs/2026-05-19-customers-deals-kanban-ux-review-fixes.md`.
18
+ * the original hex is lossy and not supported. See `.ai/specs/implemented/2026-05-19-customers-deals-kanban-ux-review-fixes.md`.
19
19
  */
20
20
  export class Migration20260519120000_pipeline_stage_color_tones extends Migration {
21
21
  override up(): void | Promise<void> {
@@ -107,7 +107,7 @@ export async function POST(req: Request) {
107
107
  const stack = error instanceof Error ? error.stack : undefined
108
108
  console.error('[data_sync.run] unhandled error', { message, stack })
109
109
  return NextResponse.json(
110
- { error: 'Failed to start data sync run.', message, stack },
110
+ { error: 'Failed to start data sync run.' },
111
111
  { status: 500 },
112
112
  )
113
113
  }