@open-mercato/core 0.6.5-develop.4534.1.b459babe6d → 0.6.5-develop.4544.1.71c003c861

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 (633) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +5 -0
  3. package/dist/generated/entities/role/index.js +3 -1
  4. package/dist/generated/entities/role/index.js.map +2 -2
  5. package/dist/generated/entities/user/index.js +3 -1
  6. package/dist/generated/entities/user/index.js.map +2 -2
  7. package/dist/generated/entity-fields-registry.js +2 -0
  8. package/dist/generated/entity-fields-registry.js.map +2 -2
  9. package/dist/helpers/integration/optimisticLockUi.js +104 -0
  10. package/dist/helpers/integration/optimisticLockUi.js.map +7 -0
  11. package/dist/helpers/integration/salesFixtures.js +17 -0
  12. package/dist/helpers/integration/salesFixtures.js.map +2 -2
  13. package/dist/modules/api_keys/backend/api-keys/page.js +9 -5
  14. package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
  15. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +17 -9
  16. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  17. package/dist/modules/auth/api/roles/acl/route.js +32 -13
  18. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  19. package/dist/modules/auth/api/roles/route.js +3 -1
  20. package/dist/modules/auth/api/roles/route.js.map +2 -2
  21. package/dist/modules/auth/api/sidebar/preferences/route.js +71 -3
  22. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  23. package/dist/modules/auth/api/users/acl/route.js +42 -19
  24. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  25. package/dist/modules/auth/api/users/route.js +3 -1
  26. package/dist/modules/auth/api/users/route.js.map +2 -2
  27. package/dist/modules/auth/backend/roles/[id]/edit/page.js +24 -4
  28. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  29. package/dist/modules/auth/backend/roles/page.js +8 -4
  30. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  31. package/dist/modules/auth/backend/users/[id]/edit/page.js +27 -5
  32. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  33. package/dist/modules/auth/backend/users/page.js +6 -2
  34. package/dist/modules/auth/backend/users/page.js.map +2 -2
  35. package/dist/modules/auth/components/AclEditor.js +3 -1
  36. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  37. package/dist/modules/auth/data/entities.js +6 -0
  38. package/dist/modules/auth/data/entities.js.map +2 -2
  39. package/dist/modules/auth/services/sidebarPreferencesService.js +32 -4
  40. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  41. package/dist/modules/business_rules/api/rules/route.js +28 -0
  42. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  43. package/dist/modules/business_rules/api/sets/route.js +28 -0
  44. package/dist/modules/business_rules/api/sets/route.js.map +2 -2
  45. package/dist/modules/business_rules/backend/rules/[id]/page.js +11 -4
  46. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +3 -3
  47. package/dist/modules/business_rules/backend/rules/page.js +20 -11
  48. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  49. package/dist/modules/business_rules/backend/sets/[id]/page.js +11 -4
  50. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  51. package/dist/modules/business_rules/backend/sets/page.js +20 -11
  52. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  53. package/dist/modules/catalog/api/categories/route.js +2 -0
  54. package/dist/modules/catalog/api/categories/route.js.map +2 -2
  55. package/dist/modules/catalog/api/products/route.js +2 -1
  56. package/dist/modules/catalog/api/products/route.js.map +2 -2
  57. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -0
  58. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  59. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +94 -40
  60. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  61. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +37 -8
  62. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  63. package/dist/modules/catalog/backend/catalog/products/optionSchemaClient.js.map +2 -2
  64. package/dist/modules/catalog/commands/variants.js +32 -31
  65. package/dist/modules/catalog/commands/variants.js.map +2 -2
  66. package/dist/modules/catalog/components/PriceKindSettings.js +12 -5
  67. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  68. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  69. package/dist/modules/catalog/components/products/ProductMediaManager.js.map +2 -2
  70. package/dist/modules/catalog/components/products/ProductsDataTable.js +5 -3
  71. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  72. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  73. package/dist/modules/catalog/components/products/variantForm.js +2 -1
  74. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  75. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +5 -0
  76. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
  77. package/dist/modules/currencies/backend/currencies/[id]/page.js +6 -3
  78. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  79. package/dist/modules/currencies/backend/currencies/page.js +18 -11
  80. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  81. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +1 -0
  82. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
  83. package/dist/modules/currencies/backend/exchange-rates/page.js +10 -6
  84. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  85. package/dist/modules/currencies/commands/currencies.js +7 -5
  86. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  87. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +26 -19
  88. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  89. package/dist/modules/customer_accounts/api/admin/roles/[id].js +28 -5
  90. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  91. package/dist/modules/customer_accounts/api/admin/roles.js +4 -2
  92. package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
  93. package/dist/modules/customer_accounts/api/admin/users/[id].js +28 -5
  94. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  95. package/dist/modules/customer_accounts/api/admin/users.js +2 -0
  96. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  97. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +16 -8
  98. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  99. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +8 -4
  100. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  101. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +8 -4
  102. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +2 -2
  103. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +29 -18
  104. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  105. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +18 -11
  106. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  107. package/dist/modules/customers/api/companies/route.js +13 -2
  108. package/dist/modules/customers/api/companies/route.js.map +2 -2
  109. package/dist/modules/customers/api/deals/route.js +2 -0
  110. package/dist/modules/customers/api/deals/route.js.map +2 -2
  111. package/dist/modules/customers/api/people/route.js +11 -2
  112. package/dist/modules/customers/api/people/route.js.map +2 -2
  113. package/dist/modules/customers/api/todos/route.js +1 -0
  114. package/dist/modules/customers/api/todos/route.js.map +2 -2
  115. package/dist/modules/customers/backend/config/customers/deals/page.js.map +2 -2
  116. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +34 -21
  117. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  118. package/dist/modules/customers/backend/customers/companies/[id]/page.js +45 -27
  119. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  120. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  121. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +22 -5
  122. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  123. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js +30 -8
  124. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js.map +2 -2
  125. package/dist/modules/customers/backend/customers/deals/[id]/page.js +1 -0
  126. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  127. package/dist/modules/customers/backend/customers/deals/page.js +16 -6
  128. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  129. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +62 -39
  130. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  131. package/dist/modules/customers/backend/customers/people/[id]/page.js +41 -26
  132. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  133. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  134. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +50 -23
  135. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  136. package/dist/modules/customers/commands/addresses.js +16 -14
  137. package/dist/modules/customers/commands/addresses.js.map +2 -2
  138. package/dist/modules/customers/commands/companies.js +1 -1
  139. package/dist/modules/customers/commands/companies.js.map +2 -2
  140. package/dist/modules/customers/commands/interactions.js +41 -4
  141. package/dist/modules/customers/commands/interactions.js.map +2 -2
  142. package/dist/modules/customers/commands/people.js +1 -1
  143. package/dist/modules/customers/commands/people.js.map +2 -2
  144. package/dist/modules/customers/commands/personCompanyLinks.js +8 -5
  145. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  146. package/dist/modules/customers/commands/pipeline-stages.js +13 -11
  147. package/dist/modules/customers/commands/pipeline-stages.js.map +3 -3
  148. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  149. package/dist/modules/customers/components/DictionarySettings.js +20 -13
  150. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  151. package/dist/modules/customers/components/DictionarySortSettings.js +4 -0
  152. package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
  153. package/dist/modules/customers/components/PipelineSettings.js +38 -23
  154. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  155. package/dist/modules/customers/components/detail/ActivityTimeline.js +1 -1
  156. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  157. package/dist/modules/customers/components/detail/AddressesSection.js +4 -0
  158. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  159. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +28 -22
  160. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  161. package/dist/modules/customers/components/detail/DealsSection.js +36 -24
  162. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  163. package/dist/modules/customers/components/detail/EmailCardActions.js +5 -0
  164. package/dist/modules/customers/components/detail/EmailCardActions.js.map +2 -2
  165. package/dist/modules/customers/components/detail/EntityTagsDialog.js +7 -0
  166. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
  167. package/dist/modules/customers/components/detail/ManageTagsDialog.js +34 -22
  168. package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
  169. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +41 -29
  170. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  171. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +14 -8
  172. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  173. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +14 -6
  174. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  175. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js +29 -13
  176. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js.map +2 -2
  177. package/dist/modules/customers/components/detail/hooks/useInteractions.js +77 -35
  178. package/dist/modules/customers/components/detail/hooks/useInteractions.js.map +2 -2
  179. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +25 -17
  180. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  181. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  182. package/dist/modules/customers/components/formConfig.js.map +2 -2
  183. package/dist/modules/customers/data/guards.js +66 -0
  184. package/dist/modules/customers/data/guards.js.map +7 -0
  185. package/dist/modules/customers/di.js +37 -0
  186. package/dist/modules/customers/di.js.map +2 -2
  187. package/dist/modules/customers/lib/todoCompatibility.js +11 -0
  188. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  189. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  190. package/dist/modules/data_sync/api/options.js +4 -4
  191. package/dist/modules/data_sync/api/options.js.map +2 -2
  192. package/dist/modules/data_sync/api/schedules/route.js +9 -1
  193. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  194. package/dist/modules/data_sync/backend/data-sync/page.js +17 -8
  195. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  196. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +43 -22
  197. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  198. package/dist/modules/data_sync/lib/sync-schedule-service.js +9 -0
  199. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  200. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +8 -1
  201. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  202. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +17 -1
  203. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  204. package/dist/modules/dictionaries/components/DictionariesManager.js +31 -10
  205. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  206. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +28 -15
  207. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  208. package/dist/modules/directory/api/organizations/route.js +3 -0
  209. package/dist/modules/directory/api/organizations/route.js.map +2 -2
  210. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -0
  211. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  212. package/dist/modules/directory/backend/directory/organizations/page.js +9 -5
  213. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  214. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +7 -3
  215. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  216. package/dist/modules/directory/backend/directory/tenants/page.js +8 -4
  217. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  218. package/dist/modules/directory/commands/organizations.js +7 -2
  219. package/dist/modules/directory/commands/organizations.js.map +2 -2
  220. package/dist/modules/entities/api/records.js +66 -0
  221. package/dist/modules/entities/api/records.js.map +2 -2
  222. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +1 -0
  223. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  224. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +8 -4
  225. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  226. package/dist/modules/entities/lib/helpers.js +17 -0
  227. package/dist/modules/entities/lib/helpers.js.map +2 -2
  228. package/dist/modules/feature_toggles/api/global/[id]/override/route.js +2 -1
  229. package/dist/modules/feature_toggles/api/global/[id]/override/route.js.map +2 -2
  230. package/dist/modules/feature_toggles/api/overrides/route.js +15 -0
  231. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  232. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js +15 -14
  233. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
  234. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js +20 -12
  235. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js.map +2 -2
  236. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +6 -2
  237. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  238. package/dist/modules/feature_toggles/components/formConfig.js +2 -1
  239. package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
  240. package/dist/modules/feature_toggles/components/overrideFormConfig.js +5 -1
  241. package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
  242. package/dist/modules/feature_toggles/data/validators.js +7 -4
  243. package/dist/modules/feature_toggles/data/validators.js.map +2 -2
  244. package/dist/modules/inbox_ops/api/settings/route.js +17 -2
  245. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  246. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +13 -8
  247. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  248. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +9 -4
  249. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  250. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +18 -11
  251. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  252. package/dist/modules/integrations/backend/integrations/page.js +12 -8
  253. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  254. package/dist/modules/messages/commands/messages.js +13 -10
  255. package/dist/modules/messages/commands/messages.js.map +2 -2
  256. package/dist/modules/perspectives/api/[tableId]/route.js +39 -30
  257. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  258. package/dist/modules/perspectives/services/perspectiveService.js +7 -0
  259. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  260. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +6 -14
  261. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +3 -3
  262. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +4 -2
  263. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  264. package/dist/modules/planner/components/AvailabilityRuleSetForm.js +2 -0
  265. package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +2 -2
  266. package/dist/modules/planner/components/AvailabilityRulesEditor.js +36 -11
  267. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  268. package/dist/modules/planner/components/AvailabilitySchedule.js +9 -5
  269. package/dist/modules/planner/components/AvailabilitySchedule.js.map +2 -2
  270. package/dist/modules/query_index/lib/engine.js +19 -0
  271. package/dist/modules/query_index/lib/engine.js.map +2 -2
  272. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -0
  273. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  274. package/dist/modules/resources/backend/resources/resource-types/page.js +4 -2
  275. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  276. package/dist/modules/resources/backend/resources/resources/[id]/page.js +14 -3
  277. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  278. package/dist/modules/resources/backend/resources/resources/page.js +8 -4
  279. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  280. package/dist/modules/resources/components/ResourceCrudForm.js +2 -0
  281. package/dist/modules/resources/components/ResourceCrudForm.js.map +2 -2
  282. package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -0
  283. package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
  284. package/dist/modules/sales/api/documents/factory.js +7 -2
  285. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  286. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +3 -1
  287. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  288. package/dist/modules/sales/backend/sales/channels/offers/page.js +13 -4
  289. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  290. package/dist/modules/sales/backend/sales/channels/page.js +16 -4
  291. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  292. package/dist/modules/sales/backend/sales/documents/[id]/page.js +68 -22
  293. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  294. package/dist/modules/sales/backend/sales/documents/create/page.js.map +2 -2
  295. package/dist/modules/sales/commands/documentAddresses.js +181 -2
  296. package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
  297. package/dist/modules/sales/commands/documents.js +29 -1
  298. package/dist/modules/sales/commands/documents.js.map +2 -2
  299. package/dist/modules/sales/commands/returns.js +12 -2
  300. package/dist/modules/sales/commands/returns.js.map +2 -2
  301. package/dist/modules/sales/commands/shared.js +15 -0
  302. package/dist/modules/sales/commands/shared.js.map +2 -2
  303. package/dist/modules/sales/commands/shipments.js +4 -1
  304. package/dist/modules/sales/commands/shipments.js.map +2 -2
  305. package/dist/modules/sales/components/AdjustmentKindSettings.js +19 -11
  306. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
  307. package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
  308. package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
  309. package/dist/modules/sales/components/PaymentMethodsSettings.js +12 -4
  310. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  311. package/dist/modules/sales/components/ShippingMethodsSettings.js +12 -4
  312. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  313. package/dist/modules/sales/components/StatusSettings.js +18 -11
  314. package/dist/modules/sales/components/StatusSettings.js.map +2 -2
  315. package/dist/modules/sales/components/TaxRatesSettings.js +12 -4
  316. package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
  317. package/dist/modules/sales/components/channels/ChannelOfferForm.js +47 -16
  318. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  319. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +8 -4
  320. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  321. package/dist/modules/sales/components/documents/AddressesSection.js +44 -25
  322. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  323. package/dist/modules/sales/components/documents/AdjustmentsSection.js +43 -23
  324. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  325. package/dist/modules/sales/components/documents/ItemsSection.js +22 -13
  326. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  327. package/dist/modules/sales/components/documents/LineItemDialog.js +23 -10
  328. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  329. package/dist/modules/sales/components/documents/PaymentDialog.js +29 -14
  330. package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
  331. package/dist/modules/sales/components/documents/PaymentsSection.js +20 -10
  332. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  333. package/dist/modules/sales/components/documents/ReturnDialog.js +26 -17
  334. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  335. package/dist/modules/sales/components/documents/ReturnsSection.js +3 -1
  336. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  337. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -5
  338. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  339. package/dist/modules/sales/components/documents/ShipmentDialog.js +21 -7
  340. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  341. package/dist/modules/sales/components/documents/ShipmentsSection.js +19 -10
  342. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  343. package/dist/modules/sales/components/documents/optimisticLock.js +27 -0
  344. package/dist/modules/sales/components/documents/optimisticLock.js.map +7 -0
  345. package/dist/modules/sales/di.js +18 -0
  346. package/dist/modules/sales/di.js.map +2 -2
  347. package/dist/modules/staff/api/job-histories.js +11 -2
  348. package/dist/modules/staff/api/job-histories.js.map +2 -2
  349. package/dist/modules/staff/api/timesheets/time-entries/route.js +11 -4
  350. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
  351. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +13 -8
  352. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  353. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +2 -1
  354. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  355. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +7 -4
  356. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  357. package/dist/modules/staff/backend/staff/team-members/page.js +4 -2
  358. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  359. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +1 -0
  360. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  361. package/dist/modules/staff/backend/staff/team-roles/page.js +4 -2
  362. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  363. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +5 -2
  364. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  365. package/dist/modules/staff/backend/staff/teams/page.js +12 -3
  366. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  367. package/dist/modules/staff/backend/staff/timesheets/page.js +4 -1
  368. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  369. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +2 -2
  370. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +12 -3
  371. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  372. package/dist/modules/staff/commands/job-histories.js +40 -3
  373. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  374. package/dist/modules/staff/components/LeaveRequestForm.js +1 -0
  375. package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
  376. package/dist/modules/staff/components/TeamForm.js +1 -0
  377. package/dist/modules/staff/components/TeamForm.js.map +2 -2
  378. package/dist/modules/staff/components/TeamMemberForm.js +1 -0
  379. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  380. package/dist/modules/staff/components/TeamRoleForm.js +1 -0
  381. package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
  382. package/dist/modules/staff/components/detail/JobHistorySection.js +20 -7
  383. package/dist/modules/staff/components/detail/JobHistorySection.js.map +2 -2
  384. package/dist/modules/staff/data/validators.js +7 -1
  385. package/dist/modules/staff/data/validators.js.map +2 -2
  386. package/dist/modules/staff/lib/leaveRequestHelpers.js +2 -1
  387. package/dist/modules/staff/lib/leaveRequestHelpers.js.map +2 -2
  388. package/dist/modules/translations/components/TranslationManager.js +12 -8
  389. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  390. package/dist/modules/workflows/api/definitions/[id]/route.js +106 -0
  391. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  392. package/dist/modules/workflows/backend/definitions/[id]/page.js +11 -3
  393. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  394. package/dist/modules/workflows/backend/definitions/page.js +19 -8
  395. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  396. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +29 -16
  397. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  398. package/dist/modules/workflows/components/formConfig.js +4 -1
  399. package/dist/modules/workflows/components/formConfig.js.map +2 -2
  400. package/dist/modules/workflows/di.js +12 -0
  401. package/dist/modules/workflows/di.js.map +2 -2
  402. package/generated/entities/role/index.ts +1 -0
  403. package/generated/entities/user/index.ts +1 -0
  404. package/generated/entity-fields-registry.ts +2 -0
  405. package/jest.setup.ts +17 -0
  406. package/package.json +8 -7
  407. package/src/helpers/integration/optimisticLockUi.ts +172 -0
  408. package/src/helpers/integration/salesFixtures.ts +29 -0
  409. package/src/modules/api_keys/backend/api-keys/page.tsx +10 -5
  410. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +19 -9
  411. package/src/modules/auth/api/roles/acl/route.ts +37 -11
  412. package/src/modules/auth/api/roles/route.ts +2 -0
  413. package/src/modules/auth/api/sidebar/preferences/route.ts +73 -0
  414. package/src/modules/auth/api/users/acl/route.ts +46 -18
  415. package/src/modules/auth/api/users/route.ts +2 -0
  416. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +29 -4
  417. package/src/modules/auth/backend/roles/page.tsx +9 -4
  418. package/src/modules/auth/backend/users/[id]/edit/page.tsx +37 -4
  419. package/src/modules/auth/backend/users/page.tsx +7 -2
  420. package/src/modules/auth/components/AclEditor.tsx +10 -1
  421. package/src/modules/auth/data/entities.ts +7 -1
  422. package/src/modules/auth/services/sidebarPreferencesService.ts +38 -4
  423. package/src/modules/business_rules/api/rules/route.ts +30 -0
  424. package/src/modules/business_rules/api/sets/route.ts +30 -0
  425. package/src/modules/business_rules/backend/rules/[id]/page.tsx +16 -4
  426. package/src/modules/business_rules/backend/rules/page.tsx +20 -11
  427. package/src/modules/business_rules/backend/sets/[id]/page.tsx +16 -4
  428. package/src/modules/business_rules/backend/sets/page.tsx +20 -11
  429. package/src/modules/catalog/api/categories/route.ts +3 -0
  430. package/src/modules/catalog/api/products/route.ts +4 -0
  431. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +5 -0
  432. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +112 -35
  433. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +56 -7
  434. package/src/modules/catalog/backend/catalog/products/optionSchemaClient.ts +2 -0
  435. package/src/modules/catalog/commands/variants.ts +32 -32
  436. package/src/modules/catalog/components/PriceKindSettings.tsx +20 -7
  437. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +1 -0
  438. package/src/modules/catalog/components/products/ProductMediaManager.tsx +2 -0
  439. package/src/modules/catalog/components/products/ProductsDataTable.tsx +8 -4
  440. package/src/modules/catalog/components/products/productForm.ts +3 -0
  441. package/src/modules/catalog/components/products/variantForm.ts +9 -0
  442. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +5 -0
  443. package/src/modules/currencies/backend/currencies/[id]/page.tsx +13 -6
  444. package/src/modules/currencies/backend/currencies/page.tsx +18 -11
  445. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +3 -0
  446. package/src/modules/currencies/backend/exchange-rates/page.tsx +10 -6
  447. package/src/modules/currencies/commands/currencies.ts +10 -5
  448. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +31 -21
  449. package/src/modules/customer_accounts/api/admin/roles/[id].ts +35 -5
  450. package/src/modules/customer_accounts/api/admin/roles.ts +2 -0
  451. package/src/modules/customer_accounts/api/admin/users/[id].ts +38 -5
  452. package/src/modules/customer_accounts/api/admin/users.ts +2 -0
  453. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -20
  454. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +9 -4
  455. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +11 -4
  456. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +28 -17
  457. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +19 -11
  458. package/src/modules/customers/AGENTS.md +2 -2
  459. package/src/modules/customers/api/companies/route.ts +14 -1
  460. package/src/modules/customers/api/deals/route.ts +3 -0
  461. package/src/modules/customers/api/people/route.ts +12 -1
  462. package/src/modules/customers/api/todos/route.ts +1 -0
  463. package/src/modules/customers/backend/config/customers/deals/page.tsx +1 -0
  464. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +36 -21
  465. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +52 -27
  466. package/src/modules/customers/backend/customers/companies/page.tsx +2 -0
  467. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +27 -5
  468. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.ts +39 -7
  469. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +1 -0
  470. package/src/modules/customers/backend/customers/deals/page.tsx +18 -6
  471. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +64 -39
  472. package/src/modules/customers/backend/customers/people/[id]/page.tsx +46 -26
  473. package/src/modules/customers/backend/customers/people/page.tsx +2 -0
  474. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +84 -24
  475. package/src/modules/customers/commands/addresses.ts +16 -14
  476. package/src/modules/customers/commands/companies.ts +3 -1
  477. package/src/modules/customers/commands/interactions.ts +50 -4
  478. package/src/modules/customers/commands/people.ts +2 -1
  479. package/src/modules/customers/commands/personCompanyLinks.ts +8 -5
  480. package/src/modules/customers/commands/pipeline-stages.ts +16 -16
  481. package/src/modules/customers/components/AddressFormatSettings.tsx +1 -0
  482. package/src/modules/customers/components/DictionarySettings.tsx +18 -13
  483. package/src/modules/customers/components/DictionarySortSettings.tsx +4 -0
  484. package/src/modules/customers/components/PipelineSettings.tsx +42 -21
  485. package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -3
  486. package/src/modules/customers/components/detail/AddressesSection.tsx +4 -0
  487. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +2 -0
  488. package/src/modules/customers/components/detail/DealsSection.tsx +4 -0
  489. package/src/modules/customers/components/detail/EmailCardActions.tsx +5 -0
  490. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +7 -0
  491. package/src/modules/customers/components/detail/ManageTagsDialog.tsx +4 -0
  492. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +4 -0
  493. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +2 -0
  494. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +23 -7
  495. package/src/modules/customers/components/detail/hooks/useInteractionMutations.ts +25 -15
  496. package/src/modules/customers/components/detail/hooks/useInteractions.ts +76 -35
  497. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +30 -17
  498. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +2 -0
  499. package/src/modules/customers/components/detail/types.ts +1 -0
  500. package/src/modules/customers/components/formConfig.tsx +2 -0
  501. package/src/modules/customers/data/guards.ts +67 -0
  502. package/src/modules/customers/di.ts +66 -0
  503. package/src/modules/customers/i18n/de.json +2 -0
  504. package/src/modules/customers/i18n/en.json +2 -0
  505. package/src/modules/customers/i18n/es.json +2 -0
  506. package/src/modules/customers/i18n/pl.json +2 -0
  507. package/src/modules/customers/lib/todoCompatibility.ts +14 -0
  508. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +2 -0
  509. package/src/modules/data_sync/api/options.ts +7 -4
  510. package/src/modules/data_sync/api/schedules/route.ts +9 -1
  511. package/src/modules/data_sync/backend/data-sync/page.tsx +18 -5
  512. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +46 -19
  513. package/src/modules/data_sync/lib/sync-schedule-service.ts +11 -0
  514. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +8 -1
  515. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +23 -0
  516. package/src/modules/dictionaries/components/DictionariesManager.tsx +32 -9
  517. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +30 -14
  518. package/src/modules/dictionaries/i18n/de.json +1 -0
  519. package/src/modules/dictionaries/i18n/en.json +1 -0
  520. package/src/modules/dictionaries/i18n/es.json +1 -0
  521. package/src/modules/dictionaries/i18n/pl.json +1 -0
  522. package/src/modules/directory/api/organizations/route.ts +3 -0
  523. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +8 -0
  524. package/src/modules/directory/backend/directory/organizations/page.tsx +10 -5
  525. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +16 -5
  526. package/src/modules/directory/backend/directory/tenants/page.tsx +8 -4
  527. package/src/modules/directory/commands/organizations.ts +7 -4
  528. package/src/modules/entities/api/records.ts +99 -0
  529. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +7 -0
  530. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +8 -4
  531. package/src/modules/entities/lib/helpers.ts +17 -0
  532. package/src/modules/feature_toggles/api/global/[id]/override/route.ts +1 -0
  533. package/src/modules/feature_toggles/api/overrides/route.ts +19 -0
  534. package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +19 -13
  535. package/src/modules/feature_toggles/components/FeatureToggleOverrideCard.tsx +22 -12
  536. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +7 -2
  537. package/src/modules/feature_toggles/components/formConfig.tsx +2 -1
  538. package/src/modules/feature_toggles/components/overrideFormConfig.tsx +10 -1
  539. package/src/modules/feature_toggles/data/validators.ts +11 -3
  540. package/src/modules/inbox_ops/api/settings/route.ts +18 -0
  541. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +15 -10
  542. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +9 -4
  543. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +20 -11
  544. package/src/modules/integrations/backend/integrations/page.tsx +13 -8
  545. package/src/modules/messages/commands/messages.ts +27 -15
  546. package/src/modules/perspectives/api/[tableId]/route.ts +11 -2
  547. package/src/modules/perspectives/services/perspectiveService.ts +13 -1
  548. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +16 -14
  549. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +6 -3
  550. package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +3 -0
  551. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +58 -15
  552. package/src/modules/planner/components/AvailabilitySchedule.tsx +22 -7
  553. package/src/modules/query_index/lib/engine.ts +34 -0
  554. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +7 -1
  555. package/src/modules/resources/backend/resources/resource-types/page.tsx +6 -3
  556. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +23 -3
  557. package/src/modules/resources/backend/resources/resources/page.tsx +15 -4
  558. package/src/modules/resources/components/ResourceCrudForm.tsx +3 -0
  559. package/src/modules/resources/components/ResourceTypeCrudForm.tsx +2 -0
  560. package/src/modules/sales/api/documents/factory.ts +13 -1
  561. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +6 -0
  562. package/src/modules/sales/backend/sales/channels/offers/page.tsx +10 -4
  563. package/src/modules/sales/backend/sales/channels/page.tsx +19 -4
  564. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +73 -20
  565. package/src/modules/sales/backend/sales/documents/create/page.tsx +2 -0
  566. package/src/modules/sales/commands/documentAddresses.ts +226 -4
  567. package/src/modules/sales/commands/documents.ts +28 -0
  568. package/src/modules/sales/commands/returns.ts +12 -3
  569. package/src/modules/sales/commands/shared.ts +36 -0
  570. package/src/modules/sales/commands/shipments.ts +17 -1
  571. package/src/modules/sales/components/AdjustmentKindSettings.tsx +20 -11
  572. package/src/modules/sales/components/DocumentNumberSettings.tsx +1 -0
  573. package/src/modules/sales/components/OrderEditingSettings.tsx +1 -0
  574. package/src/modules/sales/components/PaymentMethodsSettings.tsx +12 -4
  575. package/src/modules/sales/components/ShippingMethodsSettings.tsx +12 -4
  576. package/src/modules/sales/components/StatusSettings.tsx +20 -11
  577. package/src/modules/sales/components/TaxRatesSettings.tsx +12 -5
  578. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +67 -14
  579. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +7 -4
  580. package/src/modules/sales/components/documents/AddressesSection.tsx +35 -25
  581. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +50 -25
  582. package/src/modules/sales/components/documents/ItemsSection.tsx +24 -13
  583. package/src/modules/sales/components/documents/LineItemDialog.tsx +26 -9
  584. package/src/modules/sales/components/documents/PaymentDialog.tsx +33 -14
  585. package/src/modules/sales/components/documents/PaymentsSection.tsx +22 -10
  586. package/src/modules/sales/components/documents/ReturnDialog.tsx +28 -17
  587. package/src/modules/sales/components/documents/ReturnsSection.tsx +4 -1
  588. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +11 -4
  589. package/src/modules/sales/components/documents/ShipmentDialog.tsx +23 -8
  590. package/src/modules/sales/components/documents/ShipmentsSection.tsx +20 -10
  591. package/src/modules/sales/components/documents/optimisticLock.ts +34 -0
  592. package/src/modules/sales/components/documents/shipmentTypes.ts +1 -0
  593. package/src/modules/sales/di.ts +35 -0
  594. package/src/modules/sales/i18n/de.json +3 -0
  595. package/src/modules/sales/i18n/en.json +3 -0
  596. package/src/modules/sales/i18n/es.json +3 -0
  597. package/src/modules/sales/i18n/pl.json +3 -0
  598. package/src/modules/staff/api/job-histories.ts +12 -2
  599. package/src/modules/staff/api/timesheets/time-entries/route.ts +16 -4
  600. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +12 -7
  601. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +2 -0
  602. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +16 -5
  603. package/src/modules/staff/backend/staff/team-members/page.tsx +6 -2
  604. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +8 -0
  605. package/src/modules/staff/backend/staff/team-roles/page.tsx +6 -2
  606. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +13 -3
  607. package/src/modules/staff/backend/staff/teams/page.tsx +9 -3
  608. package/src/modules/staff/backend/staff/timesheets/page.tsx +10 -1
  609. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -0
  610. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +9 -3
  611. package/src/modules/staff/commands/job-histories.ts +42 -3
  612. package/src/modules/staff/components/LeaveRequestForm.tsx +2 -0
  613. package/src/modules/staff/components/TeamForm.tsx +2 -0
  614. package/src/modules/staff/components/TeamMemberForm.tsx +2 -0
  615. package/src/modules/staff/components/TeamRoleForm.tsx +2 -0
  616. package/src/modules/staff/components/detail/JobHistorySection.tsx +28 -6
  617. package/src/modules/staff/data/validators.ts +6 -0
  618. package/src/modules/staff/i18n/de.json +1 -0
  619. package/src/modules/staff/i18n/en.json +1 -0
  620. package/src/modules/staff/i18n/es.json +1 -0
  621. package/src/modules/staff/i18n/pl.json +1 -0
  622. package/src/modules/staff/lib/leaveRequestHelpers.ts +4 -0
  623. package/src/modules/translations/components/TranslationManager.tsx +13 -8
  624. package/src/modules/workflows/api/definitions/[id]/route.ts +112 -0
  625. package/src/modules/workflows/backend/definitions/[id]/page.tsx +20 -4
  626. package/src/modules/workflows/backend/definitions/page.tsx +20 -9
  627. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +29 -16
  628. package/src/modules/workflows/components/formConfig.tsx +5 -0
  629. package/src/modules/workflows/di.ts +20 -0
  630. package/src/modules/workflows/i18n/de.json +1 -0
  631. package/src/modules/workflows/i18n/en.json +1 -0
  632. package/src/modules/workflows/i18n/es.json +1 -0
  633. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/data_sync/components/IntegrationScheduleTab.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n CalendarClock,\n Play,\n RefreshCw,\n Save,\n ShieldAlert,\n ShieldCheck,\n Trash2,\n} from 'lucide-react'\n\ntype SyncOption = {\n integrationId: string\n title: string\n direction: 'import' | 'export' | 'bidirectional'\n runMode?: 'generic' | 'provider'\n canStartRun?: boolean\n supportedEntities: string[]\n hasCredentials: boolean\n isEnabled: boolean\n}\n\ntype SyncOptionsResponse = {\n items?: SyncOption[]\n}\n\ntype SyncScheduleRecord = {\n id: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n}\n\ntype SyncSchedulesResponse = {\n items?: SyncScheduleRecord[]\n}\n\ntype SyncScheduleEditorState = {\n id?: string\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n}\n\ntype IntegrationScheduleTabProps = {\n integrationId: string\n hasCredentials: boolean\n isEnabled: boolean\n}\n\nconst DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n\nfunction formatEntityTypeLabel(entityType: string): string {\n return entityType\n .replace(/[_-]+/g, ' ')\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase())\n}\n\nfunction buildDefaultScheduleState(entityType: string): SyncScheduleEditorState {\n const normalized = entityType.trim().toLowerCase()\n const longerInterval = normalized === 'categories' || normalized === 'attributes'\n return {\n scheduleType: 'interval',\n scheduleValue: longerInterval ? '6h' : '1h',\n timezone: DEFAULT_TIMEZONE,\n fullSync: normalized !== 'products',\n isEnabled: true,\n lastRunAt: null,\n }\n}\n\nfunction getSupportedDirections(direction: SyncOption['direction'] | null | undefined): Array<'import' | 'export'> {\n if (direction === 'export') return ['export']\n if (direction === 'bidirectional') return ['import', 'export']\n return ['import']\n}\n\nfunction buildScheduleKey(entityType: string, direction: 'import' | 'export'): string {\n return `${entityType}:${direction}`\n}\n\nfunction buildScheduleEditors(\n entityTypes: string[],\n directions: Array<'import' | 'export'>,\n records: SyncScheduleRecord[],\n): Record<string, SyncScheduleEditorState> {\n const nextEntries: Array<[string, SyncScheduleEditorState]> = []\n for (const entityType of entityTypes) {\n for (const direction of directions) {\n const record = records.find((item) => item.entityType === entityType && item.direction === direction)\n nextEntries.push([\n buildScheduleKey(entityType, direction),\n record\n ? {\n id: record.id,\n scheduleType: record.scheduleType,\n scheduleValue: record.scheduleValue,\n timezone: record.timezone,\n fullSync: record.fullSync,\n isEnabled: record.isEnabled,\n lastRunAt: record.lastRunAt,\n }\n : buildDefaultScheduleState(entityType),\n ])\n }\n }\n return Object.fromEntries(nextEntries)\n}\n\nexport function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const [option, setOption] = React.useState<SyncOption | null>(null)\n const [schedules, setSchedules] = React.useState<Record<string, SyncScheduleEditorState>>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [runningKey, setRunningKey] = React.useState<string | null>(null)\n const [savingKey, setSavingKey] = React.useState<string | null>(null)\n const [deletingKey, setDeletingKey] = React.useState<string | null>(null)\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const [optionsCall, schedulesCall] = await Promise.all([\n apiCall<SyncOptionsResponse>('/api/data_sync/options', undefined, { fallback: { items: [] } }),\n apiCall<SyncSchedulesResponse>(`/api/data_sync/schedules?integrationId=${encodeURIComponent(props.integrationId)}&page=1&pageSize=100`, undefined, { fallback: { items: [] } }),\n ])\n\n const resolvedOption = (optionsCall.result?.items ?? []).find((item) => item.integrationId === props.integrationId) ?? null\n setOption(resolvedOption)\n\n const supportedEntities = resolvedOption?.supportedEntities ?? []\n const supportedDirections = getSupportedDirections(resolvedOption?.direction)\n setSchedules(buildScheduleEditors(supportedEntities, supportedDirections, schedulesCall.result?.items ?? []))\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.loadError', 'Failed to load sync schedules.')\n flash(message, 'error')\n } finally {\n setIsLoading(false)\n }\n }, [props.integrationId, t])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n const supportedDirections = React.useMemo(\n () => getSupportedDirections(option?.direction),\n [option?.direction],\n )\n\n const rows = React.useMemo(\n () => (option?.supportedEntities ?? []).flatMap((entityType) => (\n supportedDirections.map((direction) => ({\n entityType,\n direction,\n key: buildScheduleKey(entityType, direction),\n }))\n )),\n [option?.supportedEntities, supportedDirections],\n )\n\n const updateScheduleEditor = React.useCallback((key: string, patch: Partial<SyncScheduleEditorState>, entityType: string) => {\n setSchedules((current) => ({\n ...current,\n [key]: {\n ...(current[key] ?? buildDefaultScheduleState(entityType)),\n ...patch,\n },\n }))\n }, [])\n\n const handleStartSync = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n if (!props.isEnabled) {\n flash(t('data_sync.integrationTab.integrationDisabled', 'Enable the integration before starting a sync.'), 'error')\n return\n }\n if (!props.hasCredentials) {\n flash(t('data_sync.integrationTab.credentialsMissing', 'Configure credentials before starting a sync.'), 'error')\n return\n }\n if (option?.canStartRun === false) {\n flash(t('data_sync.integrationTab.providerManaged', 'Start this integration from its provider-specific setup flow.'), 'error')\n return\n }\n\n setRunningKey(scheduleKey)\n try {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n const call = await apiCall<{ id: string }>('/api/data_sync/run', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n fullSync: scheduleState.fullSync,\n batchSize: 100,\n }),\n }, { fallback: null })\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to start sync')\n }\n\n flash(t('data_sync.integrationTab.runStarted', 'Sync run started.'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.runError', 'Failed to start sync.')\n flash(message, 'error')\n } finally {\n setRunningKey(null)\n }\n }, [option?.canStartRun, props.hasCredentials, props.integrationId, props.isEnabled, schedules, t])\n\n const handleSaveSchedule = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n setSavingKey(scheduleKey)\n try {\n const call = await apiCall<SyncScheduleRecord>('/api/data_sync/schedules', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n scheduleType: scheduleState.scheduleType,\n scheduleValue: scheduleState.scheduleValue,\n timezone: scheduleState.timezone,\n fullSync: scheduleState.fullSync,\n isEnabled: scheduleState.isEnabled,\n }),\n }, { fallback: null })\n\n if (!call.ok || !call.result) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to save schedule')\n }\n\n updateScheduleEditor(scheduleKey, {\n id: call.result.id,\n scheduleType: call.result.scheduleType,\n scheduleValue: call.result.scheduleValue,\n timezone: call.result.timezone,\n fullSync: call.result.fullSync,\n isEnabled: call.result.isEnabled,\n lastRunAt: call.result.lastRunAt,\n }, entityType)\n flash(t('data_sync.dashboard.schedule.success', 'Recurring schedule saved'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule')\n flash(message, 'error')\n } finally {\n setSavingKey(null)\n }\n }, [props.integrationId, schedules, t, updateScheduleEditor])\n\n const handleDeleteSchedule = React.useCallback(async (entityType: string, scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey]\n if (!scheduleState?.id) {\n updateScheduleEditor(scheduleKey, buildDefaultScheduleState(entityType), entityType)\n return\n }\n\n setDeletingKey(scheduleKey)\n try {\n const call = await apiCall(`/api/data_sync/schedules/${encodeURIComponent(scheduleState.id)}`, {\n method: 'DELETE',\n }, { fallback: null })\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to delete schedule')\n }\n\n setSchedules((current) => ({\n ...current,\n [scheduleKey]: buildDefaultScheduleState(entityType),\n }))\n flash(t('data_sync.dashboard.schedule.deleteSuccess', 'Recurring schedule removed'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.deleteError', 'Failed to remove recurring schedule')\n flash(message, 'error')\n } finally {\n setDeletingKey(null)\n }\n }, [schedules, t, updateScheduleEditor])\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-40 items-center justify-center rounded-lg border bg-card\">\n <Spinner />\n </div>\n )\n }\n\n if (!option) {\n return (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.notAvailable', 'This integration is not registered as a data sync provider.')}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"outline\" className={props.isEnabled ? 'border-emerald-300 text-emerald-700' : 'border-amber-300 text-amber-700'}>\n {props.isEnabled ? <ShieldCheck className=\"mr-2 h-3.5 w-3.5\" /> : <ShieldAlert className=\"mr-2 h-3.5 w-3.5\" />}\n {props.isEnabled\n ? t('data_sync.dashboard.start.status.enabled', 'Integration enabled')\n : t('data_sync.dashboard.start.status.disabled', 'Integration disabled')}\n </Badge>\n <Badge variant=\"outline\" className={props.hasCredentials ? 'border-sky-300 text-sky-700' : 'border-amber-300 text-amber-700'}>\n <CalendarClock className=\"mr-2 h-3.5 w-3.5\" />\n {props.hasCredentials\n ? t('data_sync.dashboard.start.status.credentialsReady', 'Credentials ready')\n : t('data_sync.dashboard.start.status.credentialsMissing', 'Credentials missing')}\n </Badge>\n </div>\n <div>\n <h3 className=\"text-base font-semibold\">{t('data_sync.integrationTab.title', 'Sync schedules')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('data_sync.integrationTab.description', 'Run one-off syncs or save recurring schedules for every supported entity directly from the integration detail page.')}\n </p>\n </div>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={() => void load()} disabled={isLoading}>\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('data_sync.integrationTab.refresh', 'Refresh')}\n </Button>\n </div>\n\n {!props.isEnabled ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.integrationDisabledNotice', 'The integration is disabled. You can save schedules now, but runs will stay blocked until the integration is enabled.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {!props.hasCredentials ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.credentialsMissingNotice', 'Credentials are still missing. Save schedules first if you want, but manual and scheduled runs will fail until credentials are configured.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {option.canStartRun === false ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.providerManagedNotice', 'This provider needs its own setup flow before a run can start. Use the provider tab on this page instead of generic schedules.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {rows.length === 0 ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.empty', 'This provider does not expose any schedulable sync entities yet.')}\n </AlertDescription>\n </Alert>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border\">\n <table className=\"w-full min-w-[1080px] text-sm\">\n <thead className=\"bg-muted/50 text-left\">\n <tr>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.entityType', 'Entity Type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.direction', 'Direction')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.type', 'Schedule type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.value', 'Value')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.timezone', 'Timezone')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.start.fullSync', 'Run as full sync')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.enabled', 'Schedule enabled')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.lastRun', 'Last run')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.actions', 'Actions')}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => {\n const scheduleState = schedules[row.key] ?? buildDefaultScheduleState(row.entityType)\n const isRunning = runningKey === row.key\n const isSaving = savingKey === row.key\n const isDeleting = deletingKey === row.key\n const controlsDisabled = isRunning || isSaving || isDeleting\n return (\n <tr key={row.key} className=\"border-t align-top\">\n <td className=\"px-3 py-3 font-medium\">{formatEntityTypeLabel(row.entityType)}</td>\n <td className=\"px-3 py-3\">{t(`data_sync.dashboard.direction.${row.direction}`, row.direction === 'import' ? 'Import' : 'Export')}</td>\n <td className=\"px-3 py-3\">\n <Select\n value={scheduleState.scheduleType}\n onValueChange={(value) => updateScheduleEditor(row.key, {\n scheduleType: value === 'cron' ? 'cron' : 'interval',\n }, row.entityType)}\n disabled={controlsDisabled}\n >\n <SelectTrigger size=\"lg\" className=\"min-w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"interval\">{t('data_sync.dashboard.schedule.interval', 'Interval')}</SelectItem>\n <SelectItem value=\"cron\">{t('data_sync.dashboard.schedule.cron', 'Cron')}</SelectItem>\n </SelectContent>\n </Select>\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.scheduleValue}\n onChange={(event) => updateScheduleEditor(row.key, { scheduleValue: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n placeholder={scheduleState.scheduleType === 'cron' ? '0 * * * *' : '1h'}\n />\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.timezone}\n onChange={(event) => updateScheduleEditor(row.key, { timezone: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n />\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={scheduleState.fullSync}\n onChange={(event) => updateScheduleEditor(row.key, { fullSync: event.target.checked }, row.entityType)}\n disabled={controlsDisabled}\n />\n <span>{t('data_sync.integrationTab.fullSyncShort', 'Full')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={scheduleState.isEnabled}\n onChange={(event) => updateScheduleEditor(row.key, { isEnabled: event.target.checked }, row.entityType)}\n disabled={controlsDisabled}\n />\n <span>{scheduleState.isEnabled ? t('data_sync.dashboard.schedule.status.shortEnabled', 'Scheduled') : t('data_sync.dashboard.schedule.status.shortDisabled', 'Paused')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3 text-muted-foreground\">\n {scheduleState.lastRunAt\n ? new Date(scheduleState.lastRunAt).toLocaleString()\n : scheduleState.id\n ? t('data_sync.dashboard.schedule.neverRun', 'Saved, but no scheduled execution has completed yet.')\n : t('data_sync.dashboard.schedule.none', 'No recurring schedule saved for this target yet.')}\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void handleStartSync(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || !props.isEnabled || !props.hasCredentials || option.canStartRun === false}\n >\n {isRunning ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Play className=\"mr-2 h-4 w-4\" />}\n {isRunning\n ? t('data_sync.integrationTab.starting', 'Starting...')\n : t('data_sync.integrationTab.start', 'Run now')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleSaveSchedule(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || option.canStartRun === false}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n {isSaving\n ? t('data_sync.dashboard.schedule.saving', 'Saving...')\n : t('data_sync.dashboard.schedule.save', 'Save recurring schedule')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleDeleteSchedule(row.entityType, row.key)}\n disabled={controlsDisabled || !scheduleState.id}\n >\n {isDeleting ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Trash2 className=\"mr-2 h-4 w-4\" />}\n {isDeleting\n ? t('data_sync.dashboard.schedule.deleting', 'Removing...')\n : t('data_sync.dashboard.schedule.delete', 'Remove schedule')}\n </Button>\n </div>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )}\n\n <div className=\"grid gap-3 rounded-lg border border-dashed p-4 text-sm text-muted-foreground md:grid-cols-3\">\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.interval', 'Interval')}</Label>\n <p>{t('data_sync.dashboard.schedule.intervalHelp', 'Example: `1h`, `6h`, or `24h` for repeating intervals.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.cron', 'Cron')}</Label>\n <p>{t('data_sync.dashboard.schedule.cronHelp', 'Example: `0 * * * *` runs at the start of every hour.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.integrationTab.runNowTitle', 'Manual runs')}</Label>\n <p>{t('data_sync.integrationTab.runNowHelp', 'Run now uses the full-sync checkbox from the same row and starts progress tracking immediately in Data Sync.')}</p>\n </div>\n </div>\n </section>\n )\n}\n\nexport default IntegrationScheduleTab\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n CalendarClock,\n Play,\n RefreshCw,\n Save,\n ShieldAlert,\n ShieldCheck,\n Trash2,\n} from 'lucide-react'\n\ntype SyncOption = {\n integrationId: string\n title: string\n direction: 'import' | 'export' | 'bidirectional'\n runMode?: 'generic' | 'provider'\n canStartRun?: boolean\n supportedEntities: string[]\n hasCredentials: boolean\n isEnabled: boolean\n}\n\ntype SyncOptionsResponse = {\n items?: SyncOption[]\n}\n\ntype SyncScheduleRecord = {\n id: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n updatedAt?: string | null\n}\n\ntype SyncSchedulesResponse = {\n items?: SyncScheduleRecord[]\n}\n\ntype SyncScheduleEditorState = {\n id?: string\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n lastRunAt: string | null\n updatedAt?: string | null\n}\n\ntype IntegrationScheduleTabProps = {\n integrationId: string\n hasCredentials: boolean\n isEnabled: boolean\n}\n\nconst DEFAULT_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n\nfunction formatEntityTypeLabel(entityType: string): string {\n return entityType\n .replace(/[_-]+/g, ' ')\n .replace(/\\b\\w/g, (letter) => letter.toUpperCase())\n}\n\nfunction buildDefaultScheduleState(entityType: string): SyncScheduleEditorState {\n const normalized = entityType.trim().toLowerCase()\n const longerInterval = normalized === 'categories' || normalized === 'attributes'\n return {\n scheduleType: 'interval',\n scheduleValue: longerInterval ? '6h' : '1h',\n timezone: DEFAULT_TIMEZONE,\n fullSync: normalized !== 'products',\n isEnabled: true,\n lastRunAt: null,\n updatedAt: null,\n }\n}\n\nfunction getSupportedDirections(direction: SyncOption['direction'] | null | undefined): Array<'import' | 'export'> {\n if (direction === 'export') return ['export']\n if (direction === 'bidirectional') return ['import', 'export']\n return ['import']\n}\n\nfunction buildScheduleKey(entityType: string, direction: 'import' | 'export'): string {\n return `${entityType}:${direction}`\n}\n\nfunction buildScheduleEditors(\n entityTypes: string[],\n directions: Array<'import' | 'export'>,\n records: SyncScheduleRecord[],\n): Record<string, SyncScheduleEditorState> {\n const nextEntries: Array<[string, SyncScheduleEditorState]> = []\n for (const entityType of entityTypes) {\n for (const direction of directions) {\n const record = records.find((item) => item.entityType === entityType && item.direction === direction)\n nextEntries.push([\n buildScheduleKey(entityType, direction),\n record\n ? {\n id: record.id,\n scheduleType: record.scheduleType,\n scheduleValue: record.scheduleValue,\n timezone: record.timezone,\n fullSync: record.fullSync,\n isEnabled: record.isEnabled,\n lastRunAt: record.lastRunAt,\n updatedAt: record.updatedAt ?? null,\n }\n : buildDefaultScheduleState(entityType),\n ])\n }\n }\n return Object.fromEntries(nextEntries)\n}\n\nexport function IntegrationScheduleTab(props: IntegrationScheduleTabProps) {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const [option, setOption] = React.useState<SyncOption | null>(null)\n const [schedules, setSchedules] = React.useState<Record<string, SyncScheduleEditorState>>({})\n const [isLoading, setIsLoading] = React.useState(true)\n const [runningKey, setRunningKey] = React.useState<string | null>(null)\n const [savingKey, setSavingKey] = React.useState<string | null>(null)\n const [deletingKey, setDeletingKey] = React.useState<string | null>(null)\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n try {\n const [optionsCall, schedulesCall] = await Promise.all([\n apiCall<SyncOptionsResponse>('/api/data_sync/options', undefined, { fallback: { items: [] } }),\n apiCall<SyncSchedulesResponse>(`/api/data_sync/schedules?integrationId=${encodeURIComponent(props.integrationId)}&page=1&pageSize=100`, undefined, { fallback: { items: [] } }),\n ])\n\n const resolvedOption = (optionsCall.result?.items ?? []).find((item) => item.integrationId === props.integrationId) ?? null\n setOption(resolvedOption)\n\n const supportedEntities = resolvedOption?.supportedEntities ?? []\n const supportedDirections = getSupportedDirections(resolvedOption?.direction)\n setSchedules(buildScheduleEditors(supportedEntities, supportedDirections, schedulesCall.result?.items ?? []))\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.loadError', 'Failed to load sync schedules.')\n flash(message, 'error')\n } finally {\n setIsLoading(false)\n }\n }, [props.integrationId, t])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n const supportedDirections = React.useMemo(\n () => getSupportedDirections(option?.direction),\n [option?.direction],\n )\n\n const rows = React.useMemo(\n () => (option?.supportedEntities ?? []).flatMap((entityType) => (\n supportedDirections.map((direction) => ({\n entityType,\n direction,\n key: buildScheduleKey(entityType, direction),\n }))\n )),\n [option?.supportedEntities, supportedDirections],\n )\n\n const updateScheduleEditor = React.useCallback((key: string, patch: Partial<SyncScheduleEditorState>, entityType: string) => {\n setSchedules((current) => ({\n ...current,\n [key]: {\n ...(current[key] ?? buildDefaultScheduleState(entityType)),\n ...patch,\n },\n }))\n }, [])\n\n const handleStartSync = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n if (!props.isEnabled) {\n flash(t('data_sync.integrationTab.integrationDisabled', 'Enable the integration before starting a sync.'), 'error')\n return\n }\n if (!props.hasCredentials) {\n flash(t('data_sync.integrationTab.credentialsMissing', 'Configure credentials before starting a sync.'), 'error')\n return\n }\n if (option?.canStartRun === false) {\n flash(t('data_sync.integrationTab.providerManaged', 'Start this integration from its provider-specific setup flow.'), 'error')\n return\n }\n\n setRunningKey(scheduleKey)\n try {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n // optimistic-lock-exempt: starts a new sync run (create), not a concurrent record edit\n const call = await apiCall<{ id: string }>('/api/data_sync/run', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n fullSync: scheduleState.fullSync,\n batchSize: 100,\n }),\n }, { fallback: null })\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to start sync')\n }\n\n flash(t('data_sync.integrationTab.runStarted', 'Sync run started.'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.integrationTab.runError', 'Failed to start sync.')\n flash(message, 'error')\n } finally {\n setRunningKey(null)\n }\n }, [option?.canStartRun, props.hasCredentials, props.integrationId, props.isEnabled, schedules, t])\n\n const handleSaveSchedule = React.useCallback(async (entityType: string, direction: 'import' | 'export', scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey] ?? buildDefaultScheduleState(entityType)\n setSavingKey(scheduleKey)\n try {\n // Keyed upsert (POST). When the server resolves an existing row the save\n // version-checks against this schedule's loaded `updatedAt`; for a brand\n // new row the header is empty so the create path is unaffected.\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(scheduleState.updatedAt),\n () => apiCall<SyncScheduleRecord>('/api/data_sync/schedules', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n integrationId: props.integrationId,\n entityType,\n direction,\n scheduleType: scheduleState.scheduleType,\n scheduleValue: scheduleState.scheduleValue,\n timezone: scheduleState.timezone,\n fullSync: scheduleState.fullSync,\n isEnabled: scheduleState.isEnabled,\n }),\n }, { fallback: null }),\n )\n\n if (!call.ok || !call.result) {\n const conflictError = Object.assign(\n new Error((call.result as { error?: string } | null)?.error ?? 'Failed to save schedule'),\n {\n status: call.status,\n ...(call.result && typeof call.result === 'object' ? call.result : {}),\n },\n )\n if (surfaceRecordConflict(conflictError, t)) {\n return\n }\n throw conflictError\n }\n\n updateScheduleEditor(scheduleKey, {\n id: call.result.id,\n scheduleType: call.result.scheduleType,\n scheduleValue: call.result.scheduleValue,\n timezone: call.result.timezone,\n fullSync: call.result.fullSync,\n isEnabled: call.result.isEnabled,\n lastRunAt: call.result.lastRunAt,\n updatedAt: call.result.updatedAt ?? null,\n }, entityType)\n flash(t('data_sync.dashboard.schedule.success', 'Recurring schedule saved'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.error', 'Failed to save recurring schedule')\n flash(message, 'error')\n } finally {\n setSavingKey(null)\n }\n }, [props.integrationId, schedules, t, updateScheduleEditor])\n\n const handleDeleteSchedule = React.useCallback(async (entityType: string, scheduleKey: string) => {\n const scheduleState = schedules[scheduleKey]\n if (!scheduleState?.id) {\n updateScheduleEditor(scheduleKey, buildDefaultScheduleState(entityType), entityType)\n return\n }\n\n setDeletingKey(scheduleKey)\n try {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(scheduleState.updatedAt),\n () => apiCall(`/api/data_sync/schedules/${encodeURIComponent(scheduleState.id as string)}`, {\n method: 'DELETE',\n }, { fallback: null }),\n )\n\n if (!call.ok) {\n throw new Error((call.result as { error?: string } | null)?.error ?? 'Failed to delete schedule')\n }\n\n setSchedules((current) => ({\n ...current,\n [scheduleKey]: buildDefaultScheduleState(entityType),\n }))\n flash(t('data_sync.dashboard.schedule.deleteSuccess', 'Recurring schedule removed'), 'success')\n } catch (error) {\n const message = error instanceof Error ? error.message : t('data_sync.dashboard.schedule.deleteError', 'Failed to remove recurring schedule')\n flash(message, 'error')\n } finally {\n setDeletingKey(null)\n }\n }, [schedules, t, updateScheduleEditor])\n\n if (isLoading) {\n return (\n <div className=\"flex min-h-40 items-center justify-center rounded-lg border bg-card\">\n <Spinner />\n </div>\n )\n }\n\n if (!option) {\n return (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.notAvailable', 'This integration is not registered as a data sync provider.')}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"outline\" className={props.isEnabled ? 'border-emerald-300 text-emerald-700' : 'border-amber-300 text-amber-700'}>\n {props.isEnabled ? <ShieldCheck className=\"mr-2 h-3.5 w-3.5\" /> : <ShieldAlert className=\"mr-2 h-3.5 w-3.5\" />}\n {props.isEnabled\n ? t('data_sync.dashboard.start.status.enabled', 'Integration enabled')\n : t('data_sync.dashboard.start.status.disabled', 'Integration disabled')}\n </Badge>\n <Badge variant=\"outline\" className={props.hasCredentials ? 'border-sky-300 text-sky-700' : 'border-amber-300 text-amber-700'}>\n <CalendarClock className=\"mr-2 h-3.5 w-3.5\" />\n {props.hasCredentials\n ? t('data_sync.dashboard.start.status.credentialsReady', 'Credentials ready')\n : t('data_sync.dashboard.start.status.credentialsMissing', 'Credentials missing')}\n </Badge>\n </div>\n <div>\n <h3 className=\"text-base font-semibold\">{t('data_sync.integrationTab.title', 'Sync schedules')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('data_sync.integrationTab.description', 'Run one-off syncs or save recurring schedules for every supported entity directly from the integration detail page.')}\n </p>\n </div>\n </div>\n <Button type=\"button\" variant=\"outline\" onClick={() => void load()} disabled={isLoading}>\n <RefreshCw className=\"mr-2 h-4 w-4\" />\n {t('data_sync.integrationTab.refresh', 'Refresh')}\n </Button>\n </div>\n\n {!props.isEnabled ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.integrationDisabledNotice', 'The integration is disabled. You can save schedules now, but runs will stay blocked until the integration is enabled.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {!props.hasCredentials ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n {t('data_sync.integrationTab.credentialsMissingNotice', 'Credentials are still missing. Save schedules first if you want, but manual and scheduled runs will fail until credentials are configured.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {option.canStartRun === false ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.providerManagedNotice', 'This provider needs its own setup flow before a run can start. Use the provider tab on this page instead of generic schedules.')}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {rows.length === 0 ? (\n <Alert variant=\"info\">\n <AlertDescription>\n {t('data_sync.integrationTab.empty', 'This provider does not expose any schedulable sync entities yet.')}\n </AlertDescription>\n </Alert>\n ) : (\n <div className=\"overflow-x-auto rounded-lg border\">\n <table className=\"w-full min-w-[1080px] text-sm\">\n <thead className=\"bg-muted/50 text-left\">\n <tr>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.entityType', 'Entity Type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.columns.direction', 'Direction')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.type', 'Schedule type')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.value', 'Value')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.timezone', 'Timezone')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.start.fullSync', 'Run as full sync')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.dashboard.schedule.enabled', 'Schedule enabled')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.lastRun', 'Last run')}</th>\n <th className=\"px-3 py-2 font-medium\">{t('data_sync.integrationTab.columns.actions', 'Actions')}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => {\n const scheduleState = schedules[row.key] ?? buildDefaultScheduleState(row.entityType)\n const isRunning = runningKey === row.key\n const isSaving = savingKey === row.key\n const isDeleting = deletingKey === row.key\n const controlsDisabled = isRunning || isSaving || isDeleting\n return (\n <tr key={row.key} className=\"border-t align-top\">\n <td className=\"px-3 py-3 font-medium\">{formatEntityTypeLabel(row.entityType)}</td>\n <td className=\"px-3 py-3\">{t(`data_sync.dashboard.direction.${row.direction}`, row.direction === 'import' ? 'Import' : 'Export')}</td>\n <td className=\"px-3 py-3\">\n <Select\n value={scheduleState.scheduleType}\n onValueChange={(value) => updateScheduleEditor(row.key, {\n scheduleType: value === 'cron' ? 'cron' : 'interval',\n }, row.entityType)}\n disabled={controlsDisabled}\n >\n <SelectTrigger size=\"lg\" className=\"min-w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"interval\">{t('data_sync.dashboard.schedule.interval', 'Interval')}</SelectItem>\n <SelectItem value=\"cron\">{t('data_sync.dashboard.schedule.cron', 'Cron')}</SelectItem>\n </SelectContent>\n </Select>\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.scheduleValue}\n onChange={(event) => updateScheduleEditor(row.key, { scheduleValue: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n placeholder={scheduleState.scheduleType === 'cron' ? '0 * * * *' : '1h'}\n />\n </td>\n <td className=\"px-3 py-3\">\n <Input\n value={scheduleState.timezone}\n onChange={(event) => updateScheduleEditor(row.key, { timezone: event.target.value }, row.entityType)}\n disabled={controlsDisabled}\n />\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={scheduleState.fullSync}\n onChange={(event) => updateScheduleEditor(row.key, { fullSync: event.target.checked }, row.entityType)}\n disabled={controlsDisabled}\n />\n <span>{t('data_sync.integrationTab.fullSyncShort', 'Full')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3\">\n <label className=\"flex min-h-10 items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={scheduleState.isEnabled}\n onChange={(event) => updateScheduleEditor(row.key, { isEnabled: event.target.checked }, row.entityType)}\n disabled={controlsDisabled}\n />\n <span>{scheduleState.isEnabled ? t('data_sync.dashboard.schedule.status.shortEnabled', 'Scheduled') : t('data_sync.dashboard.schedule.status.shortDisabled', 'Paused')}</span>\n </label>\n </td>\n <td className=\"px-3 py-3 text-muted-foreground\">\n {scheduleState.lastRunAt\n ? new Date(scheduleState.lastRunAt).toLocaleString()\n : scheduleState.id\n ? t('data_sync.dashboard.schedule.neverRun', 'Saved, but no scheduled execution has completed yet.')\n : t('data_sync.dashboard.schedule.none', 'No recurring schedule saved for this target yet.')}\n </td>\n <td className=\"px-3 py-3\">\n <div className=\"flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void handleStartSync(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || !props.isEnabled || !props.hasCredentials || option.canStartRun === false}\n >\n {isRunning ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Play className=\"mr-2 h-4 w-4\" />}\n {isRunning\n ? t('data_sync.integrationTab.starting', 'Starting...')\n : t('data_sync.integrationTab.start', 'Run now')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleSaveSchedule(row.entityType, row.direction, row.key)}\n disabled={controlsDisabled || option.canStartRun === false}\n >\n {isSaving ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Save className=\"mr-2 h-4 w-4\" />}\n {isSaving\n ? t('data_sync.dashboard.schedule.saving', 'Saving...')\n : t('data_sync.dashboard.schedule.save', 'Save recurring schedule')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void handleDeleteSchedule(row.entityType, row.key)}\n disabled={controlsDisabled || !scheduleState.id}\n >\n {isDeleting ? <Spinner className=\"mr-2 h-4 w-4\" /> : <Trash2 className=\"mr-2 h-4 w-4\" />}\n {isDeleting\n ? t('data_sync.dashboard.schedule.deleting', 'Removing...')\n : t('data_sync.dashboard.schedule.delete', 'Remove schedule')}\n </Button>\n </div>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )}\n\n <div className=\"grid gap-3 rounded-lg border border-dashed p-4 text-sm text-muted-foreground md:grid-cols-3\">\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.interval', 'Interval')}</Label>\n <p>{t('data_sync.dashboard.schedule.intervalHelp', 'Example: `1h`, `6h`, or `24h` for repeating intervals.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.dashboard.schedule.cron', 'Cron')}</Label>\n <p>{t('data_sync.dashboard.schedule.cronHelp', 'Example: `0 * * * *` runs at the start of every hour.')}</p>\n </div>\n <div className=\"space-y-1\">\n <Label>{t('data_sync.integrationTab.runNowTitle', 'Manual runs')}</Label>\n <p>{t('data_sync.integrationTab.runNowHelp', 'Run now uses the full-sync checkbox from the same row and starts progress tracking immediately in Data Sync.')}</p>\n </div>\n </div>\n </section>\n )\n}\n\nexport default IntegrationScheduleTab\n"],
