@open-mercato/core 0.7.0 → 0.7.1-develop.7102.1.b41f7e3e51

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 (847) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/api.js +13 -1
  3. package/dist/helpers/integration/api.js.map +2 -2
  4. package/dist/helpers/integration/communicationChannelsFixtures.js +19 -0
  5. package/dist/helpers/integration/communicationChannelsFixtures.js.map +2 -2
  6. package/dist/modules/api_keys/api/keys/route.js +1 -1
  7. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  8. package/dist/modules/api_keys/backend/api-keys/page.js +3 -1
  9. package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
  10. package/dist/modules/api_keys/di.js +9 -0
  11. package/dist/modules/api_keys/di.js.map +7 -0
  12. package/dist/modules/api_keys/services/principalService.js +31 -0
  13. package/dist/modules/api_keys/services/principalService.js.map +7 -0
  14. package/dist/modules/attachments/api/route.js +2 -2
  15. package/dist/modules/attachments/api/route.js.map +2 -2
  16. package/dist/modules/attachments/components/AttachmentLibrary.js +2 -0
  17. package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
  18. package/dist/modules/attachments/di.js +11 -1
  19. package/dist/modules/attachments/di.js.map +2 -2
  20. package/dist/modules/attachments/index.js.map +1 -1
  21. package/dist/modules/attachments/lib/attachment-service.js +313 -0
  22. package/dist/modules/attachments/lib/attachment-service.js.map +7 -0
  23. package/dist/modules/attachments/lib/scoped-upload-service.js +15 -2
  24. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  25. package/dist/modules/attachments/lib/upload-limits.js +6 -2
  26. package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
  27. package/dist/modules/audit_logs/backend/audit-logs/page.js +6 -0
  28. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  29. package/dist/modules/audit_logs/lib/display-helpers.js +19 -11
  30. package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
  31. package/dist/modules/audit_logs/services/actionLogService.js +57 -12
  32. package/dist/modules/audit_logs/services/actionLogService.js.map +3 -3
  33. package/dist/modules/auth/acl.js +30 -5
  34. package/dist/modules/auth/acl.js.map +2 -2
  35. package/dist/modules/auth/api/feature-check.js +8 -2
  36. package/dist/modules/auth/api/feature-check.js.map +2 -2
  37. package/dist/modules/auth/api/login.js +4 -1
  38. package/dist/modules/auth/api/login.js.map +2 -2
  39. package/dist/modules/auth/api/reset/validate.js +55 -0
  40. package/dist/modules/auth/api/reset/validate.js.map +7 -0
  41. package/dist/modules/auth/api/roles/acl/route.js +28 -33
  42. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  43. package/dist/modules/auth/api/session/refresh.js +25 -1
  44. package/dist/modules/auth/api/session/refresh.js.map +2 -2
  45. package/dist/modules/auth/api/users/acl/route.js +96 -51
  46. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  47. package/dist/modules/auth/api/users/route.js +13 -5
  48. package/dist/modules/auth/api/users/route.js.map +2 -2
  49. package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js +5 -1
  50. package/dist/modules/auth/backend/roles/[id]/edit/page.meta.js.map +2 -2
  51. package/dist/modules/auth/backend/roles/page.js +3 -1
  52. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  53. package/dist/modules/auth/backend/users/[id]/edit/page.meta.js +5 -1
  54. package/dist/modules/auth/backend/users/[id]/edit/page.meta.js.map +2 -2
  55. package/dist/modules/auth/backend/users/page.js +2 -1
  56. package/dist/modules/auth/backend/users/page.js.map +2 -2
  57. package/dist/modules/auth/commands/acl.js +254 -0
  58. package/dist/modules/auth/commands/acl.js.map +7 -0
  59. package/dist/modules/auth/components/AclEditor.js +39 -30
  60. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  61. package/dist/modules/auth/data/entities.js.map +2 -2
  62. package/dist/modules/auth/data/validators.js +5 -1
  63. package/dist/modules/auth/data/validators.js.map +2 -2
  64. package/dist/modules/auth/di.js +35 -9
  65. package/dist/modules/auth/di.js.map +2 -2
  66. package/dist/modules/auth/frontend/reset/[token]/page.js +46 -0
  67. package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
  68. package/dist/modules/auth/lib/grantChecks.js.map +2 -2
  69. package/dist/modules/auth/lib/userIdFilter.js +16 -0
  70. package/dist/modules/auth/lib/userIdFilter.js.map +7 -0
  71. package/dist/modules/auth/services/authService.js +16 -2
  72. package/dist/modules/auth/services/authService.js.map +2 -2
  73. package/dist/modules/auth/services/principalService.js +200 -0
  74. package/dist/modules/auth/services/principalService.js.map +7 -0
  75. package/dist/modules/auth/services/rbacService.js +67 -24
  76. package/dist/modules/auth/services/rbacService.js.map +3 -3
  77. package/dist/modules/auth/services/roleOrganizationScope.js +41 -0
  78. package/dist/modules/auth/services/roleOrganizationScope.js.map +7 -0
  79. package/dist/modules/business_rules/backend/logs/page.js +3 -1
  80. package/dist/modules/business_rules/backend/logs/page.js.map +2 -2
  81. package/dist/modules/business_rules/backend/rules/page.js +3 -1
  82. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  83. package/dist/modules/business_rules/backend/sets/page.js +3 -1
  84. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  85. package/dist/modules/business_rules/lib/expression-evaluator.js +9 -9
  86. package/dist/modules/business_rules/lib/expression-evaluator.js.map +2 -2
  87. package/dist/modules/business_rules/lib/rule-evaluator.js +10 -9
  88. package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
  89. package/dist/modules/catalog/ai-agents.js +1 -1
  90. package/dist/modules/catalog/ai-agents.js.map +1 -1
  91. package/dist/modules/catalog/ai-tools/merchandising-pack.js +2 -2
  92. package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +2 -2
  93. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +5 -2
  94. package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
  95. package/dist/modules/catalog/components/categories/CategoriesDataTable.js +2 -0
  96. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  97. package/dist/modules/catalog/components/products/ProductsDataTable.js +7 -1
  98. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  99. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +2 -0
  100. package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +2 -2
  101. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +9 -1
  102. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
  103. package/dist/modules/communication_channels/api/post/test-seed/route.js +95 -10
  104. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  105. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +3 -0
  106. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
  107. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +14 -1
  108. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
  109. package/dist/modules/communication_channels/di.js +7 -1
  110. package/dist/modules/communication_channels/di.js.map +2 -2
  111. package/dist/modules/communication_channels/lib/email-capabilities.js +4 -1
  112. package/dist/modules/communication_channels/lib/email-capabilities.js.map +2 -2
  113. package/dist/modules/communication_channels/lib/outbound-recipient.js +33 -0
  114. package/dist/modules/communication_channels/lib/outbound-recipient.js.map +7 -0
  115. package/dist/modules/communication_channels/lib/resolve-channel-type.js +51 -0
  116. package/dist/modules/communication_channels/lib/resolve-channel-type.js.map +7 -0
  117. package/dist/modules/communication_channels/lib/test-seed.js +41 -2
  118. package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
  119. package/dist/modules/configs/lib/system-status.js +9 -0
  120. package/dist/modules/configs/lib/system-status.js.map +2 -2
  121. package/dist/modules/currencies/backend/currencies/page.js +3 -1
  122. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  123. package/dist/modules/currencies/backend/exchange-rates/page.js +3 -1
  124. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  125. package/dist/modules/currencies/services/rateFetchingService.js +6 -1
  126. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  127. package/dist/modules/customer_accounts/api/admin/domain-mappings.js +9 -0
  128. package/dist/modules/customer_accounts/api/admin/domain-mappings.js.map +2 -2
  129. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +3 -1
  130. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  131. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +3 -1
  132. package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
  133. package/dist/modules/customer_accounts/services/domainMappingService.js +10 -0
  134. package/dist/modules/customer_accounts/services/domainMappingService.js.map +2 -2
  135. package/dist/modules/customers/ai-tools/companies-pack.js +4 -1
  136. package/dist/modules/customers/ai-tools/companies-pack.js.map +2 -2
  137. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +13 -2
  138. package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +2 -2
  139. package/dist/modules/customers/ai-tools/deals-pack.js +52 -6
  140. package/dist/modules/customers/ai-tools/deals-pack.js.map +2 -2
  141. package/dist/modules/customers/ai-tools/people-pack.js +4 -1
  142. package/dist/modules/customers/ai-tools/people-pack.js.map +2 -2
  143. package/dist/modules/customers/api/activities/route.js +48 -6
  144. package/dist/modules/customers/api/activities/route.js.map +2 -2
  145. package/dist/modules/customers/api/companies/[id]/people/route.js +20 -64
  146. package/dist/modules/customers/api/companies/[id]/people/route.js.map +2 -2
  147. package/dist/modules/customers/api/companies/[id]/route.js +17 -94
  148. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  149. package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js +8 -5
  150. package/dist/modules/customers/api/dashboard/widgets/customer-todos/route.js.map +2 -2
  151. package/dist/modules/customers/api/deals/[id]/companies/route.js +1 -1
  152. package/dist/modules/customers/api/deals/[id]/companies/route.js.map +2 -2
  153. package/dist/modules/customers/api/deals/[id]/people/route.js +1 -1
  154. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  155. package/dist/modules/customers/api/deals/[id]/route.js +8 -25
  156. package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
  157. package/dist/modules/customers/api/deals/[id]/stats/route.js +3 -22
  158. package/dist/modules/customers/api/deals/[id]/stats/route.js.map +2 -2
  159. package/dist/modules/customers/api/deals/aggregate/route.js +11 -4
  160. package/dist/modules/customers/api/deals/aggregate/route.js.map +2 -2
  161. package/dist/modules/customers/api/deals/map/route.js +2 -1
  162. package/dist/modules/customers/api/deals/map/route.js.map +2 -2
  163. package/dist/modules/customers/api/deals/route.js +38 -1
  164. package/dist/modules/customers/api/deals/route.js.map +2 -2
  165. package/dist/modules/customers/api/entity-roles-factory.js +4 -4
  166. package/dist/modules/customers/api/entity-roles-factory.js.map +2 -2
  167. package/dist/modules/customers/api/interactions/route.js +3 -1
  168. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  169. package/dist/modules/customers/api/interactions/tasks/route.js +27 -4
  170. package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
  171. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js +4 -3
  172. package/dist/modules/customers/api/people/[id]/companies/[linkId]/route.js.map +2 -2
  173. package/dist/modules/customers/api/people/[id]/companies/context.js +1 -1
  174. package/dist/modules/customers/api/people/[id]/companies/context.js.map +2 -2
  175. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +1 -1
  176. package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
  177. package/dist/modules/customers/api/people/[id]/route.js +11 -10
  178. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  179. package/dist/modules/customers/api/people/check-phone/route.js +51 -9
  180. package/dist/modules/customers/api/people/check-phone/route.js.map +2 -2
  181. package/dist/modules/customers/api/todos/route.js +30 -5
  182. package/dist/modules/customers/api/todos/route.js.map +2 -2
  183. package/dist/modules/customers/backend/customers/companies/page.js +4 -2
  184. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  185. package/dist/modules/customers/backend/customers/deals/[id]/page.js +6 -1
  186. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  187. package/dist/modules/customers/backend/customers/deals/page.js +5 -2
  188. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  189. package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js +4 -13
  190. package/dist/modules/customers/backend/customers/deals/pipeline/components/Lane.js.map +2 -2
  191. package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js +85 -14
  192. package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js.map +2 -2
  193. package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js +18 -0
  194. package/dist/modules/customers/backend/customers/deals/pipeline/components/toneClasses.js.map +7 -0
  195. package/dist/modules/customers/backend/customers/people/page.js +4 -2
  196. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  197. package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js +9 -3
  198. package/dist/modules/customers/backend/hooks/useEmailDuplicateCheck.js.map +2 -2
  199. package/dist/modules/customers/commands/deals.js +21 -34
  200. package/dist/modules/customers/commands/deals.js.map +2 -2
  201. package/dist/modules/customers/commands/interactions.js.map +1 -1
  202. package/dist/modules/customers/commands/personCompanyLinks.js +131 -2
  203. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  204. package/dist/modules/customers/components/CustomerTodosTable.js +1 -0
  205. package/dist/modules/customers/components/CustomerTodosTable.js.map +2 -2
  206. package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
  207. package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
  208. package/dist/modules/customers/components/calendar/AgendaList.js +2 -2
  209. package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
  210. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +1 -1
  211. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
  212. package/dist/modules/customers/components/calendar/CalendarScreen.js +1 -1
  213. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  214. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +1 -1
  215. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  216. package/dist/modules/customers/components/calendar/EventBlock.js +1 -1
  217. package/dist/modules/customers/components/calendar/EventBlock.js.map +2 -2
  218. package/dist/modules/customers/components/calendar/EventPeekPopover.js +3 -2
  219. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
  220. package/dist/modules/customers/components/calendar/editor/PeopleField.js +40 -5
  221. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  222. package/dist/modules/customers/components/calendar/editor/hooks.js +2 -1
  223. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  224. package/dist/modules/customers/components/calendar/types.js +1 -1
  225. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  226. package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
  227. package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
  228. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +3 -1
  229. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  230. package/dist/modules/customers/components/detail/DealForm.js +19 -7
  231. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  232. package/dist/modules/customers/components/detail/DealsSection.js +6 -5
  233. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  234. package/dist/modules/customers/components/detail/EntityTagsDialog.js +35 -11
  235. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
  236. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +3 -1
  237. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  238. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +19 -13
  239. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  240. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -4
  241. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  242. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +3 -3
  243. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  244. package/dist/modules/customers/components/detail/utils.js +5 -5
  245. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  246. package/dist/modules/customers/data/entities.js.map +1 -1
  247. package/dist/modules/customers/data/validators.js +32 -3
  248. package/dist/modules/customers/data/validators.js.map +2 -2
  249. package/dist/modules/customers/events.js +5 -0
  250. package/dist/modules/customers/events.js.map +2 -2
  251. package/dist/modules/customers/lib/calendar/conflicts.js +8 -2
  252. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  253. package/dist/modules/customers/lib/calendar/editorPayload.js +3 -2
  254. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  255. package/dist/modules/customers/lib/calendar/mapItem.js +8 -3
  256. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  257. package/dist/modules/customers/lib/calendar/participantIdentity.js +9 -0
  258. package/dist/modules/customers/lib/calendar/participantIdentity.js.map +7 -0
  259. package/dist/modules/customers/lib/closureStage.js +45 -0
  260. package/dist/modules/customers/lib/closureStage.js.map +7 -0
  261. package/dist/modules/customers/lib/dealStatus.js +30 -0
  262. package/dist/modules/customers/lib/dealStatus.js.map +2 -2
  263. package/dist/modules/customers/lib/detailReadAccess.js +12 -0
  264. package/dist/modules/customers/lib/detailReadAccess.js.map +7 -0
  265. package/dist/modules/customers/lib/findPeopleByAddresses.js +1 -0
  266. package/dist/modules/customers/lib/findPeopleByAddresses.js.map +2 -2
  267. package/dist/modules/customers/lib/personCompanies.js +100 -2
  268. package/dist/modules/customers/lib/personCompanies.js.map +2 -2
  269. package/dist/modules/customers/lib/todoCompatibility.js +12 -0
  270. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  271. package/dist/modules/dashboards/lib/widgetScope.js +6 -1
  272. package/dist/modules/dashboards/lib/widgetScope.js.map +2 -2
  273. package/dist/modules/data_sync/backend/data-sync/page.js +3 -1
  274. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  275. package/dist/modules/data_sync/lib/abandoned-run.js +19 -0
  276. package/dist/modules/data_sync/lib/abandoned-run.js.map +7 -0
  277. package/dist/modules/data_sync/lib/sync-engine.js +72 -6
  278. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  279. package/dist/modules/data_sync/workers/sync-export.js +5 -1
  280. package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
  281. package/dist/modules/data_sync/workers/sync-import.js +5 -1
  282. package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
  283. package/dist/modules/devices/acl.js +9 -1
  284. package/dist/modules/devices/acl.js.map +2 -2
  285. package/dist/modules/devices/api/admin/devices/[id]/route.js +5 -32
  286. package/dist/modules/devices/api/admin/devices/[id]/route.js.map +2 -2
  287. package/dist/modules/devices/api/admin/devices/route.js +14 -1
  288. package/dist/modules/devices/api/admin/devices/route.js.map +2 -2
  289. package/dist/modules/devices/api/deviceList.js +99 -12
  290. package/dist/modules/devices/api/deviceList.js.map +2 -2
  291. package/dist/modules/devices/api/deviceSerialization.js +46 -0
  292. package/dist/modules/devices/api/deviceSerialization.js.map +7 -0
  293. package/dist/modules/devices/api/openapi.js +3 -1
  294. package/dist/modules/devices/api/openapi.js.map +2 -2
  295. package/dist/modules/devices/api/route.js +9 -1
  296. package/dist/modules/devices/api/route.js.map +2 -2
  297. package/dist/modules/devices/backend/devices/[id]/page.js +10 -26
  298. package/dist/modules/devices/backend/devices/[id]/page.js.map +2 -2
  299. package/dist/modules/devices/backend/devices/create/page.js +17 -1
  300. package/dist/modules/devices/backend/devices/create/page.js.map +2 -2
  301. package/dist/modules/devices/backend/devices/page.js +28 -34
  302. package/dist/modules/devices/backend/devices/page.js.map +2 -2
  303. package/dist/modules/devices/backend/devices/useDeviceUserLabels.js +34 -0
  304. package/dist/modules/devices/backend/devices/useDeviceUserLabels.js.map +7 -0
  305. package/dist/modules/devices/backend/devices/userOptions.js +52 -0
  306. package/dist/modules/devices/backend/devices/userOptions.js.map +7 -0
  307. package/dist/modules/devices/setup.js +6 -2
  308. package/dist/modules/devices/setup.js.map +2 -2
  309. package/dist/modules/directory/backend/directory/organizations/page.js +2 -1
  310. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  311. package/dist/modules/directory/backend/directory/tenants/page.js +2 -1
  312. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  313. package/dist/modules/directory/di.js +9 -0
  314. package/dist/modules/directory/di.js.map +2 -2
  315. package/dist/modules/directory/services/organizationHierarchyService.js +29 -0
  316. package/dist/modules/directory/services/organizationHierarchyService.js.map +7 -0
  317. package/dist/modules/directory/services/organizationScopeService.js +56 -0
  318. package/dist/modules/directory/services/organizationScopeService.js.map +7 -0
  319. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  320. package/dist/modules/entities/api/records.js +4 -2
  321. package/dist/modules/entities/api/records.js.map +2 -2
  322. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +3 -1
  323. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  324. package/dist/modules/eudr/api/plots/route.js +5 -1
  325. package/dist/modules/eudr/api/plots/route.js.map +2 -2
  326. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js +3 -0
  327. package/dist/modules/eudr/backend/eudr/evidence-submissions/page.js.map +2 -2
  328. package/dist/modules/eudr/backend/eudr/plots/page.js +3 -0
  329. package/dist/modules/eudr/backend/eudr/plots/page.js.map +2 -2
  330. package/dist/modules/eudr/backend/eudr/product-mappings/page.js +3 -0
  331. package/dist/modules/eudr/backend/eudr/product-mappings/page.js.map +2 -2
  332. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js +3 -0
  333. package/dist/modules/eudr/backend/eudr/risk-assessments/page.js.map +2 -2
  334. package/dist/modules/eudr/backend/eudr/statements/page.js +3 -0
  335. package/dist/modules/eudr/backend/eudr/statements/page.js.map +2 -2
  336. package/dist/modules/eudr/commands/statements.js +0 -3
  337. package/dist/modules/eudr/commands/statements.js.map +2 -2
  338. package/dist/modules/eudr/data/validators.js +4 -1
  339. package/dist/modules/eudr/data/validators.js.map +2 -2
  340. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +1 -0
  341. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  342. package/dist/modules/feature_toggles/components/OverridesTable.js +1 -0
  343. package/dist/modules/feature_toggles/components/OverridesTable.js.map +2 -2
  344. package/dist/modules/feature_toggles/defaults.json +0 -8
  345. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js +3 -0
  346. package/dist/modules/inbox_ops/backend/inbox-ops/log/page.js.map +2 -2
  347. package/dist/modules/inbox_ops/backend/inbox-ops/page.js +3 -0
  348. package/dist/modules/inbox_ops/backend/inbox-ops/page.js.map +2 -2
  349. package/dist/modules/inbox_ops/lib/htmlToPlainText.js +1 -12
  350. package/dist/modules/inbox_ops/lib/htmlToPlainText.js.map +2 -2
  351. package/dist/modules/integrations/api/[id]/credentials/route.js +6 -1
  352. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  353. package/dist/modules/integrations/backend/integrations/[id]/page.js +38 -29
  354. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  355. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +36 -9
  356. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  357. package/dist/modules/integrations/backend/integrations/credential-secret-fields.js +55 -0
  358. package/dist/modules/integrations/backend/integrations/credential-secret-fields.js.map +7 -0
  359. package/dist/modules/integrations/data/validators.js +11 -3
  360. package/dist/modules/integrations/data/validators.js.map +2 -2
  361. package/dist/modules/integrations/lib/credentials-masking.js +6 -2
  362. package/dist/modules/integrations/lib/credentials-masking.js.map +2 -2
  363. package/dist/modules/messages/api/[id]/route.js +29 -1
  364. package/dist/modules/messages/api/[id]/route.js.map +2 -2
  365. package/dist/modules/messages/api/openapi.js +17 -2
  366. package/dist/modules/messages/api/openapi.js.map +2 -2
  367. package/dist/modules/messages/api/route.js +17 -2
  368. package/dist/modules/messages/api/route.js.map +2 -2
  369. package/dist/modules/messages/components/MessagesInboxPageClient.js +7 -14
  370. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  371. package/dist/modules/messages/components/inboxFilters.js +15 -0
  372. package/dist/modules/messages/components/inboxFilters.js.map +2 -2
  373. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js +11 -1
  374. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.js.map +2 -2
  375. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js +3 -1
  376. package/dist/modules/messages/components/message-detail/panels/MessageHeader.js.map +2 -2
  377. package/dist/modules/messages/components/message-detail/panels/thread-panel.js +2 -1
  378. package/dist/modules/messages/components/message-detail/panels/thread-panel.js.map +2 -2
  379. package/dist/modules/messages/components/messageListLabels.js +14 -2
  380. package/dist/modules/messages/components/messageListLabels.js.map +2 -2
  381. package/dist/modules/messages/data/validators.js +18 -4
  382. package/dist/modules/messages/data/validators.js.map +2 -2
  383. package/dist/modules/messages/lib/channel-sender-identity.js +26 -0
  384. package/dist/modules/messages/lib/channel-sender-identity.js.map +7 -0
  385. package/dist/modules/messages/lib/composeSourceChannelType.js +41 -0
  386. package/dist/modules/messages/lib/composeSourceChannelType.js.map +7 -0
  387. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +4 -6
  388. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  389. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +3 -1
  390. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
  391. package/dist/modules/payment_gateways/lib/webhook-processor.js +7 -0
  392. package/dist/modules/payment_gateways/lib/webhook-processor.js.map +2 -2
  393. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +3 -0
  394. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  395. package/dist/modules/push_notifications/backend/push_notifications/page.js +3 -1
  396. package/dist/modules/push_notifications/backend/push_notifications/page.js.map +2 -2
  397. package/dist/modules/push_notifications/backend/push_notifications/send/page.js +1 -1
  398. package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +2 -2
  399. package/dist/modules/query_index/lib/engine.js +150 -51
  400. package/dist/modules/query_index/lib/engine.js.map +2 -2
  401. package/dist/modules/query_index/lib/search-entity-policy.js +14 -0
  402. package/dist/modules/query_index/lib/search-entity-policy.js.map +7 -0
  403. package/dist/modules/query_index/lib/search-tokens.js +79 -3
  404. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  405. package/dist/modules/resources/backend/resources/resource-types/page.js +3 -1
  406. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  407. package/dist/modules/resources/backend/resources/resources/page.js +3 -1
  408. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  409. package/dist/modules/sales/backend/sales/channels/offers/page.js +3 -0
  410. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  411. package/dist/modules/sales/backend/sales/channels/page.js +3 -0
  412. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  413. package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
  414. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  415. package/dist/modules/sales/commands/documents.js +79 -1
  416. package/dist/modules/sales/commands/documents.js.map +2 -2
  417. package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
  418. package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
  419. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +3 -0
  420. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  421. package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
  422. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  423. package/dist/modules/sales/components/documents/ItemsSection.js +51 -22
  424. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  425. package/dist/modules/sales/components/documents/LineItemDialog.js +83 -18
  426. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  427. package/dist/modules/sales/components/documents/PaymentsSection.js +23 -9
  428. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  429. package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
  430. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  431. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +68 -14
  432. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  433. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +6 -5
  434. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
  435. package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
  436. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  437. package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
  438. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  439. package/dist/modules/sales/components/documents/lineItemShipmentLock.js +54 -0
  440. package/dist/modules/sales/components/documents/lineItemShipmentLock.js.map +7 -0
  441. package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
  442. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  443. package/dist/modules/sales/data/entities.js +8 -2
  444. package/dist/modules/sales/data/entities.js.map +2 -2
  445. package/dist/modules/sales/extension-points.js +1 -0
  446. package/dist/modules/sales/extension-points.js.map +2 -2
  447. package/dist/modules/sales/lib/salesChannelsToggleSeed.js +33 -0
  448. package/dist/modules/sales/lib/salesChannelsToggleSeed.js.map +7 -0
  449. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js +32 -0
  450. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js.map +7 -0
  451. package/dist/modules/sales/setup.js +2 -0
  452. package/dist/modules/sales/setup.js.map +2 -2
  453. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +10 -2
  454. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +2 -2
  455. package/dist/modules/sales/widgets/injection-table.js +4 -7
  456. package/dist/modules/sales/widgets/injection-table.js.map +2 -2
  457. package/dist/modules/staff/backend/staff/leave-requests/page.js +3 -0
  458. package/dist/modules/staff/backend/staff/leave-requests/page.js.map +2 -2
  459. package/dist/modules/staff/backend/staff/my-leave-requests/page.js +3 -0
  460. package/dist/modules/staff/backend/staff/my-leave-requests/page.js.map +2 -2
  461. package/dist/modules/staff/backend/staff/team-members/page.js +3 -0
  462. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  463. package/dist/modules/staff/backend/staff/team-roles/page.js +3 -0
  464. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  465. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +3 -0
  466. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  467. package/dist/modules/staff/backend/staff/teams/page.js +3 -0
  468. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  469. package/dist/modules/staff/backend/staff/timesheets/page.js +29 -18
  470. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  471. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +3 -0
  472. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  473. package/dist/modules/sync_excel/lib/adapters/customers.js +1 -0
  474. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  475. package/dist/modules/warranty_claims/api/assignees/route.js +3 -3
  476. package/dist/modules/warranty_claims/api/assignees/route.js.map +2 -2
  477. package/dist/modules/warranty_claims/api/portal/attachments/route.js +2 -2
  478. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  479. package/dist/modules/warranty_claims/api/portal/claims/route.js +12 -9
  480. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  481. package/dist/modules/warranty_claims/backend/page.js +3 -0
  482. package/dist/modules/warranty_claims/backend/page.js.map +2 -2
  483. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js +3 -0
  484. package/dist/modules/warranty_claims/backend/warranty_claims/registrations/page.js.map +2 -2
  485. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js +3 -0
  486. package/dist/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.js.map +2 -2
  487. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js +3 -0
  488. package/dist/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.js.map +2 -2
  489. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -0
  490. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
  491. package/dist/modules/wms/api/warehouseSearch.js +21 -0
  492. package/dist/modules/wms/api/warehouseSearch.js.map +7 -0
  493. package/dist/modules/wms/api/warehouses/route.js +13 -9
  494. package/dist/modules/wms/api/warehouses/route.js.map +2 -2
  495. package/dist/modules/wms/api/zones/route.js +15 -2
  496. package/dist/modules/wms/api/zones/route.js.map +2 -2
  497. package/dist/modules/wms/commands/configuration.js +121 -9
  498. package/dist/modules/wms/commands/configuration.js.map +2 -2
  499. package/dist/modules/wms/commands/inventory-actions.js +3 -0
  500. package/dist/modules/wms/commands/inventory-actions.js.map +2 -2
  501. package/dist/modules/wms/commands/shared.js +9 -1
  502. package/dist/modules/wms/commands/shared.js.map +2 -2
  503. package/dist/modules/wms/components/backend/WarehouseEditDialog.js +158 -0
  504. package/dist/modules/wms/components/backend/WarehouseEditDialog.js.map +7 -0
  505. package/dist/modules/wms/components/backend/WmsConfigurationPage.js +76 -111
  506. package/dist/modules/wms/components/backend/WmsConfigurationPage.js.map +3 -3
  507. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js +1 -0
  508. package/dist/modules/wms/components/backend/WmsInventoryConsolePage.js.map +2 -2
  509. package/dist/modules/wms/components/backend/WmsLotsListPage.js +1 -0
  510. package/dist/modules/wms/components/backend/WmsLotsListPage.js.map +2 -2
  511. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js +27 -20
  512. package/dist/modules/wms/components/backend/inventoryMutationLoaders.js.map +2 -2
  513. package/dist/modules/wms/components/backend/warehouseFormOptions.js +66 -0
  514. package/dist/modules/wms/components/backend/warehouseFormOptions.js.map +7 -0
  515. package/dist/modules/workflows/backend/definitions/page.js +3 -1
  516. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  517. package/dist/modules/workflows/backend/events/page.js +3 -1
  518. package/dist/modules/workflows/backend/events/page.js.map +2 -2
  519. package/dist/modules/workflows/backend/instances/page.js +3 -1
  520. package/dist/modules/workflows/backend/instances/page.js.map +2 -2
  521. package/dist/modules/workflows/backend/tasks/page.js +3 -1
  522. package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
  523. package/dist/modules/workflows/lib/activity-executor.js +4 -0
  524. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  525. package/package.json +9 -10
  526. package/src/helpers/integration/api.ts +33 -1
  527. package/src/helpers/integration/communicationChannelsFixtures.ts +69 -1
  528. package/src/modules/api_keys/api/keys/route.ts +3 -1
  529. package/src/modules/api_keys/backend/api-keys/page.tsx +4 -1
  530. package/src/modules/api_keys/di.ts +7 -0
  531. package/src/modules/api_keys/services/principalService.ts +33 -0
  532. package/src/modules/attachments/AGENTS.md +15 -0
  533. package/src/modules/attachments/api/route.ts +2 -2
  534. package/src/modules/attachments/components/AttachmentLibrary.tsx +3 -0
  535. package/src/modules/attachments/di.ts +16 -0
  536. package/src/modules/attachments/i18n/de.json +5 -0
  537. package/src/modules/attachments/i18n/en.json +5 -0
  538. package/src/modules/attachments/i18n/es.json +5 -0
  539. package/src/modules/attachments/i18n/ko.json +5 -0
  540. package/src/modules/attachments/i18n/pl.json +5 -0
  541. package/src/modules/attachments/index.ts +10 -0
  542. package/src/modules/attachments/lib/attachment-service.ts +468 -0
  543. package/src/modules/attachments/lib/scoped-upload-service.ts +46 -0
  544. package/src/modules/attachments/lib/upload-limits.ts +6 -2
  545. package/src/modules/audit_logs/backend/audit-logs/page.tsx +8 -0
  546. package/src/modules/audit_logs/lib/display-helpers.tsx +42 -32
  547. package/src/modules/audit_logs/services/actionLogService.ts +74 -11
  548. package/src/modules/auth/acl.ts +30 -6
  549. package/src/modules/auth/api/feature-check.ts +10 -3
  550. package/src/modules/auth/api/login.ts +9 -1
  551. package/src/modules/auth/api/reset/validate.ts +57 -0
  552. package/src/modules/auth/api/roles/acl/route.ts +42 -39
  553. package/src/modules/auth/api/session/refresh.ts +32 -1
  554. package/src/modules/auth/api/users/acl/route.ts +177 -53
  555. package/src/modules/auth/api/users/route.ts +18 -5
  556. package/src/modules/auth/backend/roles/[id]/edit/page.meta.ts +5 -2
  557. package/src/modules/auth/backend/roles/page.tsx +4 -2
  558. package/src/modules/auth/backend/users/[id]/edit/page.meta.ts +5 -2
  559. package/src/modules/auth/backend/users/page.tsx +3 -2
  560. package/src/modules/auth/commands/acl.ts +495 -0
  561. package/src/modules/auth/components/AclEditor.tsx +63 -33
  562. package/src/modules/auth/data/entities.ts +4 -1
  563. package/src/modules/auth/data/validators.ts +5 -0
  564. package/src/modules/auth/di.ts +46 -16
  565. package/src/modules/auth/frontend/reset/[token]/page.tsx +74 -0
  566. package/src/modules/auth/i18n/de.json +389 -0
  567. package/src/modules/auth/i18n/en.json +389 -0
  568. package/src/modules/auth/i18n/es.json +389 -0
  569. package/src/modules/auth/i18n/ko.json +389 -0
  570. package/src/modules/auth/i18n/pl.json +391 -2
  571. package/src/modules/auth/lib/grantChecks.ts +6 -0
  572. package/src/modules/auth/lib/userIdFilter.ts +31 -0
  573. package/src/modules/auth/services/authService.ts +18 -2
  574. package/src/modules/auth/services/principalService.ts +266 -0
  575. package/src/modules/auth/services/rbacService.ts +110 -24
  576. package/src/modules/auth/services/roleOrganizationScope.ts +70 -0
  577. package/src/modules/business_rules/backend/logs/page.tsx +4 -1
  578. package/src/modules/business_rules/backend/rules/page.tsx +4 -1
  579. package/src/modules/business_rules/backend/sets/page.tsx +4 -1
  580. package/src/modules/business_rules/lib/expression-evaluator.ts +9 -9
  581. package/src/modules/business_rules/lib/rule-evaluator.ts +10 -9
  582. package/src/modules/catalog/ai-agents.ts +1 -1
  583. package/src/modules/catalog/ai-tools/merchandising-pack.ts +2 -2
  584. package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +12 -5
  585. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +3 -0
  586. package/src/modules/catalog/components/products/ProductsDataTable.tsx +7 -0
  587. package/src/modules/catalog/i18n/de.json +1 -0
  588. package/src/modules/catalog/i18n/en.json +1 -0
  589. package/src/modules/catalog/i18n/es.json +1 -0
  590. package/src/modules/catalog/i18n/ko.json +1 -0
  591. package/src/modules/catalog/i18n/pl.json +1 -0
  592. package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +4 -0
  593. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +17 -1
  594. package/src/modules/communication_channels/api/post/test-seed/route.ts +155 -16
  595. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +4 -0
  596. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +38 -1
  597. package/src/modules/communication_channels/di.ts +7 -0
  598. package/src/modules/communication_channels/lib/adapter.ts +14 -0
  599. package/src/modules/communication_channels/lib/email-capabilities.ts +4 -0
  600. package/src/modules/communication_channels/lib/outbound-recipient.ts +71 -0
  601. package/src/modules/communication_channels/lib/resolve-channel-type.ts +99 -0
  602. package/src/modules/communication_channels/lib/test-seed.ts +64 -4
  603. package/src/modules/configs/i18n/de.json +2 -0
  604. package/src/modules/configs/i18n/en.json +2 -0
  605. package/src/modules/configs/i18n/es.json +2 -0
  606. package/src/modules/configs/i18n/ko.json +2 -0
  607. package/src/modules/configs/i18n/pl.json +2 -0
  608. package/src/modules/configs/lib/system-status.ts +9 -0
  609. package/src/modules/currencies/AGENTS.md +3 -0
  610. package/src/modules/currencies/backend/currencies/page.tsx +4 -1
  611. package/src/modules/currencies/backend/exchange-rates/page.tsx +4 -1
  612. package/src/modules/currencies/services/README.md +45 -0
  613. package/src/modules/currencies/services/rateFetchingService.ts +6 -1
  614. package/src/modules/customer_accounts/api/admin/domain-mappings.ts +7 -0
  615. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +4 -1
  616. package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +4 -1
  617. package/src/modules/customer_accounts/services/domainMappingService.ts +10 -0
  618. package/src/modules/customers/ai-tools/companies-pack.ts +4 -1
  619. package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +17 -2
  620. package/src/modules/customers/ai-tools/deals-pack.ts +66 -1
  621. package/src/modules/customers/ai-tools/people-pack.ts +4 -1
  622. package/src/modules/customers/api/activities/route.ts +45 -0
  623. package/src/modules/customers/api/companies/[id]/people/route.ts +24 -73
  624. package/src/modules/customers/api/companies/[id]/route.ts +24 -107
  625. package/src/modules/customers/api/dashboard/widgets/customer-todos/route.ts +16 -8
  626. package/src/modules/customers/api/deals/[id]/companies/route.ts +4 -1
  627. package/src/modules/customers/api/deals/[id]/people/route.ts +4 -1
  628. package/src/modules/customers/api/deals/[id]/route.ts +17 -28
  629. package/src/modules/customers/api/deals/[id]/stats/route.ts +10 -25
  630. package/src/modules/customers/api/deals/aggregate/route.ts +14 -4
  631. package/src/modules/customers/api/deals/map/route.ts +1 -0
  632. package/src/modules/customers/api/deals/route.ts +50 -1
  633. package/src/modules/customers/api/entity-roles-factory.ts +8 -4
  634. package/src/modules/customers/api/interactions/route.ts +8 -2
  635. package/src/modules/customers/api/interactions/tasks/route.ts +24 -1
  636. package/src/modules/customers/api/people/[id]/companies/[linkId]/route.ts +12 -4
  637. package/src/modules/customers/api/people/[id]/companies/context.ts +4 -1
  638. package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +4 -1
  639. package/src/modules/customers/api/people/[id]/route.ts +16 -11
  640. package/src/modules/customers/api/people/check-phone/route.ts +75 -10
  641. package/src/modules/customers/api/todos/route.ts +27 -2
  642. package/src/modules/customers/backend/customers/companies/page.tsx +5 -2
  643. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +7 -2
  644. package/src/modules/customers/backend/customers/deals/page.tsx +8 -2
  645. package/src/modules/customers/backend/customers/deals/pipeline/components/Lane.tsx +5 -14
  646. package/src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx +109 -27
  647. package/src/modules/customers/backend/customers/deals/pipeline/components/toneClasses.ts +21 -0
  648. package/src/modules/customers/backend/customers/people/page.tsx +5 -2
  649. package/src/modules/customers/backend/hooks/useEmailDuplicateCheck.ts +12 -4
  650. package/src/modules/customers/commands/deals.ts +34 -59
  651. package/src/modules/customers/commands/interactions.ts +1 -1
  652. package/src/modules/customers/commands/personCompanyLinks.ts +224 -8
  653. package/src/modules/customers/components/CustomerTodosTable.tsx +2 -0
  654. package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
  655. package/src/modules/customers/components/calendar/AgendaList.tsx +3 -3
  656. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +1 -1
  657. package/src/modules/customers/components/calendar/CalendarScreen.tsx +1 -1
  658. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +1 -1
  659. package/src/modules/customers/components/calendar/EventBlock.tsx +2 -2
  660. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +4 -2
  661. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +61 -5
  662. package/src/modules/customers/components/calendar/editor/hooks.ts +7 -1
  663. package/src/modules/customers/components/calendar/types.ts +2 -2
  664. package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
  665. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +8 -1
  666. package/src/modules/customers/components/detail/DealForm.tsx +22 -2
  667. package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
  668. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +58 -12
  669. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +8 -1
  670. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +23 -14
  671. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +5 -5
  672. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +5 -5
  673. package/src/modules/customers/components/detail/types.ts +1 -1
  674. package/src/modules/customers/components/detail/utils.ts +15 -5
  675. package/src/modules/customers/data/entities.ts +1 -1
  676. package/src/modules/customers/data/validators.ts +50 -9
  677. package/src/modules/customers/events.ts +5 -0
  678. package/src/modules/customers/i18n/de.json +9 -1
  679. package/src/modules/customers/i18n/en.json +9 -1
  680. package/src/modules/customers/i18n/es.json +9 -1
  681. package/src/modules/customers/i18n/ko.json +9 -1
  682. package/src/modules/customers/i18n/pl.json +9 -1
  683. package/src/modules/customers/lib/calendar/conflicts.ts +10 -2
  684. package/src/modules/customers/lib/calendar/editorPayload.ts +4 -3
  685. package/src/modules/customers/lib/calendar/mapItem.ts +8 -3
  686. package/src/modules/customers/lib/calendar/participantIdentity.ts +17 -0
  687. package/src/modules/customers/lib/closureStage.ts +76 -0
  688. package/src/modules/customers/lib/dealStatus.ts +42 -0
  689. package/src/modules/customers/lib/detailReadAccess.ts +35 -0
  690. package/src/modules/customers/lib/findPeopleByAddresses.ts +8 -6
  691. package/src/modules/customers/lib/personCompanies.ts +136 -1
  692. package/src/modules/customers/lib/todoCompatibility.ts +19 -1
  693. package/src/modules/dashboards/i18n/es.json +4 -4
  694. package/src/modules/dashboards/i18n/pl.json +7 -7
  695. package/src/modules/dashboards/lib/widgetScope.ts +6 -0
  696. package/src/modules/data_sync/AGENTS.md +1 -1
  697. package/src/modules/data_sync/backend/data-sync/page.tsx +4 -1
  698. package/src/modules/data_sync/lib/abandoned-run.ts +56 -0
  699. package/src/modules/data_sync/lib/adapter.ts +36 -0
  700. package/src/modules/data_sync/lib/sync-engine.ts +113 -5
  701. package/src/modules/data_sync/workers/sync-export.ts +4 -0
  702. package/src/modules/data_sync/workers/sync-import.ts +4 -0
  703. package/src/modules/devices/AGENTS.md +20 -1
  704. package/src/modules/devices/acl.ts +9 -1
  705. package/src/modules/devices/api/admin/devices/[id]/route.ts +5 -34
  706. package/src/modules/devices/api/admin/devices/route.ts +14 -1
  707. package/src/modules/devices/api/deviceList.ts +135 -11
  708. package/src/modules/devices/api/deviceSerialization.ts +50 -0
  709. package/src/modules/devices/api/openapi.ts +9 -1
  710. package/src/modules/devices/api/route.ts +9 -1
  711. package/src/modules/devices/backend/devices/[id]/page.tsx +18 -32
  712. package/src/modules/devices/backend/devices/create/page.tsx +17 -1
  713. package/src/modules/devices/backend/devices/page.tsx +44 -45
  714. package/src/modules/devices/backend/devices/useDeviceUserLabels.ts +45 -0
  715. package/src/modules/devices/backend/devices/userOptions.ts +99 -0
  716. package/src/modules/devices/i18n/de.json +3 -2
  717. package/src/modules/devices/i18n/en.json +3 -2
  718. package/src/modules/devices/i18n/es.json +3 -2
  719. package/src/modules/devices/i18n/ko.json +3 -2
  720. package/src/modules/devices/i18n/pl.json +3 -2
  721. package/src/modules/devices/setup.ts +6 -2
  722. package/src/modules/directory/backend/directory/organizations/page.tsx +3 -1
  723. package/src/modules/directory/backend/directory/tenants/page.tsx +3 -1
  724. package/src/modules/directory/di.ts +14 -2
  725. package/src/modules/directory/services/organizationHierarchyService.ts +34 -0
  726. package/src/modules/directory/services/organizationScopeService.ts +85 -0
  727. package/src/modules/directory/utils/organizationScope.ts +4 -15
  728. package/src/modules/entities/api/records.ts +2 -0
  729. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +4 -1
  730. package/src/modules/eudr/api/plots/route.ts +4 -0
  731. package/src/modules/eudr/backend/eudr/evidence-submissions/page.tsx +4 -0
  732. package/src/modules/eudr/backend/eudr/plots/page.tsx +4 -0
  733. package/src/modules/eudr/backend/eudr/product-mappings/page.tsx +4 -0
  734. package/src/modules/eudr/backend/eudr/risk-assessments/page.tsx +4 -0
  735. package/src/modules/eudr/backend/eudr/statements/page.tsx +4 -0
  736. package/src/modules/eudr/commands/statements.ts +0 -3
  737. package/src/modules/eudr/data/validators.ts +16 -1
  738. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +2 -1
  739. package/src/modules/feature_toggles/components/OverridesTable.tsx +2 -1
  740. package/src/modules/feature_toggles/defaults.json +0 -8
  741. package/src/modules/inbox_ops/backend/inbox-ops/log/page.tsx +4 -0
  742. package/src/modules/inbox_ops/backend/inbox-ops/page.tsx +4 -0
  743. package/src/modules/inbox_ops/lib/htmlToPlainText.ts +1 -17
  744. package/src/modules/integrations/api/[id]/credentials/route.ts +6 -4
  745. package/src/modules/integrations/backend/integrations/[id]/page.tsx +61 -31
  746. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +44 -9
  747. package/src/modules/integrations/backend/integrations/credential-secret-fields.ts +84 -0
  748. package/src/modules/integrations/data/validators.ts +12 -2
  749. package/src/modules/integrations/i18n/de.json +1 -0
  750. package/src/modules/integrations/i18n/en.json +1 -0
  751. package/src/modules/integrations/i18n/es.json +1 -0
  752. package/src/modules/integrations/i18n/ko.json +1 -0
  753. package/src/modules/integrations/i18n/pl.json +1 -0
  754. package/src/modules/integrations/lib/credentials-masking.ts +10 -6
  755. package/src/modules/messages/api/[id]/route.ts +55 -1
  756. package/src/modules/messages/api/openapi.ts +20 -0
  757. package/src/modules/messages/api/route.ts +31 -2
  758. package/src/modules/messages/components/MessagesInboxPageClient.tsx +9 -16
  759. package/src/modules/messages/components/inboxFilters.ts +36 -0
  760. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsConversation.ts +14 -1
  761. package/src/modules/messages/components/message-detail/panels/MessageHeader.tsx +3 -1
  762. package/src/modules/messages/components/message-detail/panels/thread-panel.tsx +2 -1
  763. package/src/modules/messages/components/message-detail/types.ts +3 -0
  764. package/src/modules/messages/components/messageListLabels.ts +52 -3
  765. package/src/modules/messages/data/validators.ts +43 -4
  766. package/src/modules/messages/lib/channel-sender-identity.ts +55 -0
  767. package/src/modules/messages/lib/composeSourceChannelType.ts +95 -0
  768. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +4 -6
  769. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +4 -1
  770. package/src/modules/payment_gateways/lib/webhook-processor.ts +11 -0
  771. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +4 -0
  772. package/src/modules/push_notifications/backend/push_notifications/page.tsx +4 -1
  773. package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +4 -4
  774. package/src/modules/query_index/lib/engine.ts +249 -61
  775. package/src/modules/query_index/lib/search-entity-policy.ts +46 -0
  776. package/src/modules/query_index/lib/search-tokens.ts +135 -3
  777. package/src/modules/resources/backend/resources/resource-types/page.tsx +4 -1
  778. package/src/modules/resources/backend/resources/resources/page.tsx +4 -1
  779. package/src/modules/sales/backend/sales/channels/offers/page.tsx +4 -0
  780. package/src/modules/sales/backend/sales/channels/page.tsx +4 -0
  781. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
  782. package/src/modules/sales/commands/documents.ts +93 -0
  783. package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
  784. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +4 -0
  785. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
  786. package/src/modules/sales/components/documents/ItemsSection.tsx +56 -18
  787. package/src/modules/sales/components/documents/LineItemDialog.tsx +137 -20
  788. package/src/modules/sales/components/documents/PaymentsSection.tsx +20 -9
  789. package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
  790. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +106 -14
  791. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +6 -5
  792. package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
  793. package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
  794. package/src/modules/sales/components/documents/lineItemShipmentLock.ts +74 -0
  795. package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
  796. package/src/modules/sales/data/entities.ts +6 -0
  797. package/src/modules/sales/extension-points.ts +1 -0
  798. package/src/modules/sales/i18n/de.json +8 -0
  799. package/src/modules/sales/i18n/en.json +8 -0
  800. package/src/modules/sales/i18n/es.json +8 -0
  801. package/src/modules/sales/i18n/ko.json +8 -0
  802. package/src/modules/sales/i18n/pl.json +8 -0
  803. package/src/modules/sales/lib/salesChannelsToggleSeed.ts +42 -0
  804. package/src/modules/sales/migrations/.snapshot-open-mercato.json +60 -0
  805. package/src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts +87 -0
  806. package/src/modules/sales/setup.ts +2 -0
  807. package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +9 -1
  808. package/src/modules/sales/widgets/injection-table.ts +4 -7
  809. package/src/modules/staff/backend/staff/leave-requests/page.tsx +4 -0
  810. package/src/modules/staff/backend/staff/my-leave-requests/page.tsx +4 -0
  811. package/src/modules/staff/backend/staff/team-members/page.tsx +6 -0
  812. package/src/modules/staff/backend/staff/team-roles/page.tsx +4 -0
  813. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +4 -0
  814. package/src/modules/staff/backend/staff/teams/page.tsx +4 -0
  815. package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1
  816. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +4 -0
  817. package/src/modules/sync_excel/lib/adapters/customers.ts +5 -0
  818. package/src/modules/warranty_claims/api/assignees/route.ts +3 -3
  819. package/src/modules/warranty_claims/api/portal/attachments/route.ts +2 -2
  820. package/src/modules/warranty_claims/api/portal/claims/route.ts +12 -9
  821. package/src/modules/warranty_claims/backend/page.tsx +4 -0
  822. package/src/modules/warranty_claims/backend/warranty_claims/registrations/page.tsx +4 -0
  823. package/src/modules/warranty_claims/backend/warranty_claims/troubleshooting-guides/page.tsx +4 -0
  824. package/src/modules/warranty_claims/backend/warranty_claims/vendor-policies/page.tsx +4 -0
  825. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +4 -0
  826. package/src/modules/wms/api/warehouseSearch.ts +23 -0
  827. package/src/modules/wms/api/warehouses/route.ts +13 -9
  828. package/src/modules/wms/api/zones/route.ts +16 -0
  829. package/src/modules/wms/commands/configuration.ts +159 -10
  830. package/src/modules/wms/commands/inventory-actions.ts +3 -0
  831. package/src/modules/wms/commands/shared.ts +17 -0
  832. package/src/modules/wms/components/backend/WarehouseEditDialog.tsx +216 -0
  833. package/src/modules/wms/components/backend/WmsConfigurationPage.tsx +95 -141
  834. package/src/modules/wms/components/backend/WmsInventoryConsolePage.tsx +2 -0
  835. package/src/modules/wms/components/backend/WmsLotsListPage.tsx +2 -0
  836. package/src/modules/wms/components/backend/inventoryMutationLoaders.ts +36 -20
  837. package/src/modules/wms/components/backend/warehouseFormOptions.ts +72 -0
  838. package/src/modules/wms/i18n/de.json +2 -0
  839. package/src/modules/wms/i18n/en.json +2 -0
  840. package/src/modules/wms/i18n/es.json +2 -0
  841. package/src/modules/wms/i18n/ko.json +2 -0
  842. package/src/modules/wms/i18n/pl.json +2 -0
  843. package/src/modules/workflows/backend/definitions/page.tsx +4 -1
  844. package/src/modules/workflows/backend/events/page.tsx +4 -1
  845. package/src/modules/workflows/backend/instances/page.tsx +4 -1
  846. package/src/modules/workflows/backend/tasks/page.tsx +4 -1
  847. package/src/modules/workflows/lib/activity-executor.ts +9 -0
