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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -1
  3. package/dist/bootstrap.js +46 -6
  4. package/dist/bootstrap.js.map +2 -2
  5. package/dist/generated/entities/organization/index.js +2 -0
  6. package/dist/generated/entities/organization/index.js.map +2 -2
  7. package/dist/generated/entity-fields-registry.js +1 -0
  8. package/dist/generated/entity-fields-registry.js.map +2 -2
  9. package/dist/helpers/integration/crmFixtures.js +4 -0
  10. package/dist/helpers/integration/crmFixtures.js.map +2 -2
  11. package/dist/modules/attachments/api/library/route.js +2 -2
  12. package/dist/modules/attachments/api/library/route.js.map +2 -2
  13. package/dist/modules/attachments/api/route.js +2 -0
  14. package/dist/modules/attachments/api/route.js.map +2 -2
  15. package/dist/modules/attachments/components/AttachmentContentPreview.js +9 -5
  16. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  17. package/dist/modules/attachments/lib/access.js +18 -0
  18. package/dist/modules/attachments/lib/access.js.map +2 -2
  19. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +3 -2
  20. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
  21. package/dist/modules/audit_logs/data/entities.js +2 -1
  22. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  23. package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
  24. package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
  25. package/dist/modules/audit_logs/services/accessLogService.js +10 -0
  26. package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
  27. package/dist/modules/auth/api/admin/nav.js +9 -0
  28. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  29. package/dist/modules/auth/api/login.js +4 -13
  30. package/dist/modules/auth/api/login.js.map +2 -2
  31. package/dist/modules/auth/commands/users.js +20 -14
  32. package/dist/modules/auth/commands/users.js.map +2 -2
  33. package/dist/modules/auth/data/entities.js +4 -2
  34. package/dist/modules/auth/data/entities.js.map +2 -2
  35. package/dist/modules/auth/lib/backendChrome.js +35 -2
  36. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  37. package/dist/modules/auth/lib/consentIntegrity.js +3 -3
  38. package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
  39. package/dist/modules/auth/migrations/Migration20260610120000.js +30 -0
  40. package/dist/modules/auth/migrations/Migration20260610120000.js.map +7 -0
  41. package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
  42. package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
  43. package/dist/modules/auth/services/authService.js +5 -3
  44. package/dist/modules/auth/services/authService.js.map +2 -2
  45. package/dist/modules/auth/services/rbacService.js +3 -2
  46. package/dist/modules/auth/services/rbacService.js.map +2 -2
  47. package/dist/modules/catalog/ai-tools/configuration-pack.js.map +1 -1
  48. package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +1 -1
  49. package/dist/modules/catalog/ai-tools/products-pack.js.map +1 -1
  50. package/dist/modules/catalog/ai-tools/variants-pack.js.map +1 -1
  51. package/dist/modules/communication_channels/data/entities.js.map +1 -1
  52. package/dist/modules/communication_channels/encryption.js.map +1 -1
  53. package/dist/modules/communication_channels/lib/thread-matcher.js.map +1 -1
  54. package/dist/modules/communication_channels/lib/thread-token.js.map +1 -1
  55. package/dist/modules/currencies/api/currencies/route.js +4 -3
  56. package/dist/modules/currencies/api/currencies/route.js.map +2 -2
  57. package/dist/modules/customer_accounts/api/admin/roles.js +2 -1
  58. package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
  59. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
  60. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
  61. package/dist/modules/customer_accounts/events.js +1 -1
  62. package/dist/modules/customer_accounts/events.js.map +1 -1
  63. package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +1 -1
  64. package/dist/modules/customers/acl.js +1 -1
  65. package/dist/modules/customers/acl.js.map +1 -1
  66. package/dist/modules/customers/ai-tools/companies-pack.js.map +1 -1
  67. package/dist/modules/customers/ai-tools/deals-pack.js.map +1 -1
  68. package/dist/modules/customers/ai-tools/people-pack.js.map +1 -1
  69. package/dist/modules/customers/api/companies/route.js +4 -4
  70. package/dist/modules/customers/api/companies/route.js.map +2 -2
  71. package/dist/modules/customers/api/deals/route.js +43 -2
  72. package/dist/modules/customers/api/deals/route.js.map +2 -2
  73. package/dist/modules/customers/api/deals/summary/route.js +402 -0
  74. package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
  75. package/dist/modules/customers/api/people/route.js +4 -4
  76. package/dist/modules/customers/api/people/route.js.map +2 -2
  77. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
  78. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
  79. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
  80. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
  81. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
  82. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  83. package/dist/modules/customers/backend/customers/deals/page.js +221 -56
  84. package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
  85. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
  86. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  87. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
  88. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  89. package/dist/modules/customers/cli.js +15 -9
  90. package/dist/modules/customers/cli.js.map +2 -2
  91. package/dist/modules/customers/commands/addresses.js +5 -5
  92. package/dist/modules/customers/commands/addresses.js.map +2 -2
  93. package/dist/modules/customers/commands/comments.js +5 -5
  94. package/dist/modules/customers/commands/comments.js.map +2 -2
  95. package/dist/modules/customers/commands/deals.js +2 -2
  96. package/dist/modules/customers/commands/deals.js.map +2 -2
  97. package/dist/modules/customers/commands/entity-roles.js +2 -1
  98. package/dist/modules/customers/commands/entity-roles.js.map +2 -2
  99. package/dist/modules/customers/commands/interactions.js +8 -5
  100. package/dist/modules/customers/commands/interactions.js.map +2 -2
  101. package/dist/modules/customers/commands/shared.js +21 -6
  102. package/dist/modules/customers/commands/shared.js.map +2 -2
  103. package/dist/modules/customers/commands/tags.js +3 -3
  104. package/dist/modules/customers/commands/tags.js.map +2 -2
  105. package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
  106. package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
  107. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
  108. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
  109. package/dist/modules/customers/components/detail/DealForm.js +100 -17
  110. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  111. package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
  112. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  113. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
  114. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  115. package/dist/modules/customers/components/detail/assignableStaff.js +21 -8
  116. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  117. package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
  118. package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
  119. package/dist/modules/customers/lib/dealsMetrics.js +82 -0
  120. package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
  121. package/dist/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.js.map +1 -1
  122. package/dist/modules/data_sync/api/run.js +1 -1
  123. package/dist/modules/data_sync/api/run.js.map +2 -2
  124. package/dist/modules/directory/api/organization-branding/route.js +214 -0
  125. package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
  126. package/dist/modules/directory/api/organizations/route.js +7 -0
  127. package/dist/modules/directory/api/organizations/route.js.map +3 -3
  128. package/dist/modules/directory/backend/directory/branding/page.js +214 -0
  129. package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
  130. package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
  131. package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
  132. package/dist/modules/directory/commands/organizations.js +8 -1
  133. package/dist/modules/directory/commands/organizations.js.map +2 -2
  134. package/dist/modules/directory/data/entities.js +3 -0
  135. package/dist/modules/directory/data/entities.js.map +2 -2
  136. package/dist/modules/directory/data/validators.js +9 -0
  137. package/dist/modules/directory/data/validators.js.map +2 -2
  138. package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
  139. package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
  140. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
  141. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
  142. package/dist/modules/directory/utils/organizationScope.js +59 -27
  143. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  144. package/dist/modules/entities/api/definitions.batch.js +2 -1
  145. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  146. package/dist/modules/entities/api/entities.js +7 -0
  147. package/dist/modules/entities/api/entities.js.map +2 -2
  148. package/dist/modules/entities/api/records.js +26 -15
  149. package/dist/modules/entities/api/records.js.map +2 -2
  150. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
  151. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  152. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
  153. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
  154. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
  155. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  156. package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
  157. package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
  158. package/dist/modules/payment_gateways/api/transactions/route.js +2 -4
  159. package/dist/modules/payment_gateways/api/transactions/route.js.map +2 -2
  160. package/dist/modules/progress/api/jobs/[id]/route.js +7 -2
  161. package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
  162. package/dist/modules/progress/api/jobs/route.js +1 -1
  163. package/dist/modules/progress/api/jobs/route.js.map +2 -2
  164. package/dist/modules/progress/lib/progressServiceImpl.js +8 -2
  165. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  166. package/dist/modules/query_index/data/entities.js +2 -1
  167. package/dist/modules/query_index/data/entities.js.map +2 -2
  168. package/dist/modules/query_index/lib/engine.js +4 -2
  169. package/dist/modules/query_index/lib/engine.js.map +2 -2
  170. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
  171. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
  172. package/dist/modules/resources/api/resources.js +2 -3
  173. package/dist/modules/resources/api/resources.js.map +2 -2
  174. package/dist/modules/sales/api/documents/factory.js +2 -2
  175. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  176. package/dist/modules/sales/commands/documents.js +7 -5
  177. package/dist/modules/sales/commands/documents.js.map +2 -2
  178. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
  179. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  180. package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
  181. package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
  182. package/dist/modules/staff/api/team-members.js +9 -2
  183. package/dist/modules/staff/api/team-members.js.map +2 -2
  184. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
  185. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  186. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
  187. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  188. package/dist/modules/staff/commands/team-members.js +1 -1
  189. package/dist/modules/staff/commands/team-members.js.map +2 -2
  190. package/dist/modules/staff/components/TeamMemberForm.js +1 -1
  191. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  192. package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
  193. package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
  194. package/dist/modules/sync_excel/api/import/route.js +1 -1
  195. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  196. package/dist/modules/workflows/api/definitions/route.js +3 -2
  197. package/dist/modules/workflows/api/definitions/route.js.map +2 -2
  198. package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
  199. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  200. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
  201. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  202. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
  203. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  204. package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
  205. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  206. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
  207. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
  208. package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
  209. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  210. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
  211. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
  212. package/generated/entities/organization/index.ts +1 -0
  213. package/generated/entity-fields-registry.ts +1 -0
  214. package/package.json +11 -12
  215. package/src/bootstrap.ts +65 -7
  216. package/src/helpers/integration/crmFixtures.ts +21 -1
  217. package/src/modules/attachments/AGENTS.md +79 -0
  218. package/src/modules/attachments/api/library/route.ts +2 -2
  219. package/src/modules/attachments/api/route.ts +2 -0
  220. package/src/modules/attachments/components/AttachmentContentPreview.tsx +6 -6
  221. package/src/modules/attachments/lib/access.ts +36 -0
  222. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +14 -2
  223. package/src/modules/audit_logs/data/entities.ts +1 -0
  224. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
  225. package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
  226. package/src/modules/audit_logs/services/accessLogService.ts +15 -0
  227. package/src/modules/auth/api/admin/nav.ts +9 -0
  228. package/src/modules/auth/api/login.ts +13 -13
  229. package/src/modules/auth/commands/users.ts +32 -15
  230. package/src/modules/auth/data/entities.ts +13 -1
  231. package/src/modules/auth/i18n/de.json +0 -1
  232. package/src/modules/auth/i18n/en.json +0 -1
  233. package/src/modules/auth/i18n/es.json +0 -1
  234. package/src/modules/auth/i18n/pl.json +0 -1
  235. package/src/modules/auth/lib/backendChrome.tsx +37 -1
  236. package/src/modules/auth/lib/consentIntegrity.ts +6 -3
  237. package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -10
  238. package/src/modules/auth/migrations/Migration20260610120000.ts +53 -0
  239. package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
  240. package/src/modules/auth/services/authService.ts +24 -4
  241. package/src/modules/auth/services/rbacService.ts +11 -2
  242. package/src/modules/catalog/ai-tools/configuration-pack.ts +1 -1
  243. package/src/modules/catalog/ai-tools/prices-offers-pack.ts +1 -1
  244. package/src/modules/catalog/ai-tools/products-pack.ts +1 -1
  245. package/src/modules/catalog/ai-tools/variants-pack.ts +1 -1
  246. package/src/modules/communication_channels/data/entities.ts +2 -2
  247. package/src/modules/communication_channels/encryption.ts +1 -1
  248. package/src/modules/communication_channels/lib/adapter.ts +1 -1
  249. package/src/modules/communication_channels/lib/thread-matcher.ts +1 -1
  250. package/src/modules/communication_channels/lib/thread-token.ts +1 -1
  251. package/src/modules/currencies/api/currencies/route.ts +4 -3
  252. package/src/modules/customer_accounts/api/admin/roles.ts +2 -1
  253. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
  254. package/src/modules/customer_accounts/events.ts +1 -1
  255. package/src/modules/customer_accounts/lib/resolveTenantContext.ts +2 -2
  256. package/src/modules/customers/acl.ts +1 -1
  257. package/src/modules/customers/ai-tools/companies-pack.ts +1 -1
  258. package/src/modules/customers/ai-tools/deals-pack.ts +1 -1
  259. package/src/modules/customers/ai-tools/people-pack.ts +1 -1
  260. package/src/modules/customers/api/companies/route.ts +4 -4
  261. package/src/modules/customers/api/deals/route.ts +51 -2
  262. package/src/modules/customers/api/deals/summary/route.ts +496 -0
  263. package/src/modules/customers/api/people/route.ts +4 -4
  264. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
  265. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
  266. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
  267. package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
  268. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
  269. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
  270. package/src/modules/customers/cli.ts +15 -15
  271. package/src/modules/customers/commands/addresses.ts +5 -5
  272. package/src/modules/customers/commands/comments.ts +5 -5
  273. package/src/modules/customers/commands/deals.ts +2 -2
  274. package/src/modules/customers/commands/entity-roles.ts +2 -1
  275. package/src/modules/customers/commands/interactions.ts +8 -5
  276. package/src/modules/customers/commands/shared.ts +26 -4
  277. package/src/modules/customers/commands/tags.ts +3 -3
  278. package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
  279. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
  280. package/src/modules/customers/components/detail/DealForm.tsx +121 -19
  281. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
  282. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
  283. package/src/modules/customers/components/detail/assignableStaff.ts +32 -8
  284. package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
  285. package/src/modules/customers/i18n/de.json +43 -0
  286. package/src/modules/customers/i18n/en.json +43 -0
  287. package/src/modules/customers/i18n/es.json +43 -0
  288. package/src/modules/customers/i18n/pl.json +43 -0
  289. package/src/modules/customers/lib/dealsMetrics.ts +159 -0
  290. package/src/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.ts +1 -1
  291. package/src/modules/data_sync/api/run.ts +1 -1
  292. package/src/modules/directory/api/organization-branding/route.ts +238 -0
  293. package/src/modules/directory/api/organizations/route.ts +7 -0
  294. package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
  295. package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
  296. package/src/modules/directory/commands/organizations.ts +9 -1
  297. package/src/modules/directory/data/entities.ts +3 -0
  298. package/src/modules/directory/data/validators.ts +12 -0
  299. package/src/modules/directory/i18n/de.json +21 -0
  300. package/src/modules/directory/i18n/en.json +21 -0
  301. package/src/modules/directory/i18n/es.json +21 -0
  302. package/src/modules/directory/i18n/pl.json +21 -0
  303. package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
  304. package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
  305. package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
  306. package/src/modules/directory/utils/organizationScope.ts +85 -30
  307. package/src/modules/entities/api/definitions.batch.ts +11 -7
  308. package/src/modules/entities/api/entities.ts +11 -0
  309. package/src/modules/entities/api/records.ts +46 -25
  310. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
  311. package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
  312. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
  313. package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
  314. package/src/modules/entities/i18n/de.json +1 -0
  315. package/src/modules/entities/i18n/en.json +1 -0
  316. package/src/modules/entities/i18n/es.json +1 -0
  317. package/src/modules/entities/i18n/pl.json +1 -0
  318. package/src/modules/payment_gateways/api/transactions/route.ts +2 -5
  319. package/src/modules/progress/api/jobs/[id]/route.ts +6 -1
  320. package/src/modules/progress/api/jobs/route.ts +1 -1
  321. package/src/modules/progress/lib/progressServiceImpl.ts +7 -1
  322. package/src/modules/query_index/data/entities.ts +1 -0
  323. package/src/modules/query_index/lib/engine.ts +11 -5
  324. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
  325. package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
  326. package/src/modules/resources/api/resources.ts +2 -3
  327. package/src/modules/sales/api/documents/factory.ts +2 -2
  328. package/src/modules/sales/commands/documents.ts +7 -5
  329. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
  330. package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
  331. package/src/modules/staff/AGENTS.md +1 -1
  332. package/src/modules/staff/api/team-members.ts +9 -2
  333. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
  334. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
  335. package/src/modules/staff/commands/team-members.ts +5 -2
  336. package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
  337. package/src/modules/staff/i18n/de.json +1 -0
  338. package/src/modules/staff/i18n/en.json +1 -0
  339. package/src/modules/staff/i18n/es.json +1 -0
  340. package/src/modules/staff/i18n/pl.json +1 -0
  341. package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
  342. package/src/modules/sync_excel/api/import/route.ts +1 -1
  343. package/src/modules/workflows/api/definitions/route.ts +3 -2
  344. package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
  345. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
  346. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
  347. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
  348. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
  349. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
  350. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Mandanten anlegen",
