@open-mercato/core 0.6.6-develop.6543.1.49fadb4c31 → 0.6.6

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 (166) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/api_keys/api/keys/route.js +9 -23
  3. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  4. package/dist/modules/api_keys/cli.js +1 -2
  5. package/dist/modules/api_keys/cli.js.map +2 -2
  6. package/dist/modules/attachments/api/file/[id]/route.js +7 -9
  7. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  8. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -9
  9. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  10. package/dist/modules/attachments/api/route.js +17 -21
  11. package/dist/modules/attachments/api/route.js.map +2 -2
  12. package/dist/modules/auth/api/users/route.js +2 -6
  13. package/dist/modules/auth/api/users/route.js.map +2 -2
  14. package/dist/modules/auth/cli.js +2 -3
  15. package/dist/modules/auth/cli.js.map +2 -2
  16. package/dist/modules/configs/lib/upgrade-actions.js +0 -22
  17. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  18. package/dist/modules/customer_accounts/widgets/injection-table.js +2 -0
  19. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  20. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -7
  21. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  22. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +7 -11
  23. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  24. package/dist/modules/dashboards/api/roles/widgets/route.js +3 -22
  25. package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
  26. package/dist/modules/dashboards/api/users/widgets/route.js +3 -22
  27. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  28. package/dist/modules/dashboards/cli.js +3 -4
  29. package/dist/modules/dashboards/cli.js.map +2 -2
  30. package/dist/modules/dashboards/lib/widgetAssignmentScope.js +1 -9
  31. package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
  32. package/dist/modules/dictionaries/commands/entries.js +14 -7
  33. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  34. package/dist/modules/dictionaries/commands/entry-operations.js +16 -7
  35. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  36. package/dist/modules/dictionaries/commands/factory.js +3 -9
  37. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  38. package/dist/modules/dictionaries/fields/dictionary.js +41 -56
  39. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  40. package/dist/modules/directory/utils/organizationScope.js +3 -10
  41. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  42. package/dist/modules/entities/api/records.js +4 -5
  43. package/dist/modules/entities/api/records.js.map +2 -2
  44. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +0 -1
  45. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
  46. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
  47. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  48. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +0 -1
  49. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
  50. package/dist/modules/entities/cli.js +1 -2
  51. package/dist/modules/entities/cli.js.map +2 -2
  52. package/dist/modules/query_index/api/status.js +8 -2
  53. package/dist/modules/query_index/api/status.js.map +2 -2
  54. package/dist/modules/sales/commands/documents.js +0 -44
  55. package/dist/modules/sales/commands/documents.js.map +2 -2
  56. package/dist/modules/sales/commands/returns.js +27 -2
  57. package/dist/modules/sales/commands/returns.js.map +2 -2
  58. package/dist/modules/sales/components/documents/AddressesSection.js +2 -6
  59. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  60. package/dist/modules/sales/components/documents/ItemsSection.js +0 -1
  61. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  62. package/dist/modules/sales/components/documents/LineItemDialog.js +0 -9
  63. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  64. package/dist/modules/sales/components/documents/SalesDocumentForm.js +0 -44
  65. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  66. package/dist/modules/sales/components/documents/ShipmentDialog.js +38 -59
  67. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  68. package/dist/modules/sales/lib/shipments/snapshots.js +1 -27
  69. package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
  70. package/dist/modules/workflows/frontend/checkout-demo/page.js +8 -1
  71. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  72. package/dist/modules/workflows/lib/activity-executor.js +1 -1
  73. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  74. package/dist/modules/workflows/lib/find-definition.js +18 -2
  75. package/dist/modules/workflows/lib/find-definition.js.map +2 -2
  76. package/dist/modules/workflows/lib/signal-handler.js +3 -2
  77. package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
  78. package/dist/modules/workflows/lib/step-handler.js +4 -8
  79. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  80. package/dist/modules/workflows/lib/task-handler.js +3 -5
  81. package/dist/modules/workflows/lib/task-handler.js.map +2 -2
  82. package/dist/modules/workflows/lib/timer-handler.js +2 -1
  83. package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
  84. package/dist/modules/workflows/lib/transition-handler.js +5 -13
  85. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  86. package/dist/modules/workflows/lib/workflow-executor.js +11 -9
  87. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  88. package/dist/modules/workflows/migrations/Migration20260715120000.js +68 -0
  89. package/dist/modules/workflows/migrations/Migration20260715120000.js.map +7 -0
  90. package/dist/modules/workflows/migrations/Migration20260716120000.js +24 -0
  91. package/dist/modules/workflows/migrations/Migration20260716120000.js.map +7 -0
  92. package/dist/modules/workflows/workflows.js +2 -0
  93. package/dist/modules/workflows/workflows.js.map +2 -2
  94. package/package.json +8 -9
  95. package/src/modules/api_keys/api/keys/route.ts +9 -27
  96. package/src/modules/api_keys/cli.ts +3 -2
  97. package/src/modules/attachments/api/file/[id]/route.ts +7 -11
  98. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -11
  99. package/src/modules/attachments/api/route.ts +21 -26
  100. package/src/modules/auth/api/users/route.ts +2 -11
  101. package/src/modules/auth/cli.ts +2 -3
  102. package/src/modules/configs/i18n/de.json +1 -5
  103. package/src/modules/configs/i18n/en.json +1 -5
  104. package/src/modules/configs/i18n/es.json +1 -5
  105. package/src/modules/configs/i18n/pl.json +1 -5
  106. package/src/modules/configs/lib/upgrade-actions.ts +0 -24
  107. package/src/modules/customer_accounts/AGENTS.md +6 -8
  108. package/src/modules/customer_accounts/widgets/injection-table.ts +2 -0
  109. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -11
  110. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +7 -13
  111. package/src/modules/dashboards/api/roles/widgets/route.ts +3 -22
  112. package/src/modules/dashboards/api/users/widgets/route.ts +3 -22
  113. package/src/modules/dashboards/cli.ts +9 -4
  114. package/src/modules/dashboards/lib/widgetAssignmentScope.ts +0 -18
  115. package/src/modules/dictionaries/commands/entries.ts +16 -7
  116. package/src/modules/dictionaries/commands/entry-operations.ts +18 -8
  117. package/src/modules/dictionaries/commands/factory.ts +2 -8
  118. package/src/modules/dictionaries/fields/dictionary.tsx +32 -55
  119. package/src/modules/dictionaries/i18n/de.json +0 -2
  120. package/src/modules/dictionaries/i18n/en.json +0 -2
  121. package/src/modules/dictionaries/i18n/es.json +0 -2
  122. package/src/modules/dictionaries/i18n/pl.json +0 -2
  123. package/src/modules/directory/utils/organizationScope.ts +2 -36
  124. package/src/modules/entities/api/records.ts +7 -5
  125. package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +0 -1
  126. package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +0 -1
  127. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +49 -51
  128. package/src/modules/entities/cli.ts +1 -2
  129. package/src/modules/entities/i18n/de.json +0 -34
  130. package/src/modules/entities/i18n/en.json +0 -34
  131. package/src/modules/entities/i18n/es.json +0 -34
  132. package/src/modules/entities/i18n/pl.json +0 -34
  133. package/src/modules/query_index/api/status.ts +8 -2
  134. package/src/modules/sales/commands/documents.ts +0 -65
  135. package/src/modules/sales/commands/returns.ts +32 -2
  136. package/src/modules/sales/components/documents/AddressesSection.tsx +2 -6
  137. package/src/modules/sales/components/documents/ItemsSection.tsx +0 -5
  138. package/src/modules/sales/components/documents/LineItemDialog.tsx +0 -10
  139. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +0 -46
  140. package/src/modules/sales/components/documents/ShipmentDialog.tsx +40 -65
  141. package/src/modules/sales/i18n/de.json +0 -4
  142. package/src/modules/sales/i18n/en.json +0 -4
  143. package/src/modules/sales/i18n/es.json +0 -4
  144. package/src/modules/sales/i18n/pl.json +0 -4
  145. package/src/modules/sales/lib/shipments/snapshots.ts +0 -30
  146. package/src/modules/workflows/frontend/checkout-demo/page.tsx +12 -1
  147. package/src/modules/workflows/lib/activity-executor.ts +22 -2
  148. package/src/modules/workflows/lib/find-definition.ts +53 -1
  149. package/src/modules/workflows/lib/signal-handler.ts +5 -4
  150. package/src/modules/workflows/lib/step-handler.ts +4 -8
  151. package/src/modules/workflows/lib/task-handler.ts +2 -4
  152. package/src/modules/workflows/lib/timer-handler.ts +3 -2
  153. package/src/modules/workflows/lib/transition-handler.ts +5 -13
  154. package/src/modules/workflows/lib/workflow-executor.ts +11 -8
  155. package/src/modules/workflows/migrations/Migration20260715120000.ts +114 -0
  156. package/src/modules/workflows/migrations/Migration20260716120000.ts +57 -0
  157. package/src/modules/workflows/workflows.ts +2 -0
  158. package/dist/modules/attachments/lib/reconcileOrganization.js +0 -110
  159. package/dist/modules/attachments/lib/reconcileOrganization.js.map +0 -7
  160. package/dist/modules/attachments/lib/requestScope.js +0 -10
  161. package/dist/modules/attachments/lib/requestScope.js.map +0 -7
  162. package/dist/modules/customers/components/detail/useDealsAccess.js +0 -16
  163. package/dist/modules/customers/components/detail/useDealsAccess.js.map +0 -7
  164. package/src/modules/attachments/lib/reconcileOrganization.ts +0 -188
  165. package/src/modules/attachments/lib/requestScope.ts +0 -27
  166. package/src/modules/customers/components/detail/useDealsAccess.ts +0 -19
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/workflows/migrations/Migration20260716120000.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations'\n\nconst CODE_WORKFLOW_ID = 'workflows.checkout-demo'\n\n/**\n * Retires the persisted checkout-demo seed rows so runtime resolves the\n * maintained CODE definition instead (issue #4211).\n *\n * Background: `workflows.checkout-demo` is now code-defined\n * (`packages/core/src/modules/workflows/workflows.ts`), and only the code\n * definition forwards the cart's line items + shipping/tax adjustments to\n * `POST /api/sales/orders`. But existing tenants still carry a persisted\n * `workflow_definitions` row from the old seed \u2014 originally seeded under ids\n * like `checkout_simple_v1` / names like `Simple Checkout Flow` and later\n * `Checkout with Payment Webhook`, then renamed to `workflows.checkout-demo`\n * by Migration20260428102318. Its `create_order` body never sent `lines`, and\n * because `findWorkflowDefinition` resolves the DB row first, that stale row\n * SHADOWS the code definition \u2014 so orders come out with zero line items and\n * $0.00 totals. Patching the persisted body is unreliable: the seed's name and\n * body shape vary across historical versions, so a fingerprint on either misses\n * whole cohorts of tenants (the defect QA hit).\n *\n * Instead, soft-delete the seeded rows. This is body/name-agnostic, so it fixes\n * every historical seed variant:\n * - New instances: `findWorkflowDefinition` filters `deleted_at IS NULL`, so\n * the retired row is skipped and the code definition is used (correct body).\n * - In-flight instances: `findDefinitionForInstance` loads the definition by\n * `id` with no soft-delete filter (WorkflowDefinition has no global filter),\n * so instances that reference the row keep resolving and advancing.\n *\n * Scope is limited to `code_workflow_id IS NULL` rows \u2014 user customizations of\n * the code definition set `code_workflow_id` and MUST NOT be retired. The\n * `deleted_at IS NULL` guard makes the migration idempotent.\n */\nexport function buildRetireSeededCheckoutDemoSql(): string {\n return `\n update \"workflow_definitions\" as \"wf\"\n set \"deleted_at\" = current_timestamp,\n \"updated_at\" = current_timestamp\n where \"wf\".\"workflow_id\" = '${CODE_WORKFLOW_ID}'\n and \"wf\".\"code_workflow_id\" is null\n and \"wf\".\"deleted_at\" is null;\n `.trim()\n}\n\nexport class Migration20260716120000 extends Migration {\n override async up(): Promise<void> {\n this.addSql(buildRetireSeededCheckoutDemoSql())\n }\n\n override async down(): Promise<void> {\n // Forward-only. Restoring `deleted_at = null` would re-shadow the maintained\n // code definition with the stale seed body and re-introduce zero-line orders\n // (#4211). The code definition supersedes the seed, so there is nothing to\n // restore.\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAE1B,MAAM,mBAAmB;AAgClB,SAAS,mCAA2C;AACzD,SAAO;AAAA;AAAA;AAAA;AAAA,kCAIyB,gBAAgB;AAAA;AAAA;AAAA,IAG9C,KAAK;AACT;AAEO,MAAM,gCAAgC,UAAU;AAAA,EACrD,MAAe,KAAoB;AACjC,SAAK,OAAO,iCAAiC,CAAC;AAAA,EAChD;AAAA,EAEA,MAAe,OAAsB;AAAA,EAKrC;AACF;",
6
+ "names": []
7
+ }
@@ -222,6 +222,8 @@ const checkoutDemo = defineWorkflow({
222
222
  customerEntityId: "{{context.customer.id}}",
223
223
  currencyCode: "{{context.cart.currency}}",
224
224
  placedAt: "{{now}}",
225
+ lines: "{{context.cart.orderLines}}",
226
+ adjustments: "{{context.cart.orderAdjustments}}",
225
227
  grandTotalGrossAmount: "{{context.cart.total}}"
226
228
  },
227
229
  validateTenantMatch: true
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/workflows/workflows.ts"],
4
- "sourcesContent": ["/**\n * Workflows Module \u2014 Code-Based Workflow Definitions\n *\n * Demo workflows shipped with the workflows module.\n * These are auto-discovered by the generator and loaded into the in-memory registry.\n */\n\nimport { defineWorkflow, createWorkflowsModuleConfig } from '@open-mercato/shared/modules/workflows'\n\n// ============================================================================\n// Simple Approval Workflow\n// ============================================================================\n\nconst simpleApproval = defineWorkflow({\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n description: 'Basic three-step approval workflow: Start \u2192 Approval \u2192 End',\n metadata: { category: 'Testing', tags: ['test', 'approval', 'simple'], icon: 'check-circle' },\n steps: [\n { stepId: 'start', stepName: 'Start', stepType: 'START', description: 'Initialize workflow' },\n {\n stepId: 'approval',\n stepName: 'Pending Approval',\n stepType: 'USER_TASK',\n description: 'Approve or reject request',\n userTaskConfig: { assignedTo: 'approver', slaDuration: 'PT1H' },\n },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Workflow complete' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_approval',\n transitionName: 'Submit for Approval',\n fromStepId: 'start',\n toStepId: 'approval',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'notify_approver',\n activityName: 'Notify Approver',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.approver.email}}',\n subject: 'Approval Required: {{context.request.title}}',\n template: 'approval_request',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n requester: '{{context.requester.name}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n {\n activityId: 'emit_approval_requested',\n activityName: 'Emit Approval Requested Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.requested',\n payload: { requestId: '{{context.request.id}}', workflowInstanceId: '{{workflow.instanceId}}' },\n },\n async: true,\n },\n ],\n },\n {\n transitionId: 'approval_to_end',\n transitionName: 'Approval Decision',\n fromStepId: 'approval',\n toStepId: 'end',\n trigger: 'manual',\n priority: 100,\n activities: [\n {\n activityId: 'notify_requester',\n activityName: 'Notify Requester of Decision',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.requester.email}}',\n subject: 'Approval Decision: {{context.request.title}}',\n template: 'approval_decision',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n decision: '{{task.result.approved}}',\n comments: '{{task.result.comments}}',\n approver: '{{task.completedBy}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n },\n {\n activityId: 'emit_approval_completed',\n activityName: 'Emit Approval Completed Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.completed',\n payload: {\n requestId: '{{context.request.id}}',\n approved: '{{task.result.approved}}',\n approvedBy: '{{task.completedBy}}',\n timestamp: '{{now}}',\n },\n },\n async: true,\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Checkout Demo Workflow\n// ============================================================================\n\nconst checkoutDemo = defineWorkflow({\n workflowId: 'workflows.checkout-demo',\n workflowName: 'Checkout with Payment Webhook',\n description: 'Realistic checkout workflow with signal-based payment confirmation',\n metadata: { category: 'E-commerce', tags: ['checkout', 'demo', 'payments', 'signals'], icon: 'ShoppingCart' },\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n description: 'Checkout process initiated',\n preConditions: [{\n ruleId: 'workflow_checkout_cart_not_empty',\n required: true,\n validationMessage: {\n en: 'Your shopping cart is empty. Please add items before starting checkout.',\n pl: 'Tw\u00F3j koszyk jest pusty. Dodaj produkty przed rozpocz\u0119ciem zakupu.',\n },\n }],\n },\n { stepId: 'cart_validation', stepName: 'Cart Validation', stepType: 'AUTOMATED', description: 'Validate cart has items and inventory is available', timeout: 'PT10S' },\n {\n stepId: 'customer_info',\n stepName: 'Customer Information',\n stepType: 'USER_TASK',\n description: 'Collect customer shipping and contact information',\n userTaskConfig: {\n formSchema: {\n type: 'object',\n properties: {\n fullName: { type: 'string', title: 'Full Name', description: \"Customer's full legal name\" },\n email: { type: 'string', format: 'email', title: 'Email Address', description: 'Email for order confirmation' },\n phone: { type: 'string', title: 'Phone Number', description: 'Contact number for delivery' },\n comment: { type: 'string', title: 'Order Comment', maxLength: 500, description: 'Special instructions or notes' },\n },\n required: ['fullName', 'email'],\n },\n slaDuration: 'PT24H',\n },\n },\n { stepId: 'payment_initiation', stepName: 'Initiate Payment', stepType: 'AUTOMATED', description: 'Send payment request to payment provider', timeout: 'PT10S' },\n {\n stepId: 'wait_payment_confirmation',\n stepName: 'Wait for Payment Confirmation',\n stepType: 'WAIT_FOR_SIGNAL',\n description: 'Waiting for payment provider webhook confirmation',\n signalConfig: { signalName: 'payment_confirmed', timeout: 'PT5M' },\n },\n { stepId: 'order_confirmation', stepName: 'Order Confirmation', stepType: 'AUTOMATED', description: 'Create order record and send confirmation', timeout: 'PT15S' },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Checkout completed successfully' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_cart',\n transitionName: 'Initialize Checkout',\n fromStepId: 'start',\n toStepId: 'cart_validation',\n trigger: 'auto',\n priority: 100,\n activities: [{\n activityId: 'log_checkout_start',\n activityName: 'Log Checkout Start',\n activityType: 'EMIT_EVENT',\n config: {\n eventName: 'checkout.started',\n payload: { customerId: '{{context.customerId}}', cartId: '{{context.cartId}}', currency: '{{context.currency}}', timestamp: '{{now}}' },\n },\n async: false,\n }],\n },\n {\n transitionId: 'cart_to_customer_info',\n transitionName: 'Collect Customer Information',\n fromStepId: 'cart_validation',\n toStepId: 'customer_info',\n trigger: 'auto',\n priority: 100,\n preConditions: [{ ruleId: 'workflow_checkout_cart_not_empty', required: true }],\n },\n {\n transitionId: 'customer_info_to_payment',\n transitionName: 'Initiate Payment',\n fromStepId: 'customer_info',\n toStepId: 'payment_initiation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'payment_to_wait_confirmation',\n transitionName: 'Wait for Confirmation',\n fromStepId: 'payment_initiation',\n toStepId: 'wait_payment_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_order',\n transitionName: 'Payment Confirmed',\n fromStepId: 'wait_payment_confirmation',\n toStepId: 'order_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_end',\n transitionName: 'Finalize Order',\n fromStepId: 'order_confirmation',\n toStepId: 'end',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'create_order',\n activityName: 'Create Order Record',\n activityType: 'CALL_API',\n config: {\n endpoint: '/api/sales/orders',\n method: 'POST',\n body: {\n customerEntityId: '{{context.customer.id}}',\n currencyCode: '{{context.cart.currency}}',\n placedAt: '{{now}}',\n grandTotalGrossAmount: '{{context.cart.total}}',\n },\n validateTenantMatch: true,\n },\n timeout: 'PT10S',\n async: false,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 10000 },\n },\n {\n activityId: 'send_confirmation_email',\n activityName: 'Send Order Confirmation Email',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.customer.email}}',\n subject: 'Order Confirmation',\n template: 'order_confirmation',\n data: { customerName: '{{context.customer.name}}', total: '{{context.cart.total}}' },\n },\n timeout: 'PT15S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Module Export\n// ============================================================================\n\nexport const workflowsConfig = createWorkflowsModuleConfig({\n moduleId: 'workflows',\n workflows: [simpleApproval, checkoutDemo],\n})\n\nexport default workflowsConfig\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Workflows Module \u2014 Code-Based Workflow Definitions\n *\n * Demo workflows shipped with the workflows module.\n * These are auto-discovered by the generator and loaded into the in-memory registry.\n */\n\nimport { defineWorkflow, createWorkflowsModuleConfig } from '@open-mercato/shared/modules/workflows'\n\n// ============================================================================\n// Simple Approval Workflow\n// ============================================================================\n\nconst simpleApproval = defineWorkflow({\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n description: 'Basic three-step approval workflow: Start \u2192 Approval \u2192 End',\n metadata: { category: 'Testing', tags: ['test', 'approval', 'simple'], icon: 'check-circle' },\n steps: [\n { stepId: 'start', stepName: 'Start', stepType: 'START', description: 'Initialize workflow' },\n {\n stepId: 'approval',\n stepName: 'Pending Approval',\n stepType: 'USER_TASK',\n description: 'Approve or reject request',\n userTaskConfig: { assignedTo: 'approver', slaDuration: 'PT1H' },\n },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Workflow complete' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_approval',\n transitionName: 'Submit for Approval',\n fromStepId: 'start',\n toStepId: 'approval',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'notify_approver',\n activityName: 'Notify Approver',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.approver.email}}',\n subject: 'Approval Required: {{context.request.title}}',\n template: 'approval_request',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n requester: '{{context.requester.name}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n {\n activityId: 'emit_approval_requested',\n activityName: 'Emit Approval Requested Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.requested',\n payload: { requestId: '{{context.request.id}}', workflowInstanceId: '{{workflow.instanceId}}' },\n },\n async: true,\n },\n ],\n },\n {\n transitionId: 'approval_to_end',\n transitionName: 'Approval Decision',\n fromStepId: 'approval',\n toStepId: 'end',\n trigger: 'manual',\n priority: 100,\n activities: [\n {\n activityId: 'notify_requester',\n activityName: 'Notify Requester of Decision',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.requester.email}}',\n subject: 'Approval Decision: {{context.request.title}}',\n template: 'approval_decision',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n decision: '{{task.result.approved}}',\n comments: '{{task.result.comments}}',\n approver: '{{task.completedBy}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n },\n {\n activityId: 'emit_approval_completed',\n activityName: 'Emit Approval Completed Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.completed',\n payload: {\n requestId: '{{context.request.id}}',\n approved: '{{task.result.approved}}',\n approvedBy: '{{task.completedBy}}',\n timestamp: '{{now}}',\n },\n },\n async: true,\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Checkout Demo Workflow\n// ============================================================================\n\nconst checkoutDemo = defineWorkflow({\n workflowId: 'workflows.checkout-demo',\n workflowName: 'Checkout with Payment Webhook',\n description: 'Realistic checkout workflow with signal-based payment confirmation',\n metadata: { category: 'E-commerce', tags: ['checkout', 'demo', 'payments', 'signals'], icon: 'ShoppingCart' },\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n description: 'Checkout process initiated',\n preConditions: [{\n ruleId: 'workflow_checkout_cart_not_empty',\n required: true,\n validationMessage: {\n en: 'Your shopping cart is empty. Please add items before starting checkout.',\n pl: 'Tw\u00F3j koszyk jest pusty. Dodaj produkty przed rozpocz\u0119ciem zakupu.',\n },\n }],\n },\n { stepId: 'cart_validation', stepName: 'Cart Validation', stepType: 'AUTOMATED', description: 'Validate cart has items and inventory is available', timeout: 'PT10S' },\n {\n stepId: 'customer_info',\n stepName: 'Customer Information',\n stepType: 'USER_TASK',\n description: 'Collect customer shipping and contact information',\n userTaskConfig: {\n formSchema: {\n type: 'object',\n properties: {\n fullName: { type: 'string', title: 'Full Name', description: \"Customer's full legal name\" },\n email: { type: 'string', format: 'email', title: 'Email Address', description: 'Email for order confirmation' },\n phone: { type: 'string', title: 'Phone Number', description: 'Contact number for delivery' },\n comment: { type: 'string', title: 'Order Comment', maxLength: 500, description: 'Special instructions or notes' },\n },\n required: ['fullName', 'email'],\n },\n slaDuration: 'PT24H',\n },\n },\n { stepId: 'payment_initiation', stepName: 'Initiate Payment', stepType: 'AUTOMATED', description: 'Send payment request to payment provider', timeout: 'PT10S' },\n {\n stepId: 'wait_payment_confirmation',\n stepName: 'Wait for Payment Confirmation',\n stepType: 'WAIT_FOR_SIGNAL',\n description: 'Waiting for payment provider webhook confirmation',\n signalConfig: { signalName: 'payment_confirmed', timeout: 'PT5M' },\n },\n { stepId: 'order_confirmation', stepName: 'Order Confirmation', stepType: 'AUTOMATED', description: 'Create order record and send confirmation', timeout: 'PT15S' },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Checkout completed successfully' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_cart',\n transitionName: 'Initialize Checkout',\n fromStepId: 'start',\n toStepId: 'cart_validation',\n trigger: 'auto',\n priority: 100,\n activities: [{\n activityId: 'log_checkout_start',\n activityName: 'Log Checkout Start',\n activityType: 'EMIT_EVENT',\n config: {\n eventName: 'checkout.started',\n payload: { customerId: '{{context.customerId}}', cartId: '{{context.cartId}}', currency: '{{context.currency}}', timestamp: '{{now}}' },\n },\n async: false,\n }],\n },\n {\n transitionId: 'cart_to_customer_info',\n transitionName: 'Collect Customer Information',\n fromStepId: 'cart_validation',\n toStepId: 'customer_info',\n trigger: 'auto',\n priority: 100,\n preConditions: [{ ruleId: 'workflow_checkout_cart_not_empty', required: true }],\n },\n {\n transitionId: 'customer_info_to_payment',\n transitionName: 'Initiate Payment',\n fromStepId: 'customer_info',\n toStepId: 'payment_initiation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'payment_to_wait_confirmation',\n transitionName: 'Wait for Confirmation',\n fromStepId: 'payment_initiation',\n toStepId: 'wait_payment_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_order',\n transitionName: 'Payment Confirmed',\n fromStepId: 'wait_payment_confirmation',\n toStepId: 'order_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_end',\n transitionName: 'Finalize Order',\n fromStepId: 'order_confirmation',\n toStepId: 'end',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'create_order',\n activityName: 'Create Order Record',\n activityType: 'CALL_API',\n config: {\n endpoint: '/api/sales/orders',\n method: 'POST',\n body: {\n customerEntityId: '{{context.customer.id}}',\n currencyCode: '{{context.cart.currency}}',\n placedAt: '{{now}}',\n lines: '{{context.cart.orderLines}}',\n adjustments: '{{context.cart.orderAdjustments}}',\n grandTotalGrossAmount: '{{context.cart.total}}',\n },\n validateTenantMatch: true,\n },\n timeout: 'PT10S',\n async: false,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 10000 },\n },\n {\n activityId: 'send_confirmation_email',\n activityName: 'Send Order Confirmation Email',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.customer.email}}',\n subject: 'Order Confirmation',\n template: 'order_confirmation',\n data: { customerName: '{{context.customer.name}}', total: '{{context.cart.total}}' },\n },\n timeout: 'PT15S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Module Export\n// ============================================================================\n\nexport const workflowsConfig = createWorkflowsModuleConfig({\n moduleId: 'workflows',\n workflows: [simpleApproval, checkoutDemo],\n})\n\nexport default workflowsConfig\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6543.1.49fadb4c31",
3
+ "version": "0.6.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -253,16 +253,16 @@
253
253
  "zod": "^4.4.3"
254
254
  },
255
255
  "peerDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6543.1.49fadb4c31",
257
- "@open-mercato/shared": "0.6.6-develop.6543.1.49fadb4c31",
258
- "@open-mercato/ui": "0.6.6-develop.6543.1.49fadb4c31",
256
+ "@open-mercato/ai-assistant": "0.6.6",
257
+ "@open-mercato/shared": "0.6.6",
258
+ "@open-mercato/ui": "0.6.6",
259
259
  "react": "^19.0.0",
260
260
  "react-dom": "^19.0.0"
261
261
  },