@@ -94,12 +94,28 @@ function applyExportCounters(batch: ExportBatch): SyncCounterDelta {
94
94
  }
95
95
 
96
96
  // Adapter batches can legitimately outlast the stale-job sweep window (slow upstream
97
- // APIs), so the engine must heartbeat while a batch is still being produced.
97
+ // APIs), so the engine must heartbeat while a batch is still being produced. The same
98
+ // tick also polls cancellation, so a cancel lands within one interval instead of waiting
99
+ // out the batch — sharing this timer rather than adding a second one that would double
100
+ // the per-interval round-trips for the whole life of a run.
98
101
  const HEARTBEAT_TICK_MS = (STALE_JOB_TIMEOUT_SECONDS * 1000) / 4
99
102
 
100
103
  // Runs `tick` on an interval only while the source iterator is pending, so heartbeats
101
104
  // stop the moment the producer dies and genuinely stale jobs still get swept. The outer
102
105
  // finally closes the adapter generator on early exits (cancellation, ownership conflict).
106
+ // Our own abort, as opposed to a failure that merely coincided with one. Adapters are told to
107
+ // return rather than throw, but `signal.throwIfAborted()` and an aborted `fetch` both surface as
108
+ // this, and either is a cancellation rather than a fault.
109
+ //
110
+ // Matched structurally on `name` rather than with `instanceof Error`, because those two throw a
111
+ // `DOMException`, and whether that inherits from `Error` depends on the runtime — it does under
112
+ // bare Node 24 and does NOT under the jest environment this is tested in. An `instanceof` test
113
+ // therefore passes or fails on where the code runs, which is not something cancellation should
114
+ // depend on.
115
+ function isAbortError(error: unknown): boolean {
116
+ return typeof error === 'object' && error !== null && (error as { name?: unknown }).name === 'AbortError'
117
+ }
118
+
103
119
  async function* withHeartbeat<T>(source: AsyncIterable<T>, tick: () => void, intervalMs: number): AsyncGenerator<T, void, undefined> {
104
120
  const iterator = source[Symbol.asyncIterator]()
105
121
  try {
@@ -187,6 +203,32 @@ export function createSyncEngine(deps: EngineDeps) {
187
203
  }
188
204
  }
189
205
 
206
+ // Rides the heartbeat timer, which is the only thing that runs while the adapter is still
207
+ // producing a batch — the engine's own cancellation check sits in the batch handler and is
208
+ // reached only after a yield. Swallows its own errors because it runs on a timer, where an
209
+ // unhandled rejection is fatal, and stops polling once it has aborted.
210
+ function makeCancellationTick(progressJobId: string | null | undefined, scope: SyncScope, controller: AbortController): () => void {
211
+ if (!progressJobId) return () => {}
212
+ let inFlight = false
213
+ return () => {
214
+ if (inFlight || controller.signal.aborted) return
215
+ inFlight = true
216
+ progressService.isCancellationRequested(progressJobId, scope.tenantId, scope.organizationId)
217
+ .then((cancelled) => {
218
+ if (cancelled) controller.abort()
219
+ })
220
+ .catch((error) => {
221
+ logger.warn('Cancellation poll failed', {
222
+ progressJobId,
223
+ error: error instanceof Error ? error.message : String(error),
224
+ })
225
+ })
226
+ .finally(() => {
227
+ inFlight = false
228
+ })
229
+ }
230
+ }
231
+
190
232
  async function refreshCoverageSnapshots(entityTypes: string[] | undefined, scope: SyncScope): Promise<void> {
191
233
  if (!entityTypes || entityTypes.length === 0) return
192
234
 
@@ -559,10 +601,17 @@ export function createSyncEngine(deps: EngineDeps) {
559
601
  let processedCount = await seedProcessedCount(run.progressJobId, scope)
560
602
  let totalCount: number | null = null
561
603
  let committedBatches = activeRun.batchesCompleted ?? 0
604
+ // Whether the last committed batch said the source was exhausted. Distinguishes a stream that
605
+ // drained from one the adapter stopped early — see the post-stream finalize below.
606
+ let streamReportedDone = false
562
607
  // Captured while the triggering job's span is still the active one, so
563
608
  // every rooted batch trace can link back to it.
564
609
  const runTrace = captureTelemetryTrace()
565
610
  const spanAttributes = runSpanAttributes(run, providerKey, scope)
611
+ // Declared outside the try because both the catch and the completion path below read it.
612
+ const cancellation = new AbortController()
613
+ const heartbeat = makeHeartbeatTick(run.progressJobId, scope)
614
+ const pollCancellation = makeCancellationTick(run.progressJobId, scope, cancellation)
566
615
 
567
616
  try {
568
617
  const streamResult = await forEachBatch(
@@ -576,8 +625,11 @@ export function createSyncEngine(deps: EngineDeps) {
576
625
  scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },
577
626
  runId: run.id,
578
627
  parameters: (run.parameters ?? {}) as RunParameters,
628
+ signal: cancellation.signal,
579
629
  }),
580
- makeHeartbeatTick(run.progressJobId, scope),
630
+ // `finally` so a synchronous throw from the heartbeat cannot also stop cancellation
631
+ // from being observed for the rest of the run.
632
+ () => { try { heartbeat() } finally { pollCancellation() } },
581
633
  HEARTBEAT_TICK_MS,
582
634
  ),
583
635
  {
@@ -592,7 +644,7 @@ export function createSyncEngine(deps: EngineDeps) {
592
644
  'data_sync.batch_size': batch.items.length,
593
645
  })
594
646
 
595
- if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId, scope.tenantId, scope.organizationId)) {
647
+ if (cancellation.signal.aborted || (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId, scope.tenantId, scope.organizationId))) {
596
648
  await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
597
649
  return 'stop'
598
650
  }