6
6
  "directory.audit.tenants.delete": "Mandanten löschen",
7
7
  "directory.audit.tenants.update": "Mandanten aktualisieren",
8
+ "directory.branding.actions.reset": "Standardlogo verwenden",
9
+ "directory.branding.actions.save": "Branding speichern",
10
+ "directory.branding.currentScope": "Aktuelle Organisation",
11
+ "directory.branding.description": "Legen Sie das Logo fest, das in der Backend-Seitenleiste für die aktuell ausgewählte Organisation verwendet wird.",
12
+ "directory.branding.errors.blocked": "Das Speichern des Brandings wurde blockiert.",
13
+ "directory.branding.errors.invalidLogoUrl": "Geben Sie eine gültige Bild-URL ein.",
14
+ "directory.branding.errors.load": "Organisationsbranding konnte nicht geladen werden",
15
+ "directory.branding.errors.notFound": "Organisation nicht gefunden",
16
+ "directory.branding.errors.organizationRequired": "Wählen Sie eine einzelne Organisation aus, bevor Sie das Seitenleisten-Branding ändern.",
17
+ "directory.branding.errors.save": "Organisationsbranding konnte nicht aktualisiert werden",
18
+ "directory.branding.errors.upload": "Logo konnte nicht hochgeladen werden",
19
+ "directory.branding.file.hint": "PNG, JPG, WebP oder SVG funktioniert am besten. Hochgeladene Dateien werden als Organisationsanhänge gespeichert.",
20
+ "directory.branding.file.label": "Logo hochladen",
21
+ "directory.branding.flash.saved": "Organisationsbranding aktualisiert",
22
+ "directory.branding.loading": "Organisationsbranding wird geladen...",
23
+ "directory.branding.nav": "Organisationsbranding",
24
+ "directory.branding.previewAlt": "Logovorschau für {{name}}",
25
+ "directory.branding.title": "Organisationsbranding",
26
+ "directory.branding.url.hint": "Verwenden Sie eine externe Bild-URL oder lassen Sie das Feld leer, um auf das Standardlogo von Open Mercato zurückzufallen.",
27
+ "directory.branding.url.label": "Logo-URL",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Verzeichnis",
9
30
  "directory.nav.organizations": "Organisationen",