5
+ "mappings": "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",
6
6
  "names": ["supportedDirections"]
7
7
  }
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { findAndCountWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
3
4
  import { SyncSchedule } from "../data/entities.js";
4
5
  function createSyncScheduleService(em, schedulerService) {
5
6
  function requireScheduler() {
@@ -71,6 +72,14 @@ function createSyncScheduleService(em, schedulerService) {
71
72
  },
72
73
  async saveSchedule(input, scope) {
73
74
  const existing = input.id ? await getById(input.id, scope) : await getByKey(input.integrationId, input.entityType, input.direction, scope);
75
+ if (existing) {
76
+ enforceCommandOptimisticLock({
77
+ resourceKind: "data_sync.schedule",
78
+ resourceId: existing.id,
79
+ current: existing.updatedAt ?? null,
80
+ expected: input.expectedUpdatedAt ?? null
81
+ });
82
+ }
74
83
  const row = existing ?? em.create(SyncSchedule, {
75
84
  id: randomUUID(),
76
85
  integrationId: input.integrationId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/data_sync/lib/sync-schedule-service.ts"],
4
- "sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SyncSchedule } from '../data/entities'\n\ntype SyncScope = {\n organizationId: string\n tenantId: string\n}\n\ntype SchedulerServiceLike = {\n register: (registration: {\n id: string\n name: string\n description?: string\n scopeType: 'organization'\n organizationId: string\n tenantId: string\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone?: string\n targetType: 'queue'\n targetQueue: string\n targetPayload: Record<string, unknown>\n requireFeature?: string\n sourceType: 'module'\n sourceModule: string\n isEnabled?: boolean\n }) => Promise<void>\n unregister: (scheduleId: string) => Promise<void>\n}\n\nexport function createSyncScheduleService(em: EntityManager, schedulerService?: SchedulerServiceLike) {\n function requireScheduler(): SchedulerServiceLike {\n if (!schedulerService) {\n throw new Error('Scheduler module is not available')\n }\n return schedulerService\n }\n\n function buildScheduleName(row: SyncSchedule): string {\n return `Data sync: ${row.integrationId} ${row.entityType} ${row.direction}`\n }\n\n function buildScheduleDescription(row: SyncSchedule): string {\n return `Scheduled ${row.direction} for ${row.integrationId} (${row.entityType})`\n }\n\n async function getById(id: string, scope: SyncScope): Promise<SyncSchedule | null> {\n return findOneWithDecryption(\n em,\n SyncSchedule,\n {\n id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n }\n\n async function getByKey(\n integrationId: string,\n entityType: string,\n direction: 'import' | 'export',\n scope: SyncScope,\n ): Promise<SyncSchedule | null> {\n return findOneWithDecryption(\n em,\n SyncSchedule,\n {\n integrationId,\n entityType,\n direction,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n }\n\n return {\n getById,\n getByKey,\n\n async listSchedules(query: {\n integrationId?: string\n entityType?: string\n direction?: 'import' | 'export'\n page: number\n pageSize: number\n }, scope: SyncScope): Promise<{ items: SyncSchedule[]; total: number }> {\n const where: FilterQuery<SyncSchedule> = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }\n\n if (query.integrationId) where.integrationId = query.integrationId\n if (query.entityType) where.entityType = query.entityType\n if (query.direction) where.direction = query.direction\n\n const [items, total] = await findAndCountWithDecryption(\n em,\n SyncSchedule,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n },\n scope,\n )\n\n return { items, total }\n },\n\n async saveSchedule(input: {\n id?: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n }, scope: SyncScope): Promise<SyncSchedule> {\n const existing = input.id\n ? await getById(input.id, scope)\n : await getByKey(input.integrationId, input.entityType, input.direction, scope)\n\n const row = existing ?? em.create(SyncSchedule, {\n id: randomUUID(),\n integrationId: input.integrationId,\n entityType: input.entityType,\n direction: input.direction,\n scheduleType: input.scheduleType,\n scheduleValue: input.scheduleValue,\n timezone: input.timezone,\n fullSync: input.fullSync,\n isEnabled: input.isEnabled,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n row.integrationId = input.integrationId\n row.entityType = input.entityType\n row.direction = input.direction\n row.scheduleType = input.scheduleType\n row.scheduleValue = input.scheduleValue\n row.timezone = input.timezone\n row.fullSync = input.fullSync\n row.isEnabled = input.isEnabled\n row.scheduledJobId = row.scheduledJobId ?? row.id\n\n if (!existing) {\n em.persist(row)\n }\n\n await em.flush()\n\n await requireScheduler().register({\n id: row.scheduledJobId,\n name: buildScheduleName(row),\n description: buildScheduleDescription(row),\n scopeType: 'organization',\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n scheduleType: row.scheduleType,\n scheduleValue: row.scheduleValue,\n timezone: row.timezone,\n targetType: 'queue',\n targetQueue: 'data-sync-scheduled',\n targetPayload: {\n scheduleId: row.id,\n scope,\n },\n requireFeature: 'data_sync.run',\n sourceType: 'module',\n sourceModule: 'data_sync',\n isEnabled: row.isEnabled,\n })\n\n return row\n },\n\n async deleteSchedule(id: string, scope: SyncScope): Promise<boolean> {\n const row = await getById(id, scope)\n if (!row) return false\n\n const scheduledJobId = row.scheduledJobId ?? row.id\n await requireScheduler().unregister(scheduledJobId)\n\n row.deletedAt = new Date()\n row.isEnabled = false\n await em.flush()\n return true\n },\n }\n}\n\nexport type SyncScheduleService = ReturnType<typeof createSyncScheduleService>\n"],
5
- "mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,4BAA4B,6BAA6B;AAClE,SAAS,oBAAoB;AA6BtB,SAAS,0BAA0B,IAAmB,kBAAyC;AACpG,WAAS,mBAAyC;AAChD,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AACA,WAAO;AAAA,EACT;AAEA,WAAS,kBAAkB,KAA2B;AACpD,WAAO,cAAc,IAAI,aAAa,IAAI,IAAI,UAAU,IAAI,IAAI,SAAS;AAAA,EAC3E;AAEA,WAAS,yBAAyB,KAA2B;AAC3D,WAAO,aAAa,IAAI,SAAS,QAAQ,IAAI,aAAa,KAAK,IAAI,UAAU;AAAA,EAC/E;AAEA,iBAAe,QAAQ,IAAY,OAAgD;AACjF,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,QACE;AAAA,QACA,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,iBAAe,SACb,eACA,YACA,WACA,OAC8B;AAC9B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IAEA,MAAM,cAAc,OAMjB,OAAqE;AACtE,YAAM,QAAmC;AAAA,QACvC,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,WAAW;AAAA,MACb;AAEA,UAAI,MAAM,cAAe,OAAM,gBAAgB,MAAM;AACrD,UAAI,MAAM,WAAY,OAAM,aAAa,MAAM;AAC/C,UAAI,MAAM,UAAW,OAAM,YAAY,MAAM;AAE7C,YAAM,CAAC,OAAO,KAAK,IAAI,MAAM;AAAA,QAC3B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACE,SAAS,EAAE,WAAW,OAAO;AAAA,UAC7B,OAAO,MAAM;AAAA,UACb,SAAS,MAAM,OAAO,KAAK,MAAM;AAAA,QACnC;AAAA,QACA;AAAA,MACF;AAEA,aAAO,EAAE,OAAO,MAAM;AAAA,IACxB;AAAA,IAEA,MAAM,aAAa,OAUhB,OAAyC;AAC1C,YAAM,WAAW,MAAM,KACnB,MAAM,QAAQ,MAAM,IAAI,KAAK,IAC7B,MAAM,SAAS,MAAM,eAAe,MAAM,YAAY,MAAM,WAAW,KAAK;AAEhF,YAAM,MAAM,YAAY,GAAG,OAAO,cAAc;AAAA,QAC9C,IAAI,WAAW;AAAA,QACf,eAAe,MAAM;AAAA,QACrB,YAAY,MAAM;AAAA,QAClB,WAAW,MAAM;AAAA,QACjB,cAAc,MAAM;AAAA,QACpB,eAAe,MAAM;AAAA,QACrB,UAAU,MAAM;AAAA,QAChB,UAAU,MAAM;AAAA,QAChB,WAAW,MAAM;AAAA,QACjB,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,MAClB,CAAC;AAED,UAAI,gBAAgB,MAAM;AAC1B,UAAI,aAAa,MAAM;AACvB,UAAI,YAAY,MAAM;AACtB,UAAI,eAAe,MAAM;AACzB,UAAI,gBAAgB,MAAM;AAC1B,UAAI,WAAW,MAAM;AACrB,UAAI,WAAW,MAAM;AACrB,UAAI,YAAY,MAAM;AACtB,UAAI,iBAAiB,IAAI,kBAAkB,IAAI;AAE/C,UAAI,CAAC,UAAU;AACb,WAAG,QAAQ,GAAG;AAAA,MAChB;AAEA,YAAM,GAAG,MAAM;AAEf,YAAM,iBAAiB,EAAE,SAAS;AAAA,QAChC,IAAI,IAAI;AAAA,QACR,MAAM,kBAAkB,GAAG;AAAA,QAC3B,aAAa,yBAAyB,GAAG;AAAA,QACzC,WAAW;AAAA,QACX,gBAAgB,MAAM;AAAA,QACtB,UAAU,MAAM;AAAA,QAChB,cAAc,IAAI;AAAA,QAClB,eAAe,IAAI;AAAA,QACnB,UAAU,IAAI;AAAA,QACd,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,eAAe;AAAA,UACb,YAAY,IAAI;AAAA,UAChB;AAAA,QACF;AAAA,QACA,gBAAgB;AAAA,QAChB,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,WAAW,IAAI;AAAA,MACjB,CAAC;AAED,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,eAAe,IAAY,OAAoC;AACnE,YAAM,MAAM,MAAM,QAAQ,IAAI,KAAK;AACnC,UAAI,CAAC,IAAK,QAAO;AAEjB,YAAM,iBAAiB,IAAI,kBAAkB,IAAI;AACjD,YAAM,iBAAiB,EAAE,WAAW,cAAc;AAElD,UAAI,YAAY,oBAAI,KAAK;AACzB,UAAI,YAAY;AAChB,YAAM,GAAG,MAAM;AACf,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { SyncSchedule } from '../data/entities'\n\ntype SyncScope = {\n organizationId: string\n tenantId: string\n}\n\ntype SchedulerServiceLike = {\n register: (registration: {\n id: string\n name: string\n description?: string\n scopeType: 'organization'\n organizationId: string\n tenantId: string\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone?: string\n targetType: 'queue'\n targetQueue: string\n targetPayload: Record<string, unknown>\n requireFeature?: string\n sourceType: 'module'\n sourceModule: string\n isEnabled?: boolean\n }) => Promise<void>\n unregister: (scheduleId: string) => Promise<void>\n}\n\nexport function createSyncScheduleService(em: EntityManager, schedulerService?: SchedulerServiceLike) {\n function requireScheduler(): SchedulerServiceLike {\n if (!schedulerService) {\n throw new Error('Scheduler module is not available')\n }\n return schedulerService\n }\n\n function buildScheduleName(row: SyncSchedule): string {\n return `Data sync: ${row.integrationId} ${row.entityType} ${row.direction}`\n }\n\n function buildScheduleDescription(row: SyncSchedule): string {\n return `Scheduled ${row.direction} for ${row.integrationId} (${row.entityType})`\n }\n\n async function getById(id: string, scope: SyncScope): Promise<SyncSchedule | null> {\n return findOneWithDecryption(\n em,\n SyncSchedule,\n {\n id,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n }\n\n async function getByKey(\n integrationId: string,\n entityType: string,\n direction: 'import' | 'export',\n scope: SyncScope,\n ): Promise<SyncSchedule | null> {\n return findOneWithDecryption(\n em,\n SyncSchedule,\n {\n integrationId,\n entityType,\n direction,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n }\n\n return {\n getById,\n getByKey,\n\n async listSchedules(query: {\n integrationId?: string\n entityType?: string\n direction?: 'import' | 'export'\n page: number\n pageSize: number\n }, scope: SyncScope): Promise<{ items: SyncSchedule[]; total: number }> {\n const where: FilterQuery<SyncSchedule> = {\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }\n\n if (query.integrationId) where.integrationId = query.integrationId\n if (query.entityType) where.entityType = query.entityType\n if (query.direction) where.direction = query.direction\n\n const [items, total] = await findAndCountWithDecryption(\n em,\n SyncSchedule,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: query.pageSize,\n offset: (query.page - 1) * query.pageSize,\n },\n scope,\n )\n\n return { items, total }\n },\n\n async saveSchedule(input: {\n id?: string\n integrationId: string\n entityType: string\n direction: 'import' | 'export'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n fullSync: boolean\n isEnabled: boolean\n expectedUpdatedAt?: string | null\n }, scope: SyncScope): Promise<SyncSchedule> {\n const existing = input.id\n ? await getById(input.id, scope)\n : await getByKey(input.integrationId, input.entityType, input.direction, scope)\n\n if (existing) {\n enforceCommandOptimisticLock({\n resourceKind: 'data_sync.schedule',\n resourceId: existing.id,\n current: existing.updatedAt ?? null,\n expected: input.expectedUpdatedAt ?? null,\n })\n }\n\n const row = existing ?? em.create(SyncSchedule, {\n id: randomUUID(),\n integrationId: input.integrationId,\n entityType: input.entityType,\n direction: input.direction,\n scheduleType: input.scheduleType,\n scheduleValue: input.scheduleValue,\n timezone: input.timezone,\n fullSync: input.fullSync,\n isEnabled: input.isEnabled,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n\n row.integrationId = input.integrationId\n row.entityType = input.entityType\n row.direction = input.direction\n row.scheduleType = input.scheduleType\n row.scheduleValue = input.scheduleValue\n row.timezone = input.timezone\n row.fullSync = input.fullSync\n row.isEnabled = input.isEnabled\n row.scheduledJobId = row.scheduledJobId ?? row.id\n\n if (!existing) {\n em.persist(row)\n }\n\n await em.flush()\n\n await requireScheduler().register({\n id: row.scheduledJobId,\n name: buildScheduleName(row),\n description: buildScheduleDescription(row),\n scopeType: 'organization',\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n scheduleType: row.scheduleType,\n scheduleValue: row.scheduleValue,\n timezone: row.timezone,\n targetType: 'queue',\n targetQueue: 'data-sync-scheduled',\n targetPayload: {\n scheduleId: row.id,\n scope,\n },\n requireFeature: 'data_sync.run',\n sourceType: 'module',\n sourceModule: 'data_sync',\n isEnabled: row.isEnabled,\n })\n\n return row\n },\n\n async deleteSchedule(id: string, scope: SyncScope): Promise<boolean> {\n const row = await getById(id, scope)\n if (!row) return false\n\n const scheduledJobId = row.scheduledJobId ?? row.id\n await requireScheduler().unregister(scheduledJobId)\n\n row.deletedAt = new Date()\n row.isEnabled = false\n await em.flush()\n return true\n },\n }\n}\n\nexport type SyncScheduleService = ReturnType<typeof createSyncScheduleService>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,6 +4,7 @@ import { Dictionary, DictionaryEntry } from "@open-mercato/core/modules/dictiona
4
4
  import { resolveDictionariesRouteContext } from "@open-mercato/core/modules/dictionaries/api/context";
5
5
  import { updateDictionaryEntrySchema } from "@open-mercato/core/modules/dictionaries/data/validators";
6
6
  import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
7
8
  import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
8
9
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
9
10
  import {
@@ -60,7 +61,13 @@ async function PATCH(req, ctx) {
60
61
  entryId: ctx.params?.entryId
61
62
  });
62
63
  const dictionary = await loadDictionary(context, dictionaryId);
63
- await loadEntry(context, dictionary, entryId);
64
+ const entry = await loadEntry(context, dictionary, entryId);
65
+ enforceCommandOptimisticLock({
66
+ resourceKind: "dictionaries.entry",
67
+ resourceId: entry.id,
68
+ current: entry.updatedAt ?? null,
69
+ request: req
70
+ });
64
71
  const rawBody = await req.json().catch(() => ({}));
65
72
  const payload = updateDictionaryEntrySchema.parse(rawBody);
66
73
  const commandBus = context.container.resolve("commandBus");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/dictionaries/api/%5BdictionaryId%5D/entries/%5BentryId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { updateDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n dictionaryEntryParamsSchema,\n dictionaryEntryResponseSchema,\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n updateDictionaryEntrySchema as updateEntryDocSchema,\n} from '../../../openapi'\nconst paramsSchema = z.object({\n dictionaryId: z.string().uuid(),\n entryId: z.string().uuid(),\n})\n\nasync function loadDictionary(context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>, id: string) {\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const dictionary = await context.em.findOne(Dictionary, {\n id,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nasync function loadEntry(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n dictionary: Dictionary,\n entryId: string,\n) {\n const entry = await context.em.findOne(DictionaryEntry, {\n id: entryId,\n dictionary,\n organizationId: dictionary.organizationId,\n tenantId: context.tenantId,\n })\n if (!entry) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.entry_not_found', 'Dictionary entry not found') })\n }\n return entry\n}\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string; entryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId, entryId } = paramsSchema.parse({\n dictionaryId: ctx.params?.dictionaryId,\n entryId: ctx.params?.entryId,\n })\n const dictionary = await loadDictionary(context, dictionaryId)\n await loadEntry(context, dictionary, entryId)\n const rawBody = await req.json().catch(() => ({}))\n const payload = updateDictionaryEntrySchema.parse(rawBody)\n // These nested routes don't use the CRUD factory, so invoke the command bus explicitly.\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const input = { ...(payload as Record<string, unknown>), id: entryId }\n const { result, logEntry } = await commandBus.execute('dictionaries.entries.update', {\n input,\n ctx: context.ctx,\n })\n const updateResult = (result ?? {}) as { entryId?: string | null }\n const updatedEntryId = typeof updateResult.entryId === 'string' ? updateResult.entryId : null\n if (!updatedEntryId) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_update_failed', 'Failed to update dictionary entry') })\n }\n const updated = await findOneWithDecryption(\n context.em.fork(),\n DictionaryEntry,\n updatedEntryId,\n { populate: ['dictionary'] },\n { tenantId: context.auth.tenantId ?? null, organizationId: context.auth.orgId ?? null },\n )\n if (!updated) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_update_failed', 'Failed to update dictionary entry') })\n }\n const response = NextResponse.json({\n id: updated.id,\n value: updated.value,\n label: updated.label,\n color: updated.color,\n icon: updated.icon,\n position: updated.position ?? 0,\n isDefault: updated.isDefault ?? false,\n createdAt: updated.createdAt,\n updatedAt: updated.updatedAt,\n })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.entry',\n resourceId: updatedEntryId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id/entries/:entryId.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary entry' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string; entryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId, entryId } = paramsSchema.parse({\n dictionaryId: ctx.params?.dictionaryId,\n entryId: ctx.params?.entryId,\n })\n const dictionary = await loadDictionary(context, dictionaryId)\n const entry = await loadEntry(context, dictionary, entryId)\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const { logEntry } = await commandBus.execute('dictionaries.entries.delete', {\n input: { body: { id: entry.id } },\n ctx: context.ctx,\n })\n const response = NextResponse.json({ ok: true })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.entry',\n resourceId: entry.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id/entries/:entryId.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary entry' }, { status: 500 })\n }\n}\n\nconst dictionaryEntryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary entry',\n description: 'Updates the specified dictionary entry using the command bus pipeline.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: updateEntryDocSchema,\n description: 'Fields to update on the dictionary entry.',\n },\n responses: [\n { status: 200, description: 'Dictionary entry updated.', schema: dictionaryEntryResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary or entry not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update entry', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryEntryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary entry',\n description: 'Deletes the specified dictionary entry via the command bus.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Entry deleted.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary or entry not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete entry', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary entry resource',\n pathParams: dictionaryEntryParamsSchema,\n methods: {\n PATCH: dictionaryEntryPatchDoc,\n DELETE: dictionaryEntryDeleteDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { updateDictionaryEntrySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n dictionaryEntryParamsSchema,\n dictionaryEntryResponseSchema,\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n updateDictionaryEntrySchema as updateEntryDocSchema,\n} from '../../../openapi'\nconst paramsSchema = z.object({\n dictionaryId: z.string().uuid(),\n entryId: z.string().uuid(),\n})\n\nasync function loadDictionary(context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>, id: string) {\n if (!context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const dictionary = await context.em.findOne(Dictionary, {\n id,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nasync function loadEntry(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n dictionary: Dictionary,\n entryId: string,\n) {\n const entry = await context.em.findOne(DictionaryEntry, {\n id: entryId,\n dictionary,\n organizationId: dictionary.organizationId,\n tenantId: context.tenantId,\n })\n if (!entry) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.entry_not_found', 'Dictionary entry not found') })\n }\n return entry\n}\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string; entryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n if (!context.auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const { dictionaryId, entryId } = paramsSchema.parse({\n dictionaryId: ctx.params?.dictionaryId,\n entryId: ctx.params?.entryId,\n })\n const dictionary = await loadDictionary(context, dictionaryId)\n const entry = await loadEntry(context, dictionary, entryId)\n enforceCommandOptimisticLock({\n resourceKind: 'dictionaries.entry',\n resourceId: entry.id,\n current: entry.updatedAt ?? null,\n request: req,\n })\n const rawBody = await req.json().catch(() => ({}))\n const payload = updateDictionaryEntrySchema.parse(rawBody)\n // These nested routes don't use the CRUD factory, so invoke the command bus explicitly.\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const input = { ...(payload as Record<string, unknown>), id: entryId }\n const { result, logEntry } = await commandBus.execute('dictionaries.entries.update', {\n input,\n ctx: context.ctx,\n })\n const updateResult = (result ?? {}) as { entryId?: string | null }\n const updatedEntryId = typeof updateResult.entryId === 'string' ? updateResult.entryId : null\n if (!updatedEntryId) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_update_failed', 'Failed to update dictionary entry') })\n }\n const updated = await findOneWithDecryption(\n context.em.fork(),\n DictionaryEntry,\n updatedEntryId,\n { populate: ['dictionary'] },\n { tenantId: context.auth.tenantId ?? null, organizationId: context.auth.orgId ?? null },\n )\n if (!updated) {\n throw new CrudHttpError(500, { error: context.translate('dictionaries.errors.entry_update_failed', 'Failed to update dictionary entry') })\n }\n const response = NextResponse.json({\n id: updated.id,\n value: updated.value,\n label: updated.label,\n color: updated.color,\n icon: updated.icon,\n position: updated.position ?? 0,\n isDefault: updated.isDefault ?? false,\n createdAt: updated.createdAt,\n updatedAt: updated.updatedAt,\n })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.entry',\n resourceId: updatedEntryId,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id/entries/:entryId.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary entry' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string; entryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId, entryId } = paramsSchema.parse({\n dictionaryId: ctx.params?.dictionaryId,\n entryId: ctx.params?.entryId,\n })\n const dictionary = await loadDictionary(context, dictionaryId)\n const entry = await loadEntry(context, dictionary, entryId)\n const commandBus = (context.container.resolve('commandBus') as CommandBus)\n const { logEntry } = await commandBus.execute('dictionaries.entries.delete', {\n input: { body: { id: entry.id } },\n ctx: context.ctx,\n })\n const response = NextResponse.json({ ok: true })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'dictionaries.entry',\n resourceId: entry.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n })\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id/entries/:entryId.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary entry' }, { status: 500 })\n }\n}\n\nconst dictionaryEntryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary entry',\n description: 'Updates the specified dictionary entry using the command bus pipeline.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: updateEntryDocSchema,\n description: 'Fields to update on the dictionary entry.',\n },\n responses: [\n { status: 200, description: 'Dictionary entry updated.', schema: dictionaryEntryResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary or entry not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update entry', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryEntryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary entry',\n description: 'Deletes the specified dictionary entry via the command bus.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Entry deleted.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary or entry not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete entry', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary entry resource',\n pathParams: dictionaryEntryParamsSchema,\n methods: {\n PATCH: dictionaryEntryPatchDoc,\n DELETE: dictionaryEntryDeleteDoc,\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,YAAY,uBAAuB;AAC5C,SAAS,uCAAuC;AAChD,SAAS,mCAAmC;AAC5C,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oCAAoC;AAE7C,SAAS,kCAAkC;AAE3C,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,+BAA+B;AAAA,OAC1B;AACP,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,cAAc,EAAE,OAAO,EAAE,KAAK;AAAA,EAC9B,SAAS,EAAE,OAAO,EAAE,KAAK;AAC3B,CAAC;AAED,eAAe,eAAe,SAAsE,IAAY;AAC9G,MAAI,CAAC,QAAQ,gBAAgB;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,6CAA6C,kCAAkC,EAAE,CAAC;AAAA,EAC5I;AACA,QAAM,aAAa,MAAM,QAAQ,GAAG,QAAQ,YAAY;AAAA,IACtD;AAAA,IACA,gBAAgB,QAAQ;AAAA,IACxB,UAAU,QAAQ;AAAA,IAClB,WAAW;AAAA,EACb,CAAC;AACD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,iCAAiC,sBAAsB,EAAE,CAAC;AAAA,EACpH;AACA,SAAO;AACT;AAEA,eAAe,UACb,SACA,YACA,SACA;AACA,QAAM,QAAQ,MAAM,QAAQ,GAAG,QAAQ,iBAAiB;AAAA,IACtD,IAAI;AAAA,IACJ;AAAA,IACA,gBAAgB,WAAW;AAAA,IAC3B,UAAU,QAAQ;AAAA,EACpB,CAAC;AACD,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,uCAAuC,4BAA4B,EAAE,CAAC;AAAA,EAChI;AACA,SAAO;AACT;AAEO,MAAM,WAAW;AAAA,EACtB,OAAO,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AAAA,EACrE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACxE;AAEA,eAAsB,MAAM,KAAc,KAA+D;AACvG,MAAI;AACF,UAAM,UAAU,MAAM,gCAAgC,GAAG;AACzD,QAAI,CAAC,QAAQ,MAAM;AACjB,aAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACrE;AACA,UAAM,EAAE,cAAc,QAAQ,IAAI,aAAa,MAAM;AAAA,MACnD,cAAc,IAAI,QAAQ;AAAA,MAC1B,SAAS,IAAI,QAAQ;AAAA,IACvB,CAAC;AACD,UAAM,aAAa,MAAM,eAAe,SAAS,YAAY;AAC7D,UAAM,QAAQ,MAAM,UAAU,SAAS,YAAY,OAAO;AAC1D,iCAA6B;AAAA,MAC3B,cAAc;AAAA,MACd,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM,aAAa;AAAA,MAC5B,SAAS;AAAA,IACX,CAAC;AACD,UAAM,UAAU,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACjD,UAAM,UAAU,4BAA4B,MAAM,OAAO;AAEzD,UAAM,aAAc,QAAQ,UAAU,QAAQ,YAAY;AAC1D,UAAM,QAAQ,EAAE,GAAI,SAAqC,IAAI,QAAQ;AACrE,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,WAAW,QAAQ,+BAA+B;AAAA,MACnF;AAAA,MACA,KAAK,QAAQ;AAAA,IACf,CAAC;AACD,UAAM,eAAgB,UAAU,CAAC;AACjC,UAAM,iBAAiB,OAAO,aAAa,YAAY,WAAW,aAAa,UAAU;AACzF,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,2CAA2C,mCAAmC,EAAE,CAAC;AAAA,IAC3I;AACA,UAAM,UAAU,MAAM;AAAA,MACpB,QAAQ,GAAG,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA,EAAE,UAAU,CAAC,YAAY,EAAE;AAAA,MAC3B,EAAE,UAAU,QAAQ,KAAK,YAAY,MAAM,gBAAgB,QAAQ,KAAK,SAAS,KAAK;AAAA,IACxF;AACA,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,UAAU,2CAA2C,mCAAmC,EAAE,CAAC;AAAA,IAC3I;AACA,UAAM,WAAW,aAAa,KAAK;AAAA,MACjC,IAAI,QAAQ;AAAA,MACZ,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ;AAAA,MACf,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,UAAU,QAAQ,YAAY;AAAA,MAC9B,WAAW,QAAQ,aAAa;AAAA,MAChC,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,IACrB,CAAC;AACD,QAAI,UAAU,aAAa,UAAU,MAAM,UAAU,WAAW;AAC9D,eAAS,QAAQ;AAAA,QACf;AAAA,QACA,2BAA2B;AAAA,UACzB,IAAI,SAAS;AAAA,UACb,WAAW,SAAS;AAAA,UACpB,WAAW,SAAS;AAAA,UACpB,aAAa,SAAS,eAAe;AAAA,UACrC,cAAc,SAAS,gBAAgB;AAAA,UACvC,YAAY;AAAA,UACZ,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAAI;AAAA,QACtF,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,YAAQ,MAAM,8DAA8D,GAAG;AAC/E,WAAO,aAAa,KAAK,EAAE,OAAO,oCAAoC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1F;AACF;AAEA,eAAsB,OAAO,KAAc,KAA+D;AACxG,MAAI;AACF,UAAM,UAAU,MAAM,gCAAgC,GAAG;AACzD,UAAM,EAAE,cAAc,QAAQ,IAAI,aAAa,MAAM;AAAA,MACnD,cAAc,IAAI,QAAQ;AAAA,MAC1B,SAAS,IAAI,QAAQ;AAAA,IACvB,CAAC;AACD,UAAM,aAAa,MAAM,eAAe,SAAS,YAAY;AAC7D,UAAM,QAAQ,MAAM,UAAU,SAAS,YAAY,OAAO;AAC1D,UAAM,aAAc,QAAQ,UAAU,QAAQ,YAAY;AAC1D,UAAM,EAAE,SAAS,IAAI,MAAM,WAAW,QAAQ,+BAA+B;AAAA,MAC3E,OAAO,EAAE,MAAM,EAAE,IAAI,MAAM,GAAG,EAAE;AAAA,MAChC,KAAK,QAAQ;AAAA,IACf,CAAC;AACD,UAAM,WAAW,aAAa,KAAK,EAAE,IAAI,KAAK,CAAC;AAC/C,QAAI,UAAU,aAAa,UAAU,MAAM,UAAU,WAAW;AAC9D,eAAS,QAAQ;AAAA,QACf;AAAA,QACA,2BAA2B;AAAA,UACzB,IAAI,SAAS;AAAA,UACb,WAAW,SAAS;AAAA,UACpB,WAAW,SAAS;AAAA,UACpB,aAAa,SAAS,eAAe;AAAA,UACrC,cAAc,SAAS,gBAAgB;AAAA,UACvC,YAAY,MAAM;AAAA,UAClB,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAAI;AAAA,QACtF,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,YAAQ,MAAM,+DAA+D,GAAG;AAChF,WAAO,aAAa,KAAK,EAAE,OAAO,oCAAoC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1F;AACF;AAEA,MAAM,0BAA4C;AAAA,EAChD,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,eAAe;AAAA,EACtB,aAAa;AAAA,IACX,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,aAAa;AAAA,EACf;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,6BAA6B,QAAQ,8BAA8B;AAAA,EACjG;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,wBAAwB;AAAA,IACjF,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,wBAAwB;AAAA,IACvF,EAAE,QAAQ,KAAK,aAAa,iCAAiC,QAAQ,wBAAwB;AAAA,IAC7F,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,wBAAwB;AAAA,EACxF;AACF;AAEA,MAAM,2BAA6C;AAAA,EACjD,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,eAAe;AAAA,EACtB,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,kBAAkB,QAAQ,qBAAqB;AAAA,EAC7E;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,wBAAwB;AAAA,IACjF,EAAE,QAAQ,KAAK,aAAa,2BAA2B,QAAQ,wBAAwB;AAAA,IACvF,EAAE,QAAQ,KAAK,aAAa,iCAAiC,QAAQ,wBAAwB;AAAA,IAC7F,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,wBAAwB;AAAA,EACxF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,SAAS;AAAA,IACP,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { Dictionary } from "@open-mercato/core/modules/dictionaries/data/entities";
4
4
  import { resolveDictionariesRouteContext } from "@open-mercato/core/modules/dictionaries/api/context";
5
5
  import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
6
+ import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
6
7
  import {
7
8
  resolveDictionaryEntrySortMode
8
9
  } from "@open-mercato/core/modules/dictionaries/lib/entrySort";
@@ -15,8 +16,10 @@ import {
15
16
  dictionaryUpdateSchema,
16
17
  upsertDictionarySchema
17
18
  } from "../openapi.js";
19
+ import { dictionaryKeySchema } from "@open-mercato/core/modules/dictionaries/data/validators";
18
20
  const paramsSchema = z.object({ dictionaryId: z.string().uuid() });
19
- const updateSchema = upsertDictionarySchema.partial().refine((data) => Object.keys(data).length > 0, {
21
+ const updateKeySchema = z.string().trim().min(1).max(100);
22
+ const updateSchema = upsertDictionarySchema.partial().extend({ key: updateKeySchema.optional() }).refine((data) => Object.keys(data).length > 0, {
20
23
  message: "Provide at least one field to update."
21
24
  });
22
25
  const metadata = {
@@ -84,6 +87,12 @@ async function PATCH(req, ctx) {
84
87
  const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId });
85
88
  const payload = updateSchema.parse(await req.json().catch(() => ({})));
86
89
  const dictionary = await loadDictionary(context, dictionaryId);
90
+ enforceCommandOptimisticLock({
91
+ resourceKind: "dictionaries.dictionary",
92
+ resourceId: dictionary.id,
93
+ current: dictionary.updatedAt ?? null,
94
+ request: req
95
+ });
87
96
  if (isProtectedCurrencyDictionary(dictionary)) {
88
97
  if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {
89
98
  throw new CrudHttpError(400, { error: context.translate("dictionaries.errors.currency_protected", "The currency dictionary cannot be modified or deleted.") });
@@ -95,6 +104,10 @@ async function PATCH(req, ctx) {
95
104
  if (payload.key) {
96
105
  const key = payload.key.trim().toLowerCase();
97
106
  if (key !== dictionary.key) {
107
+ const strictKey = dictionaryKeySchema.safeParse(key);
108
+ if (!strictKey.success) {
109
+ throw new CrudHttpError(400, { error: context.translate("dictionaries.errors.invalid_key", "Use lowercase letters, numbers, hyphen, or underscore.") });
110
+ }
98
111
  const organizationId = context.organizationId;
99
112
  if (!organizationId) {
100
113
  throw new CrudHttpError(400, { error: context.translate("dictionaries.errors.organization_required", "Organization context is required") });
@@ -146,6 +159,9 @@ async function PATCH(req, ctx) {
146
159
  if (isCrudHttpError(err)) {
147
160
  return NextResponse.json(err.body, { status: err.status });
148
161
  }
162
+ if (err instanceof z.ZodError) {
163
+ return NextResponse.json({ error: err.issues[0]?.message ?? "Validation failed" }, { status: 400 });
164
+ }
149
165
  console.error("[dictionaries/:id.PATCH] Unexpected error", err);
150
166
  return NextResponse.json({ error: "Failed to update dictionary" }, { status: 500 });
151
167
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/dictionaries/api/%5BdictionaryId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryIdParamsSchema,\n dictionaryUpdateSchema,\n upsertDictionarySchema,\n} from '../openapi'\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\nconst updateSchema = upsertDictionarySchema\n .partial()\n .refine((data) => Object.keys(data).length > 0, {\n message: 'Provide at least one field to update.',\n })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nfunction isProtectedCurrencyDictionary(dictionary: Dictionary) {\n const key = dictionary.key?.trim().toLowerCase() ?? ''\n return key === 'currency' || key === 'currencies'\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionary' }, { status: 500 })\n }\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = updateSchema.parse(await req.json().catch(() => ({})))\n const dictionary = await loadDictionary(context, dictionaryId)\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n if (payload.isActive === false) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n }\n\n if (payload.key) {\n const key = payload.key.trim().toLowerCase()\n if (key !== dictionary.key) {\n const organizationId = context.organizationId\n if (!organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const existing = await context.em.findOne(Dictionary, {\n key,\n organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n dictionary.key = key\n }\n }\n\n if (payload.name) {\n dictionary.name = payload.name.trim()\n }\n if (payload.description !== undefined) {\n dictionary.description = payload.description ? payload.description.trim() : null\n }\n if (payload.isActive !== undefined) {\n dictionary.isActive = Boolean(payload.isActive)\n if (!dictionary.isActive) {\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n } else {\n dictionary.deletedAt = null\n }\n }\n if (payload.entrySortMode !== undefined) {\n dictionary.entrySortMode = payload.entrySortMode\n }\n\n dictionary.updatedAt = new Date()\n await context.em.flush()\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId)\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n\n dictionary.isActive = false\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n await context.em.flush()\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary' }, { status: 500 })\n }\n}\n\nconst dictionaryGetDoc: OpenApiMethodDoc = {\n summary: 'Get dictionary',\n description: 'Returns details for the specified dictionary, including inheritance flags.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary details.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary',\n description: 'Updates mutable attributes of the dictionary. Currency dictionaries are protected from modification.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: dictionaryUpdateSchema,\n description: 'Fields to update on the dictionary.',\n },\n responses: [\n { status: 200, description: 'Dictionary updated.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or protected dictionary', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary',\n description: 'Soft deletes the dictionary unless it is the protected currency dictionary.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary archived.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Protected dictionary cannot be deleted', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary resource',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryGetDoc,\n PATCH: dictionaryPatchDoc,\n DELETE: dictionaryDeleteDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { Dictionary } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { resolveDictionariesRouteContext } from '@open-mercato/core/modules/dictionaries/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n resolveDictionaryEntrySortMode,\n} from '@open-mercato/core/modules/dictionaries/lib/entrySort'\nimport {\n dictionariesErrorSchema,\n dictionariesOkSchema,\n dictionariesTag,\n dictionaryDetailSchema,\n dictionaryIdParamsSchema,\n dictionaryUpdateSchema,\n upsertDictionarySchema,\n} from '../openapi'\nimport { dictionaryKeySchema } from '@open-mercato/core/modules/dictionaries/data/validators'\n\nconst paramsSchema = z.object({ dictionaryId: z.string().uuid() })\n// System dictionaries use namespaced keys (e.g. `sales.deal_loss_reason`,\n// `resources.activity-types`) that the strict create-key regex rejects. The\n// manager edit dialog disables the key field but still resubmits the existing\n// key, so the update parse must accept any stored key verbatim. The strict\n// user-key regex is only enforced below when the key actually changes.\nconst updateKeySchema = z.string().trim().min(1).max(100)\nconst updateSchema = upsertDictionarySchema\n .partial()\n .extend({ key: updateKeySchema.optional() })\n .refine((data) => Object.keys(data).length > 0, {\n message: 'Provide at least one field to update.',\n })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dictionaries.view'] },\n PATCH: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['dictionaries.manage'] },\n}\n\nfunction isProtectedCurrencyDictionary(dictionary: Dictionary) {\n const key = dictionary.key?.trim().toLowerCase() ?? ''\n return key === 'currency' || key === 'currencies'\n}\n\nasync function loadDictionary(\n context: Awaited<ReturnType<typeof resolveDictionariesRouteContext>>,\n id: string,\n options: { allowInherited?: boolean } = {},\n) {\n const { allowInherited = false } = options\n if (!allowInherited && !context.organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const baseFilter = {\n id,\n tenantId: context.tenantId,\n deletedAt: null,\n }\n const filter = allowInherited\n ? {\n ...baseFilter,\n ...(context.readableOrganizationIds.length\n ? { organizationId: { $in: context.readableOrganizationIds } }\n : {}),\n }\n : {\n ...baseFilter,\n organizationId: context.organizationId,\n }\n const dictionary = await context.em.findOne(Dictionary, filter)\n if (!dictionary) {\n throw new CrudHttpError(404, { error: context.translate('dictionaries.errors.not_found', 'Dictionary not found') })\n }\n return dictionary\n}\n\nexport async function GET(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId, { allowInherited: true })\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n organizationId: dictionary.organizationId,\n isInherited: context.organizationId ? dictionary.organizationId !== context.organizationId : false,\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to load dictionary' }, { status: 500 })\n }\n}\n\nexport async function PATCH(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const payload = updateSchema.parse(await req.json().catch(() => ({})))\n const dictionary = await loadDictionary(context, dictionaryId)\n\n enforceCommandOptimisticLock({\n resourceKind: 'dictionaries.dictionary',\n resourceId: dictionary.id,\n current: dictionary.updatedAt ?? null,\n request: req,\n })\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n if (payload.key && payload.key.trim().toLowerCase() !== dictionary.key) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n if (payload.isActive === false) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n }\n\n if (payload.key) {\n const key = payload.key.trim().toLowerCase()\n if (key !== dictionary.key) {\n const strictKey = dictionaryKeySchema.safeParse(key)\n if (!strictKey.success) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.invalid_key', 'Use lowercase letters, numbers, hyphen, or underscore.') })\n }\n const organizationId = context.organizationId\n if (!organizationId) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.organization_required', 'Organization context is required') })\n }\n const existing = await context.em.findOne(Dictionary, {\n key,\n organizationId,\n tenantId: context.tenantId,\n deletedAt: null,\n })\n if (existing) {\n throw new CrudHttpError(409, { error: context.translate('dictionaries.errors.duplicate', 'A dictionary with this key already exists') })\n }\n dictionary.key = key\n }\n }\n\n if (payload.name) {\n dictionary.name = payload.name.trim()\n }\n if (payload.description !== undefined) {\n dictionary.description = payload.description ? payload.description.trim() : null\n }\n if (payload.isActive !== undefined) {\n dictionary.isActive = Boolean(payload.isActive)\n if (!dictionary.isActive) {\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n } else {\n dictionary.deletedAt = null\n }\n }\n if (payload.entrySortMode !== undefined) {\n dictionary.entrySortMode = payload.entrySortMode\n }\n\n dictionary.updatedAt = new Date()\n await context.em.flush()\n\n return NextResponse.json({\n id: dictionary.id,\n key: dictionary.key,\n name: dictionary.name,\n description: dictionary.description,\n isSystem: dictionary.isSystem,\n isActive: dictionary.isActive,\n managerVisibility: dictionary.managerVisibility,\n entrySortMode: resolveDictionaryEntrySortMode(dictionary.entrySortMode),\n createdAt: dictionary.createdAt,\n updatedAt: dictionary.updatedAt,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: err.issues[0]?.message ?? 'Validation failed' }, { status: 400 })\n }\n console.error('[dictionaries/:id.PATCH] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to update dictionary' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { dictionaryId?: string } }) {\n try {\n const context = await resolveDictionariesRouteContext(req)\n const { dictionaryId } = paramsSchema.parse({ dictionaryId: ctx.params?.dictionaryId })\n const dictionary = await loadDictionary(context, dictionaryId)\n\n if (isProtectedCurrencyDictionary(dictionary)) {\n throw new CrudHttpError(400, { error: context.translate('dictionaries.errors.currency_protected', 'The currency dictionary cannot be modified or deleted.') })\n }\n\n dictionary.isActive = false\n dictionary.deletedAt = dictionary.deletedAt ?? new Date()\n await context.em.flush()\n\n return NextResponse.json({ ok: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('[dictionaries/:id.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Failed to delete dictionary' }, { status: 500 })\n }\n}\n\nconst dictionaryGetDoc: OpenApiMethodDoc = {\n summary: 'Get dictionary',\n description: 'Returns details for the specified dictionary, including inheritance flags.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary details.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid parameters', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to load dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryPatchDoc: OpenApiMethodDoc = {\n summary: 'Update dictionary',\n description: 'Updates mutable attributes of the dictionary. Currency dictionaries are protected from modification.',\n tags: [dictionariesTag],\n requestBody: {\n contentType: 'application/json',\n schema: dictionaryUpdateSchema,\n description: 'Fields to update on the dictionary.',\n },\n responses: [\n { status: 200, description: 'Dictionary updated.', schema: dictionaryDetailSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or protected dictionary', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 409, description: 'Dictionary key already exists', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to update dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nconst dictionaryDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete dictionary',\n description: 'Soft deletes the dictionary unless it is the protected currency dictionary.',\n tags: [dictionariesTag],\n responses: [\n { status: 200, description: 'Dictionary archived.', schema: dictionariesOkSchema },\n ],\n errors: [\n { status: 400, description: 'Protected dictionary cannot be deleted', schema: dictionariesErrorSchema },\n { status: 401, description: 'Authentication required', schema: dictionariesErrorSchema },\n { status: 404, description: 'Dictionary not found', schema: dictionariesErrorSchema },\n { status: 500, description: 'Failed to delete dictionary', schema: dictionariesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dictionariesTag,\n summary: 'Dictionary resource',\n pathParams: dictionaryIdParamsSchema,\n methods: {\n GET: dictionaryGetDoc,\n PATCH: dictionaryPatchDoc,\n DELETE: dictionaryDeleteDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -15,7 +15,9 @@ import {
15
15
  } from "@open-mercato/ui/primitives/select";
16
16
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
17
17
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
18
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
18
+ import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
19
+ import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
20
+ import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
19
21
  import { useT } from "@open-mercato/shared/lib/i18n/context";
20
22
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
21
23
  import { DictionaryEntriesEditor } from "./DictionaryEntriesEditor.js";
@@ -69,7 +71,8 @@ function DictionariesManager() {
69
71
  entrySortMode: dictionaryEntrySortModes.includes(item.entrySortMode) ? item.entrySortMode : DEFAULT_DICTIONARY_ENTRY_SORT_MODE,
70
72
  organizationId: typeof item.organizationId === "string" ? item.organizationId : "",
71
73
  isInherited: item.isInherited === true,
72
- managerVisibility: item.managerVisibility === "hidden" ? "hidden" : "default"
74
+ managerVisibility: item.managerVisibility === "hidden" ? "hidden" : "default",
75
+ updatedAt: typeof item.updatedAt === "string" ? item.updatedAt : null
73
76
  })) : [];
74
77
  const filtered = list.filter((dictionary) => dictionary.managerVisibility !== "hidden");
75
78
  setItems(filtered);
@@ -183,13 +186,19 @@ function DictionariesManager() {
183
186
  }
184
187
  flash(t("dictionaries.config.success.create", "Dictionary created."), "success");
185
188
  } else if (dialog.dictionary) {
186
- const call = await apiCall(`/api/dictionaries/${dialog.dictionary.id}`, {
187
- method: "PATCH",
188
- headers: { "content-type": "application/json" },
189
- body: JSON.stringify(payload)
190
- });
189
+ const call = await withScopedApiRequestHeaders(
190
+ buildOptimisticLockHeader(dialog.dictionary.updatedAt),
191
+ () => apiCall(`/api/dictionaries/${dialog.dictionary.id}`, {
192
+ method: "PATCH",
193
+ headers: { "content-type": "application/json" },
194
+ body: JSON.stringify(payload)
195
+ })
196
+ );
191
197
  if (!call.ok) {
192
- throw new Error(typeof call.result?.error === "string" ? call.result.error : "Failed to update dictionary");
198
+ throw Object.assign(
199
+ new Error(typeof call.result?.error === "string" ? call.result.error : "Failed to update dictionary"),
200
+ { status: call.status, ...call.result && typeof call.result === "object" ? call.result : {} }
201
+ );
193
202
  }
194
203
  flash(t("dictionaries.config.success.update", "Dictionary updated."), "success");
195
204
  }
@@ -197,6 +206,9 @@ function DictionariesManager() {
197
206
  await loadDictionaries();
198
207
  setErrors({});
199
208
  } catch (err) {
209
+ if (surfaceRecordConflict(err, t)) {
210
+ return;
211
+ }
200
212
  console.error("Failed to save dictionary", err);
201
213
  flash(t("dictionaries.config.error.save", "Failed to save dictionary."), "error");
202
214
  } finally {
@@ -222,13 +234,22 @@ function DictionariesManager() {
222
234
  if (!confirmed) return;
223
235
  setDeleting(dictionary.id);
224
236
  try {
225
- const call = await apiCall(`/api/dictionaries/${dictionary.id}`, { method: "DELETE" });
237
+ const call = await withScopedApiRequestHeaders(
238
+ buildOptimisticLockHeader(dictionary.updatedAt),
239
+ () => apiCall(`/api/dictionaries/${dictionary.id}`, { method: "DELETE" })
240
+ );
226
241
  if (!call.ok) {
227
- throw new Error(typeof call.result?.error === "string" ? call.result.error : "Failed to delete dictionary");
242
+ throw Object.assign(
243
+ new Error(typeof call.result?.error === "string" ? call.result.error : "Failed to delete dictionary"),
244
+ { status: call.status, ...call.result && typeof call.result === "object" ? call.result : {} }
245
+ );
228
246
  }
229
247
  flash(t("dictionaries.config.success.delete", "Dictionary deleted."), "success");
230
248
  await loadDictionaries();
231
249
  } catch (err) {
250
+ if (surfaceRecordConflict(err, t)) {
251
+ return;
252
+ }
232
253
  console.error("Failed to delete dictionary", err);
233
254
  flash(t("dictionaries.config.error.delete", "Failed to delete dictionary."), "error");
234
255
  } finally {