@@ -621,6 +673,7 @@ export function createSyncEngine(deps: EngineDeps) {
621
673
  { expectedBatchesCompleted: committedBatches, persistSharedCursor },
622
674
  )
623
675
  committedBatches += 1
676
+ streamReportedDone = batch.hasMore === false
624
677
 
625
678
  await updateProgress(run.progressJobId, processedCount, totalCount, scope)
626
679
  await refreshCoverageSnapshots(batch.refreshCoverageEntityTypes, scope)
@@ -657,6 +710,15 @@ export function createSyncEngine(deps: EngineDeps) {
657
710
  })
658
711
  return
659
712
  }
713
+ // An adapter that honours the signal may reject instead of returning, so our own abort is
714
+ // a cancellation rather than a fault — and must not leave an `error` entry in the
715
+ // integration log for a run the operator cancelled on purpose. Anything else that merely
716
+ // coincided with the cancel — a rejecting commit, an upstream 500 — is a genuine failure
717
+ // and keeps its log entry, its message, its `failed` status and its failed event.
718
+ if (cancellation.signal.aborted && isAbortError(error)) {
719
+ await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
720
+ return
721
+ }
660
722
  const message = error instanceof Error ? error.message : 'Sync import failed'
661
723
  await integrationLogService.write(
662
724
  {
@@ -671,6 +733,19 @@ export function createSyncEngine(deps: EngineDeps) {
671
733
  return
672
734
  }
673
735
 
736
+ // An adapter that honours the signal stops mid-batch and returns WITHOUT yielding, so the
737
+ // batch handler — which owns the only other `cancelled` transition — never runs and the
738
+ // stream reports `completed`.
739
+ //
740
+ // `streamReportedDone` keeps that from swallowing a run that genuinely finished: an adapter
741
+ // that ignores the signal and drains after reporting `hasMore: false` delivered everything it
742
+ // had, even when the cancel landed during the final read. Calling that cancelled would tell
743
+ // the operator a complete sync was partial and leave a finished run resumable.
744
+ if (cancellation.signal.aborted && !streamReportedDone) {
745
+ await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
746
+ return
747
+ }
748
+
674
749
  await finalizeRun(run.id, 'completed', scope, undefined, operationalTelemetry)
675
750
  },
676
751
 
@@ -753,10 +828,17 @@ export function createSyncEngine(deps: EngineDeps) {
753
828
  const mapping = await resolveMapping(adapter, run.entityType, scope)
754
829
  let processedCount = await seedProcessedCount(run.progressJobId, scope)
755
830
  let committedBatches = activeRun.batchesCompleted ?? 0
831
+ // Whether the last committed batch said the source was exhausted. Distinguishes a stream that
832
+ // drained from one the adapter stopped early — see the post-stream finalize below.
833
+ let streamReportedDone = false
756
834
  // Captured while the triggering job's span is still the active one, so
757
835
  // every rooted batch trace can link back to it.
758
836
  const runTrace = captureTelemetryTrace()
759
837
  const spanAttributes = runSpanAttributes(run, providerKey, scope)
838
+ // Declared outside the try because both the catch and the completion path below read it.
839
+ const cancellation = new AbortController()
840
+ const heartbeat = makeHeartbeatTick(run.progressJobId, scope)
841
+ const pollCancellation = makeCancellationTick(run.progressJobId, scope, cancellation)
760
842
 
761
843
  try {
762
844
  const streamResult = await forEachBatch(
@@ -770,8 +852,11 @@ export function createSyncEngine(deps: EngineDeps) {
770
852
  scope: { organizationId: scope.organizationId, tenantId: scope.tenantId },
771
853
  runId: run.id,
772
854
  parameters: (run.parameters ?? {}) as RunParameters,
855
+ signal: cancellation.signal,
773
856
  }),
774
- makeHeartbeatTick(run.progressJobId, scope),
857
+ // `finally` so a synchronous throw from the heartbeat cannot also stop cancellation
858
+ // from being observed for the rest of the run.
859
+ () => { try { heartbeat() } finally { pollCancellation() } },
775
860
  HEARTBEAT_TICK_MS,
776
861
  ),
777
862
  {
@@ -786,7 +871,7 @@ export function createSyncEngine(deps: EngineDeps) {
786
871
  'data_sync.batch_size': batch.results.length,
787
872
  })
788
873
 
789
- if (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId, scope.tenantId, scope.organizationId)) {
874
+ if (cancellation.signal.aborted || (run.progressJobId && await progressService.isCancellationRequested(run.progressJobId, scope.tenantId, scope.organizationId))) {
790
875
  await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
791
876
  return 'stop'
792
877
  }
@@ -815,6 +900,7 @@ export function createSyncEngine(deps: EngineDeps) {
815
900
  { expectedBatchesCompleted: committedBatches, persistSharedCursor },
816
901
  )
817
902
  committedBatches += 1
903
+ streamReportedDone = batch.hasMore === false
818
904
  await updateProgress(run.progressJobId, processedCount, null, scope)
819
905
  await logExportItemFailures(run.id, run.integrationId, batch.results, scope)
820
906
 
@@ -846,6 +932,15 @@ export function createSyncEngine(deps: EngineDeps) {
846
932
  })
847
933
  return
848
934
  }