10
31
  "directory.nav.organizations.create": "Organisation erstellen",
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Create tenant",
6
6
  "directory.audit.tenants.delete": "Delete tenant",
7
7
  "directory.audit.tenants.update": "Update tenant",
8
+ "directory.branding.actions.reset": "Use default logo",
9
+ "directory.branding.actions.save": "Save branding",
10
+ "directory.branding.currentScope": "Current organization",
11
+ "directory.branding.description": "Set the logo used in the backend sidebar for the currently selected organization.",
12
+ "directory.branding.errors.blocked": "Branding save was blocked.",
13
+ "directory.branding.errors.invalidLogoUrl": "Enter a valid image URL.",
14
+ "directory.branding.errors.load": "Failed to load organization branding",
15
+ "directory.branding.errors.notFound": "Organization not found",
16
+ "directory.branding.errors.organizationRequired": "Select a single organization before changing sidebar branding.",
17
+ "directory.branding.errors.save": "Failed to update organization branding",
18
+ "directory.branding.errors.upload": "Failed to upload logo",
19
+ "directory.branding.file.hint": "PNG, JPG, WebP, or SVG works best. Uploaded files are stored as organization attachments.",
20
+ "directory.branding.file.label": "Upload logo",
21
+ "directory.branding.flash.saved": "Organization branding updated",
22
+ "directory.branding.loading": "Loading organization branding...",
23
+ "directory.branding.nav": "Organization branding",
24
+ "directory.branding.previewAlt": "{{name}} logo preview",
25
+ "directory.branding.title": "Organization branding",
26
+ "directory.branding.url.hint": "Use an external image URL or leave empty to fall back to the default Open Mercato logo.",
27
+ "directory.branding.url.label": "Logo URL",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Directory",
9
30
  "directory.nav.organizations": "Organizations",
