@open-mercato/core 0.6.6-develop.6094.1.28b081ea16 → 0.6.6-develop.6126.1.0b9392fdae

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 (317) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/catalog_product/index.js +52 -0
  3. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  4. package/dist/generated/entities/catalog_product_variant/index.js +4 -0
  5. package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +28 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/api/products/route.js +103 -33
  9. package/dist/modules/catalog/api/products/route.js.map +2 -2
  10. package/dist/modules/catalog/api/variants/route.js +4 -0
  11. package/dist/modules/catalog/api/variants/route.js.map +2 -2
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
  17. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  18. package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
  19. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  20. package/dist/modules/catalog/commands/products.js +251 -14
  21. package/dist/modules/catalog/commands/products.js.map +2 -2
  22. package/dist/modules/catalog/commands/variants.js +72 -1
  23. package/dist/modules/catalog/commands/variants.js.map +2 -2
  24. package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
  25. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
  26. package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
  27. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  28. package/dist/modules/catalog/components/products/productForm.js +164 -2
  29. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  30. package/dist/modules/catalog/components/products/variantForm.js +2 -0
  31. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  32. package/dist/modules/catalog/data/entities.js +93 -0
  33. package/dist/modules/catalog/data/entities.js.map +2 -2
  34. package/dist/modules/catalog/data/types.js +28 -0
  35. package/dist/modules/catalog/data/types.js.map +2 -2
  36. package/dist/modules/catalog/data/validators.js +100 -5
  37. package/dist/modules/catalog/data/validators.js.map +2 -2
  38. package/dist/modules/catalog/lib/gtin.js +61 -0
  39. package/dist/modules/catalog/lib/gtin.js.map +7 -0
  40. package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
  41. package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
  42. package/dist/modules/catalog/translations.js +1 -1
  43. package/dist/modules/catalog/translations.js.map +2 -2
  44. package/dist/modules/configs/api/cache/route.js +60 -10
  45. package/dist/modules/configs/api/cache/route.js.map +3 -3
  46. package/dist/modules/configs/components/CachePanel.js +52 -29
  47. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  48. package/dist/modules/currencies/api/currencies/options/route.js +18 -4
  49. package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
  50. package/dist/modules/currencies/services/rateFetchingService.js +35 -18
  51. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
  55. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  56. package/dist/modules/customers/backend/calendar/page.js +10 -0
  57. package/dist/modules/customers/backend/calendar/page.js.map +7 -0
  58. package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
  59. package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
  60. package/dist/modules/customers/cli.js +1 -0
  61. package/dist/modules/customers/cli.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
  63. package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
  65. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
  66. package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
  67. package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
  68. package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
  69. package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
  70. package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
  71. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
  72. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
  73. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
  74. package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
  75. package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
  76. package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
  77. package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
  78. package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
  79. package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
  80. package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
  81. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
  82. package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
  83. package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
  85. package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
  87. package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
  88. package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
  89. package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
  90. package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
  91. package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
  92. package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
  93. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
  94. package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
  95. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
  96. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
  97. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
  98. package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
  99. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
  100. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
  101. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
  102. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
  103. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
  104. package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
  105. package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
  106. package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
  107. package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
  108. package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
  109. package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
  110. package/dist/modules/customers/components/calendar/types.js +30 -0
  111. package/dist/modules/customers/components/calendar/types.js.map +7 -0
  112. package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
  113. package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
  114. package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
  115. package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/categories.js +28 -0
  117. package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
  119. package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
  120. package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
  121. package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
  122. package/dist/modules/customers/lib/calendar/grid.js +48 -0
  123. package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
  124. package/dist/modules/customers/lib/calendar/layout.js +39 -0
  125. package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
  126. package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
  127. package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
  128. package/dist/modules/customers/lib/calendar/preferences.js +71 -0
  129. package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
  130. package/dist/modules/customers/lib/calendar/range.js +46 -0
  131. package/dist/modules/customers/lib/calendar/range.js.map +7 -0
  132. package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
  133. package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
  134. package/dist/modules/dashboards/services/widgetDataService.js +5 -5
  135. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  136. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
  137. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  138. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
  139. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  140. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
  141. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  142. package/dist/modules/dictionaries/api/route.js +33 -1
  143. package/dist/modules/dictionaries/api/route.js.map +2 -2
  144. package/dist/modules/feature_toggles/lib/queries.js +36 -52
  145. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  146. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
  147. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
  148. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
  149. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
  150. package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
  151. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  152. package/dist/modules/perspectives/services/perspectiveService.js +5 -3
  153. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  154. package/dist/modules/planner/api/access.js +9 -0
  155. package/dist/modules/planner/api/access.js.map +2 -2
  156. package/dist/modules/planner/api/availability-date-specific.js +22 -1
  157. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  158. package/dist/modules/planner/api/availability-weekly.js +22 -1
  159. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  160. package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
  161. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  162. package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
  163. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  164. package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
  165. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  166. package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
  167. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  168. package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
  169. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  170. package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
  171. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  172. package/dist/modules/staff/api/team-members/self/route.js +40 -0
  173. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  174. package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
  175. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  176. package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
  177. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  178. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
  179. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  180. package/dist/modules/translations/api/context.js +7 -0
  181. package/dist/modules/translations/api/context.js.map +2 -2
  182. package/dist/modules/translations/api/put/locales.js +33 -1
  183. package/dist/modules/translations/api/put/locales.js.map +2 -2
  184. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
  185. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  186. package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
  187. package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
  188. package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
  189. package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
  190. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
  191. package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
  192. package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
  193. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  194. package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
  195. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
  196. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
  197. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
  198. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  199. package/dist/modules/workflows/lib/graph-utils.js +8 -0
  200. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  201. package/generated/entities/catalog_product/index.ts +26 -0
  202. package/generated/entities/catalog_product_variant/index.ts +2 -0
  203. package/generated/entity-fields-registry.ts +28 -0
  204. package/package.json +7 -7
  205. package/src/modules/catalog/api/products/route.ts +123 -39
  206. package/src/modules/catalog/api/variants/route.ts +4 -0
  207. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
  208. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
  209. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
  210. package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
  211. package/src/modules/catalog/commands/products.ts +307 -2
  212. package/src/modules/catalog/commands/variants.ts +90 -1
  213. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
  214. package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
  215. package/src/modules/catalog/components/products/productForm.ts +262 -0
  216. package/src/modules/catalog/components/products/variantForm.ts +4 -0
  217. package/src/modules/catalog/data/entities.ts +97 -0
  218. package/src/modules/catalog/data/types.ts +36 -0
  219. package/src/modules/catalog/data/validators.ts +159 -3
  220. package/src/modules/catalog/i18n/de.json +64 -0
  221. package/src/modules/catalog/i18n/en.json +64 -0
  222. package/src/modules/catalog/i18n/es.json +64 -0
  223. package/src/modules/catalog/i18n/pl.json +64 -0
  224. package/src/modules/catalog/lib/gtin.ts +66 -0
  225. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
  226. package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
  227. package/src/modules/catalog/translations.ts +1 -1
  228. package/src/modules/configs/api/cache/route.ts +72 -10
  229. package/src/modules/configs/components/CachePanel.tsx +59 -29
  230. package/src/modules/currencies/api/currencies/options/route.ts +22 -4
  231. package/src/modules/currencies/services/rateFetchingService.ts +63 -26
  232. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
  233. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  234. package/src/modules/customer_accounts/i18n/de.json +1 -1
  235. package/src/modules/customer_accounts/i18n/en.json +1 -1
  236. package/src/modules/customer_accounts/i18n/es.json +1 -1
  237. package/src/modules/customer_accounts/i18n/pl.json +1 -1
  238. package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
  239. package/src/modules/customers/backend/calendar/page.tsx +12 -0
  240. package/src/modules/customers/cli.ts +1 -0
  241. package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
  242. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
  243. package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
  244. package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
  245. package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
  246. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
  247. package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
  248. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
  249. package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
  250. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
  251. package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
  252. package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
  253. package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
  254. package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
  255. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
  256. package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
  257. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
  258. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
  259. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
  260. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
  261. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
  262. package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
  263. package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
  264. package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
  265. package/src/modules/customers/components/calendar/types.ts +163 -0
  266. package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
  267. package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
  268. package/src/modules/customers/i18n/de.json +195 -0
  269. package/src/modules/customers/i18n/en.json +195 -0
  270. package/src/modules/customers/i18n/es.json +195 -0
  271. package/src/modules/customers/i18n/pl.json +195 -0
  272. package/src/modules/customers/lib/calendar/categories.ts +26 -0
  273. package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
  274. package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
  275. package/src/modules/customers/lib/calendar/grid.ts +50 -0
  276. package/src/modules/customers/lib/calendar/layout.ts +47 -0
  277. package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
  278. package/src/modules/customers/lib/calendar/preferences.ts +95 -0
  279. package/src/modules/customers/lib/calendar/range.ts +45 -0
  280. package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
  281. package/src/modules/dashboards/services/widgetDataService.ts +8 -6
  282. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
  283. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
  284. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
  285. package/src/modules/dictionaries/api/route.ts +36 -1
  286. package/src/modules/feature_toggles/lib/queries.ts +41 -60
  287. package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
  288. package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
  289. package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
  290. package/src/modules/perspectives/services/perspectiveService.ts +9 -5
  291. package/src/modules/planner/api/access.ts +9 -0
  292. package/src/modules/planner/api/availability-date-specific.ts +22 -1
  293. package/src/modules/planner/api/availability-weekly.ts +22 -1
  294. package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
  295. package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
  296. package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
  297. package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
  298. package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
  299. package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
  300. package/src/modules/staff/api/team-members/self/route.ts +43 -0
  301. package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
  302. package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
  303. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
  304. package/src/modules/translations/api/context.ts +7 -0
  305. package/src/modules/translations/api/put/locales.ts +35 -1
  306. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
  307. package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
  308. package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
  309. package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
  310. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
  311. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
  312. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
  313. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
  314. package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
  315. package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
  316. package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
  317. package/src/modules/workflows/lib/graph-utils.ts +21 -1