262
262
  "devDependencies": {
263
- "@open-mercato/ai-assistant": "0.6.6-develop.6543.1.49fadb4c31",
264
- "@open-mercato/shared": "0.6.6-develop.6543.1.49fadb4c31",
265
- "@open-mercato/ui": "0.6.6-develop.6543.1.49fadb4c31",
263
+ "@open-mercato/ai-assistant": "0.6.6",
264
+ "@open-mercato/shared": "0.6.6",
265
+ "@open-mercato/ui": "0.6.6",
266
266
  "@testing-library/dom": "^10.4.1",
267
267
  "@testing-library/jest-dom": "^6.9.1",
268
268
  "@testing-library/react": "^16.3.1",
@@ -285,6 +285,5 @@
285
285
  "type": "git",
286
286
  "url": "https://github.com/open-mercato/open-mercato",
287
287
  "directory": "packages/core"
288
- },
289
- "stableVersion": "0.6.5"
288
+ }
290
289
  }
@@ -13,8 +13,6 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
13
13
  import { enforceTenantSelection, resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
14
14
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
15
15
  import { assertActorCanGrantRoles } from '@open-mercato/core/modules/auth/lib/grantChecks'
16
- import { isOrganizationAccessAllowed } from '@open-mercato/shared/lib/auth/organizationAccess'
17
- import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
18
16
 
19
17
  type ApiKeyCrudCtx = CrudCtx & {
20
18
  __apiKeySecret?: { secret: string; prefix: string }
@@ -324,34 +322,18 @@ const crud = makeCrudRoute<
324
322
  const { translate } = await resolveTranslations()
325
323
  if (!auth?.tenantId) throw json({ error: translate('api_keys.errors.tenantRequired', 'Tenant context required') }, { status: 400 })
326
324
  const em = (ctx.container.resolve('em') as EntityManager)
325
+ const record = await em.findOne(ApiKey, { id, deletedAt: null })
326
+ if (!record) throw json({ error: translate('api_keys.errors.notFound', 'Not found') }, { status: 404 })
327
327
  const scopedCtx = ctx as ApiKeyCrudCtx
328
328
  const isSuperAdmin = await resolveIsSuperAdmin(scopedCtx)
329
- const allowedIds = ctx.organizationScope?.allowedIds ?? null
330
- const recordFilter: FilterQuery<ApiKey> = {
331
- id,
332
- tenantId: auth.tenantId,
333
- deletedAt: null,
334
- }
335
- if (!isSuperAdmin && Array.isArray(allowedIds)) {
336
- recordFilter.organizationId = { $in: allowedIds }
329
+ if (!isSuperAdmin && record.tenantId && record.tenantId !== auth.tenantId) {
330
+ throw json({ error: translate('api_keys.errors.forbidden', 'Forbidden') }, { status: 403 })
337
331
  }
338
- const record = await findOneWithDecryption(
339
- em,
340
- ApiKey,
341
- recordFilter,
342
- undefined,
343
- { tenantId: auth.tenantId, organizationId: null },
344
- )
345
- if (
346
- !record ||
347
- record.tenantId !== auth.tenantId ||
348
- !isOrganizationAccessAllowed({
349
- isSuperAdmin,
350
- allowedOrganizationIds: allowedIds,
351
- targetOrganizationId: record.organizationId ?? null,
352
- })
353
- ) {
354
- throw json({ error: translate('api_keys.errors.notFound', 'Not found') }, { status: 404 })
332
+ const allowedIds = ctx.organizationScope?.allowedIds ?? null
333
+ if (record.organizationId && Array.isArray(allowedIds) && allowedIds.length > 0) {
334
+ if (!allowedIds.includes(record.organizationId)) {
335
+ throw json({ error: translate('api_keys.errors.organizationOutOfScope', 'Organization out of scope') }, { status: 403 })
336
+ }
355
337
  }
356
338
  scopedCtx.__apiKeyOrganizationId = record.organizationId ?? null
357
339
  },
@@ -3,7 +3,6 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
3
3
  import { createApiKey } from './services/apiKeyService'
4
4
  import { Organization } from '@open-mercato/core/modules/directory/data/entities'
5
5
  import { Role } from '@open-mercato/core/modules/auth/data/entities'
6
- import { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'
7
6
 
8
7
  function parseArgs(rest: string[]): Record<string, string> {
9
8
  const args: Record<string, string> = {}
@@ -48,7 +47,9 @@ const addApiKey: ModuleCli = {
48
47
  return
49
48
  }
50
49
 
51
- const roleInputs = parseCommaSeparatedList(rolesCsv)
50
+ const roleInputs = rolesCsv
51
+ ? rolesCsv.split(',').map((value) => value.trim()).filter(Boolean)
52
+ : []
52
53
  const roleIds: string[] = []
53
54
  if (roleInputs.length) {
54
55
  for (const token of roleInputs) {
@@ -15,7 +15,6 @@ import {
15
15
  canRenderInlineAttachment,
16
16
  } from "@open-mercato/core/modules/attachments/lib/security";
17
17
  import { StorageDriverFactory } from '../../../lib/drivers';
18
- import { resolveAttachmentOrganizationId } from '@open-mercato/core/modules/attachments/lib/requestScope';
19
18
 
20
19
  export const metadata = {
21
20
  GET: { requireAuth: false },
@@ -33,19 +32,16 @@ export async function GET(
33
32
  );
34
33
  }
35
34
  const auth = await getAuthFromRequest(req);
36
- const container = await createRequestContainer();
37
- const em = container.resolve("em") as EntityManager;
35
+ const { resolve } = await createRequestContainer();
36
+ const em = resolve("em") as EntityManager;
38
37
  const storageDriverFactory =
39
- (container.resolve("storageDriverFactory") as StorageDriverFactory | null) ??
38
+ (resolve("storageDriverFactory") as StorageDriverFactory | null) ??
40
39
  new StorageDriverFactory(em);
41
40
 
42
- const scopedAuth = auth
43
- ? { ...auth, orgId: await resolveAttachmentOrganizationId(container, auth, req) }
44
- : auth;
45
41
  const findFilter: Record<string, unknown> = { id };
46
- if (scopedAuth && !isSuperAdminAuth(scopedAuth)) {
47
- if (scopedAuth.tenantId) findFilter.tenantId = scopedAuth.tenantId;
48
- if (scopedAuth.orgId) findFilter.organizationId = scopedAuth.orgId;
42
+ if (auth && !isSuperAdminAuth(auth)) {
43
+ if (auth.tenantId) findFilter.tenantId = auth.tenantId;
44
+ if (auth.orgId) findFilter.organizationId = auth.orgId;
49
45
  }
50
46
  const attachment = await em.findOne(Attachment, findFilter);
51
47
  if (!attachment) {
@@ -64,7 +60,7 @@ export async function GET(
64
60
  );
65
61
  }
66
62
 
67
- const access = checkAttachmentAccess(scopedAuth, attachment, partition);
63
+ const access = checkAttachmentAccess(auth, attachment, partition);
68
64
  if (!access.ok) {
69
65
  const message = access.status === 401 ? "Unauthorized" : "Forbidden";
70
66
  return NextResponse.json({ error: message }, { status: access.status });
@@ -20,7 +20,6 @@ import {
20
20
  validateImageMagicBytes,
21
21
  } from '@open-mercato/core/modules/attachments/lib/imageSafety'
22
22
  import { StorageDriverFactory } from '../../../../lib/drivers'
23
- import { resolveAttachmentOrganizationId } from '@open-mercato/core/modules/attachments/lib/requestScope'
24
23
  import { createLogger } from '@open-mercato/shared/lib/logger'
25
24
 
26
25
  const logger = createLogger('attachments').child({ component: 'image' })
@@ -52,18 +51,15 @@ export async function GET(
52
51
  }
53
52
  const { width, height, cropType } = parsedQuery.data
54
53
 
55
- const container = await createRequestContainer()
56
- const em = container.resolve('em') as EntityManager
54
+ const { resolve } = await createRequestContainer()
55
+ const em = resolve('em') as EntityManager
57
56
  const storageDriverFactory =
58
- (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
57
+ (resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
59
58
 
60
- const scopedAuth = auth
61
- ? { ...auth, orgId: await resolveAttachmentOrganizationId(container, auth, req) }
62
- : auth
63
59
  const findFilter: Record<string, unknown> = { id }
64
- if (scopedAuth && !isSuperAdminAuth(scopedAuth)) {
65
- if (scopedAuth.tenantId) findFilter.tenantId = scopedAuth.tenantId
66
- if (scopedAuth.orgId) findFilter.organizationId = scopedAuth.orgId
60
+ if (auth && !isSuperAdminAuth(auth)) {
61
+ if (auth.tenantId) findFilter.tenantId = auth.tenantId
62
+ if (auth.orgId) findFilter.organizationId = auth.orgId
67
63
  }
68
64
  const attachment = await em.findOne(Attachment, findFilter)
69
65
  if (!attachment) {
@@ -76,7 +72,7 @@ export async function GET(
76
72
  if (!partition) {
77
73
  return NextResponse.json({ error: 'Partition misconfigured' }, { status: 500 })
78
74
  }
79
- const access = checkAttachmentAccess(scopedAuth, attachment, partition)
75
+ const access = checkAttachmentAccess(auth, attachment, partition)
80
76
  if (!access.ok) {
81
77
  const message = access.status === 401 ? 'Unauthorized' : 'Forbidden'
82
78
  return NextResponse.json({ error: message }, { status: access.status })
@@ -13,7 +13,6 @@ import { StorageDriverFactory } from '../lib/drivers'
13
13
  import { OcrService, shouldUseLlmOcr } from '../lib/ocrService'
14
14
  import { clearAttachmentThumbnailCache } from '../lib/thumbnailCache'
15
15
  import { assertAttachmentScopeInvariant } from '../lib/access'
16
- import { resolveAttachmentOrganizationId } from '../lib/requestScope'
17
16
  import {
18
17
  mergeAttachmentMetadata,
19
18
  normalizeAttachmentAssignments,
@@ -199,11 +198,10 @@ export async function GET(req: Request) {
199
198
  }
200
199
  const { entityId, recordId, page, pageSize } = parsedQuery.data
201
200
 
202
- const container = await createRequestContainer()
203
- const em = container.resolve('em') as EntityManager
204
- const orgId = await resolveAttachmentOrganizationId(container, auth, req)
201
+ const { resolve } = await createRequestContainer()
202
+ const em = resolve('em') as EntityManager
205
203
  const filter: Record<string, unknown> = { entityId, recordId, tenantId: auth.tenantId! }
206
- if (orgId) filter.organizationId = orgId
204
+ if (auth.orgId) filter.organizationId = auth.orgId
207
205
  const orderBy: Record<string, 'ASC' | 'DESC'> = { createdAt: 'DESC' }
208
206
  const usePaging = typeof page === 'number' && typeof pageSize === 'number'
209
207
  const total = usePaging ? await em.count(Attachment, filter) : null
@@ -226,7 +224,7 @@ export async function GET(req: Request) {
226
224
  },
227
225
  {
228
226
  tenantId: auth.tenantId ?? null,
229
- organizationId: orgId ?? null,
227
+ organizationId: auth.orgId ?? null,
230
228
  },
231
229
  )
232
230
  return NextResponse.json({
@@ -277,6 +275,7 @@ export async function POST(req: Request) {
277
275
  }, { status: 400 })
278
276
  }
279
277
  const tenantId = auth.tenantId
278
+ const orgId = auth.orgId
280
279
 
281
280
  const contentType = req.headers.get('content-type') || ''
282
281
  if (!contentType.toLowerCase().includes('multipart/form-data')) {
@@ -302,13 +301,11 @@ export async function POST(req: Request) {
302
301
  const tags = parseFormTags(form.get('tags'))
303
302
  const assignmentsFromForm = parseFormAssignments(form.get('assignments'))
304
303
 
305
- const container = await createRequestContainer()
306
- const em = container.resolve('em') as EntityManager
307
- const dataEngine = container.resolve('dataEngine')
304
+ const { resolve } = await createRequestContainer()
305
+ const em = resolve('em') as EntityManager
306
+ const dataEngine = resolve('dataEngine')
308
307
  const storageDriverFactory =
309
- (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
310
- const orgId = await resolveAttachmentOrganizationId(container, auth, req)
311
- if (!orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
308
+ (resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
312
309
  await ensureDefaultPartitions(em)
313
310
  // Optional per-field validations
314
311
  let partitionFromField: string | null = null
@@ -439,12 +436,12 @@ export async function POST(req: Request) {
439
436
  }
440
437
  const metadata = mergeAttachmentMetadata(null, { assignments, tags })
441
438
  const attachmentId = randomUUID()
442
- assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: orgId })
439
+ assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: auth.orgId })
443
440
  const att = em.create(Attachment, {
444
441
  id: attachmentId,
445
442
  entityId,
446
443
  recordId,
447
- organizationId: orgId,
444
+ organizationId: auth.orgId!,
448
445
  tenantId: auth.tenantId!,
449
446
  fileName: safeName,
450
447
  mimeType: fileMimeType,
@@ -548,19 +545,17 @@ export async function DELETE(req: Request) {
548
545
  const url = new URL(req.url)
549
546
  const id = url.searchParams.get('id') || ''
550
547
  if (!id) return NextResponse.json({ error: 'Attachment id is required' }, { status: 400 })
551
- const container = await createRequestContainer()
552
- const em = container.resolve('em') as EntityManager
553
- const dataEngine = container.resolve('dataEngine')
548
+ const { resolve } = await createRequestContainer()
549
+ const em = resolve('em') as EntityManager
550
+ const dataEngine = resolve('dataEngine')
554
551
  const storageDriverFactory =
555
- (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
556
- // Resolve the currently selected organization (#3765) so a multi-org admin who
557
- // switched the header org deletes within that org, not their pinned home org.
558
- // A superadmin browsing with "All organizations" selected has no concrete org,
559
- // so resolution returns null and the delete falls back to a tenant-only scope
560
- // (#3764) — letting them remove any attachment in the tenant.
561
- const orgId = await resolveAttachmentOrganizationId(container, auth, req)
552
+ (resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
553
+ // Mirror the GET handler's superadmin bypass: a superadmin browsing with "All
554
+ // organizations" selected has no concrete org, so scope the delete by tenant
555
+ // only and let them remove any attachment in the tenant. Non-superadmins stay
556
+ // scoped to their own organization (#3764).
562
557
  const deleteFilter: Record<string, unknown> = { id, tenantId: auth.tenantId! }
563
- if (orgId) deleteFilter.organizationId = orgId
558
+ if (auth.orgId) deleteFilter.organizationId = auth.orgId
564
559
  const record = await em.findOne(Attachment, deleteFilter)
565
560
  if (!record) return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })
566
561
  await em.remove(record).flush()
@@ -570,7 +565,7 @@ export async function DELETE(req: Request) {
570
565
  if (record.storagePath) {
571
566
  const delDriver = await storageDriverFactory.resolveForPartition(record.partitionCode, {
572
567
  tenantId: record.tenantId ?? auth.tenantId!,
573
- organizationId: record.organizationId ?? orgId ?? '',
568
+ organizationId: record.organizationId ?? auth.orgId ?? '',
574
569
  })
575
570
  await delDriver.delete(record.partitionCode, record.storagePath)
576
571
  }
@@ -22,7 +22,6 @@ import {
22
22
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
23
23
  import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
24
24
  import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
25
- import { parseBooleanFlag } from '@open-mercato/shared/lib/boolean'
26
25
  import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
27
26
  import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
28
27
  import { sql } from 'kysely'
@@ -42,7 +41,6 @@ const querySchema = z.object({
42
41
  search: z.string().optional(),
43
42
  name: z.string().optional(),
44
43
  organizationId: z.string().uuid().optional(),
45
- scopeToActiveOrganization: z.boolean().optional(),
46
44
  roleIds: z.array(z.string().uuid()).optional(),
47
45
  }).passthrough()
48
46
 
@@ -182,7 +180,6 @@ export async function GET(req: Request) {
182
180
  search: url.searchParams.get('search') || undefined,
183
181
  name: url.searchParams.get('name') || undefined,
184
182
  organizationId: url.searchParams.get('organizationId') || undefined,
185
- scopeToActiveOrganization: parseBooleanFlag(url.searchParams.get('scopeToActiveOrganization') || undefined),
186
183
  roleIds: rawRoleIds.length ? rawRoleIds : undefined,
187
184
  })
188
185
  if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })
@@ -198,7 +195,7 @@ export async function GET(req: Request) {
198
195
  } catch (err) {
199
196
  logger.error('Failed to resolve rbac', { err })
200
197
  }
201
- const { id, page, pageSize, search, name, organizationId, scopeToActiveOrganization, roleIds } = parsed.data
198
+ const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data
202
199
  const filters: any[] = [{ deletedAt: null }]
203
200
  const actorTenantId = auth.tenantId ? String(auth.tenantId) : null
204
201
  let effectiveTenantId: string | null = null
@@ -247,12 +244,6 @@ export async function GET(req: Request) {
247
244
  ? effectiveSelectedOrganizationId
248
245
  : auth.orgId ?? null
249
246
  if (organizationId) filters.push({ organizationId })
250
- // Recipient/assignee pickers scope to the caller's active organization so they never
251
- // suggest users outside it. A message composed here is stamped with the caller's
252
- // active org (auth.orgId), and the message detail endpoint enforces
253
- // hasOrganizationAccess(scope.organizationId, message.organizationId); scoping the
254
- // suggestions to the same org keeps a picked recipient able to open what they were sent.
255
- if (scopeToActiveOrganization) filters.push({ organizationId: auth.orgId ?? null })
256
247
  const trimmedName = typeof name === 'string' ? name.trim() : ''
257
248
  if (trimmedName) {
258
249
  const searchPattern = `%${escapeLikePattern(trimmedName)}%`
@@ -629,7 +620,7 @@ export const openApi: OpenApiRouteDoc = {
629
620
  GET: {
630
621
  summary: 'List users',
631
622
  description:
632
- 'Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters. Pass scopeToActiveOrganization=1 to restrict results to the caller\'s active organization (used by recipient/assignee pickers so suggestions stay within the org that owns the resulting record).',
623
+ 'Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters.',
633
624
  query: querySchema,
634
625
  responses: [
635
626
  { status: 200, description: 'User collection', schema: userListResponseSchema },
@@ -9,7 +9,6 @@ import { Tenant, Organization } from '@open-mercato/core/modules/directory/data/
9
9
  import { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'
10
10
  import { ensureRoles, setupInitialTenant, ensureDefaultRoleAcls, ensureCustomRoleAcls, OrgSlugExistsError, DerivedUserPasswordRequiredError } from './lib/setup-app'
11
11
  import { normalizeTenantId } from './lib/tenantAccess'
12
- import { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'
13
12
  import { computeEmailHash, emailHashLookupValues } from './lib/emailHash'
14
13
  import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
15
14
  import { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'
@@ -72,7 +71,7 @@ const addUser: ModuleCli = {
72
71
  })
73
72
  await em.persist(u).flush()
74
73
  if (rolesCsv) {
75
- const names = parseCommaSeparatedList(rolesCsv)
74
+ const names = rolesCsv.split(',').map(s => s.trim()).filter(Boolean)
76
75
  for (const name of names) {
77
76
  const role = await resolveTenantScopedRole(em, name, normalizedTenantId)
78
77
  const link = em.create(UserRole, { user: u, role })
@@ -494,7 +493,7 @@ const setupApp: ModuleCli = {
494
493
  const container = await createRequestContainer()
495
494
  const em = container.resolve<EntityManager>('em')
496
495
  const roleNames = rolesCsv
497
- ? parseCommaSeparatedList(rolesCsv)
496
+ ? rolesCsv.split(',').map((s) => s.trim()).filter(Boolean)
498
497
  : undefined
499
498
 
500
499
  try {
@@ -189,9 +189,5 @@
189
189
  "configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Warmup-Drosselfenster",
190
190
  "configs.systemStatus.variables.scheduleAutoReindex.description": "Automatisch Query-Index-Neuindizierungsaufträge bei Lücken planen.",
191
191
  "configs.systemStatus.variables.scheduleAutoReindex.label": "Automatische Neuindizierung",
192
- "configs.systemStatus.viewDocs": "Dokumentation anzeigen",
193
- "configs.upgrades.attachmentsOrgReconcile.cta": "Anhang-Organisationen abgleichen",
194
- "configs.upgrades.attachmentsOrgReconcile.loading": "Anhang-Organisationen werden abgeglichen…",
195
- "configs.upgrades.attachmentsOrgReconcile.message": "Version {{version}} behebt Anhänge, die unter der falschen Organisation gespeichert wurden. Führen Sie den Abgleich aus, um vorhandene Anhänge in die Organisation des zugehörigen Datensatzes zu verschieben.",
196
- "configs.upgrades.attachmentsOrgReconcile.success": "Anhang-Organisationen abgeglichen."
192
+ "configs.systemStatus.viewDocs": "Dokumentation anzeigen"
197
193
  }
@@ -189,9 +189,5 @@
189
189
  "configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Warmup throttle window",
190
190
  "configs.systemStatus.variables.scheduleAutoReindex.description": "Automatically schedule query index reindex jobs when gaps appear.",
191
191
  "configs.systemStatus.variables.scheduleAutoReindex.label": "Auto reindex",
192
- "configs.systemStatus.viewDocs": "View documentation",
193
- "configs.upgrades.attachmentsOrgReconcile.cta": "Reconcile attachment organizations",
194
- "configs.upgrades.attachmentsOrgReconcile.loading": "Reconciling attachment organizations…",
195
- "configs.upgrades.attachmentsOrgReconcile.message": "Version {{version}} fixes attachments that were saved under the wrong organization. Run the reconciliation to move existing attachments to the organization of the record they belong to.",
196
- "configs.upgrades.attachmentsOrgReconcile.success": "Attachment organizations reconciled."
192
+ "configs.systemStatus.viewDocs": "View documentation"
197
193
  }
@@ -189,9 +189,5 @@
189
189
  "configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Ventana de limitación del calentamiento",
190
190
  "configs.systemStatus.variables.scheduleAutoReindex.description": "Programar automáticamente trabajos de reindexación cuando se detecten brechas.",
191
191
  "configs.systemStatus.variables.scheduleAutoReindex.label": "Reindexación automática",
192
- "configs.systemStatus.viewDocs": "Ver documentación",
193
- "configs.upgrades.attachmentsOrgReconcile.cta": "Reconciliar organizaciones de adjuntos",
194
- "configs.upgrades.attachmentsOrgReconcile.loading": "Reconciliando organizaciones de adjuntos…",
195
- "configs.upgrades.attachmentsOrgReconcile.message": "La versión {{version}} corrige los adjuntos guardados en la organización incorrecta. Ejecuta la reconciliación para mover los adjuntos existentes a la organización del registro al que pertenecen.",
196
- "configs.upgrades.attachmentsOrgReconcile.success": "Organizaciones de adjuntos reconciliadas."
192
+ "configs.systemStatus.viewDocs": "Ver documentación"
197
193
  }
@@ -189,9 +189,5 @@
189
189
  "configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Okno throttlingu rozgrzewki",
190
190
  "configs.systemStatus.variables.scheduleAutoReindex.description": "Automatycznie uruchamia przebudowę indeksu zapytań, gdy wykryte zostaną luki.",
191
191
  "configs.systemStatus.variables.scheduleAutoReindex.label": "Automatyczna przebudowa indeksu",
192
- "configs.systemStatus.viewDocs": "Zobacz dokumentację",
193
- "configs.upgrades.attachmentsOrgReconcile.cta": "Uzgodnij organizacje załączników",
194
- "configs.upgrades.attachmentsOrgReconcile.loading": "Uzgadnianie organizacji załączników…",
195
- "configs.upgrades.attachmentsOrgReconcile.message": "Wersja {{version}} naprawia załączniki zapisane pod niewłaściwą organizacją. Uruchom uzgadnianie, aby przenieść istniejące załączniki do organizacji rekordu, do którego należą.",
196
- "configs.upgrades.attachmentsOrgReconcile.success": "Organizacje załączników zostały uzgodnione."
192
+ "configs.systemStatus.viewDocs": "Zobacz dokumentację"
197
193
  }
@@ -1,11 +1,6 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import * as semver from 'semver'
3
3
  import type { AppContainer } from '@open-mercato/shared/lib/di/container'
4
- import { createLogger } from '@open-mercato/shared/lib/logger'
5
- import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
6
- import { reconcileAttachmentOrganizations } from '@open-mercato/core/modules/attachments/lib/reconcileOrganization'
7
-
8
- const logger = createLogger('configs').child({ component: 'upgrade-actions' })
9
4
 
10
5
  export type UpgradeActionContext = {
11
6
  tenantId: string
@@ -43,25 +38,6 @@ export function compareVersions(a: string, b: string): number {
43
38
  }
44
39
 
45
40
  export const upgradeActions: UpgradeActionDefinition[] = [
46
- {
47
- id: 'attachments.reconcile-organization',
48
- version: '0.6.6',
49
- messageKey: 'configs.upgrades.attachmentsOrgReconcile.message',
50
- ctaKey: 'configs.upgrades.attachmentsOrgReconcile.cta',
51
- successKey: 'configs.upgrades.attachmentsOrgReconcile.success',
52
- loadingKey: 'configs.upgrades.attachmentsOrgReconcile.loading',
53
- async run({ container, em, tenantId }) {
54
- const queryEngine = container.resolve('queryEngine') as QueryEngine
55
- const report = await reconcileAttachmentOrganizations({ em, queryEngine, tenantId })
56
- logger.info('attachments organization reconcile completed', {
57
- tenantId,
58
- scanned: report.scanned,
59
- updated: report.updated,
60
- unresolved: report.unresolved,
61
- skippedVirtual: report.skippedVirtual,
62
- })
63
- },
64
- },
65
41
  {
66
42
  id: 'customers.seed-interaction-statuses',
67
43
  version: '0.6.5',
@@ -306,17 +306,15 @@ Both link to `/backend/customer_accounts/{sourceEntityId}` for staff review.
306
306
 
307
307
  | Spot ID | Widget | Purpose |
308
308
  |---------|--------|---------|
309
- | `customers.person` | `account-status` | Alias for hosts requesting the bare `customers.person` entity spot |
310
- | `crud-form:customers.person` | `account-status` | Shows portal account status on the person v2 detail page (`people-v2/[id]`) |
311
- | `customers.company` | `company-users` | Alias for hosts requesting the bare `customers.company` entity spot |
312
- | `crud-form:customers.company` | `company-users` | Shows portal users on the company v2 detail page (`companies-v2/[id]`) |
309
+ | `customers.person` | `account-status` | Shows portal account status on Customers v2 person detail forms |
310
+ | `crud-form:customers.person` | `account-status` | Alias for CrudForm hosts deriving the spot from `customers.person` |
311
+ | `crud-form:customers:customer_person_profile:fields` | `account-status` | Shows portal account status on CRM person detail page |
312
+ | `customers.company` | `company-users` | Shows portal users on Customers v2 company detail forms |
313
+ | `crud-form:customers.company` | `company-users` | Alias for CrudForm hosts deriving the spot from `customers.company` |
314
+ | `crud-form:customers:customer_company_profile:fields` | `company-users` | Shows portal users linked to a CRM company |
313
315
 
314
316
  Both inject as column 2 groups with priority 200, gated by `customer_accounts.view` feature.
315
317
 
316
- CrudForm derives its spot id as `crud-form:${entityId}` with every `:` normalized to `.`, so a
317
- registered `crud-form:` key whose entity segment still contains `:` can never be requested by any
318
- host. Register against the normalized (dot) form only — `injection-table.test.ts` enforces this.
319
-
320
318
  ## Backend Pages
321
319
 
322
320
  | Path | Purpose |