10
31
  "directory.nav.organizations.create": "Create Organization",
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Crear inquilino",
6
6
  "directory.audit.tenants.delete": "Eliminar inquilino",
7
7
  "directory.audit.tenants.update": "Actualizar inquilino",
8
+ "directory.branding.actions.reset": "Usar logo predeterminado",
9
+ "directory.branding.actions.save": "Guardar marca",
10
+ "directory.branding.currentScope": "Organización actual",
11
+ "directory.branding.description": "Define el logo usado en la barra lateral del backend para la organización seleccionada.",
12
+ "directory.branding.errors.blocked": "Se bloqueó el guardado de la marca.",
13
+ "directory.branding.errors.invalidLogoUrl": "Introduce una URL de imagen válida.",
14
+ "directory.branding.errors.load": "No se pudo cargar la marca de la organización",
15
+ "directory.branding.errors.notFound": "Organización no encontrada",
16
+ "directory.branding.errors.organizationRequired": "Selecciona una sola organización antes de cambiar la marca de la barra lateral.",
17
+ "directory.branding.errors.save": "No se pudo actualizar la marca de la organización",
18
+ "directory.branding.errors.upload": "No se pudo subir el logo",
19
+ "directory.branding.file.hint": "PNG, JPG, WebP o SVG funcionan mejor. Los archivos subidos se guardan como adjuntos de la organización.",
20
+ "directory.branding.file.label": "Subir logo",
21
+ "directory.branding.flash.saved": "Marca de la organización actualizada",
22
+ "directory.branding.loading": "Cargando marca de la organización...",
23
+ "directory.branding.nav": "Marca de la organización",
24
+ "directory.branding.previewAlt": "Vista previa del logo de {{name}}",
25
+ "directory.branding.title": "Marca de la organización",
26
+ "directory.branding.url.hint": "Usa una URL de imagen externa o deja el campo vacío para volver al logo predeterminado de Open Mercato.",
27
+ "directory.branding.url.label": "URL del logo",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Directorio",
9
30
  "directory.nav.organizations": "Organizaciones",
10
31
  "directory.nav.organizations.create": "Crear organización",
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Utwórz najemcę",
6
6
  "directory.audit.tenants.delete": "Usuń najemcę",
7
7
  "directory.audit.tenants.update": "Aktualizuj najemcę",
8
+ "directory.branding.actions.reset": "Użyj domyślnego logo",
9
+ "directory.branding.actions.save": "Zapisz branding",
10
+ "directory.branding.currentScope": "Aktualna organizacja",
11
+ "directory.branding.description": "Ustaw logo używane w sidebarze backendu dla aktualnie wybranej organizacji.",
12
+ "directory.branding.errors.blocked": "Zapis brandingu został zablokowany.",
13
+ "directory.branding.errors.invalidLogoUrl": "Podaj poprawny adres URL obrazka.",
14
+ "directory.branding.errors.load": "Nie udało się wczytać brandingu organizacji",
15
+ "directory.branding.errors.notFound": "Nie znaleziono organizacji",
16
+ "directory.branding.errors.organizationRequired": "Wybierz pojedynczą organizację przed zmianą brandingu sidebara.",
17
+ "directory.branding.errors.save": "Nie udało się zaktualizować brandingu organizacji",
18
+ "directory.branding.errors.upload": "Nie udało się przesłać logo",
19
+ "directory.branding.file.hint": "Najlepiej sprawdzi się PNG, JPG, WebP albo SVG. Przesłane pliki są zapisywane jako załączniki organizacji.",
20
+ "directory.branding.file.label": "Prześlij logo",
21
+ "directory.branding.flash.saved": "Branding organizacji zaktualizowany",
22
+ "directory.branding.loading": "Ładowanie brandingu organizacji...",
23
+ "directory.branding.nav": "Branding organizacji",
24
+ "directory.branding.previewAlt": "Podgląd logo {{name}}",
25
+ "directory.branding.title": "Branding organizacji",
26
+ "directory.branding.url.hint": "Użyj zewnętrznego adresu obrazka albo zostaw puste, aby wrócić do domyślnego logo Open Mercato.",
27
+ "directory.branding.url.label": "Adres URL logo",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Katalog",
9
30
  "directory.nav.organizations": "Organizacje",
10
31
  "directory.nav.organizations.create": "Utwórz organizację",
@@ -21,6 +21,7 @@
21
21
  "scale": null,
22
22
  "default": "'[]'",
23
23
  "comment": null,
24
+ "collation": null,
24
25
  "enumItems": [],
25
26
  "mappedType": "json"
26
27
  },
@@ -37,6 +38,7 @@
37
38
  "scale": null,
38
39
  "default": "'[]'",
39
40
  "comment": null,
41
+ "collation": null,
40
42
  "enumItems": [],
41
43
  "mappedType": "json"
42
44
  },
@@ -53,6 +55,7 @@
53
55
  "scale": null,
54
56
  "default": null,
55
57
  "comment": null,
58
+ "collation": null,
56
59
  "enumItems": [],
57
60
  "mappedType": "datetime"
58
61
  },
@@ -69,6 +72,7 @@
69
72
  "scale": null,
70
73
  "default": null,
71
74
  "comment": null,
75
+ "collation": null,
72
76
  "enumItems": [],
73
77
  "mappedType": "datetime"
74
78
  },
@@ -85,6 +89,7 @@
85
89
  "scale": null,
86
90
  "default": "0",
87
91
  "comment": null,
92
+ "collation": null,
88
93
  "enumItems": [],
89
94
  "mappedType": "integer"
90
95
  },
@@ -101,6 +106,7 @@
101
106
  "scale": null,
102
107
  "default": "'[]'",
103
108
  "comment": null,
109
+ "collation": null,
104
110
  "enumItems": [],
105
111
  "mappedType": "json"
106
112
  },
@@ -117,6 +123,7 @@
117
123
  "scale": null,
118
124
  "default": "gen_random_uuid()",
119
125
  "comment": null,
126
+ "collation": null,
120
127
  "enumItems": [],
121
128
  "mappedType": "uuid"
122
129
  },
@@ -133,9 +140,27 @@
133
140
  "scale": null,