@@ -4,6 +4,10 @@ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
4
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
5
  import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
6
6
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import {
8
+ runCrudMutationGuardAfterSuccess,
9
+ validateCrudMutationGuard
10
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
7
11
  import { perspectiveSaveSchema } from "@open-mercato/core/modules/perspectives/data/validators";
8
12
  import {
9
13
  loadPerspectivesState,
@@ -127,6 +131,7 @@ async function POST(req, ctx) {
127
131
  const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0);
128
132
  const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0);
129
133
  const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0;
134
+ const targetRoleIds = Array.from(/* @__PURE__ */ new Set([...applyToRoles, ...clearRoleIds]));
130
135
  if (hasRoleOps) {
131
136
  const canApplyToRoles = await rbac.userHasAllFeatures?.(
132
137
  auth.sub,
@@ -137,7 +142,6 @@ async function POST(req, ctx) {
137
142
  return NextResponse.json({ error: "Forbidden", requiredFeatures: ["perspectives.role_defaults"] }, { status: 403 });
138
143
  }
139
144
  const roleScope = auth.tenantId ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] } : { tenantId: null };
140
- const targetRoleIds = Array.from(/* @__PURE__ */ new Set([...applyToRoles, ...clearRoleIds]));
141
145
  const roles = await em.find(Role, {
142
146
  id: { $in: targetRoleIds },
143
147
  ...roleScope,
@@ -149,8 +153,48 @@ async function POST(req, ctx) {
149
153
  return NextResponse.json({ error: "Invalid roles", missing }, { status: 400 });
150
154
  }
151
155
  }
156
+ const guardResourceId = parsed.data.perspectiveId ?? tableId;
157
+ const guardResult = await validateCrudMutationGuard(container, {
158
+ tenantId: auth.tenantId ?? "",
159
+ organizationId: auth.orgId ?? null,
160
+ userId: auth.sub,
161
+ resourceKind: "perspectives.perspective",
162
+ resourceId: guardResourceId,
163
+ operation: "custom",
164
+ requestMethod: req.method,
165
+ requestHeaders: req.headers,
166
+ mutationPayload: { ...parsed.data, tableId }
167
+ });
168
+ if (guardResult && !guardResult.ok) {
169
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
170
+ }
171
+ let roleGuardResult = null;
172
+ if (hasRoleOps) {
173
+ roleGuardResult = await validateCrudMutationGuard(container, {
174
+ tenantId: auth.tenantId ?? "",
175
+ organizationId: auth.orgId ?? null,
176
+ userId: auth.sub,
177
+ resourceKind: "perspectives.role_perspective",
178
+ resourceId: targetRoleIds.join(","),
179
+ operation: "custom",
180
+ requestMethod: req.method,
181
+ requestHeaders: req.headers,
182
+ mutationPayload: {
183
+ tableId,
184
+ applyToRoles,
185
+ clearRoleIds,
186
+ name: parsed.data.name,
187
+ settings: parsed.data.settings,
188
+ setRoleDefault: parsed.data.setRoleDefault ?? false
189
+ }
190
+ });
191
+ }
192
+ if (roleGuardResult && !roleGuardResult.ok) {
193
+ return NextResponse.json(roleGuardResult.body, { status: roleGuardResult.status });
194
+ }
152
195
  let saved = null;
153
196
  let updatedRolePerspectives = null;
197
+ let clearedRolePerspectiveCount = 0;
154
198
  try {
155
199
  await withAtomicFlush(em, [
156
200
  async () => {
@@ -178,7 +222,7 @@ async function POST(req, ctx) {
178
222
  },
179
223
  async () => {
180
224
  if (clearRoleIds.length) {
181
- await clearRolePerspectives(em, cache, {
225
+ clearedRolePerspectiveCount = await clearRolePerspectives(em, cache, {
182
226
  tableId,
183
227
  tenantId: auth.tenantId ?? null,
184
228
  organizationId: auth.orgId ?? null,
@@ -193,6 +237,33 @@ async function POST(req, ctx) {
193
237
  }
194
238
  throw err;
195
239
  }
240
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
241
+ await runCrudMutationGuardAfterSuccess(container, {
242
+ tenantId: auth.tenantId ?? "",
243
+ organizationId: auth.orgId ?? null,
244
+ userId: auth.sub,
245
+ resourceKind: "perspectives.perspective",
246
+ resourceId: guardResourceId,
247
+ operation: "custom",
248
+ requestMethod: req.method,
249
+ requestHeaders: req.headers,
250
+ metadata: guardResult.metadata ?? null
251
+ });
252
+ }
253
+ const didWriteRolePerspectives = applyToRoles.length > 0 || clearedRolePerspectiveCount > 0;
254
+ if (didWriteRolePerspectives && roleGuardResult?.ok && roleGuardResult.shouldRunAfterSuccess) {
255
+ await runCrudMutationGuardAfterSuccess(container, {
256
+ tenantId: auth.tenantId ?? "",
257
+ organizationId: auth.orgId ?? null,
258
+ userId: auth.sub,
259
+ resourceKind: "perspectives.role_perspective",
260
+ resourceId: targetRoleIds.join(","),
261
+ operation: "custom",
262
+ requestMethod: req.method,
263
+ requestHeaders: req.headers,
264
+ metadata: roleGuardResult.metadata ?? null
265
+ });
266
+ }
196
267
  return NextResponse.json({
197
268
  perspective: saved,
198
269
  rolePerspectives: updatedRolePerspectives ?? [],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/perspectives/api/%5BtableId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'\nimport {\n loadPerspectivesState,\n saveUserPerspective,\n saveRolePerspectives,\n clearRolePerspectives,\n type PerspectiveScope,\n} from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n perspectivesTag,\n perspectivesErrorSchema,\n perspectivesIndexResponseSchema,\n perspectiveSaveResponseSchema,\n} from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n POST: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nfunction buildScope(auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>): PerspectiveScope {\n return {\n userId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n\nexport async function GET(_req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(_req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const assignedRoleNames = Array.isArray(auth.roles)\n ? Array.from(new Set(auth.roles.filter((role): role is string => typeof role === 'string' && role.trim().length > 0)))\n : []\n const assignedRoles = assignedRoleNames.length\n ? await em.find(Role, {\n name: { $in: assignedRoleNames as any },\n deletedAt: null,\n } as any, { orderBy: { name: 'asc' } })\n : []\n const assignedRoleIds = assignedRoles.map((role) => role.id)\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await em.find(Role, { ...roleScope as any, deletedAt: null } as any, { orderBy: { name: 'asc' } })\n : assignedRoles\n\n const state = await loadPerspectivesState(em, cache, {\n scope: buildScope(auth),\n tableId,\n roleIds: assignedRoleIds,\n })\n\n const rolePerspectiveByRole = new Map<string, { hasDefault: boolean; count: number }>()\n for (const item of state.rolePerspectives) {\n const entry = rolePerspectiveByRole.get(item.roleId) ?? { hasDefault: false, count: 0 }\n entry.count += 1\n entry.hasDefault = entry.hasDefault || item.isDefault\n rolePerspectiveByRole.set(item.roleId, entry)\n }\n\n return NextResponse.json({\n tableId,\n perspectives: state.personal,\n defaultPerspectiveId: state.personalDefaultId,\n rolePerspectives: state.rolePerspectives.map((rp) => ({\n ...rp,\n roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null,\n })),\n roles: availableRoles.map((role) => {\n const stats = rolePerspectiveByRole.get(role.id)\n return {\n id: role.id,\n name: role.name,\n hasPerspective: Boolean(stats?.count),\n hasDefault: Boolean(stats?.hasDefault),\n }\n }),\n canApplyToRoles,\n })\n}\n\nexport async function POST(req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = perspectiveSaveSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const scope = buildScope(auth)\n\n const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)\n const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)\n const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0\n\n if (hasRoleOps) {\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: ['perspectives.role_defaults'] }, { status: 403 })\n }\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))\n const roles = await em.find(Role, {\n id: { $in: targetRoleIds as any },\n ...(roleScope as any),\n deletedAt: null,\n } as any)\n const validRoleIds = new Set(roles.map((role) => role.id))\n\n const missing = targetRoleIds.filter((id) => !validRoleIds.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n }\n\n let saved: Awaited<ReturnType<typeof saveUserPerspective>> | null = null\n let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null\n\n try {\n await withAtomicFlush(em, [\n async () => {\n saved = await saveUserPerspective(em, cache, {\n scope,\n tableId,\n input: parsed.data,\n request: req,\n })\n },\n async () => {\n if (applyToRoles.length) {\n updatedRolePerspectives = await saveRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n input: {\n roleIds: applyToRoles,\n name: parsed.data.name,\n settings: parsed.data.settings,\n setDefault: parsed.data.setRoleDefault ?? false,\n },\n })\n }\n },\n async () => {\n if (clearRoleIds.length) {\n await clearRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n roleIds: clearRoleIds,\n })\n }\n },\n ], { transaction: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n return NextResponse.json({\n perspective: saved,\n rolePerspectives: updatedRolePerspectives ?? [],\n clearedRoleIds: clearRoleIds ?? [],\n })\n}\n\nconst perspectivePathParamsSchema = z.object({\n tableId: z.string().min(1),\n})\n\nconst perspectivesGetDoc: OpenApiMethodDoc = {\n summary: 'Load perspectives for a table',\n description: 'Returns personal perspectives and available role defaults for the requested table identifier.',\n tags: [perspectivesTag],\n responses: [\n { status: 200, description: 'Current perspectives and defaults.', schema: perspectivesIndexResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid table identifier', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n ],\n}\n\nconst perspectivesPostDoc: OpenApiMethodDoc = {\n summary: 'Create or update a perspective',\n description: 'Saves a personal perspective and optionally applies the same configuration to selected roles.',\n tags: [perspectivesTag],\n requestBody: {\n contentType: 'application/json',\n schema: perspectiveSaveSchema,\n description: 'Perspective payload including optional role defaults.',\n },\n responses: [\n { status: 200, description: 'Perspective saved successfully.', schema: perspectiveSaveResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or invalid roles provided', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 403, description: 'Missing perspectives.role_defaults feature for role updates', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Manage table perspectives',\n pathParams: perspectivePathParamsSchema,\n methods: {\n GET: perspectivesGetDoc,\n POST: perspectivesPostDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { perspectiveSaveSchema } from '@open-mercato/core/modules/perspectives/data/validators'\nimport {\n loadPerspectivesState,\n saveUserPerspective,\n saveRolePerspectives,\n clearRolePerspectives,\n type PerspectiveScope,\n} from '@open-mercato/core/modules/perspectives/services/perspectiveService'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n perspectivesTag,\n perspectivesErrorSchema,\n perspectivesIndexResponseSchema,\n perspectiveSaveResponseSchema,\n} from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n POST: { requireAuth: true, requireFeatures: ['perspectives.use'] },\n}\n\nconst decodeParam = (value: string | string[] | undefined): string => {\n if (!value) return ''\n const raw = Array.isArray(value) ? value[0] : value\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n}\n\nfunction buildScope(auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>): PerspectiveScope {\n return {\n userId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n\nexport async function GET(_req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(_req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const assignedRoleNames = Array.isArray(auth.roles)\n ? Array.from(new Set(auth.roles.filter((role): role is string => typeof role === 'string' && role.trim().length > 0)))\n : []\n const assignedRoles = assignedRoleNames.length\n ? await em.find(Role, {\n name: { $in: assignedRoleNames as any },\n deletedAt: null,\n } as any, { orderBy: { name: 'asc' } })\n : []\n const assignedRoleIds = assignedRoles.map((role) => role.id)\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await em.find(Role, { ...roleScope as any, deletedAt: null } as any, { orderBy: { name: 'asc' } })\n : assignedRoles\n\n const state = await loadPerspectivesState(em, cache, {\n scope: buildScope(auth),\n tableId,\n roleIds: assignedRoleIds,\n })\n\n const rolePerspectiveByRole = new Map<string, { hasDefault: boolean; count: number }>()\n for (const item of state.rolePerspectives) {\n const entry = rolePerspectiveByRole.get(item.roleId) ?? { hasDefault: false, count: 0 }\n entry.count += 1\n entry.hasDefault = entry.hasDefault || item.isDefault\n rolePerspectiveByRole.set(item.roleId, entry)\n }\n\n return NextResponse.json({\n tableId,\n perspectives: state.personal,\n defaultPerspectiveId: state.personalDefaultId,\n rolePerspectives: state.rolePerspectives.map((rp) => ({\n ...rp,\n roleName: availableRoles.find((role) => role.id === rp.roleId)?.name ?? assignedRoles.find((role) => role.id === rp.roleId)?.name ?? null,\n })),\n roles: availableRoles.map((role) => {\n const stats = rolePerspectiveByRole.get(role.id)\n return {\n id: role.id,\n name: role.name,\n hasPerspective: Boolean(stats?.count),\n hasDefault: Boolean(stats?.hasDefault),\n }\n }),\n canApplyToRoles,\n })\n}\n\nexport async function POST(req: Request, ctx: { params: { tableId: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const tableId = decodeParam(ctx.params?.tableId).trim()\n if (!tableId) return NextResponse.json({ error: 'Invalid table id' }, { status: 400 })\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = perspectiveSaveSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n const cache = ((): import('@open-mercato/cache').CacheStrategy | null => {\n try {\n return container.resolve('cache') as import('@open-mercato/cache').CacheStrategy\n } catch {\n return null\n }\n })()\n const rbac = container.resolve('rbacService') as {\n userHasAllFeatures?: (\n userId: string,\n features: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ) => Promise<boolean>\n }\n\n const scope = buildScope(auth)\n\n const applyToRoles = Array.from(new Set(parsed.data.applyToRoles ?? [])).filter((id) => id.trim().length > 0)\n const clearRoleIds = Array.from(new Set(parsed.data.clearRoleIds ?? [])).filter((id) => id.trim().length > 0)\n const hasRoleOps = applyToRoles.length > 0 || clearRoleIds.length > 0\n const targetRoleIds = Array.from(new Set([...applyToRoles, ...clearRoleIds]))\n\n if (hasRoleOps) {\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['perspectives.role_defaults'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: ['perspectives.role_defaults'] }, { status: 403 })\n }\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const roles = await em.find(Role, {\n id: { $in: targetRoleIds as any },\n ...(roleScope as any),\n deletedAt: null,\n } as any)\n const validRoleIds = new Set(roles.map((role) => role.id))\n\n const missing = targetRoleIds.filter((id) => !validRoleIds.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n }\n\n const guardResourceId = parsed.data.perspectiveId ?? tableId\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.perspective',\n resourceId: guardResourceId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { ...parsed.data, tableId },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let roleGuardResult: Awaited<ReturnType<typeof validateCrudMutationGuard>> | null = null\n if (hasRoleOps) {\n roleGuardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.role_perspective',\n resourceId: targetRoleIds.join(','),\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: {\n tableId,\n applyToRoles,\n clearRoleIds,\n name: parsed.data.name,\n settings: parsed.data.settings,\n setRoleDefault: parsed.data.setRoleDefault ?? false,\n },\n })\n }\n if (roleGuardResult && !roleGuardResult.ok) {\n return NextResponse.json(roleGuardResult.body, { status: roleGuardResult.status })\n }\n\n let saved: Awaited<ReturnType<typeof saveUserPerspective>> | null = null\n let updatedRolePerspectives: Awaited<ReturnType<typeof saveRolePerspectives>> | null = null\n let clearedRolePerspectiveCount = 0\n\n try {\n await withAtomicFlush(em, [\n async () => {\n saved = await saveUserPerspective(em, cache, {\n scope,\n tableId,\n input: parsed.data,\n request: req,\n })\n },\n async () => {\n if (applyToRoles.length) {\n updatedRolePerspectives = await saveRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n input: {\n roleIds: applyToRoles,\n name: parsed.data.name,\n settings: parsed.data.settings,\n setDefault: parsed.data.setRoleDefault ?? false,\n },\n })\n }\n },\n async () => {\n if (clearRoleIds.length) {\n clearedRolePerspectiveCount = await clearRolePerspectives(em, cache, {\n tableId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n roleIds: clearRoleIds,\n })\n }\n },\n ], { transaction: true })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.perspective',\n resourceId: guardResourceId,\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const didWriteRolePerspectives = applyToRoles.length > 0 || clearedRolePerspectiveCount > 0\n if (didWriteRolePerspectives && roleGuardResult?.ok && roleGuardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n resourceKind: 'perspectives.role_perspective',\n resourceId: targetRoleIds.join(','),\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: roleGuardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({\n perspective: saved,\n rolePerspectives: updatedRolePerspectives ?? [],\n clearedRoleIds: clearRoleIds ?? [],\n })\n}\n\nconst perspectivePathParamsSchema = z.object({\n tableId: z.string().min(1),\n})\n\nconst perspectivesGetDoc: OpenApiMethodDoc = {\n summary: 'Load perspectives for a table',\n description: 'Returns personal perspectives and available role defaults for the requested table identifier.',\n tags: [perspectivesTag],\n responses: [\n { status: 200, description: 'Current perspectives and defaults.', schema: perspectivesIndexResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid table identifier', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n ],\n}\n\nconst perspectivesPostDoc: OpenApiMethodDoc = {\n summary: 'Create or update a perspective',\n description: 'Saves a personal perspective and optionally applies the same configuration to selected roles.',\n tags: [perspectivesTag],\n requestBody: {\n contentType: 'application/json',\n schema: perspectiveSaveSchema,\n description: 'Perspective payload including optional role defaults.',\n },\n responses: [\n { status: 200, description: 'Perspective saved successfully.', schema: perspectiveSaveResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed or invalid roles provided', schema: perspectivesErrorSchema },\n { status: 401, description: 'Authentication required', schema: perspectivesErrorSchema },\n { status: 403, description: 'Missing perspectives.role_defaults feature for role updates', schema: perspectivesErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: perspectivesTag,\n summary: 'Manage table perspectives',\n pathParams: perspectivePathParamsSchema,\n methods: {\n GET: perspectivesGetDoc,\n POST: perspectivesPostDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -202,13 +202,14 @@ async function deleteUserPerspective(em, cache, options) {
202
202
  tableId,
203
203
  deletedAt: null
204
204
  });
205
- if (!existing) return;
205
+ if (!existing) return false;
206
206
  existing.deletedAt = /* @__PURE__ */ new Date();
207
207
  existing.isDefault = false;
208
208
  await em.flush();
209
209
  if (cache?.deleteByTags) {
210
210
  await cache.deleteByTags([userTag(scope, tableId)]);
211
211
  }
212
+ return true;
212
213
  }
213
214
  async function saveRolePerspectives(em, cache, options) {
214
215
  const { tableId, input } = options;
@@ -282,8 +283,8 @@ async function clearRolePerspectives(em, cache, options) {
282
283
  const { tableId, roleIds } = options;
283
284
  const tenantId = options.tenantId ?? null;
284
285
  const organizationId = options.organizationId ?? null;
285
- if (!roleIds.length) return;
286
- await em.nativeUpdate(
286
+ if (!roleIds.length) return 0;
287
+ const affected = await em.nativeUpdate(
287
288
  RolePerspective,
288
289
  {
289
290
  roleId: { $in: roleIds },
@@ -298,6 +299,7 @@ async function clearRolePerspectives(em, cache, options) {
298
299
  const tags = roleIds.map((roleId) => roleTag(roleId, tableId, tenantId));
299
300
  await cache.deleteByTags(tags);
300
301
  }
302
+ return affected;
301
303
  }
302
304
  export {
303
305
  clearRolePerspectives,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/perspectives/services/perspectiveService.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { Perspective, RolePerspective } from '../data/entities'\nimport type {\n PerspectiveSettings,\n PerspectiveSaveInput,\n RolePerspectiveSaveInput,\n} from '../data/validators'\n\nexport type PerspectiveScope = {\n userId: string\n tenantId?: string | null\n organizationId?: string | null\n}\n\nexport type ResolvedPerspective = {\n id: string\n name: string\n tableId: string\n settings: PerspectiveSettings\n isDefault: boolean\n createdAt: string\n updatedAt?: string | null\n}\n\nexport type ResolvedRolePerspective = {\n id: string\n roleId: string\n tableId: string\n name: string\n settings: PerspectiveSettings\n isDefault: boolean\n tenantId: string | null\n organizationId: string | null\n createdAt: string\n updatedAt?: string | null\n}\n\nexport type PerspectivesState = {\n tableId: string\n personal: ResolvedPerspective[]\n personalDefaultId: string | null\n rolePerspectives: ResolvedRolePerspective[]\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000\n\nconst nullish = <T extends string | null | undefined>(value: T): string | null =>\n value == null ? null : value\n\nconst scopeKey = (scope: PerspectiveScope) =>\n `${scope.userId}:${scope.tenantId ?? 'null'}:${scope.organizationId ?? 'null'}`\n\nconst userCacheKey = (scope: PerspectiveScope, tableId: string, roleIds: string[]) =>\n `perspectives:user-state:${scopeKey(scope)}:${tableId}:${roleIds.sort((a, b) => a.localeCompare(b)).join(',')}`\n\nconst userTag = (scope: PerspectiveScope, tableId?: string) =>\n tableId\n ? `perspectives:user:${scopeKey(scope)}:${tableId}`\n : `perspectives:user:${scopeKey(scope)}`\n\nconst roleTag = (roleId: string, tableId?: string, tenantId?: string | null) => {\n const tenant = tenantId ?? 'null'\n return tableId ? `perspectives:role:${roleId}:${tenant}:${tableId}` : `perspectives:role:${roleId}:${tenant}`\n}\n\nfunction isResolvedPerspective(value: unknown): value is ResolvedPerspective {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<ResolvedPerspective>\n return typeof record.id === 'string'\n && typeof record.name === 'string'\n && typeof record.tableId === 'string'\n && typeof record.isDefault === 'boolean'\n && typeof record.createdAt === 'string'\n}\n\nfunction isResolvedRolePerspective(value: unknown): value is ResolvedRolePerspective {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<ResolvedRolePerspective>\n return typeof record.id === 'string'\n && typeof record.roleId === 'string'\n && typeof record.tableId === 'string'\n && typeof record.name === 'string'\n && typeof record.isDefault === 'boolean'\n && typeof record.createdAt === 'string'\n}\n\nfunction isPerspectivesState(value: unknown): value is PerspectivesState {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<PerspectivesState>\n if (typeof record.tableId !== 'string') return false\n if (!Array.isArray(record.personal) || record.personal.some((item) => !isResolvedPerspective(item))) return false\n if (record.personalDefaultId !== null && typeof record.personalDefaultId !== 'string') return false\n if (!Array.isArray(record.rolePerspectives) || record.rolePerspectives.some((item) => !isResolvedRolePerspective(item))) return false\n return true\n}\n\n/**\n * Defensive migration for legacy filter state shapes captured before the\n * advanced-filter tree (SPEC-048). Existing perspectives only store either\n * advanced-filter URL params (tree shape with `v:2` or a `root` key) or\n * undefined \u2014 this helper is a safety net for legacy `FilterValues`-shaped\n * records (flat key/value records of column filters) that could only appear\n * if old saved-view JSON were imported.\n *\n * - Tree-shaped state (`v:2` or `root` key) is passed through unchanged.\n * - Undefined / null filters are passed through unchanged.\n * - Legacy `FilterValues`-shaped records are dropped (set to `undefined`)\n * because there is no reliable mapping back to the new operator model;\n * the user sees an empty tree and can recreate.\n */\nexport function maybeMigrateLegacyFilterValues(settings: PerspectiveSettings): PerspectiveSettings {\n const filters = settings.filters\n if (!filters || typeof filters !== 'object') return settings\n const record = filters as Record<string, unknown>\n if ('v' in record && record.v === 2) return settings\n if ('root' in record) return settings\n if (typeof console !== 'undefined') {\n console.warn('[perspectives] Dropping legacy filterValues shape; please re-create the perspective with the new filter UI.')\n }\n return { ...settings, filters: undefined }\n}\n\nfunction toResolvedPerspective(entity: Perspective): ResolvedPerspective {\n const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)\n return {\n id: entity.id,\n name: entity.name,\n tableId: entity.tableId,\n isDefault: !!entity.isDefault,\n settings,\n createdAt: entity.createdAt.toISOString(),\n updatedAt: entity.updatedAt ? entity.updatedAt.toISOString() : null,\n }\n}\n\nfunction toResolvedRolePerspective(entity: RolePerspective): ResolvedRolePerspective {\n const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)\n return {\n id: entity.id,\n roleId: entity.roleId,\n tableId: entity.tableId,\n name: entity.name,\n isDefault: !!entity.isDefault,\n settings,\n tenantId: nullish(entity.tenantId),\n organizationId: nullish(entity.organizationId),\n createdAt: entity.createdAt.toISOString(),\n updatedAt: entity.updatedAt ? entity.updatedAt.toISOString() : null,\n }\n}\n\nexport async function loadPerspectivesState(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: { scope: PerspectiveScope; tableId: string; roleIds?: string[] },\n): Promise<PerspectivesState> {\n const { scope, tableId } = options\n const roleIds = Array.isArray(options.roleIds) ? options.roleIds.filter((id) => id && id.length > 0) : []\n const uniqueRoles = Array.from(new Set(roleIds))\n const cacheKey = cache && uniqueRoles.length <= 16 ? userCacheKey(scope, tableId, uniqueRoles) : null\n\n if (cache && cacheKey) {\n const cached = await cache.get(cacheKey)\n if (cached && isPerspectivesState(cached)) return cached\n }\n\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n\n const [personal, roleRecords] = await Promise.all([\n em.find(Perspective, {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n deletedAt: null,\n }, { orderBy: { updatedAt: 'desc' } }),\n uniqueRoles.length\n ? em.find(RolePerspective, {\n roleId: { $in: uniqueRoles as any },\n tableId,\n deletedAt: null,\n $and: [\n { $or: [{ tenantId }, { tenantId: null }] },\n { $or: [{ organizationId }, { organizationId: null }] },\n ],\n } as any, { orderBy: { updatedAt: 'desc' } })\n : [],\n ])\n\n const personalResolved = personal.map(toResolvedPerspective)\n const personalDefaultId = personalResolved.find((p) => p.isDefault)?.id ?? null\n const roleResolved = roleRecords.map(toResolvedRolePerspective)\n\n const state: PerspectivesState = {\n tableId,\n personal: personalResolved,\n personalDefaultId,\n rolePerspectives: roleResolved,\n }\n\n if (cache && cacheKey) {\n await cache.set(cacheKey, state, {\n ttl: CACHE_TTL_MS,\n tags: [\n userTag(scope, tableId),\n ...uniqueRoles.map((roleId) => roleTag(roleId, tableId, tenantId)),\n ],\n })\n }\n\n return state\n}\n\nexport async function saveUserPerspective(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: {\n scope: PerspectiveScope\n tableId: string\n input: PerspectiveSaveInput\n request?: Request | Headers | null\n },\n): Promise<ResolvedPerspective> {\n const { scope, tableId, input } = options\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n\n let entity: Perspective | null = null\n if (input.perspectiveId) {\n entity = await em.findOne(Perspective, {\n id: input.perspectiveId,\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n deletedAt: null,\n })\n if (!entity) {\n throw Object.assign(new Error('Perspective not found'), { code: 'NOT_FOUND' })\n }\n enforceCommandOptimisticLock({\n resourceKind: 'perspectives.perspective',\n resourceId: entity.id,\n current: entity.updatedAt ?? null,\n request: options.request ?? null,\n })\n } else {\n entity = await em.findOne(Perspective, {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n name: input.name,\n deletedAt: null,\n })\n }\n\n const now = new Date()\n if (!entity) {\n entity = em.create(Perspective, {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n name: input.name,\n settingsJson: input.settings,\n isDefault: Boolean(input.isDefault),\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entity)\n } else {\n entity.name = input.name\n entity.settingsJson = input.settings\n entity.updatedAt = now\n if (input.isDefault === true) entity.isDefault = true\n if (input.isDefault === false) entity.isDefault = false\n }\n\n if (input.isDefault === true) {\n await em.nativeUpdate(\n Perspective,\n {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n id: { $ne: entity.id } as any,\n deletedAt: null,\n },\n { isDefault: false, updatedAt: now },\n )\n entity.isDefault = true\n }\n\n await em.flush()\n\n if (cache?.deleteByTags) {\n await cache.deleteByTags([userTag(scope, tableId)])\n }\n\n return toResolvedPerspective(entity)\n}\n\nexport async function deleteUserPerspective(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: { scope: PerspectiveScope; tableId: string; perspectiveId: string },\n): Promise<void> {\n const { scope, tableId, perspectiveId } = options\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n\n const existing = await em.findOne(Perspective, {\n id: perspectiveId,\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n deletedAt: null,\n })\n if (!existing) return\n\n existing.deletedAt = new Date()\n existing.isDefault = false\n await em.flush()\n\n if (cache?.deleteByTags) {\n await cache.deleteByTags([userTag(scope, tableId)])\n }\n}\n\nexport async function saveRolePerspectives(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: {\n tableId: string\n tenantId?: string | null\n organizationId?: string | null\n input: RolePerspectiveSaveInput\n },\n): Promise<ResolvedRolePerspective[]> {\n const { tableId, input } = options\n const tenantId = options.tenantId ?? null\n const organizationId = options.organizationId ?? null\n const now = new Date()\n const touchedRoleIds = new Set<string>()\n\n const results: ResolvedRolePerspective[] = []\n\n // Prefetch every matching role perspective in a single query, then index by role id\n // so the loop resolves create/update without a lookup per role.\n const recordByRole = new Map<string, RolePerspective>()\n if (input.roleIds.length) {\n const existingRecords = await em.find(RolePerspective, {\n roleId: { $in: input.roleIds },\n tableId,\n tenantId,\n organizationId,\n name: input.name,\n deletedAt: null,\n })\n for (const existing of existingRecords) recordByRole.set(existing.roleId, existing)\n }\n\n for (const roleId of input.roleIds) {\n let record = recordByRole.get(roleId) ?? null\n if (!record) {\n record = em.create(RolePerspective, {\n roleId,\n tableId,\n tenantId,\n organizationId,\n name: input.name,\n settingsJson: input.settings,\n isDefault: Boolean(input.setDefault),\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n recordByRole.set(roleId, record)\n } else {\n record.settingsJson = input.settings\n record.updatedAt = now\n if (input.setDefault === true) record.isDefault = true\n if (input.setDefault === false) record.isDefault = false\n }\n\n if (input.setDefault === true) {\n await em.nativeUpdate(\n RolePerspective,\n {\n roleId,\n tableId,\n tenantId,\n organizationId,\n id: { $ne: record.id } as any,\n deletedAt: null,\n },\n { isDefault: false, updatedAt: now },\n )\n record.isDefault = true\n }\n\n touchedRoleIds.add(roleId)\n results.push(toResolvedRolePerspective(record))\n }\n\n if (input.roleIds.length) {\n await em.flush()\n }\n\n if (cache?.deleteByTags && touchedRoleIds.size > 0) {\n const tags = Array.from(touchedRoleIds).map((roleId) => roleTag(roleId, tableId, tenantId))\n await cache.deleteByTags(tags)\n }\n\n return results\n}\n\nexport async function clearRolePerspectives(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: {\n tableId: string\n tenantId?: string | null\n organizationId?: string | null\n roleIds: string[]\n },\n): Promise<void> {\n const { tableId, roleIds } = options\n const tenantId = options.tenantId ?? null\n const organizationId = options.organizationId ?? null\n if (!roleIds.length) return\n\n await em.nativeUpdate(\n RolePerspective,\n {\n roleId: { $in: roleIds as any },\n tableId,\n tenantId,\n organizationId,\n deletedAt: null,\n },\n { deletedAt: new Date(), isDefault: false },\n )\n\n if (cache?.deleteByTags) {\n const tags = roleIds.map((roleId) => roleTag(roleId, tableId, tenantId))\n await cache.deleteByTags(tags)\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport { enforceCommandOptimisticLock } from '@open-mercato/shared/lib/crud/optimistic-lock-command'\nimport { Perspective, RolePerspective } from '../data/entities'\nimport type {\n PerspectiveSettings,\n PerspectiveSaveInput,\n RolePerspectiveSaveInput,\n} from '../data/validators'\n\nexport type PerspectiveScope = {\n userId: string\n tenantId?: string | null\n organizationId?: string | null\n}\n\nexport type ResolvedPerspective = {\n id: string\n name: string\n tableId: string\n settings: PerspectiveSettings\n isDefault: boolean\n createdAt: string\n updatedAt?: string | null\n}\n\nexport type ResolvedRolePerspective = {\n id: string\n roleId: string\n tableId: string\n name: string\n settings: PerspectiveSettings\n isDefault: boolean\n tenantId: string | null\n organizationId: string | null\n createdAt: string\n updatedAt?: string | null\n}\n\nexport type PerspectivesState = {\n tableId: string\n personal: ResolvedPerspective[]\n personalDefaultId: string | null\n rolePerspectives: ResolvedRolePerspective[]\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000\n\nconst nullish = <T extends string | null | undefined>(value: T): string | null =>\n value == null ? null : value\n\nconst scopeKey = (scope: PerspectiveScope) =>\n `${scope.userId}:${scope.tenantId ?? 'null'}:${scope.organizationId ?? 'null'}`\n\nconst userCacheKey = (scope: PerspectiveScope, tableId: string, roleIds: string[]) =>\n `perspectives:user-state:${scopeKey(scope)}:${tableId}:${roleIds.sort((a, b) => a.localeCompare(b)).join(',')}`\n\nconst userTag = (scope: PerspectiveScope, tableId?: string) =>\n tableId\n ? `perspectives:user:${scopeKey(scope)}:${tableId}`\n : `perspectives:user:${scopeKey(scope)}`\n\nconst roleTag = (roleId: string, tableId?: string, tenantId?: string | null) => {\n const tenant = tenantId ?? 'null'\n return tableId ? `perspectives:role:${roleId}:${tenant}:${tableId}` : `perspectives:role:${roleId}:${tenant}`\n}\n\nfunction isResolvedPerspective(value: unknown): value is ResolvedPerspective {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<ResolvedPerspective>\n return typeof record.id === 'string'\n && typeof record.name === 'string'\n && typeof record.tableId === 'string'\n && typeof record.isDefault === 'boolean'\n && typeof record.createdAt === 'string'\n}\n\nfunction isResolvedRolePerspective(value: unknown): value is ResolvedRolePerspective {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<ResolvedRolePerspective>\n return typeof record.id === 'string'\n && typeof record.roleId === 'string'\n && typeof record.tableId === 'string'\n && typeof record.name === 'string'\n && typeof record.isDefault === 'boolean'\n && typeof record.createdAt === 'string'\n}\n\nfunction isPerspectivesState(value: unknown): value is PerspectivesState {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<PerspectivesState>\n if (typeof record.tableId !== 'string') return false\n if (!Array.isArray(record.personal) || record.personal.some((item) => !isResolvedPerspective(item))) return false\n if (record.personalDefaultId !== null && typeof record.personalDefaultId !== 'string') return false\n if (!Array.isArray(record.rolePerspectives) || record.rolePerspectives.some((item) => !isResolvedRolePerspective(item))) return false\n return true\n}\n\n/**\n * Defensive migration for legacy filter state shapes captured before the\n * advanced-filter tree (SPEC-048). Existing perspectives only store either\n * advanced-filter URL params (tree shape with `v:2` or a `root` key) or\n * undefined \u2014 this helper is a safety net for legacy `FilterValues`-shaped\n * records (flat key/value records of column filters) that could only appear\n * if old saved-view JSON were imported.\n *\n * - Tree-shaped state (`v:2` or `root` key) is passed through unchanged.\n * - Undefined / null filters are passed through unchanged.\n * - Legacy `FilterValues`-shaped records are dropped (set to `undefined`)\n * because there is no reliable mapping back to the new operator model;\n * the user sees an empty tree and can recreate.\n */\nexport function maybeMigrateLegacyFilterValues(settings: PerspectiveSettings): PerspectiveSettings {\n const filters = settings.filters\n if (!filters || typeof filters !== 'object') return settings\n const record = filters as Record<string, unknown>\n if ('v' in record && record.v === 2) return settings\n if ('root' in record) return settings\n if (typeof console !== 'undefined') {\n console.warn('[perspectives] Dropping legacy filterValues shape; please re-create the perspective with the new filter UI.')\n }\n return { ...settings, filters: undefined }\n}\n\nfunction toResolvedPerspective(entity: Perspective): ResolvedPerspective {\n const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)\n return {\n id: entity.id,\n name: entity.name,\n tableId: entity.tableId,\n isDefault: !!entity.isDefault,\n settings,\n createdAt: entity.createdAt.toISOString(),\n updatedAt: entity.updatedAt ? entity.updatedAt.toISOString() : null,\n }\n}\n\nfunction toResolvedRolePerspective(entity: RolePerspective): ResolvedRolePerspective {\n const settings = maybeMigrateLegacyFilterValues((entity.settingsJson ?? {}) as PerspectiveSettings)\n return {\n id: entity.id,\n roleId: entity.roleId,\n tableId: entity.tableId,\n name: entity.name,\n isDefault: !!entity.isDefault,\n settings,\n tenantId: nullish(entity.tenantId),\n organizationId: nullish(entity.organizationId),\n createdAt: entity.createdAt.toISOString(),\n updatedAt: entity.updatedAt ? entity.updatedAt.toISOString() : null,\n }\n}\n\nexport async function loadPerspectivesState(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: { scope: PerspectiveScope; tableId: string; roleIds?: string[] },\n): Promise<PerspectivesState> {\n const { scope, tableId } = options\n const roleIds = Array.isArray(options.roleIds) ? options.roleIds.filter((id) => id && id.length > 0) : []\n const uniqueRoles = Array.from(new Set(roleIds))\n const cacheKey = cache && uniqueRoles.length <= 16 ? userCacheKey(scope, tableId, uniqueRoles) : null\n\n if (cache && cacheKey) {\n const cached = await cache.get(cacheKey)\n if (cached && isPerspectivesState(cached)) return cached\n }\n\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n\n const [personal, roleRecords] = await Promise.all([\n em.find(Perspective, {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n deletedAt: null,\n }, { orderBy: { updatedAt: 'desc' } }),\n uniqueRoles.length\n ? em.find(RolePerspective, {\n roleId: { $in: uniqueRoles as any },\n tableId,\n deletedAt: null,\n $and: [\n { $or: [{ tenantId }, { tenantId: null }] },\n { $or: [{ organizationId }, { organizationId: null }] },\n ],\n } as any, { orderBy: { updatedAt: 'desc' } })\n : [],\n ])\n\n const personalResolved = personal.map(toResolvedPerspective)\n const personalDefaultId = personalResolved.find((p) => p.isDefault)?.id ?? null\n const roleResolved = roleRecords.map(toResolvedRolePerspective)\n\n const state: PerspectivesState = {\n tableId,\n personal: personalResolved,\n personalDefaultId,\n rolePerspectives: roleResolved,\n }\n\n if (cache && cacheKey) {\n await cache.set(cacheKey, state, {\n ttl: CACHE_TTL_MS,\n tags: [\n userTag(scope, tableId),\n ...uniqueRoles.map((roleId) => roleTag(roleId, tableId, tenantId)),\n ],\n })\n }\n\n return state\n}\n\nexport async function saveUserPerspective(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: {\n scope: PerspectiveScope\n tableId: string\n input: PerspectiveSaveInput\n request?: Request | Headers | null\n },\n): Promise<ResolvedPerspective> {\n const { scope, tableId, input } = options\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n\n let entity: Perspective | null = null\n if (input.perspectiveId) {\n entity = await em.findOne(Perspective, {\n id: input.perspectiveId,\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n deletedAt: null,\n })\n if (!entity) {\n throw Object.assign(new Error('Perspective not found'), { code: 'NOT_FOUND' })\n }\n enforceCommandOptimisticLock({\n resourceKind: 'perspectives.perspective',\n resourceId: entity.id,\n current: entity.updatedAt ?? null,\n request: options.request ?? null,\n })\n } else {\n entity = await em.findOne(Perspective, {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n name: input.name,\n deletedAt: null,\n })\n }\n\n const now = new Date()\n if (!entity) {\n entity = em.create(Perspective, {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n name: input.name,\n settingsJson: input.settings,\n isDefault: Boolean(input.isDefault),\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entity)\n } else {\n entity.name = input.name\n entity.settingsJson = input.settings\n entity.updatedAt = now\n if (input.isDefault === true) entity.isDefault = true\n if (input.isDefault === false) entity.isDefault = false\n }\n\n if (input.isDefault === true) {\n await em.nativeUpdate(\n Perspective,\n {\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n id: { $ne: entity.id } as any,\n deletedAt: null,\n },\n { isDefault: false, updatedAt: now },\n )\n entity.isDefault = true\n }\n\n await em.flush()\n\n if (cache?.deleteByTags) {\n await cache.deleteByTags([userTag(scope, tableId)])\n }\n\n return toResolvedPerspective(entity)\n}\n\nexport async function deleteUserPerspective(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: { scope: PerspectiveScope; tableId: string; perspectiveId: string },\n): Promise<boolean> {\n const { scope, tableId, perspectiveId } = options\n const tenantId = scope.tenantId ?? null\n const organizationId = scope.organizationId ?? null\n\n const existing = await em.findOne(Perspective, {\n id: perspectiveId,\n userId: scope.userId,\n tenantId,\n organizationId,\n tableId,\n deletedAt: null,\n })\n if (!existing) return false\n\n existing.deletedAt = new Date()\n existing.isDefault = false\n await em.flush()\n\n if (cache?.deleteByTags) {\n await cache.deleteByTags([userTag(scope, tableId)])\n }\n\n return true\n}\n\nexport async function saveRolePerspectives(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: {\n tableId: string\n tenantId?: string | null\n organizationId?: string | null\n input: RolePerspectiveSaveInput\n },\n): Promise<ResolvedRolePerspective[]> {\n const { tableId, input } = options\n const tenantId = options.tenantId ?? null\n const organizationId = options.organizationId ?? null\n const now = new Date()\n const touchedRoleIds = new Set<string>()\n\n const results: ResolvedRolePerspective[] = []\n\n // Prefetch every matching role perspective in a single query, then index by role id\n // so the loop resolves create/update without a lookup per role.\n const recordByRole = new Map<string, RolePerspective>()\n if (input.roleIds.length) {\n const existingRecords = await em.find(RolePerspective, {\n roleId: { $in: input.roleIds },\n tableId,\n tenantId,\n organizationId,\n name: input.name,\n deletedAt: null,\n })\n for (const existing of existingRecords) recordByRole.set(existing.roleId, existing)\n }\n\n for (const roleId of input.roleIds) {\n let record = recordByRole.get(roleId) ?? null\n if (!record) {\n record = em.create(RolePerspective, {\n roleId,\n tableId,\n tenantId,\n organizationId,\n name: input.name,\n settingsJson: input.settings,\n isDefault: Boolean(input.setDefault),\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n recordByRole.set(roleId, record)\n } else {\n record.settingsJson = input.settings\n record.updatedAt = now\n if (input.setDefault === true) record.isDefault = true\n if (input.setDefault === false) record.isDefault = false\n }\n\n if (input.setDefault === true) {\n await em.nativeUpdate(\n RolePerspective,\n {\n roleId,\n tableId,\n tenantId,\n organizationId,\n id: { $ne: record.id } as any,\n deletedAt: null,\n },\n { isDefault: false, updatedAt: now },\n )\n record.isDefault = true\n }\n\n touchedRoleIds.add(roleId)\n results.push(toResolvedRolePerspective(record))\n }\n\n if (input.roleIds.length) {\n await em.flush()\n }\n\n if (cache?.deleteByTags && touchedRoleIds.size > 0) {\n const tags = Array.from(touchedRoleIds).map((roleId) => roleTag(roleId, tableId, tenantId))\n await cache.deleteByTags(tags)\n }\n\n return results\n}\n\nexport async function clearRolePerspectives(\n em: EntityManager,\n cache: CacheStrategy | null | undefined,\n options: {\n tableId: string\n tenantId?: string | null\n organizationId?: string | null\n roleIds: string[]\n },\n): Promise<number> {\n const { tableId, roleIds } = options\n const tenantId = options.tenantId ?? null\n const organizationId = options.organizationId ?? null\n if (!roleIds.length) return 0\n\n const affected = await em.nativeUpdate(\n RolePerspective,\n {\n roleId: { $in: roleIds as any },\n tableId,\n tenantId,\n organizationId,\n deletedAt: null,\n },\n { deletedAt: new Date(), isDefault: false },\n )\n\n if (cache?.deleteByTags) {\n const tags = roleIds.map((roleId) => roleTag(roleId, tableId, tenantId))\n await cache.deleteByTags(tags)\n }\n\n return affected\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -43,8 +43,17 @@ async function assertAvailabilityWriteAccess(ctx, params, translate) {
43
43
  }
44
44
  return access;
45
45
  }
46
+ function resolveAvailabilityActorId(auth) {
47
+ if (auth) {
48
+ if (typeof auth.sub === "string" && auth.sub.trim().length > 0) return auth.sub;
49
+ if (typeof auth.userId === "string" && auth.userId.trim().length > 0) return auth.userId;
50
+ if (typeof auth.keyId === "string" && auth.keyId.trim().length > 0) return auth.keyId;
51
+ }
52
+ return "system";
53
+ }
46
54
  export {
47
55
  assertAvailabilityWriteAccess,
56
+ resolveAvailabilityActorId,
48
57
  resolveAvailabilityWriteAccess
49
58
  };
50
59
  //# sourceMappingURL=access.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/planner/api/access.ts"],
4
- "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\ntype TranslateFn = (key: string, fallback?: string) => string\n\nexport type AvailabilityAccessContext = {\n container: AwilixContainer\n auth: AuthContext | null\n selectedOrganizationId?: string | null\n}\n\nexport type AvailabilityWriteAccess = {\n canManageAll: boolean\n canManageSelf: boolean\n canManageUnavailability: boolean\n memberId: string | null\n tenantId: string | null\n organizationId: string | null\n unregistered?: boolean\n}\n\ntype AvailabilityAccessResolver = {\n resolveAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n ): Promise<AvailabilityWriteAccess>\n}\n\nfunction buildForbiddenError(translate: TranslateFn) {\n return new CrudHttpError(403, {\n error: translate('planner.availability.errors.unauthorized', 'Unauthorized'),\n })\n}\n\nfunction buildStaffModuleNotLoadedError() {\n return new CrudHttpError(403, { error: 'staff_module_not_loaded' })\n}\n\nexport async function resolveAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n): Promise<AvailabilityWriteAccess> {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n const resolver = ctx.container.resolve<AvailabilityAccessResolver | undefined>(\n 'availabilityAccessResolver',\n { allowUnregistered: true },\n )\n if (!resolver) {\n console.warn(\n '[planner] staff_module_not_loaded \u2014 availabilityAccessResolver unregistered; denying availability write access',\n )\n return {\n canManageAll: false,\n canManageSelf: false,\n canManageUnavailability: false,\n memberId: null,\n tenantId,\n organizationId,\n unregistered: true,\n }\n }\n return resolver.resolveAvailabilityWriteAccess(ctx)\n}\n\nexport async function assertAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n params: { subjectType: string; subjectId: string; requiresUnavailability?: boolean },\n translate: TranslateFn,\n): Promise<AvailabilityWriteAccess> {\n const access = await resolveAvailabilityWriteAccess(ctx)\n if (access.unregistered) throw buildStaffModuleNotLoadedError()\n if (access.canManageAll) return access\n if (!access.canManageSelf) throw buildForbiddenError(translate)\n if (!access.memberId || params.subjectType !== 'member' || params.subjectId !== access.memberId) {\n throw buildForbiddenError(translate)\n }\n if (params.requiresUnavailability && !access.canManageUnavailability) {\n throw buildForbiddenError(translate)\n }\n return access\n}\n"],
5
- "mappings": "AAEA,SAAS,qBAAqB;AA0B9B,SAAS,oBAAoB,WAAwB;AACnD,SAAO,IAAI,cAAc,KAAK;AAAA,IAC5B,OAAO,UAAU,4CAA4C,cAAc;AAAA,EAC7E,CAAC;AACH;AAEA,SAAS,iCAAiC;AACxC,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,0BAA0B,CAAC;AACpE;AAEA,eAAsB,+BACpB,KACkC;AAClC,QAAM,WAAW,IAAI,MAAM,YAAY;AACvC,QAAM,iBAAiB,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACxE,QAAM,WAAW,IAAI,UAAU;AAAA,IAC7B;AAAA,IACA,EAAE,mBAAmB,KAAK;AAAA,EAC5B;AACA,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,MACL,cAAc;AAAA,MACd,eAAe;AAAA,MACf,yBAAyB;AAAA,MACzB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF;AACA,SAAO,SAAS,+BAA+B,GAAG;AACpD;AAEA,eAAsB,8BACpB,KACA,QACA,WACkC;AAClC,QAAM,SAAS,MAAM,+BAA+B,GAAG;AACvD,MAAI,OAAO,aAAc,OAAM,+BAA+B;AAC9D,MAAI,OAAO,aAAc,QAAO;AAChC,MAAI,CAAC,OAAO,cAAe,OAAM,oBAAoB,SAAS;AAC9D,MAAI,CAAC,OAAO,YAAY,OAAO,gBAAgB,YAAY,OAAO,cAAc,OAAO,UAAU;AAC/F,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,MAAI,OAAO,0BAA0B,CAAC,OAAO,yBAAyB;AACpE,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,SAAO;AACT;",
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\ntype TranslateFn = (key: string, fallback?: string) => string\n\nexport type AvailabilityAccessContext = {\n container: AwilixContainer\n auth: AuthContext | null\n selectedOrganizationId?: string | null\n}\n\nexport type AvailabilityWriteAccess = {\n canManageAll: boolean\n canManageSelf: boolean\n canManageUnavailability: boolean\n memberId: string | null\n tenantId: string | null\n organizationId: string | null\n unregistered?: boolean\n}\n\ntype AvailabilityAccessResolver = {\n resolveAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n ): Promise<AvailabilityWriteAccess>\n}\n\nfunction buildForbiddenError(translate: TranslateFn) {\n return new CrudHttpError(403, {\n error: translate('planner.availability.errors.unauthorized', 'Unauthorized'),\n })\n}\n\nfunction buildStaffModuleNotLoadedError() {\n return new CrudHttpError(403, { error: 'staff_module_not_loaded' })\n}\n\nexport async function resolveAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n): Promise<AvailabilityWriteAccess> {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n const resolver = ctx.container.resolve<AvailabilityAccessResolver | undefined>(\n 'availabilityAccessResolver',\n { allowUnregistered: true },\n )\n if (!resolver) {\n console.warn(\n '[planner] staff_module_not_loaded \u2014 availabilityAccessResolver unregistered; denying availability write access',\n )\n return {\n canManageAll: false,\n canManageSelf: false,\n canManageUnavailability: false,\n memberId: null,\n tenantId,\n organizationId,\n unregistered: true,\n }\n }\n return resolver.resolveAvailabilityWriteAccess(ctx)\n}\n\nexport async function assertAvailabilityWriteAccess(\n ctx: AvailabilityAccessContext,\n params: { subjectType: string; subjectId: string; requiresUnavailability?: boolean },\n translate: TranslateFn,\n): Promise<AvailabilityWriteAccess> {\n const access = await resolveAvailabilityWriteAccess(ctx)\n if (access.unregistered) throw buildStaffModuleNotLoadedError()\n if (access.canManageAll) return access\n if (!access.canManageSelf) throw buildForbiddenError(translate)\n if (!access.memberId || params.subjectType !== 'member' || params.subjectId !== access.memberId) {\n throw buildForbiddenError(translate)\n }\n if (params.requiresUnavailability && !access.canManageUnavailability) {\n throw buildForbiddenError(translate)\n }\n return access\n}\n\nexport function resolveAvailabilityActorId(auth: AuthContext): string {\n if (auth) {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n }\n return 'system'\n}\n"],
5
+ "mappings": "AAEA,SAAS,qBAAqB;AA0B9B,SAAS,oBAAoB,WAAwB;AACnD,SAAO,IAAI,cAAc,KAAK;AAAA,IAC5B,OAAO,UAAU,4CAA4C,cAAc;AAAA,EAC7E,CAAC;AACH;AAEA,SAAS,iCAAiC;AACxC,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,0BAA0B,CAAC;AACpE;AAEA,eAAsB,+BACpB,KACkC;AAClC,QAAM,WAAW,IAAI,MAAM,YAAY;AACvC,QAAM,iBAAiB,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACxE,QAAM,WAAW,IAAI,UAAU;AAAA,IAC7B;AAAA,IACA,EAAE,mBAAmB,KAAK;AAAA,EAC5B;AACA,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,MACL,cAAc;AAAA,MACd,eAAe;AAAA,MACf,yBAAyB;AAAA,MACzB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF;AACA,SAAO,SAAS,+BAA+B,GAAG;AACpD;AAEA,eAAsB,8BACpB,KACA,QACA,WACkC;AAClC,QAAM,SAAS,MAAM,+BAA+B,GAAG;AACvD,MAAI,OAAO,aAAc,OAAM,+BAA+B;AAC9D,MAAI,OAAO,aAAc,QAAO;AAChC,MAAI,CAAC,OAAO,cAAe,OAAM,oBAAoB,SAAS;AAC9D,MAAI,CAAC,OAAO,YAAY,OAAO,gBAAgB,YAAY,OAAO,cAAc,OAAO,UAAU;AAC/F,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,MAAI,OAAO,0BAA0B,CAAC,OAAO,yBAAyB;AACpE,UAAM,oBAAoB,SAAS;AAAA,EACrC;AACA,SAAO;AACT;AAEO,SAAS,2BAA2B,MAA2B;AACpE,MAAI,MAAM;AACR,QAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAC5E,QAAI,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAClF,QAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAAA,EAClF;AACA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -9,9 +9,13 @@ import { parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
9
9
  import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
10
10
  import { plannerAvailabilityDateSpecificReplaceSchema } from "../data/validators.js";
11
11
  import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
12
+ import {
13
+ runCrudMutationGuardAfterSuccess,
14
+ validateCrudMutationGuard
15
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
12
16
  import { PlannerAvailabilityRule } from "../data/entities.js";
13
17
  import { parseAvailabilityRuleWindow } from "../lib/availabilitySchedule.js";
14
- import { assertAvailabilityWriteAccess } from "./access.js";
18
+ import { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from "./access.js";
15
19
  const metadata = {
16
20
  POST: { requireAuth: true }
17
21
  };
@@ -80,8 +84,25 @@ async function POST(req) {
80
84
  }
81
85
  }
82
86
  }
87
+ const guardInput = {
88
+ tenantId: input.tenantId,
89
+ organizationId: input.organizationId,
90
+ userId: resolveAvailabilityActorId(ctx.auth),
91
+ resourceKind: "planner.availability",
92
+ resourceId: input.subjectId,
93
+ operation: "custom",
94
+ requestMethod: req.method,
95
+ requestHeaders: req.headers
96
+ };
97
+ const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input });
98
+ if (guardResult && !guardResult.ok) {
99
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
100
+ }
83
101
  const commandBus = ctx.container.resolve("commandBus");
84
102
  const { logEntry } = await commandBus.execute("planner.availability.date-specific.replace", { input, ctx });
103
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
104
+ await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null });
105
+ }
85
106
  const response = NextResponse.json({ ok: true });
86
107
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
87
108
  response.headers.set(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/planner/api/availability-date-specific.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { plannerAvailabilityDateSpecificReplaceSchema } from '../data/validators'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { PlannerAvailabilityRule } from '../data/entities'\nimport { parseAvailabilityRuleWindow } from '../lib/availabilitySchedule'\nimport { assertAvailabilityWriteAccess } from './access'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\ntype RequestContext = {\n ctx: CommandRuntimeContext\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('planner.availability.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('planner.availability.errors.organizationRequired', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return { ctx }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx } = await resolveRequestContext(req)\n const { translate } = await resolveTranslations()\n const payload = await req.json().catch(() => ({}))\n const normalized = normalizeDateSpecificPayload(payload)\n const input = parseScopedCommandInput(plannerAvailabilityDateSpecificReplaceSchema, normalized, ctx, translate)\n const isUnavailability = input.kind === 'unavailability' || input.isAvailable === false\n const access = await assertAvailabilityWriteAccess(\n ctx,\n { subjectType: input.subjectType, subjectId: input.subjectId, requiresUnavailability: isUnavailability },\n translate,\n )\n if (!access.canManageAll && !access.canManageUnavailability) {\n const dateSet = resolveDateSet(input)\n if (dateSet.size) {\n const em = ctx.container.resolve('em') as EntityManager\n const rules = await findWithDecryption(\n em,\n PlannerAvailabilityRule,\n {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n subjectType: input.subjectType,\n subjectId: input.subjectId,\n kind: 'unavailability',\n deletedAt: null,\n },\n undefined,\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const blocked = rules.some((rule) => {\n const window = parseAvailabilityRuleWindow(rule)\n if (window.repeat !== 'once') return false\n return dateSet.has(formatDateKey(window.startAt))\n })\n if (blocked) {\n throw new CrudHttpError(403, { error: translate('planner.availability.errors.unauthorized', 'Unauthorized') })\n }\n }\n }\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute('planner.availability.date-specific.replace', { input, ctx })\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 ?? 'planner.availability',\n resourceId: logEntry.resourceId ?? null,\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 const { translate } = await resolveTranslations()\n console.error('planner.availability.date-specific.replace failed', err)\n return NextResponse.json(\n { error: translate('planner.availability.errors.updateDateSpecific', 'Failed to save date-specific availability.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi = {\n tag: 'Planner',\n summary: 'Replace date-specific availability',\n methods: {\n POST: {\n summary: 'Replace date-specific availability',\n description: 'Replaces date-specific availability rules for the subject in a single request.',\n requestBody: {\n contentType: 'application/json',\n schema: plannerAvailabilityDateSpecificReplaceSchema,\n },\n responses: [\n { status: 200, description: 'Date-specific availability updated', schema: z.object({ ok: z.literal(true) }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n\ntype DateSpecificPayload = {\n date?: string\n dates?: string[]\n kind?: string\n isAvailable?: boolean\n [key: string]: unknown\n}\n\nfunction normalizeDateSpecificPayload(payload: unknown): DateSpecificPayload {\n if (!payload || typeof payload !== 'object') return {}\n const data = { ...(payload as Record<string, unknown>) } as DateSpecificPayload\n if (!data.date && Array.isArray(data.dates) && data.dates.length > 0) {\n const first = data.dates.find((value) => typeof value === 'string' && value.length > 0)\n if (first) data.date = first\n }\n if (data.isAvailable === undefined && typeof data.kind === 'string') {\n data.isAvailable = data.kind !== 'unavailability'\n }\n return data\n}\n\nfunction resolveDateSet(input: { date?: string; dates?: string[] }): Set<string> {\n const dates = new Set<string>()\n if (typeof input.date === 'string' && input.date.length > 0) {\n dates.add(input.date)\n }\n if (Array.isArray(input.dates)) {\n input.dates.forEach((value) => {\n if (typeof value === 'string' && value.length > 0) {\n dates.add(value)\n }\n })\n }\n return dates\n}\n\nfunction formatDateKey(value: Date): string {\n const year = value.getFullYear()\n const month = String(value.getMonth() + 1).padStart(2, '0')\n const day = String(value.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { plannerAvailabilityDateSpecificReplaceSchema } from '../data/validators'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { PlannerAvailabilityRule } from '../data/entities'\nimport { parseAvailabilityRuleWindow } from '../lib/availabilitySchedule'\nimport { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from './access'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\ntype RequestContext = {\n ctx: CommandRuntimeContext\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('planner.availability.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('planner.availability.errors.organizationRequired', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return { ctx }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx } = await resolveRequestContext(req)\n const { translate } = await resolveTranslations()\n const payload = await req.json().catch(() => ({}))\n const normalized = normalizeDateSpecificPayload(payload)\n const input = parseScopedCommandInput(plannerAvailabilityDateSpecificReplaceSchema, normalized, ctx, translate)\n const isUnavailability = input.kind === 'unavailability' || input.isAvailable === false\n const access = await assertAvailabilityWriteAccess(\n ctx,\n { subjectType: input.subjectType, subjectId: input.subjectId, requiresUnavailability: isUnavailability },\n translate,\n )\n if (!access.canManageAll && !access.canManageUnavailability) {\n const dateSet = resolveDateSet(input)\n if (dateSet.size) {\n const em = ctx.container.resolve('em') as EntityManager\n const rules = await findWithDecryption(\n em,\n PlannerAvailabilityRule,\n {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n subjectType: input.subjectType,\n subjectId: input.subjectId,\n kind: 'unavailability',\n deletedAt: null,\n },\n undefined,\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const blocked = rules.some((rule) => {\n const window = parseAvailabilityRuleWindow(rule)\n if (window.repeat !== 'once') return false\n return dateSet.has(formatDateKey(window.startAt))\n })\n if (blocked) {\n throw new CrudHttpError(403, { error: translate('planner.availability.errors.unauthorized', 'Unauthorized') })\n }\n }\n }\n const guardInput = {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: resolveAvailabilityActorId(ctx.auth),\n resourceKind: 'planner.availability',\n resourceId: input.subjectId,\n operation: 'custom' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute('planner.availability.date-specific.replace', { input, ctx })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null })\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 ?? 'planner.availability',\n resourceId: logEntry.resourceId ?? null,\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 const { translate } = await resolveTranslations()\n console.error('planner.availability.date-specific.replace failed', err)\n return NextResponse.json(\n { error: translate('planner.availability.errors.updateDateSpecific', 'Failed to save date-specific availability.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi = {\n tag: 'Planner',\n summary: 'Replace date-specific availability',\n methods: {\n POST: {\n summary: 'Replace date-specific availability',\n description: 'Replaces date-specific availability rules for the subject in a single request.',\n requestBody: {\n contentType: 'application/json',\n schema: plannerAvailabilityDateSpecificReplaceSchema,\n },\n responses: [\n { status: 200, description: 'Date-specific availability updated', schema: z.object({ ok: z.literal(true) }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n\ntype DateSpecificPayload = {\n date?: string\n dates?: string[]\n kind?: string\n isAvailable?: boolean\n [key: string]: unknown\n}\n\nfunction normalizeDateSpecificPayload(payload: unknown): DateSpecificPayload {\n if (!payload || typeof payload !== 'object') return {}\n const data = { ...(payload as Record<string, unknown>) } as DateSpecificPayload\n if (!data.date && Array.isArray(data.dates) && data.dates.length > 0) {\n const first = data.dates.find((value) => typeof value === 'string' && value.length > 0)\n if (first) data.date = first\n }\n if (data.isAvailable === undefined && typeof data.kind === 'string') {\n data.isAvailable = data.kind !== 'unavailability'\n }\n return data\n}\n\nfunction resolveDateSet(input: { date?: string; dates?: string[] }): Set<string> {\n const dates = new Set<string>()\n if (typeof input.date === 'string' && input.date.length > 0) {\n dates.add(input.date)\n }\n if (Array.isArray(input.dates)) {\n input.dates.forEach((value) => {\n if (typeof value === 'string' && value.length > 0) {\n dates.add(value)\n }\n })\n }\n return dates\n}\n\nfunction formatDateKey(value: Date): string {\n const year = value.getFullYear()\n const month = String(value.getMonth() + 1).padStart(2, '0')\n const day = String(value.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,7 +8,11 @@ import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/er
8
8
  import { parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
9
9
  import { plannerAvailabilityWeeklyReplaceSchema } from "../data/validators.js";
10
10
  import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
11
- import { assertAvailabilityWriteAccess } from "./access.js";
11
+ import {
12
+ runCrudMutationGuardAfterSuccess,
13
+ validateCrudMutationGuard
14
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
15
+ import { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from "./access.js";
12
16
  const metadata = {
13
17
  POST: { requireAuth: true }
14
18
  };
@@ -43,8 +47,25 @@ async function POST(req) {
43
47
  const payload = await req.json().catch(() => ({}));
44
48
  const input = parseScopedCommandInput(plannerAvailabilityWeeklyReplaceSchema, payload, ctx, translate);
45
49
  await assertAvailabilityWriteAccess(ctx, { subjectType: input.subjectType, subjectId: input.subjectId }, translate);
50
+ const guardInput = {
51
+ tenantId: input.tenantId,
52
+ organizationId: input.organizationId,
53
+ userId: resolveAvailabilityActorId(ctx.auth),
54
+ resourceKind: "planner.availability",
55
+ resourceId: input.subjectId,
56
+ operation: "custom",
57
+ requestMethod: req.method,
58
+ requestHeaders: req.headers
59
+ };
60
+ const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input });
61
+ if (guardResult && !guardResult.ok) {
62
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
63
+ }
46
64
  const commandBus = ctx.container.resolve("commandBus");
47
65
  const { logEntry } = await commandBus.execute("planner.availability.weekly.replace", { input, ctx });
66
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
67
+ await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null });
68
+ }
48
69
  const response = NextResponse.json({ ok: true });
49
70
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
50
71
  response.headers.set(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/planner/api/availability-weekly.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { plannerAvailabilityWeeklyReplaceSchema } from '../data/validators'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { assertAvailabilityWriteAccess } from './access'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\ntype RequestContext = {\n ctx: CommandRuntimeContext\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('planner.availability.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('planner.availability.errors.organizationRequired', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return { ctx }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx } = await resolveRequestContext(req)\n const { translate } = await resolveTranslations()\n const payload = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(plannerAvailabilityWeeklyReplaceSchema, payload, ctx, translate)\n await assertAvailabilityWriteAccess(ctx, { subjectType: input.subjectType, subjectId: input.subjectId }, translate)\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute('planner.availability.weekly.replace', { input, ctx })\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 ?? 'planner.availability',\n resourceId: logEntry.resourceId ?? null,\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 const { translate } = await resolveTranslations()\n console.error('planner.availability.weekly.replace failed', err)\n return NextResponse.json(\n { error: translate('planner.availability.errors.updateWeekly', 'Failed to save weekly availability.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi = {\n tag: 'Planner',\n summary: 'Replace weekly availability',\n methods: {\n POST: {\n summary: 'Replace weekly availability',\n description: 'Replaces weekly availability rules for the subject in a single request.',\n requestBody: {\n contentType: 'application/json',\n schema: plannerAvailabilityWeeklyReplaceSchema,\n },\n responses: [\n { status: 200, description: 'Weekly availability updated', schema: z.object({ ok: z.literal(true) }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { plannerAvailabilityWeeklyReplaceSchema } from '../data/validators'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { assertAvailabilityWriteAccess, resolveAvailabilityActorId } from './access'\n\nexport const metadata = {\n POST: { requireAuth: true },\n}\n\ntype RequestContext = {\n ctx: CommandRuntimeContext\n}\n\nasync function resolveRequestContext(req: Request): Promise<RequestContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('planner.availability.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('planner.availability.errors.organizationRequired', 'Organization context is required'),\n })\n }\n\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return { ctx }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx } = await resolveRequestContext(req)\n const { translate } = await resolveTranslations()\n const payload = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(plannerAvailabilityWeeklyReplaceSchema, payload, ctx, translate)\n await assertAvailabilityWriteAccess(ctx, { subjectType: input.subjectType, subjectId: input.subjectId }, translate)\n const guardInput = {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: resolveAvailabilityActorId(ctx.auth),\n resourceKind: 'planner.availability',\n resourceId: input.subjectId,\n operation: 'custom' as const,\n requestMethod: req.method,\n requestHeaders: req.headers,\n }\n const guardResult = await validateCrudMutationGuard(ctx.container, { ...guardInput, mutationPayload: input })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n const commandBus = ctx.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute('planner.availability.weekly.replace', { input, ctx })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(ctx.container, { ...guardInput, metadata: guardResult.metadata ?? null })\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 ?? 'planner.availability',\n resourceId: logEntry.resourceId ?? null,\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 const { translate } = await resolveTranslations()\n console.error('planner.availability.weekly.replace failed', err)\n return NextResponse.json(\n { error: translate('planner.availability.errors.updateWeekly', 'Failed to save weekly availability.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi = {\n tag: 'Planner',\n summary: 'Replace weekly availability',\n methods: {\n POST: {\n summary: 'Replace weekly availability',\n description: 'Replaces weekly availability rules for the subject in a single request.',\n requestBody: {\n contentType: 'application/json',\n schema: plannerAvailabilityWeeklyReplaceSchema,\n },\n responses: [\n { status: 200, description: 'Weekly availability updated', schema: z.object({ ok: z.literal(true) }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }