@open-mercato/core 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7294.1.0ef99fc518

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 (261) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  3. package/dist/modules/auth/components/AclEditor.js +21 -3
  4. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  5. package/dist/modules/customers/backend/customers/companies/page.js +6 -4
  6. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  7. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
  8. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
  9. package/dist/modules/customers/backend/customers/deals/page.js +6 -4
  10. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  11. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
  12. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
  13. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
  14. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/people/page.js +6 -4
  16. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  17. package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
  18. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  19. package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
  20. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  21. package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
  22. package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
  23. package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
  24. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  25. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
  26. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  27. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
  28. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  29. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
  30. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  31. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
  32. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  33. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
  34. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
  35. package/dist/modules/customers/components/detail/schedule/index.js +3 -1
  36. package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
  37. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
  38. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  39. package/dist/modules/customers/components/formConfig.js +17 -9
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +12 -7
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/assignableStaff.js +103 -22
  44. package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
  45. package/dist/modules/customers/setup.js +10 -2
  46. package/dist/modules/customers/setup.js.map +2 -2
  47. package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
  48. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  49. package/dist/modules/data_sync/api/schedules/route.js +5 -0
  50. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  51. package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
  52. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  53. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
  54. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  55. package/dist/modules/data_sync/data/validators.js +13 -2
  56. package/dist/modules/data_sync/data/validators.js.map +2 -2
  57. package/dist/modules/data_sync/lib/schedule-value.js +30 -0
  58. package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
  59. package/dist/modules/eudr/data/validators.js.map +2 -2
  60. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
  61. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  62. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
  63. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
  64. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
  65. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  66. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
  67. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
  68. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
  69. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
  70. package/dist/modules/portal/lib/navigation.js +3 -4
  71. package/dist/modules/portal/lib/navigation.js.map +2 -2
  72. package/dist/modules/query_index/lib/engine.js +79 -14
  73. package/dist/modules/query_index/lib/engine.js.map +2 -2
  74. package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
  75. package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
  76. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
  77. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  78. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
  79. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
  80. package/dist/modules/staff/extension-points.js +16 -0
  81. package/dist/modules/staff/extension-points.js.map +2 -2
  82. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
  83. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
  84. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
  85. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
  86. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
  87. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
  88. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
  89. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
  90. package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
  91. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  92. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
  93. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
  94. package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
  95. package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
  96. package/dist/modules/warranty_claims/api/assign/route.js +3 -3
  97. package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
  98. package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
  99. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  100. package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
  101. package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
  102. package/dist/modules/warranty_claims/api/events/route.js +4 -4
  103. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  104. package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
  105. package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
  106. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
  107. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  108. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
  109. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  110. package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
  111. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  112. package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
  113. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  114. package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
  115. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  116. package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
  117. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  118. package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
  119. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  120. package/dist/modules/warranty_claims/api/risk/route.js +2 -2
  121. package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
  122. package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
  123. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  124. package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
  125. package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
  126. package/dist/modules/warranty_claims/api/stats/route.js +2 -2
  127. package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
  128. package/dist/modules/warranty_claims/api/submit/route.js +3 -3
  129. package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
  130. package/dist/modules/warranty_claims/api/transition/route.js +3 -3
  131. package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
  132. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
  133. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
  134. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
  135. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
  136. package/dist/modules/warranty_claims/backend/page.js +4 -2
  137. package/dist/modules/warranty_claims/backend/page.js.map +2 -2
  138. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
  139. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
  140. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
  141. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
  142. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
  143. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
  144. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
  145. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
  146. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
  147. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
  148. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
  149. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  150. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
  151. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
  152. package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
  153. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  154. package/package.json +7 -7
  155. package/src/modules/auth/api/users/acl/route.ts +4 -3
  156. package/src/modules/auth/components/AclEditor.tsx +38 -2
  157. package/src/modules/auth/i18n/de.json +5 -1
  158. package/src/modules/auth/i18n/en.json +5 -1
  159. package/src/modules/auth/i18n/es.json +5 -1
  160. package/src/modules/auth/i18n/ko.json +5 -1
  161. package/src/modules/auth/i18n/pl.json +5 -1
  162. package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
  163. package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
  164. package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
  165. package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
  166. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
  167. package/src/modules/customers/backend/customers/people/page.tsx +6 -4
  168. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
  169. package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
  170. package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
  171. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
  172. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
  173. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
  174. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
  175. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
  176. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
  177. package/src/modules/customers/components/detail/schedule/index.ts +1 -1
  178. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
  179. package/src/modules/customers/components/formConfig.tsx +35 -12
  180. package/src/modules/customers/data/validators.ts +14 -8
  181. package/src/modules/customers/i18n/de.json +1 -0
  182. package/src/modules/customers/i18n/en.json +1 -0
  183. package/src/modules/customers/i18n/es.json +1 -0
  184. package/src/modules/customers/i18n/ko.json +1 -0
  185. package/src/modules/customers/i18n/pl.json +1 -0
  186. package/src/modules/customers/lib/assignableStaff.ts +172 -34
  187. package/src/modules/customers/setup.ts +8 -0
  188. package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
  189. package/src/modules/data_sync/api/schedules/route.ts +5 -0
  190. package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
  191. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
  192. package/src/modules/data_sync/data/validators.ts +26 -2
  193. package/src/modules/data_sync/i18n/de.json +2 -0
  194. package/src/modules/data_sync/i18n/en.json +2 -0
  195. package/src/modules/data_sync/i18n/es.json +2 -0
  196. package/src/modules/data_sync/i18n/ko.json +2 -0
  197. package/src/modules/data_sync/i18n/pl.json +2 -0
  198. package/src/modules/data_sync/lib/schedule-value.ts +45 -0
  199. package/src/modules/eudr/data/validators.ts +3 -4
  200. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
  201. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
  202. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
  203. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
  204. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
  205. package/src/modules/portal/i18n/de.json +1 -0
  206. package/src/modules/portal/i18n/en.json +1 -0
  207. package/src/modules/portal/i18n/es.json +1 -0
  208. package/src/modules/portal/i18n/ko.json +1 -0
  209. package/src/modules/portal/i18n/pl.json +1 -0
  210. package/src/modules/portal/lib/navigation.ts +8 -3
  211. package/src/modules/query_index/lib/engine.ts +111 -16
  212. package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
  213. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
  214. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
  215. package/src/modules/staff/extension-points.ts +16 -0
  216. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
  217. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
  218. package/src/modules/staff/i18n/de.json +3 -0
  219. package/src/modules/staff/i18n/en.json +3 -0
  220. package/src/modules/staff/i18n/es.json +3 -0
  221. package/src/modules/staff/i18n/ko.json +3 -0
  222. package/src/modules/staff/i18n/pl.json +3 -0
  223. package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
  224. package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
  225. package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
  226. package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
  227. package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
  228. package/src/modules/warranty_claims/api/assign/route.ts +3 -3
  229. package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
  230. package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
  231. package/src/modules/warranty_claims/api/events/route.ts +4 -4
  232. package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
  233. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
  234. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
  235. package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
  236. package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
  237. package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
  238. package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
  239. package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
  240. package/src/modules/warranty_claims/api/risk/route.ts +2 -2
  241. package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
  242. package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
  243. package/src/modules/warranty_claims/api/stats/route.ts +2 -2
  244. package/src/modules/warranty_claims/api/submit/route.ts +3 -3
  245. package/src/modules/warranty_claims/api/transition/route.ts +3 -3
  246. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
  247. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
  248. package/src/modules/warranty_claims/backend/page.tsx +4 -2
  249. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
  250. package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
  251. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
  252. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
  253. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
  254. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
  255. package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
  256. package/src/modules/workflows/i18n/de.json +1 -1
  257. package/src/modules/workflows/i18n/en.json +1 -1
  258. package/src/modules/workflows/i18n/es.json +1 -1
  259. package/src/modules/workflows/i18n/ko.json +1 -1
  260. package/src/modules/workflows/i18n/pl.json +1 -1
  261. package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