134
141
  "default": "true",
135
142
  "comment": null,
143
+ "collation": null,
136
144
  "enumItems": [],
137
145
  "mappedType": "boolean"
138
146
  },
147
+ "logo_url": {
148
+ "name": "logo_url",
149
+ "type": "text",
150
+ "unsigned": false,
151
+ "autoincrement": false,
152
+ "primary": false,
153
+ "nullable": true,
154
+ "unique": false,
155
+ "length": null,
156
+ "precision": null,
157
+ "scale": null,
158
+ "default": null,
159
+ "comment": null,
160
+ "collation": null,
161
+ "enumItems": [],
162
+ "mappedType": "text"
163
+ },
139
164
  "name": {
140
165
  "name": "name",
141
166
  "type": "text",
@@ -149,6 +174,7 @@
149
174
  "scale": null,
150
175
  "default": null,
151
176
  "comment": null,
177
+ "collation": null,
152
178
  "enumItems": [],
153
179
  "mappedType": "text"
154
180
  },
@@ -165,6 +191,7 @@
165
191
  "scale": null,
166
192
  "default": null,
167
193
  "comment": null,
194
+ "collation": null,
168
195
  "enumItems": [],
169
196
  "mappedType": "uuid"
170
197
  },
@@ -181,6 +208,7 @@
181
208
  "scale": null,
182
209
  "default": null,
183
210
  "comment": null,
211
+ "collation": null,
184
212
  "enumItems": [],
185
213
  "mappedType": "uuid"
186
214
  },
@@ -197,6 +225,7 @@
197
225
  "scale": null,
198
226
  "default": null,
199
227
  "comment": null,
228
+ "collation": null,
200
229
  "enumItems": [],
201
230
  "mappedType": "text"
202
231
  },
@@ -213,6 +242,7 @@
213
242
  "scale": null,
214
243
  "default": null,
215
244
  "comment": null,
245
+ "collation": null,
216
246
  "enumItems": [],
217
247
  "mappedType": "uuid"
218
248
  },
@@ -229,6 +259,7 @@
229
259
  "scale": null,
230
260
  "default": null,
231
261
  "comment": null,
262
+ "collation": null,
232
263
  "enumItems": [],
233
264
  "mappedType": "text"
234
265
  },
@@ -245,6 +276,7 @@
245
276
  "scale": null,
246
277
  "default": null,
247
278
  "comment": null,
279
+ "collation": null,
248
280
  "enumItems": [],
249
281
  "mappedType": "datetime"
250
282
  }
@@ -273,6 +305,7 @@
273
305
  }
274
306
  ],
275
307
  "checks": [],
308
+ "triggers": [],
276
309
  "foreignKeys": {
277
310
  "organizations_tenant_id_foreign": {
278
311
  "columnNames": [
@@ -305,6 +338,7 @@
305
338
  "scale": null,
306
339
  "default": null,
307
340
  "comment": null,
341
+ "collation": null,
308
342
  "enumItems": [],
309
343
  "mappedType": "datetime"
310
344
  },
@@ -321,6 +355,7 @@
321
355
  "scale": null,
322
356
  "default": null,
323
357
  "comment": null,
358
+ "collation": null,
324
359
  "enumItems": [],
325
360
  "mappedType": "datetime"
326
361
  },
@@ -337,6 +372,7 @@
337
372
  "scale": null,
338
373
  "default": "gen_random_uuid()",
339
374
  "comment": null,
375
+ "collation": null,
340
376
  "enumItems": [],
341
377
  "mappedType": "uuid"
342
378
  },
@@ -353,6 +389,7 @@
353
389
  "scale": null,
354
390
  "default": "true",
355
391
  "comment": null,
392
+ "collation": null,
356
393
  "enumItems": [],
357
394
  "mappedType": "boolean"
358
395
  },
@@ -369,6 +406,7 @@
369
406
  "scale": null,
370
407
  "default": null,
371
408
  "comment": null,
409
+ "collation": null,
372
410
  "enumItems": [],
373
411
  "mappedType": "text"
374
412
  },
@@ -385,6 +423,7 @@
385
423
  "scale": null,
386
424
  "default": null,
387
425
  "comment": null,
426
+ "collation": null,
388
427
  "enumItems": [],
389
428
  "mappedType": "datetime"
390
429
  }
@@ -402,6 +441,7 @@
402
441
  }
403
442
  ],
404
443
  "checks": [],
444
+ "triggers": [],
405
445
  "foreignKeys": {},
406
446
  "comment": null
407
447
  }
@@ -0,0 +1,13 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ export class Migration20260607222259_directory extends Migration {
4
+
5
+ override up(): void | Promise<void> {
6
+ this.addSql(`alter table "organizations" add "logo_url" text null;`);
7
+ }
8
+
9
+ override down(): void | Promise<void> {
10
+ this.addSql(`alter table "organizations" drop column "logo_url";`);
11
+ }
12
+
13
+ }
@@ -7,6 +7,8 @@
7
7
  // drop every cache entry tagged for that tenant; the TTL is the backstop
8
8
  // for races where the event fires after a request reads the cache.
9
9
 
10
+ import { buildOrgScopeTenantCacheTag } from '@open-mercato/core/modules/directory/utils/organizationScope'
11
+
10
12
  type CacheService = {
11
13
  deleteByTags(tags: string[]): Promise<number>
12
14
  }
@@ -32,7 +34,7 @@ export default async function handle(
32
34
  }
33
35
  if (!cache) return