935
+ // An adapter that honours the signal may reject instead of returning, so our own abort is
936
+ // a cancellation rather than a fault — and must not leave an `error` entry in the
937
+ // integration log for a run the operator cancelled on purpose. Anything else that merely
938
+ // coincided with the cancel — a rejecting commit, an upstream 500 — is a genuine failure
939
+ // and keeps its log entry, its message, its `failed` status and its failed event.
940
+ if (cancellation.signal.aborted && isAbortError(error)) {
941
+ await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
942
+ return
943
+ }
849
944
  const message = error instanceof Error ? error.message : 'Sync export failed'
850
945
  await integrationLogService.write(
851
946
  {
@@ -860,6 +955,19 @@ export function createSyncEngine(deps: EngineDeps) {
860
955
  return
861
956
  }
862
957
 
958
+ // An adapter that honours the signal stops mid-batch and returns WITHOUT yielding, so the
959
+ // batch handler — which owns the only other `cancelled` transition — never runs and the
960
+ // stream reports `completed`.
961
+ //
962
+ // `streamReportedDone` keeps that from swallowing a run that genuinely finished: an adapter
963
+ // that ignores the signal and drains after reporting `hasMore: false` delivered everything it
964
+ // had, even when the cancel landed during the final read. Calling that cancelled would tell
965
+ // the operator a complete sync was partial and leave a finished run resumable.
966
+ if (cancellation.signal.aborted && !streamReportedDone) {
967
+ await finalizeRun(run.id, 'cancelled', scope, undefined, operationalTelemetry)
968
+ return
969
+ }
970
+
863
971
  await finalizeRun(run.id, 'completed', scope, undefined, operationalTelemetry)
864
972
  },
865
973
  }