@@ -31,6 +31,8 @@
31
31
  "data_sync.dashboard.schedule.interval": "간격",
32
32
  "data_sync.dashboard.schedule.intervalHelp": "예: 반복 간격으로 `1h`, `6h`, `24h`를 사용하세요.",
33
33
  "data_sync.dashboard.schedule.intervalValue": "간격",
34
+ "data_sync.dashboard.schedule.invalidCron": "스케줄러가 해석할 수 있는 cron 표현식을 입력하세요. 예: `0 * * * *`.",
35
+ "data_sync.dashboard.schedule.invalidInterval": "`15m`, `1h`, `24h`처럼 정수 뒤에 s, m, h 또는 d를 붙여 1분 이상으로 입력하세요.",
34
36
  "data_sync.dashboard.schedule.invalidValue": "저장하기 전에 일정 값을 입력하세요.",
35
37
  "data_sync.dashboard.schedule.lastRun": "마지막 예약 실행: {value}",
36
38
  "data_sync.dashboard.schedule.neverRun": "저장되었지만 아직 완료된 예약 실행이 없습니다.",
@@ -31,6 +31,8 @@
31
31
  "data_sync.dashboard.schedule.interval": "Interwał",
32
32
  "data_sync.dashboard.schedule.intervalHelp": "Przykład: `1h`, `6h` lub `24h` dla powtarzalnych interwałów.",