34
36
  try {
35
- await cache.deleteByTags([`org-scope:tenant:${tenantId}`])
37
+ await cache.deleteByTags([buildOrgScopeTenantCacheTag(tenantId)])
36
38
  } catch {
37
39
  // best-effort; TTL is the backstop.
38
40
  }
@@ -21,9 +21,11 @@ export type OrganizationScope = {
21
21
  // OrganizationScope is a pure function of (userId, tenantId, selectedOrgId,
22
22
  // requestedTenant) between membership changes; caching it bypasses 1
23
23
  // SELECT on `organizations` per CRUD request. TTL is short (60s default)
24
- // to keep staleness bounded for membership/visibility changes. Tag-based
25
- // invalidation kicks the cache when user_organizations or organizations
26
- // mutate (wired via invalidateOrganizationScopeCacheFor).
24
+ // to keep staleness bounded as a backstop. Tag-based invalidation also fires
25
+ // eagerly: per-user entries are dropped by RbacService.invalidateUserCache
26
+ // (every ACL/role grant change goes through it — see buildOrgScopeUserCacheTag)
27
+ // and per-tenant entries by the directory.organization.* subscriber plus
28
+ // RbacService.invalidateTenantCache (role-ACL changes).
27
29
  const ORG_SCOPE_CACHE_KEY_PREFIX = 'org-scope'
28
30
  // Phase 4 default-off until the same readiness probe (`GET /api/customers/people`)
29
31
  // stays green with the cache layer engaged. Set `OM_ORG_SCOPE_CACHE_TTL_MS=60000`
@@ -49,10 +51,23 @@ function buildOrgScopeCacheKey(parts: {
49
51
  return `${ORG_SCOPE_CACHE_KEY_PREFIX}:${parts.userId}:${parts.effectiveTenantId}:${selected}:${requested}`
50
52
  }
51
53
 
54
+ // Tag builders are exported so the modules that own the "this user's scope
55
+ // changed" / "this tenant's org tree changed" signals (auth RBAC invalidation,
56
+ // the directory.organization.* subscriber) can drop the matching cross-request
57
+ // cache entries without re-deriving the tag format. Keeping the format in one
58
+ // place is what lets the TTL be enabled safely (issue #2259).
59
+ export function buildOrgScopeUserCacheTag(userId: string): string {
60
+ return `${ORG_SCOPE_CACHE_KEY_PREFIX}:user:${userId}`
61
+ }
62
+
63
+ export function buildOrgScopeTenantCacheTag(tenantId: string): string {
64
+ return `${ORG_SCOPE_CACHE_KEY_PREFIX}:tenant:${tenantId}`
65
+ }
66
+
52
67
  function buildOrgScopeCacheTags(parts: { userId: string; effectiveTenantId: string }): string[] {
53
68
  return [
54
- `${ORG_SCOPE_CACHE_KEY_PREFIX}:user:${parts.userId}`,
55
- `${ORG_SCOPE_CACHE_KEY_PREFIX}:tenant:${parts.effectiveTenantId}`,
69
+ buildOrgScopeUserCacheTag(parts.userId),
70
+ buildOrgScopeTenantCacheTag(parts.effectiveTenantId),
56
71
  ]
57
72
  }
58
73
 
@@ -82,7 +97,7 @@ export async function invalidateOrganizationScopeCacheForUser(
82
97
  const cache = resolveCacheFromContainer(container)
83
98
  if (!cache?.deleteByTags) return
84
99
  try {
85
- await cache.deleteByTags([`${ORG_SCOPE_CACHE_KEY_PREFIX}:user:${userId}`])
100
+ await cache.deleteByTags([buildOrgScopeUserCacheTag(userId)])
86
101
  } catch (err) {
87
102
  console.warn('[org-scope:cache] invalidate user failed', err)
88
103
  }
@@ -95,12 +110,36 @@ export async function invalidateOrganizationScopeCacheForTenant(
95
110
  const cache = resolveCacheFromContainer(container)
96
111
  if (!cache?.deleteByTags) return
97
112
  try {
98
- await cache.deleteByTags([`${ORG_SCOPE_CACHE_KEY_PREFIX}:tenant:${tenantId}`])
113
+ await cache.deleteByTags([buildOrgScopeTenantCacheTag(tenantId)])
99
114
  } catch (err) {
100
115
  console.warn('[org-scope:cache] invalidate tenant failed', err)
101
116
  }
102
117
  }
103
118
 
119
+ // Issue #2259 — per-request memoization. resolveOrganizationScopeForRequest
120
+ // runs at least twice per CRUD request: once for the route-level feature check
121
+ // (resolveFeatureCheckContext) and once inside the shared factory's withCtx.
122
+ // Those two call sites use different request-scoped DI containers but are handed
123
+ // the SAME Request instance, so memoizing the resolved scope on a WeakMap keyed
124
+ // by that request collapses the duplicate work — and the duplicate
125
+ // `organizations` SELECT — into a single resolution. The inner map is keyed by
126
+ // the same identity tuple as the cross-request cache key, so distinct explicit
127
+ // selectedId/tenant overrides on one request stay independent. There is no
128
+ // staleness risk: the memo lives only for the lifetime of one request and is
129
+ // dropped with the request object by the GC.
130
+ const orgScopeRequestMemo = new WeakMap<object, Map<string, Promise<OrganizationScope>>>()
131
+
132
+ function getRequestScopeMemo(request: unknown): Map<string, Promise<OrganizationScope>> | null {
133
+ if (!request || (typeof request !== 'object' && typeof request !== 'function')) return null
134
+ const key = request as object
135
+ let memo = orgScopeRequestMemo.get(key)
136
+ if (!memo) {
137
+ memo = new Map<string, Promise<OrganizationScope>>()
138
+ orgScopeRequestMemo.set(key, memo)
139
+ }
140
+ return memo
141
+ }
142
+
104
143
  function normalizeOrganizationId(value: unknown): string | null {
105
144
  if (typeof value !== 'string') return null
106
145
  const trimmed = value.trim()
@@ -402,35 +441,51 @@ export async function resolveOrganizationScopeForRequest({
402
441
  })
403
442
  : null
404
443
 
405
- if (cache && cacheKey && typeof cache.get === 'function') {
406
- try {
407
- const cached = await cache.get(cacheKey)
408
- if (isValidCachedScope(cached)) return cached
409
- } catch (err) {
410
- console.warn('[org-scope:cache] read failed', err)
411
- }
444
+ const requestMemo = getRequestScopeMemo(request)
445
+ if (requestMemo && cacheKey) {
446
+ const memoized = requestMemo.get(cacheKey)
447
+ if (memoized) return memoized
412
448
  }
413
449
 
414
- const baseScope = await resolveOrganizationScope({
415
- em,
416
- rbac,
417
- auth: scopedAuth,
418
- selectedId: rawSelected,
419
- tenantId: effectiveTenantId,
420
- })
450
+ const resolveScope = async (): Promise<OrganizationScope> => {
451
+ if (cache && cacheKey && typeof cache.get === 'function') {
452
+ try {
453
+ const cached = await cache.get(cacheKey)
454
+ if (isValidCachedScope(cached)) return cached
455
+ } catch (err) {
456
+ console.warn('[org-scope:cache] read failed', err)
457
+ }
458
+ }
421
459
 
422
- if (cache && cacheKey && userId && typeof cache.set === 'function') {
423
- try {
424
- await cache.set(cacheKey, baseScope, {
425
- ttl: ttlMs,
426
- tags: buildOrgScopeCacheTags({ userId, effectiveTenantId }),
427
- })
428
- } catch (err) {
429
- console.warn('[org-scope:cache] write failed', err)
460
+ const baseScope = await resolveOrganizationScope({
461
+ em,
462
+ rbac,
463
+ auth: scopedAuth,
464
+ selectedId: rawSelected,
465
+ tenantId: effectiveTenantId,
466
+ })
467
+
468
+ if (cache && cacheKey && userId && typeof cache.set === 'function') {
469
+ try {
470
+ await cache.set(cacheKey, baseScope, {
471
+ ttl: ttlMs,
472
+ tags: buildOrgScopeCacheTags({ userId, effectiveTenantId }),
473
+ })
474
+ } catch (err) {
475
+ console.warn('[org-scope:cache] write failed', err)
476
+ }
430
477
  }
478
+
479
+ return baseScope
480
+ }
481
+
482
+ if (requestMemo && cacheKey) {
483
+ const pending = resolveScope()
484
+ requestMemo.set(cacheKey, pending)
485
+ return pending
431
486
  }
432
487
 
433
- return baseScope
488
+ return resolveScope()
434
489
  }
435
490
 
436
491
  export type FeatureCheckContext = {
@@ -13,16 +13,20 @@ export const metadata = {
13
13
  POST: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },
14
14
  }
15
15
 
16
+ const MAX_DEFINITIONS_PER_BATCH = 1000
17
+
16
18
  const batchSchema = z
17
19
  .object({
18
20
  entityId: z.string().regex(/^[a-z0-9_]+:[a-z0-9_]+$/),
19
- definitions: z.array(
20
- upsertCustomFieldDefSchema
21
- .omit({ entityId: true })
22
- .extend({
23
- configJson: z.any().optional(),
24
- })
25
- ),
21
+ definitions: z
22
+ .array(
23
+ upsertCustomFieldDefSchema
24
+ .omit({ entityId: true })
25
+ .extend({
26
+ configJson: z.any().optional(),
27
+ })
28
+ )
29
+ .max(MAX_DEFINITIONS_PER_BATCH),
26
30
  })
27
31
  .extend(customFieldEntityConfigSchema.shape)
28
32
 
@@ -7,6 +7,7 @@ import { getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'
7
7
  import { upsertCustomEntitySchema } from '@open-mercato/core/modules/entities/data/validators'
8
8
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
9
9
  import { isSystemEntitySelectable } from '@open-mercato/shared/lib/entities/system-entities'
10
+ import { SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'
10
11
 
11
12
  export const metadata = {
12
13
  GET: { requireAuth: true },
@@ -107,6 +108,16 @@ export async function POST(req: Request) {
107
108
  const { resolve } = await createRequestContainer()
108
109
  const em = resolve('em') as any
109
110
 
111
+ // A registration for a module-declared, table-backed system entity would flip
112
+ // query-engine classification to doc storage for the whole entity type (#2939's
113
+ // failure mode via another door) — refuse to create one.
114
+ if (isOrmBackedSystemEntityId(em, input.entityId)) {
115
+ return NextResponse.json(
116
+ { error: 'System entities cannot be registered as custom entities', code: SYSTEM_ENTITY_RECORDS_BLOCKED_CODE, entityId: input.entityId },
117
+ { status: 400 },
118
+ )
119
+ }
120
+
110
121
  const where: any = { entityId: input.entityId, organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }
111
122
  let ent = await em.findOne(CustomEntity, where)
112
123
  if (!ent) ent = em.create(CustomEntity, { ...where, createdAt: new Date() })