@@ -7,6 +7,7 @@ import {
7
7
  DATA_SYNC_LOCK_DURATION_MS,
8
8
  DATA_SYNC_MAX_STALLED_COUNT,
9
9
  } from '../lib/queue-policy'
10
+ import { failAbandonedRun } from '../lib/abandoned-run'
10
11
  import { createLogger } from '@open-mercato/shared/lib/logger'
11
12
 
12
13
  const logger = createLogger('data_sync').child({ component: 'sync-export' })
@@ -27,6 +28,9 @@ export const metadata: WorkerMeta = {
27
28
  concurrency: 5,
28
29
  lockDuration: DATA_SYNC_LOCK_DURATION_MS,
29
30
  maxStalledCount: DATA_SYNC_MAX_STALLED_COUNT,
31
+ // Declared on the worker, not on the enqueueing queue: the abandonment this reports is a worker
32
+ // restart, and the process that restarts never constructs the producer's queue instance.
33
+ onJobAbandoned: failAbandonedRun,
30
34
  }
31
35
 
32
36
  type HandlerContext = JobContext & {
@@ -7,6 +7,7 @@ import {
7
7
  DATA_SYNC_LOCK_DURATION_MS,
8
8
  DATA_SYNC_MAX_STALLED_COUNT,
9
9
  } from '../lib/queue-policy'
10
+ import { failAbandonedRun } from '../lib/abandoned-run'
10
11
  import { createLogger } from '@open-mercato/shared/lib/logger'
11
12
 
12
13
  const logger = createLogger('data_sync').child({ component: 'sync-import' })
@@ -27,6 +28,9 @@ export const metadata: WorkerMeta = {
27
28
  concurrency: 5,
28
29
  lockDuration: DATA_SYNC_LOCK_DURATION_MS,
29
30
  maxStalledCount: DATA_SYNC_MAX_STALLED_COUNT,
31
+ // Declared on the worker, not on the enqueueing queue: the abandonment this reports is a worker
32
+ // restart, and the process that restarts never constructs the producer's queue instance.
33
+ onJobAbandoned: failAbandonedRun,
30
34
  }
31
35
 
32
36
  type HandlerContext = JobContext & {
@@ -23,6 +23,14 @@ delivery logic** (sender, providers, delivery rows, workers live in the `push_no
23
23
  — they carry undo snapshots, query-index side effects, and domain events.
24
24
  - Treat `push_token` as a secret: **never** include it in list/response field sets. Only
25
25
  `push_provider` and `push_token_updated_at` are exposed.
26
+ - Serialize every GET response in **camelCase**, like every other module. The list routes run
27
+ `transformDeviceListItem` (`api/deviceList.ts`) over the query engine's raw projection and the admin
28
+ detail route uses `serializeDeviceDetail` (`api/deviceSerialization.ts`). Both also emit the legacy
29
+ snake_case keys as **deprecated aliases** via `toDeprecatedSnakeCaseAliases`, kept for one minor
30
+ version per `BACKWARD_COMPATIBILITY.md` § 7 and pinned by `TC-DEV-007`; when the bridge is dropped,
31
+ remove the alias spread from those two functions and the deprecated keys from `deviceListItemSchema`
32
+ / `deviceDetailItemSchema`. Adding a column means adding it to `deviceListFields`, the transform, and
33
+ the schemas. See `.ai/specs/2026-08-24-devices-api-camelcase-responses.md` (#5513).
26
34
  - Honor the `pushToken` tri-state on `PUT`: absent key = leave unchanged; explicit `null` = clear
27
35
  (revoked OS permission) and bump `push_token_updated_at`. The command uses own-property presence.
28
36
  - Keep the list route's CRUD cache tag aligned with the command's `resourceKind`. The list reads
@@ -89,9 +97,20 @@ write busts both caches (see cache-tag note above).
89
97
  ## ACL
90
98
 
91
99
  `devices.view`, `devices.manage` (self-serve), `devices.admin` (cross-user). Defaults in `setup.ts`:
92
- `superadmin`/`admin` get `devices.*`; `employee` gets `view` + `manage`. Run
100
+ `superadmin`/`admin` get `devices.*` **plus `auth.users.list`**; `employee` gets `view` + `manage`. Run
93
101
  `yarn mercato auth sync-role-acls` after changing `acl.ts`/`setup.ts` to backfill existing tenants.
94
102
 
103
+ `devices.admin` declares `dependsOn: ['auth.users.list']`: the admin screens name owners by person,
104
+ and the register form's owner picker rejects values that do not resolve to a directory entry, so a
105
+ role holding `devices.admin` without the dependency cannot complete that form. `dependsOn` only
106
+ surfaces the gap in the ACL editor — it does not grant. Run the sync command above after deploying
107
+ an ACL change here.
108
+
109
+ The picker is `allowCustomValues: false`. That rejects free text and an id belonging to nobody; it
110
+ does **not** reject a raw id that is already a known option, because `ComboboxInput` matches on
111
+ `option.value` and `CrudForm` keeps the unfiltered first page as suggestions for the form's
112
+ lifetime. Either way the submitted value is a real user in the caller's scope.
113
+
95
114
  ## Validation Commands
96
115
 
97
116
  ```bash
@@ -1,7 +1,15 @@
1
1
  export const features = [
2
2
  { id: 'devices.view', title: 'View own devices', module: 'devices' },
3
3
  { id: 'devices.manage', title: 'Manage own devices', module: 'devices' },
4
- { id: 'devices.admin', title: 'Manage devices across users', module: 'devices' },
4
+ {
5
+ id: 'devices.admin',
6
+ title: 'Manage devices across users',
7
+ module: 'devices',
8
+ // Managing devices across users means naming the owner, and the admin screens name them by
9
+ // person rather than by UUID: the register form's owner picker and the owner column on both
10
+ // list and detail resolve through `GET /api/auth/users`, which `auth.users.list` gates.
11
+ dependsOn: ['auth.users.list'],
12
+ },
5
13
  ]
6
14
 
7
15
  export default features
@@ -15,6 +15,8 @@ import { updateDeviceSchema } from '../../../../data/validators'
15
15
  import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
16
16
  import { resolveDeviceActorUserId } from '../../../auth'
17
17
  import { executeUpdate, executeDeactivate, type DeviceMutationContext } from '../../../deviceOps'
18
+ import { serializeDeviceDetail, deviceDetailItemSchema } from '../../../deviceSerialization'
19
+ import { DEPRECATED_SNAKE_CASE_NOTICE } from '../../../openapi'
18
20
 
19
21
  const logger = createLogger('devices')
20
22
 
@@ -38,23 +40,6 @@ async function loadDevice(
38
40
  return findOneWithDecryption(em, UserDevice, { id, tenantId, deletedAt: null }, undefined, { tenantId })
39
41
  }
40
42
 
41
- // push_token is a secret and is never returned.
42
- function serializeDevice(device: UserDevice) {
43
- return {
44
- id: device.id,
45
- user_id: device.userId,
46
- device_id: device.deviceId,
47
- platform: device.platform,
48
- client_app_version: device.clientAppVersion ?? null,
49
- os_version: device.osVersion ?? null,
50
- push_provider: device.pushProvider ?? null,
51
- push_token_updated_at: device.pushTokenUpdatedAt ? device.pushTokenUpdatedAt.toISOString() : null,
52
- last_seen_at: device.lastSeenAt ? device.lastSeenAt.toISOString() : null,
53
- created_at: device.createdAt ? device.createdAt.toISOString() : null,
54
- updated_at: device.updatedAt ? device.updatedAt.toISOString() : null,
55
- }
56
- }
57
-
58
43
  export async function GET(req: Request, { params }: { params: { id: string } }) {
59
44
  const { translate } = await resolveTranslations()
60
45
  try {
@@ -76,7 +61,7 @@ export async function GET(req: Request, { params }: { params: { id: string } })
76
61
  if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId: device.organizationId ?? null })) {
77
62
  return NextResponse.json({ error: translate('devices.errors.forbidden', 'Access denied') }, { status: 403 })
78
63
  }
79
- return NextResponse.json({ item: serializeDevice(device) })
64
+ return NextResponse.json({ item: serializeDeviceDetail(device) })
80
65
  } catch (err) {
81
66
  if (isCrudHttpError(err)) {
82
67
  return NextResponse.json(err.body, { status: err.status })
@@ -177,21 +162,7 @@ export async function DELETE(req: Request, { params }: { params: { id: string }
177
162
 
178
163
  const okResponseSchema = z.object({ ok: z.literal(true), id: z.string().uuid().optional() })
179
164
  const errorResponseSchema = z.object({ error: z.string() })
180
- const detailResponseSchema = z.object({
181
- item: z.object({
182
- id: z.string().uuid(),
183
- user_id: z.string().uuid(),
184
- device_id: z.string(),
185
- platform: z.enum(['ios', 'android', 'web']),
186
- client_app_version: z.string().nullable(),
187
- os_version: z.string().nullable(),
188
- push_provider: z.string().nullable(),
189
- push_token_updated_at: z.string().nullable(),
190
- last_seen_at: z.string().nullable(),
191
- created_at: z.string().nullable(),
192
- updated_at: z.string().nullable(),
193
- }),
194
- })
165
+ const detailResponseSchema = z.object({ item: deviceDetailItemSchema })
195
166
 
196
167
  export const openApi: OpenApiRouteDoc = {
197
168
  tag: 'Devices (admin)',
@@ -199,7 +170,7 @@ export const openApi: OpenApiRouteDoc = {
199
170
  methods: {
200
171
  GET: {
201
172
  summary: 'Get any device',
202
- description: 'Admin: fetch a single device by id (push_token is never returned).',
173
+ description: `Admin: fetch a single device by id (push_token is never returned). ${DEPRECATED_SNAKE_CASE_NOTICE}`,
203
174
  responses: [{ status: 200, description: 'Device', schema: detailResponseSchema }],
204
175
  errors: [
205
176
  { status: 400, description: 'Invalid id', schema: errorResponseSchema },
@@ -2,6 +2,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { NextResponse } from 'next/server'
3
3
  import { z } from 'zod'
4
4
  import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
5
+ import { toHeaderLabel } from '@open-mercato/shared/lib/crud/exporters'
5
6
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
7
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
7
8
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
@@ -21,7 +22,14 @@ import {
21
22
  } from '../../../data/validators'
22
23
  import { resolveDeviceActorUserId } from '../../auth'
23
24
  import { createDevicesCrudOpenApi, createPagedListResponseSchema } from '../../openapi'
24
- import { deviceListSchema, deviceListFields, deviceListSortFieldMap, deviceListItemSchema } from '../../deviceList'
25
+ import {
26
+ deviceListSchema,
27
+ deviceListFields,
28
+ deviceListSortFieldMap,
29
+ deviceListItemSchema,
30
+ deviceExportColumnFields,
31
+ transformDeviceListItem,
32
+ } from '../../deviceList'
25
33
  import { executeRegister, type DeviceMutationContext } from '../../deviceOps'
26
34
 
27
35
  const logger = createLogger('devices')
@@ -55,6 +63,11 @@ const crud = makeCrudRoute({
55
63
  entityId: E.devices.user_device,
56
64
  fields: deviceListFields,
57
65
  sortFieldMap: deviceListSortFieldMap,
66
+ // The query engine projects raw column names; expose them in camelCase like every other module.
67
+ transformItem: transformDeviceListItem,
68
+ // Without an explicit column set the factory's default export spreads each item, so the deprecated
69
+ // snake_case aliases would double every column in the CSV/JSON/XML export.
70
+ export: { columns: deviceExportColumnFields.map((field) => ({ field, header: toHeaderLabel(field) })) },
58
71
  // Tenant + org scope is enforced by the factory (orm.tenantField + orm.orgField); only the optional
59
72
  // userId/platform narrowing is left to do here.
60
73
  buildFilters: async (query) => {
@@ -30,25 +30,149 @@ export const deviceListFields: string[] = [
30
30
  'updated_at',
31
31
  ]
32
32
 
33
+ // The admin list enables exports, and the factory's default export derives its columns by spreading
34
+ // each item — which would emit both spellings of every aliased key. Pin the canonical camelCase set
35
+ // so the export stays single-spelled and does not change shape when the aliases are dropped.
36
+ export const deviceExportColumnFields: string[] = [
37
+ 'id',
38
+ 'tenantId',
39
+ 'organizationId',
40
+ 'userId',
41
+ 'deviceId',
42
+ 'platform',
43
+ 'clientAppVersion',
44
+ 'osVersion',
45
+ 'locale',
46
+ 'pushProvider',
47
+ 'pushTokenUpdatedAt',
48
+ 'lastSeenAt',
49
+ 'createdAt',
50
+ 'updatedAt',
51
+ ]
52
+
33
53
  export const deviceListSortFieldMap: Record<string, string> = {
34
54
  lastSeenAt: 'last_seen_at',
35
55
  createdAt: 'created_at',
36
56
  updatedAt: 'updated_at',
37
57
  }
38
58
 
59
+ // The query engine projects the raw column names listed in `deviceListFields`. Every other module
60
+ // serializes its list items in camelCase (`sales/orders` → `orderNumber`, `warranty_claims` →
61
+ // `claimNumber`), so the list routes run this transform to expose the platform convention (#5513).
62
+ // The snake_case keys stay alongside as deprecated aliases for one minor version, per the
63
+ // deprecation protocol in BACKWARD_COMPATIBILITY.md § 7 (API Route URLs — response fields).
64
+ export function transformDeviceListItem(item: unknown): unknown {
65
+ const record = toRecord(item)
66
+ if (!Object.keys(record).length) return item
67
+ const camel = {
68
+ id: readString(record, 'id', 'id'),
69
+ tenantId: readString(record, 'tenant_id', 'tenantId'),
70
+ organizationId: readString(record, 'organization_id', 'organizationId'),
71
+ userId: readString(record, 'user_id', 'userId'),
72
+ deviceId: readString(record, 'device_id', 'deviceId'),
73
+ platform: readString(record, 'platform', 'platform'),
74
+ clientAppVersion: readString(record, 'client_app_version', 'clientAppVersion'),
75
+ osVersion: readString(record, 'os_version', 'osVersion'),
76
+ locale: readString(record, 'locale', 'locale'),
77
+ pushProvider: readString(record, 'push_provider', 'pushProvider'),
78
+ pushTokenUpdatedAt: toIso(record.push_token_updated_at ?? record.pushTokenUpdatedAt),
79
+ lastSeenAt: toIso(record.last_seen_at ?? record.lastSeenAt),
80
+ createdAt: toIso(record.created_at ?? record.createdAt),
81
+ updatedAt: toIso(record.updated_at ?? record.updatedAt),
82
+ }
83
+ // Spread the raw record first so anything the projection carries beyond the declared field set
84
+ // (custom-field keys, future columns) survives; the aliases then restate the snake_case keys with
85
+ // the same normalized values as their camelCase counterparts.
86
+ return { ...record, ...camel, ...toDeprecatedSnakeCaseAliases(camel) }
87
+ }
88
+
89
+ /**
90
+ * @deprecated Snake_case device response keys are superseded by the camelCase keys and are removed in
91
+ * the next minor release. Read `deviceId`, `userId`, `lastSeenAt`, … instead. See UPGRADE_NOTES.md.
92
+ */
93
+ export function toDeprecatedSnakeCaseAliases(item: DeviceResponseItem): Record<string, unknown> {
94
+ const aliases: Record<string, unknown> = {
95
+ tenant_id: item.tenantId,
96
+ organization_id: item.organizationId,
97
+ user_id: item.userId,
98
+ device_id: item.deviceId,
99
+ client_app_version: item.clientAppVersion,
100
+ os_version: item.osVersion,
101
+ push_provider: item.pushProvider,
102
+ push_token_updated_at: item.pushTokenUpdatedAt,
103
+ last_seen_at: item.lastSeenAt,
104
+ created_at: item.createdAt,
105
+ updated_at: item.updatedAt,
106
+ }
107
+ // The detail response never carried tenant/org, so an absent camelCase source must not grow a new
108
+ // key here — only keys the caller actually supplied get an alias.
109
+ for (const key of Object.keys(aliases)) {
110
+ if (aliases[key] === undefined) delete aliases[key]
111
+ }
112
+ return aliases
113
+ }
114
+
115
+ export type DeviceResponseItem = {
116
+ tenantId?: string | null
117
+ organizationId?: string | null
118
+ userId: string | null
119
+ deviceId: string | null
120
+ clientAppVersion: string | null
121
+ osVersion: string | null
122
+ pushProvider: string | null
123
+ pushTokenUpdatedAt?: string | null
124
+ lastSeenAt?: string | null
125
+ createdAt?: string | null
126
+ updatedAt: string | null
127
+ }
128
+
129
+ function toRecord(value: unknown): Record<string, unknown> {
130
+ return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}
131
+ }
132
+
133
+ function readString(record: Record<string, unknown>, snakeKey: string, camelKey: string): string | null {
134
+ const value = record[snakeKey] ?? record[camelKey]
135
+ return typeof value === 'string' ? value : null
136
+ }
137
+
138
+ export function toIso(value: unknown): string | null {
139
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString()
140
+ if (typeof value === 'string') {
141
+ const date = new Date(value)
142
+ return Number.isNaN(date.getTime()) ? value : date.toISOString()
143
+ }
144
+ return null
145
+ }
146
+
147
+ // In-code contract markers. The shared zodToJsonSchema converter does not currently emit per-property
148
+ // descriptions for object schemas, so the rendered deprecation notice lives on the endpoint description
149
+ // (`DEPRECATED_SNAKE_CASE_NOTICE` in ./openapi); these become visible for free if that changes.
150
+ const deprecatedAlias = (of: string) => `Deprecated alias for \`${of}\`; removed in the next minor release.`
151
+
39
152
  export const deviceListItemSchema = z.object({
40
153
  id: z.string().uuid(),
41
- tenant_id: z.string().uuid(),
42
- organization_id: z.string().uuid().nullable().optional(),
43
- user_id: z.string().uuid(),
44
- device_id: z.string(),
154
+ tenantId: z.string().uuid(),
155
+ organizationId: z.string().uuid().nullable().optional(),
156
+ userId: z.string().uuid(),
157
+ deviceId: z.string(),
45
158
  platform: z.enum(['ios', 'android', 'web']),
46
- client_app_version: z.string().nullable().optional(),
47
- os_version: z.string().nullable().optional(),
159
+ clientAppVersion: z.string().nullable().optional(),
160
+ osVersion: z.string().nullable().optional(),
48
161
  locale: z.string().nullable().optional(),
49
- push_provider: z.string().nullable().optional(),
50
- push_token_updated_at: z.string().nullable().optional(),
51
- last_seen_at: z.string().nullable().optional(),
52
- created_at: z.string().nullable().optional(),
53
- updated_at: z.string().nullable().optional(),
162
+ pushProvider: z.string().nullable().optional(),
163
+ pushTokenUpdatedAt: z.string().nullable().optional(),
164
+ lastSeenAt: z.string().nullable().optional(),
165
+ createdAt: z.string().nullable().optional(),
166
+ updatedAt: z.string().nullable().optional(),
167
+ tenant_id: z.string().uuid().describe(deprecatedAlias('tenantId')),
168
+ organization_id: z.string().uuid().nullable().optional().describe(deprecatedAlias('organizationId')),
169
+ user_id: z.string().uuid().describe(deprecatedAlias('userId')),
170
+ device_id: z.string().describe(deprecatedAlias('deviceId')),
171
+ client_app_version: z.string().nullable().optional().describe(deprecatedAlias('clientAppVersion')),
172
+ os_version: z.string().nullable().optional().describe(deprecatedAlias('osVersion')),
173
+ push_provider: z.string().nullable().optional().describe(deprecatedAlias('pushProvider')),
174
+ push_token_updated_at: z.string().nullable().optional().describe(deprecatedAlias('pushTokenUpdatedAt')),
175
+ last_seen_at: z.string().nullable().optional().describe(deprecatedAlias('lastSeenAt')),
176
+ created_at: z.string().nullable().optional().describe(deprecatedAlias('createdAt')),
177
+ updated_at: z.string().nullable().optional().describe(deprecatedAlias('updatedAt')),
54
178
  })
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod'
2
+ import type { UserDevice } from '../data/entities'
3
+ import { toDeprecatedSnakeCaseAliases, toIso } from './deviceList'
4
+
5
+ // Detail contract for `GET /api/devices/admin/devices/:id`. Keys are camelCase like every other
6
+ // module's responses (#5513); the snake_case keys stay alongside as deprecated aliases for one minor
7
+ // version, per the deprecation protocol in BACKWARD_COMPATIBILITY.md § 7.
8
+ // push_token is a secret and is never returned.
9
+ export function serializeDeviceDetail(device: UserDevice): Record<string, unknown> {
10
+ const camel = {
11
+ id: device.id,
12
+ userId: device.userId,
13
+ deviceId: device.deviceId,
14
+ platform: device.platform,
15
+ clientAppVersion: device.clientAppVersion ?? null,
16
+ osVersion: device.osVersion ?? null,
17
+ pushProvider: device.pushProvider ?? null,
18
+ pushTokenUpdatedAt: toIso(device.pushTokenUpdatedAt),
19
+ lastSeenAt: toIso(device.lastSeenAt),
20
+ createdAt: toIso(device.createdAt),
21
+ updatedAt: toIso(device.updatedAt),
22
+ }
23
+ return { ...camel, ...toDeprecatedSnakeCaseAliases(camel) }
24
+ }
25
+
26
+ // See the note in ./deviceList — the rendered deprecation notice lives on the endpoint description.
27
+ const deprecatedAlias = (of: string) => `Deprecated alias for \`${of}\`; removed in the next minor release.`
28
+
29
+ export const deviceDetailItemSchema = z.object({
30
+ id: z.string().uuid(),
31
+ userId: z.string().uuid(),
32
+ deviceId: z.string(),
33
+ platform: z.enum(['ios', 'android', 'web']),
34
+ clientAppVersion: z.string().nullable(),
35
+ osVersion: z.string().nullable(),
36
+ pushProvider: z.string().nullable(),
37
+ pushTokenUpdatedAt: z.string().nullable(),
38
+ lastSeenAt: z.string().nullable(),
39
+ createdAt: z.string().nullable(),
40
+ updatedAt: z.string().nullable(),
41
+ user_id: z.string().uuid().describe(deprecatedAlias('userId')),
42
+ device_id: z.string().describe(deprecatedAlias('deviceId')),
43
+ client_app_version: z.string().nullable().describe(deprecatedAlias('clientAppVersion')),
44
+ os_version: z.string().nullable().describe(deprecatedAlias('osVersion')),
45
+ push_provider: z.string().nullable().describe(deprecatedAlias('pushProvider')),
46
+ push_token_updated_at: z.string().nullable().describe(deprecatedAlias('pushTokenUpdatedAt')),
47
+ last_seen_at: z.string().nullable().describe(deprecatedAlias('lastSeenAt')),
48
+ created_at: z.string().nullable().describe(deprecatedAlias('createdAt')),
49
+ updated_at: z.string().nullable().describe(deprecatedAlias('updatedAt')),
50
+ })
@@ -12,10 +12,18 @@ export function createPagedListResponseSchema(itemSchema: ZodTypeAny) {
12
12
 
13
13
  // createCrudOpenApiFactory already falls back to the shared default create/ok response schemas
14
14
  // when omitted, so there is nothing module-specific to re-pass or re-export here.
15
+ // The shared zodToJsonSchema converter does not emit per-property descriptions for object schemas, so
16
+ // the `.describe()` markers on the deprecated keys in deviceList.ts never reach the document. State the
17
+ // deprecation in the endpoint description, which does render (#5513).
18
+ export const DEPRECATED_SNAKE_CASE_NOTICE =
19
+ 'Response keys are camelCase. The snake_case keys (`user_id`, `device_id`, `last_seen_at`, …) are ' +
20
+ 'deprecated aliases of their camelCase counterparts, retained for one minor version and removed in ' +
21
+ 'the next; read `userId`, `deviceId`, `lastSeenAt`, … instead.'
22
+
15
23
  const buildDevicesCrudOpenApi = createCrudOpenApiFactory({
16
24
  defaultTag: 'Devices',
17
25
  makeListDescription: ({ pluralLower }) =>
18
- `Returns the authenticated user's registered ${pluralLower} (admins may list across users).`,
26
+ `Returns the authenticated user's registered ${pluralLower} (admins may list across users). ${DEPRECATED_SNAKE_CASE_NOTICE}`,
19
27
  })
20
28
 
21
29
  export function createDevicesCrudOpenApi(options: CrudOpenApiOptions): OpenApiRouteDoc {