33
33
  "data_sync.dashboard.schedule.intervalValue": "Interwał",
34
+ "data_sync.dashboard.schedule.invalidCron": "Podaj wyrażenie cron, które scheduler potrafi przetworzyć, na przykład `0 * * * *`.",
35
+ "data_sync.dashboard.schedule.invalidInterval": "Podaj liczbę całkowitą z jednostką s, m, h lub d (na przykład `15m`, `1h` lub `24h`), o długości co najmniej jednej minuty.",
34
36
  "data_sync.dashboard.schedule.invalidValue": "Podaj wartość harmonogramu przed zapisaniem.",
35
37
  "data_sync.dashboard.schedule.lastRun": "Ostatni zaplanowany przebieg: {value}",
36
38
  "data_sync.dashboard.schedule.neverRun": "Zapisano, ale żadne zaplanowane wykonanie jeszcze się nie zakończyło.",
@@ -0,0 +1,45 @@
1
+ import type { ScheduleValueKind } from '@open-mercato/shared/lib/schedule/invalidScheduleValue'
2
+
3
+ export const SCHEDULE_VALUE_FIELD = 'scheduleValue'
4
+
5
+ export const SCHEDULE_VALUE_FORMAT_MESSAGES: Record<ScheduleValueKind, string> = {
6
+ interval: 'Interval must be a whole number followed by s, m, h or d (for example 15m, 1h or 24h) and cover at least one minute.',
7
+ cron: 'Cron expression could not be parsed. Use the five-field format, for example "0 * * * *".',
8
+ }
9
+
10
+ export type ScheduleValueErrorBody = {
11
+ error: string
12
+ details: {
13
+ formErrors: string[]
14
+ fieldErrors: Record<string, string[]>
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Mirrors zod's `flatten()` shape so a scheduler-reported format failure and a
20
+ * schema-reported one reach the client as the same structured field error
21
+ * instead of an internal scheduler message.
22
+ */
23
+ export function buildScheduleValueErrorBody(scheduleType: ScheduleValueKind): ScheduleValueErrorBody {
24
+ return {
25
+ error: 'Invalid payload',
26
+ details: {
27
+ formErrors: [],
28
+ fieldErrors: { [SCHEDULE_VALUE_FIELD]: [SCHEDULE_VALUE_FORMAT_MESSAGES[scheduleType]] },
29
+ },
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Lets a form render its own translated hint next to the field instead of
35
+ * flashing the API's English message as a toast.
36
+ */
37
+ export function hasScheduleValueFieldError(payload: unknown): boolean {
38
+ if (typeof payload !== 'object' || payload === null) return false
39
+ const details = (payload as { details?: unknown }).details
40
+ if (typeof details !== 'object' || details === null) return false
41
+ const fieldErrors = (details as { fieldErrors?: unknown }).fieldErrors
42
+ if (typeof fieldErrors !== 'object' || fieldErrors === null) return false
43
+ const messages = (fieldErrors as Record<string, unknown>)[SCHEDULE_VALUE_FIELD]
44
+ return Array.isArray(messages) && messages.length > 0
45
+ }
@@ -127,10 +127,9 @@ const pastOrNowIsoDateTimeSchema = () => isoDateTimeSchema().refine((value) => v
127
127
  const isoDateOrDateTimeSchema = () => z.union([isoDateSchema(), isoDateTimeSchema()])
128
128
 
129
129
  // Country codes are validated through ICU rather than the shared country helper.
130
- // That helper imports language-subtag-registry's JSON, and the build strips the
131
- // import attribute Node's ESM loader requires, so importing it from server-side
132
- // code (CLI, workers) fails with ERR_IMPORT_ATTRIBUTE_MISSING. UI components can
133
- // keep using the helper because bundlers resolve the JSON themselves.
130
+ // ICU keeps this schema self-contained (no dependency on the generated country
131
+ // list) and accepts any region code Node's DisplayNames can resolve, including
132
+ // codes the shared helper supplements separately (e.g. XK).
134
133
  const countryDisplayNames = typeof Intl !== 'undefined' && typeof Intl.DisplayNames !== 'undefined'
135
134
  ? new Intl.DisplayNames(['en'], { type: 'region' })
136
135
  : null
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
  import React, { useEffect, useMemo, useState, useCallback } from 'react'
3
3
  import { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'
4
- import { useRouter } from 'next/navigation'
4
+ import { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
6
  import { Button } from '@open-mercato/ui/primitives/button'
7
7
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
@@ -29,7 +29,6 @@ function WidgetIcon({ className }: { className?: string }) {
29
29
 
30
30
  export default function PortalDashboardPage({ params }: Props) {
31
31
  const t = useT()
32
- const router = useRouter()
33
32
  const { auth } = usePortalContext()
34
33
  const { user, loading } = auth
35
34
 
@@ -47,11 +46,13 @@ export default function PortalDashboardPage({ params }: Props) {
47
46
  setHiddenWidgets(user ? loadHiddenWidgets(params.orgSlug, user.id) : new Set())
48
47
  }
49
48
 
49
+ // Leaving an authenticated page for the login page crosses the public/authenticated
50
+ // boundary, so it must be a full page load — see `replaceWithPageReload`.
50
51
  useEffect(() => {
51
52
  if (!loading && !user) {
52
- router.replace(`/${params.orgSlug}/portal/login`)
53
+ replaceWithPageReload(`/${params.orgSlug}/portal/login`)
53
54
  }
54
- }, [loading, user, router, params.orgSlug])
55
+ }, [loading, user, params.orgSlug])
55
56
 
56
57
  useEffect(() => {
57
58
  clearLegacyHiddenWidgetsKey()
@@ -79,7 +79,9 @@ export default function PortalInvitePage({ params }: Props) {
79
79
  return
80
80
  }
81
81
 
82
- if (result.status === 400) {
82
+ if (result.status === 409) {
83
+ setError(t('portal.invite.error.accountExists', 'This email address already has a portal account. Sign in below instead of accepting the invitation.'))
84
+ } else if (result.status === 400) {
83
85
  setError(t('portal.invite.error.invalidToken', 'Invalid or expired invitation.'))
84
86
  } else {
85
87
  setError(result.result?.error || t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))
@@ -12,6 +12,7 @@ import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
12
12
  import { SearchX } from 'lucide-react'
13
13
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
14
14
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
15
+ import { navigateWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'
15
16
  import { usePortalContext } from '@open-mercato/ui/portal/PortalContext'
16
17
  import { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'
17
18
 
@@ -47,7 +48,7 @@ export default function PortalLoginPage({ params }: Props) {
47
48
  })
48
49
 
49
50
  if (result.ok && result.result?.ok) {
50
- window.location.assign(`/${orgSlug}/portal/dashboard`)
51
+ navigateWithPageReload(`/${orgSlug}/portal/dashboard`)
51
52
  return
52
53
  }
53
54
 
@@ -2,7 +2,7 @@
2
2
  import { useEffect, useMemo } from 'react'
3
3
  import { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'
4
4
  import Link from 'next/link'
5
- import { useRouter } from 'next/navigation'
5
+ import { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'
6
6
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
8
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
@@ -16,16 +16,16 @@ type Props = { params: { orgSlug: string } }
16
16
 
17
17
  export default function PortalLandingPage({ params }: Props) {
18
18
  const t = useT()
19
- const router = useRouter()
20
19
  const orgSlug = params.orgSlug
21
20
  const { auth, tenant } = usePortalContext()
22
21
 
23
- // Redirect authenticated users to dashboard
22
+ // Redirect authenticated users to dashboard. This crosses the public/authenticated
23
+ // boundary, so it must be a full page load — see `replaceWithPageReload`.
24
24
  useEffect(() => {
25
25
  if (!auth.loading && auth.user) {
26
- router.replace(`/${orgSlug}/portal/dashboard`)
26
+ replaceWithPageReload(`/${orgSlug}/portal/dashboard`)
27
27
  }
28
- }, [auth.loading, auth.user, router, orgSlug])
28
+ }, [auth.loading, auth.user, orgSlug])
29
29
 
30
30
  const injectionContext = useMemo(
31
31
  () => ({ orgSlug }),
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
  import { useEffect, useMemo } from 'react'
3
3
  import { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'
4
- import { useRouter } from 'next/navigation'
4
+ import { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'
5
5
  import { useT } from '@open-mercato/shared/lib/i18n/context'
6
6
  import { Badge } from '@open-mercato/ui/primitives/badge'
7
7
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
@@ -14,15 +14,16 @@ type Props = { params: { orgSlug: string } }
14
14
 
15
15
  export default function PortalProfilePage({ params }: Props) {
16
16
  const t = useT()
17
- const router = useRouter()
18
17
  const { auth } = usePortalContext()
19
18
  const { user, roles, resolvedFeatures, isPortalAdmin, loading } = auth
20
19
 
20
+ // Leaving an authenticated page for the login page crosses the public/authenticated
21
+ // boundary, so it must be a full page load — see `replaceWithPageReload`.
21
22
  useEffect(() => {
22
23
  if (!loading && !user) {
23
- router.replace(`/${params.orgSlug}/portal/login`)
24
+ replaceWithPageReload(`/${params.orgSlug}/portal/login`)
24
25
  }
25
- }, [loading, user, router, params.orgSlug])
26
+ }, [loading, user, params.orgSlug])
26
27
 
27
28
  const injectionContext = useMemo(
28
29
  () => ({ orgSlug: params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin }),
@@ -27,6 +27,7 @@
27
27
  "portal.invite.confirmPassword.placeholder": "••••••••",
28
28
  "portal.invite.description": "Erstellen Sie Ihr Portalkonto, um diese Einladung anzunehmen.",
29
29
  "portal.invite.displayName": "Anzeigename",
30
+ "portal.invite.error.accountExists": "Diese E-Mail-Adresse hat bereits ein Portalkonto. Melden Sie sich unten an, anstatt die Einladung anzunehmen.",
30
31
  "portal.invite.error.displayNameRequired": "Der Anzeigename ist erforderlich.",
31
32
  "portal.invite.error.generic": "Annahme der Einladung fehlgeschlagen. Bitte versuchen Sie es erneut.",
32
33
  "portal.invite.error.invalidToken": "Ungültige oder abgelaufene Einladung.",
@@ -27,6 +27,7 @@
27
27
  "portal.invite.confirmPassword.placeholder": "••••••••",
28
28
  "portal.invite.description": "Create your portal account to accept this invitation.",
29
29
  "portal.invite.displayName": "Display Name",
30
+ "portal.invite.error.accountExists": "This email address already has a portal account. Sign in below instead of accepting the invitation.",
30
31
  "portal.invite.error.displayNameRequired": "Display name is required.",
31
32
  "portal.invite.error.generic": "Invitation acceptance failed. Please try again.",
32
33
  "portal.invite.error.invalidToken": "Invalid or expired invitation.",
@@ -27,6 +27,7 @@
27
27
  "portal.invite.confirmPassword.placeholder": "••••••••",
28
28
  "portal.invite.description": "Crea tu cuenta del portal para aceptar esta invitación.",
29
29
  "portal.invite.displayName": "Nombre para mostrar",
30
+ "portal.invite.error.accountExists": "Esta dirección de correo electrónico ya tiene una cuenta del portal. Inicia sesión abajo en lugar de aceptar la invitación.",
30
31
  "portal.invite.error.displayNameRequired": "El nombre para mostrar es obligatorio.",
31
32
  "portal.invite.error.generic": "Error al aceptar la invitación. Inténtalo de nuevo.",
32
33
  "portal.invite.error.invalidToken": "Invitación no válida o expirada.",
@@ -27,6 +27,7 @@
27
27
  "portal.invite.confirmPassword.placeholder": "••••••••",
28
28
  "portal.invite.description": "이 초대를 수락하려면 포털 계정을 만드세요.",
29
29
  "portal.invite.displayName": "표시 이름",
30
+ "portal.invite.error.accountExists": "이 이메일 주소에는 이미 포털 계정이 있습니다. 초대를 수락하는 대신 아래에서 로그인하세요.",
30
31
  "portal.invite.error.displayNameRequired": "표시 이름이 필요합니다.",
31
32
  "portal.invite.error.generic": "초대 수락에 실패했습니다. 다시 시도해 주세요.",
32
33
  "portal.invite.error.invalidToken": "초대가 유효하지 않거나 만료되었습니다.",
@@ -27,6 +27,7 @@
27
27
  "portal.invite.confirmPassword.placeholder": "••••••••",
28
28
  "portal.invite.description": "Utwórz konto w portalu, aby przyjąć to zaproszenie.",
29
29
  "portal.invite.displayName": "Nazwa wyświetlana",
30
+ "portal.invite.error.accountExists": "Ten adres e-mail ma już konto w portalu. Zaloguj się poniżej zamiast przyjmować zaproszenie.",
30
31
  "portal.invite.error.displayNameRequired": "Nazwa wyświetlana jest wymagana.",
31
32
  "portal.invite.error.generic": "Nie udało się przyjąć zaproszenia. Spróbuj ponownie.",
32
33
  "portal.invite.error.invalidToken": "Nieprawidłowe lub wygasłe zaproszenie.",
@@ -1,3 +1,8 @@
1
- export function navigateWithPageReload(path: string): void {
2
- window.location.assign(path)
3
- }
1
+ /**
2
+ * Portal-local import path for the boundary-crossing navigation helpers.
3
+ *
4
+ * The implementation lives in `@open-mercato/shared/lib/navigation/pageReload` so `@open-mercato/ui`
5
+ * — which does not depend on `@open-mercato/core` — can reach it from the portal shell and hooks.
6
+ * This re-export keeps the published import path stable for existing callers.
7
+ */
8
+ export { navigateWithPageReload, replaceWithPageReload } from '@open-mercato/shared/lib/navigation/pageReload'
@@ -10,6 +10,14 @@ import { readCoverageSnapshot, refreshCoverageSnapshot } from './coverage'
10
10
  import { createProfiler, shouldEnableProfiler, type Profiler } from '@open-mercato/shared/lib/profiler'
11
11
  import type { VectorIndexService } from '@open-mercato/search/vector'
12
12
  import { decryptIndexDocCustomFields } from '@open-mercato/shared/lib/encryption/indexDoc'
13
+ import {
14
+ buildCustomFieldKindIndex,
15
+ createCustomFieldKindMapResolver,
16
+ emptyCustomFieldKindIndex,
17
+ mergeCustomFieldKindIndexes,
18
+ type CustomFieldKindIndex,
19
+ type CustomFieldKindMapResolver,
20
+ } from '@open-mercato/shared/lib/custom-fields/kinds'
13
21
  import { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
14
22
  import {
15
23
  applyJoinFilters,
@@ -42,6 +50,22 @@ import { createBoundedTtlMemo } from '@open-mercato/shared/lib/query/bounded-ttl
42
50
 
43
51
  const logger = createLogger('query_index').child({ component: 'engine' })
44
52
 
53
+ /**
54
+ * Plain-row shape of the `custom_field_defs` columns the key and kind lookups read. The
55
+ * scope columns are load-bearing: a key can carry an organization-specific definition that
56
+ * overrides the tenant-wide or global one with a different `kind`, so the winner can only be
57
+ * picked once the row being decrypted is known.
58
+ */
59
+ type CustomFieldDefRow = {
60
+ entityId: string
61
+ key: string
62
+ kind: unknown
63
+ organizationId: string | null
64
+ tenantId: string | null
65
+ configJson: unknown
66
+ updatedAt: unknown
67
+ }
68
+
45
69
  /** Operators `buildCfFilterExpression` compiles; anything else yields no predicate. */
46
70
  const CF_FILTER_SUPPORTED_OPS = new Set<FilterOp>([
47
71
  'eq', 'ne', 'in', 'nin', 'like', 'ilike', 'exists', 'gt', 'gte', 'lt', 'lte',
@@ -219,7 +243,7 @@ function createQueryProfiler(entity: string): Profiler {
219
243
 
220
244
  export class HybridQueryEngine implements QueryEngine {
221
245
  private coverageStatsTtlMs: number
222
- private customFieldKeysCache = new Map<string, { expiresAt: number; value: string[] }>()
246
+ private customFieldDefsCache = new Map<string, { expiresAt: number; value: CustomFieldDefRow[] }>()
223
247
  private customFieldKeysTtlMs: number
224
248
  private columnCache = new Map<string, boolean>()
225
249
  private customEntityCache = new Map<string, boolean>()
@@ -1217,6 +1241,20 @@ export class HybridQueryEngine implements QueryEngine {
1217
1241
  const listCountCapWarning: ListCountCapWarning | undefined = counted.warning
1218
1242
 
1219
1243
  const dekKeyCache = new Map<string | null, string | null>()
1244
+ // Resolved lazily and only once per query, then memoized per scope: rows of one
1245
+ // organization share a kind map. `resolveCustomFieldKinds()` still runs for every
1246
+ // row once encryption is active, but the underlying `custom_field_defs` lookup is
1247
+ // TTL-cached, so a warm cache adds no extra query.
1248
+ let customFieldKinds: Promise<CustomFieldKindMapResolver> | null = null
1249
+ const resolveCustomFieldKinds = (): Promise<CustomFieldKindMapResolver> => {
1250
+ if (!customFieldKinds) {
1251
+ customFieldKinds = this.resolveCustomFieldKindIndex(
1252
+ Array.from(new Set(indexSources.map((src) => String(src.entityId)))),
1253
+ opts.tenantId ?? null,
1254
+ ).then((index) => createCustomFieldKindMapResolver(index))
1255
+ }
1256
+ return customFieldKinds
1257
+ }
1220
1258
 
1221
1259
  const decryptRow = async (item: Record<string, unknown>): Promise<Record<string, unknown>> => {
1222
1260
  let next = item
@@ -1237,13 +1275,16 @@ export class HybridQueryEngine implements QueryEngine {
1237
1275
  }
1238
1276
  if (encSvc) {
1239
1277
  try {
1278
+ // The kind is resolved against the very scope the value is decrypted with, so an
1279
+ // organization-specific definition can never type another organization's value.
1280
+ const cfScope = {
1281
+ tenantId: (next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null) as string | null,
1282
+ organizationId: (next?.organization_id ?? next?.organizationId ?? fallbackOrgId ?? null) as string | null,
1283
+ }
1284
+ const resolveKinds = await resolveCustomFieldKinds()
1240
1285
  next = await decryptIndexDocCustomFields(
1241
- next,
1242
- {
1243
- tenantId: (next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null) as string | null,
1244
- organizationId: (next?.organization_id ?? next?.organizationId ?? null) as string | null,
1245
- },
1246
- encSvc as any, dekKeyCache,
1286
+ next, cfScope, encSvc as any, dekKeyCache,
1287
+ resolveKinds(cfScope.organizationId, cfScope.tenantId),
1247
1288
  )
1248
1289
  } catch { /* keep next as-is */ }
1249
1290
  }
@@ -2193,37 +2234,91 @@ export class HybridQueryEngine implements QueryEngine {
2193
2234
  return present
2194
2235
  }
2195
2236
 
2196
- private async resolveAvailableCustomFieldKeys(entityIds: string[], tenantId: string | null): Promise<string[]> {
2237
+ private async resolveCustomFieldDefs(
2238
+ entityIds: string[],
2239
+ tenantId: string | null,
2240
+ ): Promise<CustomFieldDefRow[]> {
2197
2241
  if (!entityIds.length) return []
2198
2242
  const cacheKey = this.customFieldKeysCacheKey(entityIds, tenantId)
2199
2243
  const now = Date.now()
2200
- const cached = this.customFieldKeysCache.get(cacheKey)
2244
+ const cached = this.customFieldDefsCache.get(cacheKey)
2201
2245
  if (cached && cached.expiresAt > now) return cached.value.slice()
2202
2246
 
2203
2247
  const db = this.getDb() as any
2204
2248
  const rows = await db
2205
2249
  .selectFrom('custom_field_defs')
2206
- .select('key')
2250
+ .select(['entity_id', 'key', 'kind', 'organization_id', 'tenant_id', 'config_json', 'updated_at'])
2207
2251
  .where('entity_id', 'in', entityIds)
2208
2252
  .where('is_active', '=', true)
2209
2253
  .where((eb: any) => eb.or([
2210
2254
  eb('tenant_id', '=', tenantId),
2211
2255
  eb('tenant_id', 'is', null),
2212
2256
  ]))
2213
- .execute() as Array<{ key: unknown }>
2214
- const keys = new Set<string>()
2257
+ .execute() as Array<Record<string, unknown>>
2258
+ const result: CustomFieldDefRow[] = []
2215
2259
  for (const row of rows) {
2216
2260
  const key = row.key
2217
- if (typeof key === 'string' && key.trim().length) keys.add(key.trim())
2218
- else if (key != null) keys.add(String(key))
2261
+ const normalized = typeof key === 'string' ? key.trim() : key == null ? '' : String(key)
2262
+ if (!normalized.length) continue
2263
+ const entityId = typeof row.entity_id === 'string' ? row.entity_id : String(row.entity_id ?? '')
2264
+ result.push({
2265
+ entityId,
2266
+ key: normalized,
2267
+ kind: row.kind,
2268
+ organizationId: typeof row.organization_id === 'string' ? row.organization_id : null,
2269
+ tenantId: typeof row.tenant_id === 'string' ? row.tenant_id : null,
2270
+ configJson: row.config_json ?? null,
2271
+ updatedAt: row.updated_at ?? null,
2272
+ })
2219
2273
  }
2220
- const result = Array.from(keys)
2221
2274
  if (this.customFieldKeysTtlMs > 0) {
2222
- this.customFieldKeysCache.set(cacheKey, { expiresAt: now + this.customFieldKeysTtlMs, value: result })
2275
+ this.customFieldDefsCache.set(cacheKey, { expiresAt: now + this.customFieldKeysTtlMs, value: result })
2223
2276
  }
2224
2277
  return result.slice()
2225
2278
  }
2226
2279
 
2280
+ private async resolveAvailableCustomFieldKeys(entityIds: string[], tenantId: string | null): Promise<string[]> {
2281
+ const defs = await this.resolveCustomFieldDefs(entityIds, tenantId)
2282
+ return Array.from(new Set(defs.map((def) => def.key)))
2283
+ }
2284
+
2285
+ /**
2286
+ * Kind lookup for the `cf:`/`cf_` keys of an index document, so encrypted string-typed
2287
+ * custom fields keep their type on read instead of being `JSON.parse`d back into numbers
2288
+ * or booleans (issue #5968). Backed by the same TTL cache as the key lookup, so this adds
2289
+ * no round trip on a warm cache and fails open to an empty index on a cold-cache error.
2290
+ *
2291
+ * The index keeps every scope's definition per key rather than collapsing to one kind:
2292
+ * the caller resolves the winner against each row's own `organization_id`, the same scope
2293
+ * the row is decrypted with, so an organization's override never types another
2294
+ * organization's value.
2295
+ *
2296
+ * `entityIds` MUST arrive in `indexSources` order: `buildCfJsonExprSql` reads a `cf` value
2297
+ * with `coalesce(source0, source1, …)`, so a key defined by several sources has to be typed
2298
+ * by the same source the value came from — hence the priority-ordered merge rather than
2299
+ * whatever order the rows happen to come back in.
2300
+ */
2301
+ private async resolveCustomFieldKindIndex(
2302
+ entityIds: string[],
2303
+ tenantId: string | null,
2304
+ ): Promise<CustomFieldKindIndex> {
2305
+ try {
2306
+ const defs = await this.resolveCustomFieldDefs(entityIds, tenantId)
2307
+ const byEntityId = new Map<string, CustomFieldDefRow[]>()
2308
+ for (const def of defs) {
2309
+ const group = byEntityId.get(def.entityId) ?? []
2310
+ group.push(def)
2311
+ byEntityId.set(def.entityId, group)
2312
+ }
2313
+ return mergeCustomFieldKindIndexes(
2314
+ entityIds.map((entityId) => buildCustomFieldKindIndex(byEntityId.get(entityId) ?? [])),
2315
+ )
2316
+ } catch (err) {
2317
+ logger.warn('Failed to resolve custom field kinds', { entityIds, err })
2318
+ return emptyCustomFieldKindIndex()
2319
+ }
2320
+ }
2321
+
2227
2322
  private async entityHasActiveCustomFields(entityId: string, tenantId: string | null): Promise<boolean> {
2228
2323
  try {
2229
2324
  const keys = await this.resolveAvailableCustomFieldKeys([entityId], tenantId)