@open-mercato/core 0.6.7-develop.6605.1.761dd4c810 → 0.6.7-develop.6621.1.cdbe9dee3d

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 (114) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/session/refresh.js +12 -11
  3. package/dist/modules/auth/api/session/refresh.js.map +2 -2
  4. package/dist/modules/auth/lib/rateLimitCheck.js +4 -4
  5. package/dist/modules/auth/lib/rateLimitCheck.js.map +2 -2
  6. package/dist/modules/catalog/api/categories/route.js +8 -14
  7. package/dist/modules/catalog/api/categories/route.js.map +2 -2
  8. package/dist/modules/catalog/api/tags/route.js +3 -10
  9. package/dist/modules/catalog/api/tags/route.js.map +2 -2
  10. package/dist/modules/customers/api/interactions/[id]/visibility/route.js +15 -17
  11. package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +2 -2
  12. package/dist/modules/customers/api/people/[id]/email-threads/route.js +6 -4
  13. package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +2 -2
  14. package/dist/modules/customers/api/people/[id]/emails/route.js +19 -17
  15. package/dist/modules/customers/api/people/[id]/emails/route.js.map +2 -2
  16. package/dist/modules/customers/components/detail/CustomFieldValuesList.js +22 -5
  17. package/dist/modules/customers/components/detail/CustomFieldValuesList.js.map +2 -2
  18. package/dist/modules/customers/components/detail/create/CreateDealForm.js +5 -2
  19. package/dist/modules/customers/components/detail/create/CreateDealForm.js.map +2 -2
  20. package/dist/modules/customers/components/detail/customFieldUtils.js +10 -3
  21. package/dist/modules/customers/components/detail/customFieldUtils.js.map +2 -2
  22. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.meta.js +4 -2
  23. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.meta.js.map +2 -2
  24. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.meta.js +4 -2
  25. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.meta.js.map +2 -2
  26. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +3 -2
  27. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  28. package/dist/modules/entities/backend/entities/user/create/page.js +2 -1
  29. package/dist/modules/entities/backend/entities/user/create/page.js.map +2 -2
  30. package/dist/modules/entities/data/entities.js.map +1 -1
  31. package/dist/modules/integrations/api/[id]/credentials/route.js +10 -7
  32. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  33. package/dist/modules/integrations/api/[id]/health/route.js +6 -4
  34. package/dist/modules/integrations/api/[id]/health/route.js.map +2 -2
  35. package/dist/modules/integrations/api/[id]/route.js +4 -2
  36. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  37. package/dist/modules/integrations/api/[id]/state/route.js +7 -5
  38. package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
  39. package/dist/modules/integrations/api/[id]/version/route.js +7 -5
  40. package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
  41. package/dist/modules/integrations/api/logs/route.js +4 -2
  42. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  43. package/dist/modules/integrations/api/route.js +4 -2
  44. package/dist/modules/integrations/api/route.js.map +2 -2
  45. package/dist/modules/integrations/api/umes-read.js +2 -1
  46. package/dist/modules/integrations/api/umes-read.js.map +2 -2
  47. package/dist/modules/integrations/lib/organization-scope.js +12 -0
  48. package/dist/modules/integrations/lib/organization-scope.js.map +7 -0
  49. package/dist/modules/payment_gateways/lib/gateway-service.js +10 -1
  50. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  51. package/dist/modules/sales/api/document-history/route.js +6 -9
  52. package/dist/modules/sales/api/document-history/route.js.map +2 -2
  53. package/dist/modules/sales/api/quotes/accept/route.js +3 -3
  54. package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
  55. package/dist/modules/sales/api/returns/[id]/route.js +6 -9
  56. package/dist/modules/sales/api/returns/[id]/route.js.map +2 -2
  57. package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -2
  58. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  59. package/dist/modules/sales/components/documents/AddressesSection.js +6 -4
  60. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  61. package/dist/modules/sales/workflows.js +1 -1
  62. package/dist/modules/sales/workflows.js.map +1 -1
  63. package/dist/modules/workflows/api/definitions/[id]/route.js +10 -1
  64. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  65. package/dist/modules/workflows/lib/event-trigger-service.js +44 -3
  66. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  67. package/package.json +7 -7
  68. package/src/modules/auth/api/session/refresh.ts +19 -11
  69. package/src/modules/auth/lib/rateLimitCheck.ts +4 -4
  70. package/src/modules/catalog/api/categories/route.ts +14 -16
  71. package/src/modules/catalog/api/tags/route.ts +6 -10
  72. package/src/modules/catalog/i18n/pl.json +9 -9
  73. package/src/modules/customer_accounts/i18n/pl.json +129 -129
  74. package/src/modules/customers/api/interactions/[id]/visibility/route.ts +24 -20
  75. package/src/modules/customers/api/people/[id]/email-threads/route.ts +10 -6
  76. package/src/modules/customers/api/people/[id]/emails/route.ts +25 -21
  77. package/src/modules/customers/components/detail/CustomFieldValuesList.tsx +28 -4
  78. package/src/modules/customers/components/detail/create/CreateDealForm.tsx +7 -2
  79. package/src/modules/customers/components/detail/customFieldUtils.ts +15 -3
  80. package/src/modules/data_sync/i18n/pl.json +68 -68
  81. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.meta.ts +4 -2
  82. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.meta.ts +4 -2
  83. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +2 -1
  84. package/src/modules/entities/backend/entities/user/create/page.tsx +1 -0
  85. package/src/modules/entities/data/entities.ts +1 -1
  86. package/src/modules/entities/i18n/de.json +2 -0
  87. package/src/modules/entities/i18n/en.json +2 -0
  88. package/src/modules/entities/i18n/es.json +2 -0
  89. package/src/modules/entities/i18n/pl.json +2 -0
  90. package/src/modules/integrations/api/[id]/credentials/route.ts +10 -7
  91. package/src/modules/integrations/api/[id]/health/route.ts +6 -4
  92. package/src/modules/integrations/api/[id]/route.ts +4 -2
  93. package/src/modules/integrations/api/[id]/state/route.ts +7 -5
  94. package/src/modules/integrations/api/[id]/version/route.ts +7 -5
  95. package/src/modules/integrations/api/logs/route.ts +4 -2
  96. package/src/modules/integrations/api/route.ts +4 -2
  97. package/src/modules/integrations/api/umes-read.ts +2 -1
  98. package/src/modules/integrations/i18n/de.json +0 -76
  99. package/src/modules/integrations/i18n/en.json +0 -76
  100. package/src/modules/integrations/i18n/es.json +0 -76
  101. package/src/modules/integrations/i18n/pl.json +0 -76
  102. package/src/modules/integrations/lib/organization-scope.ts +27 -0
  103. package/src/modules/payment_gateways/lib/gateway-service.ts +12 -1
  104. package/src/modules/sales/api/document-history/route.ts +10 -9
  105. package/src/modules/sales/api/quotes/accept/route.ts +3 -3
  106. package/src/modules/sales/api/returns/[id]/route.ts +10 -9
  107. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +26 -19
  108. package/src/modules/sales/components/documents/AddressesSection.tsx +8 -4
  109. package/src/modules/sales/i18n/pl.json +24 -24
  110. package/src/modules/sales/workflows.ts +1 -1
  111. package/src/modules/staff/i18n/pl.json +178 -178
  112. package/src/modules/workflows/api/definitions/[id]/route.ts +14 -1
  113. package/src/modules/workflows/i18n/pl.json +128 -128
  114. package/src/modules/workflows/lib/event-trigger-service.ts +87 -8
@@ -1,80 +1,4 @@
1
1
  {
2
- "example.mutationLifecycle.description": "Validation page for mutation guards, sync event subscribers, client-side event filtering, and command interceptors.",
3
- "example.mutationLifecycle.hintHeading": "What should be visible and how it should work",
4
- "example.mutationLifecycle.m1.createFailed": "Guard probe failed: expected 201, got {value}.",
5
- "example.mutationLifecycle.m1.description": "Create a todo via the CRUD pipeline to verify the mutation guard registry evaluates the `example.todo-limit` guard on POST.",
6
- "example.mutationLifecycle.m1.hint1": "1. Guard `example.todo-limit` targets `example.todo` on `create` operations with priority 50.",
7
- "example.mutationLifecycle.m1.hint2": "2. Guard validates `organizationId` presence — creation fails with 422 if missing.",
8
- "example.mutationLifecycle.m1.hint3": "3. Multiple guards run by priority order; first rejection stops the pipeline.",
9
- "example.mutationLifecycle.m1.hint4": "4. Legacy `crudMutationGuardService` is automatically bridged as a guard with priority 0.",
10
- "example.mutationLifecycle.m1.run": "Run guard probe",
11
- "example.mutationLifecycle.m1.title": "Phase m1 — Mutation Guard Registry",
12
- "example.mutationLifecycle.m2.createFailed": "Could not create todo for sync subscriber probe.",
13
- "example.mutationLifecycle.m2.description": "Run a multi-step probe: create a todo (verify auto-default-priority), mark as done then try to revert (verify prevent-uncomplete blocks with 422), and delete (verify audit-delete fires).",
14
- "example.mutationLifecycle.m2.hint1": "1. `auto-default-priority`: Injects `priority: 'normal'` when creating a todo without explicit priority (sync before-create).",
15
- "example.mutationLifecycle.m2.hint2": "2. `prevent-uncomplete`: Blocks reverting completed todos to pending with 422 (sync before-update).",
16
- "example.mutationLifecycle.m2.hint3": "3. `audit-delete`: Logs deletion audit trail to server console (sync after-delete, non-blocking).",
17
- "example.mutationLifecycle.m2.hint4": "4. Sync subscribers run inside the CRUD pipeline, before/after the actual mutation — unlike async subscribers which run post-commit.",
18
- "example.mutationLifecycle.m2.partial": "One or more sync subscriber probes failed. Review rows below.",
19
- "example.mutationLifecycle.m2.probe.auditDelete.error": "Expected 200, got {value}.",
20
- "example.mutationLifecycle.m2.probe.auditDelete.label": "audit-delete (after-delete)",
21
- "example.mutationLifecycle.m2.probe.auditDelete.ok": "Todo deleted (200). Sync after-event example.todo.deleted fired — audit-delete subscriber logged to server console.",
22
- "example.mutationLifecycle.m2.probe.defaultPriority.error": "Expected 201, got {value}.",
23
- "example.mutationLifecycle.m2.probe.defaultPriority.label": "auto-default-priority (before-create)",
24
- "example.mutationLifecycle.m2.probe.defaultPriority.ok": "Todo created (201). Sync before-event example.todo.creating fired — auto-default-priority subscriber injects priority if absent.",
25
- "example.mutationLifecycle.m2.probe.preventUncomplete.error": "Expected 422 (blocked), got {value}.",
26
- "example.mutationLifecycle.m2.probe.preventUncomplete.label": "prevent-uncomplete (before-update)",
27
- "example.mutationLifecycle.m2.probe.preventUncomplete.markDoneFailed": "Failed to mark todo as done: status {value}.",
28
- "example.mutationLifecycle.m2.probe.preventUncomplete.ok": "Revert blocked (422). Sync before-event example.todo.updating fired — prevent-uncomplete subscriber rejected the operation.",
29
- "example.mutationLifecycle.m2.probe.skipped": "Skipped — no todo was created in probe 1.",
30
- "example.mutationLifecycle.m2.run": "Run sync subscriber probe",
31
- "example.mutationLifecycle.m2.title": "Phase m2 — Sync Event Subscribers",
32
- "example.mutationLifecycle.m3.description": "Widget injection event handlers can now declare an operation filter to skip events for specific CRUD operations.",
33
- "example.mutationLifecycle.m3.hint1": "1. Widgets can declare `eventHandlers.filter.operations` to skip specific CRUD operations (e.g., only fire on `update`).",
34
- "example.mutationLifecycle.m3.hint2": "2. CrudForm now passes `operation` ('create' or 'update') in the injection context.",
35
- "example.mutationLifecycle.m3.hint3": "3. Widgets without a filter continue to fire for all operations (backward compatible).",
36
- "example.mutationLifecycle.m3.hint4": "4. Type: `WidgetInjectionEventFilter = { operations?: ('create' | 'update' | 'delete')[] }`",
37
- "example.mutationLifecycle.m3.note": "The filter is a type-level extension. See Phase G page for CrudForm injection demo.",
38
- "example.mutationLifecycle.m3.openPhaseG": "Open Phase G — CrudForm injection",
39
- "example.mutationLifecycle.m3.title": "Phase m3 — Client-Side Event Filtering",
40
- "example.mutationLifecycle.m4.description": "The `example.audit-logging` interceptor wraps all `customers.*` command bus operations with timing metadata.",
41
- "example.mutationLifecycle.m4.hint1": "1. `example.audit-logging` intercepts all `customers.*` commands (wildcard pattern).",
42
- "example.mutationLifecycle.m4.hint2": "2. `beforeExecute` stores `auditStartedAt` timestamp in metadata.",
43
- "example.mutationLifecycle.m4.hint3": "3. `afterExecute` reads metadata and logs: `[example:audit] Command {id} completed in {ms}ms`.",
44
- "example.mutationLifecycle.m4.hint4": "4. Pattern matching supports `*` (all), exact ID, and `prefix.*` (namespace wildcard).",
45
- "example.mutationLifecycle.m4.hint5": "5. Interceptors run on both `execute()` and `undo()` code paths.",
46
- "example.mutationLifecycle.m4.note": "Create or edit a customer, then check server console for the audit log entry.",
47
- "example.mutationLifecycle.m4.openCustomers": "Open customers",
48
- "example.mutationLifecycle.m4.title": "Phase m4 — Command Interceptors",
49
- "example.mutationLifecycle.title": "UMES Phase M — Mutation Lifecycle",
50
- "example.umes.queryExtensions.description": "Validation page for Phase N query engine extensions: enricher opt-in, surface-aware registry, sync query events, and scope guard re-application.",
51
- "example.umes.queryExtensions.enrichers.description": "Registers three enrichers (API-only, query-enabled for all engines, basic-only) and probes `getEnrichersForEntity` with different surface selectors.",
52
- "example.umes.queryExtensions.enrichers.hint1": "1. No selector / `api-response` surface: returns all 3 enrichers (backward compatible).",
53
- "example.umes.queryExtensions.enrichers.hint2": "2. `query-engine` surface (no engine filter): returns 2 enrichers (query-enabled + basic-only).",
54
- "example.umes.queryExtensions.enrichers.hint3": "3. `query-engine` + `basic` engine: returns 2 enrichers (query-enabled defaults to both, basic-only matches).",
55
- "example.umes.queryExtensions.enrichers.hint4": "4. `query-engine` + `hybrid` engine: returns 1 enricher (only query-enabled, basic-only excluded).",
56
- "example.umes.queryExtensions.enrichers.run": "Run enricher registry probe",
57
- "example.umes.queryExtensions.enrichers.title": "N.1 Surface-Aware Enricher Registry",
58
- "example.umes.queryExtensions.entityIds.description": "Validates `entityIdToEventEntity` which converts query engine format (`module:entity`) to event format (`module.entity`).",
59
- "example.umes.queryExtensions.entityIds.hint1": "1. `customers:person` should convert to `customers.person`.",
60
- "example.umes.queryExtensions.entityIds.hint2": "2. `catalog:product:variant` should convert to `catalog.product.variant` (multiple colons).",
61
- "example.umes.queryExtensions.entityIds.hint3": "3. All conversions should show `ok: true`.",
62
- "example.umes.queryExtensions.entityIds.run": "Run entity ID conversion probe",
63
- "example.umes.queryExtensions.entityIds.title": "N.4 Entity ID Conversion",
64
- "example.umes.queryExtensions.hintHeading": "What should be visible and how it should work",
65
- "example.umes.queryExtensions.scopeGuards.description": "Demonstrates that `reapplyScopeGuards` restores tenant/org constraints after subscriber modifications while preserving other query options.",
66
- "example.umes.queryExtensions.scopeGuards.hint1": "1. Tampered tenantId/organizationId should be restored to original values.",
67
- "example.umes.queryExtensions.scopeGuards.hint2": "2. Non-scope fields (filters, withDeleted) should remain unchanged.",
68
- "example.umes.queryExtensions.scopeGuards.hint3": "3. This ensures subscribers cannot bypass multi-tenant isolation.",
69
- "example.umes.queryExtensions.scopeGuards.run": "Run scope guard probe",
70
- "example.umes.queryExtensions.scopeGuards.title": "N.3 Scope Guard Re-application",
71
- "example.umes.queryExtensions.subscribers.description": "Registers three sync subscribers (querying, queried, wildcard) and probes `collectQuerySubscribers` with different event IDs.",
72
- "example.umes.queryExtensions.subscribers.hint1": "1. `example.todo.querying` should match 2 subscribers (exact + wildcard `example.*.querying`).",
73
- "example.umes.queryExtensions.subscribers.hint2": "2. `example.todo.queried` should match 1 subscriber (exact match only).",
74
- "example.umes.queryExtensions.subscribers.hint3": "3. `customers.person.querying` should match 0 subscribers (no cross-entity leakage).",
75
- "example.umes.queryExtensions.subscribers.run": "Run subscriber collection probe",
76
- "example.umes.queryExtensions.subscribers.title": "N.2 Sync Query Event Subscribers",
77
- "example.umes.queryExtensions.title": "UMES Phase N — Query Engine Extensibility",
78
2
  "integrations.bundle.configureIntegration": "Konfiguruj",
79
3
  "integrations.bundle.integrationToggles": "Integracje",
80
4
  "integrations.bundle.sharedCredentials": "Współdzielone dane",
@@ -0,0 +1,27 @@
1
+ type OrganizationScopedAuth = {
2
+ orgId?: string | null
3
+ actorOrgId?: unknown
4
+ } | null | undefined
5
+
6
+ /**
7
+ * Resolves the organization an integrations request is scoped to.
8
+ *
9
+ * Integrations are configured per organization — `integration_credentials` and
10
+ * `integration_states` both require a non-null `organization_id` — so there is no
11
+ * meaningful "all organizations" view of this module. When an operator selects that
12
+ * option the super-admin cookie override clears `auth.orgId` and preserves the actor's
13
+ * own organization in `actorOrgId`; fall back to it so the module keeps showing the
14
+ * operator's own configuration instead of failing.
15
+ *
16
+ * Answering 401 for that case is not merely wrong but self-perpetuating: `apiFetch`
17
+ * reads 401 as an expired session and redirects through `/api/auth/session/refresh`,
18
+ * which succeeds and returns to the same page, reloading forever.
19
+ */
20
+ export function resolveIntegrationsOrganizationId(auth: OrganizationScopedAuth): string | null {
21
+ if (!auth) return null
22
+ const selected = auth.orgId
23
+ if (typeof selected === 'string' && selected.trim().length > 0) return selected
24
+ const actorOrgId = auth.actorOrgId
25
+ if (typeof actorOrgId === 'string' && actorOrgId.trim().length > 0) return actorOrgId
26
+ return null
27
+ }
@@ -45,6 +45,12 @@ function assertManualActionAllowed(action: ManualGatewayAction, transaction: Gat
45
45
  }
46
46
  }
47
47
 
48
+ function assertCaptureAmountAllowed(amount: number | undefined, transaction: GatewayTransaction): void {
49
+ if (amount !== undefined && amount > Number(transaction.amount)) {
50
+ throw conflict(`Capture amount ${amount} exceeds authorized transaction amount ${transaction.amount}`)
51
+ }
52
+ }
53
+
48
54
  function applyAdapterResultStatus(
49
55
  action: ManualGatewayAction,
50
56
  transaction: GatewayTransaction,
@@ -192,6 +198,7 @@ export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
192
198
  operationId?: string
193
199
  payload: Record<string, unknown>
194
200
  scope: { organizationId: string; tenantId: string }
201
+ assertInitialAllowed?: (transaction: GatewayTransaction) => void
195
202
  invoke: (context: {
196
203
  adapter: GatewayAdapter
197
204
  credentials: Record<string, unknown>
@@ -210,7 +217,10 @@ export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
210
217
  operationId: input.operationId,
211
218
  payload: input.payload,
212
219
  scope: input.scope,
213
- assertInitialAllowed: () => assertManualActionAllowed(input.action, transaction),
220
+ assertInitialAllowed: () => {
221
+ assertManualActionAllowed(input.action, transaction)
222
+ input.assertInitialAllowed?.(transaction)
223
+ },
214
224
  })
215
225
  if (prepared.kind === 'completed') {
216
226
  if (prepared.result.status !== transaction.unifiedStatus) {
@@ -487,6 +497,7 @@ export function createPaymentGatewayService(deps: PaymentGatewayServiceDeps) {
487
497
  operationId,
488
498
  payload: { amount: amount ?? null },
489
499
  scope,
500
+ assertInitialAllowed: (transaction) => assertCaptureAmountAllowed(amount, transaction),
490
501
  invoke: ({ adapter, credentials, transaction, idempotencyKey }) => adapter.capture({
491
502
  sessionId: readProviderSessionId(transaction),
492
503
  amount,
@@ -4,6 +4,7 @@ import { loadAuditLogDisplayMaps } from '@open-mercato/core/modules/audit_logs/a
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
6
6
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
7
+ import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
7
8
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
8
9
  import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
9
10
  import { NextResponse } from 'next/server'
@@ -67,12 +68,12 @@ export async function GET(req: Request) {
67
68
  }
68
69
 
69
70
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
70
- const organizationId = scope?.selectedId ?? auth.orgId ?? null
71
- if (!organizationId) {
72
- throw new CrudHttpError(400, {
73
- error: translate('sales.documents.errors.organization_required', 'Organization context is required'),
74
- })
75
- }
71
+ // Resolve the organization scope the same way every other scoped read does.
72
+ // Under "All organizations" (super-admin) `rbacOrganizationId` is null and
73
+ // `where` is empty, so the read scopes by tenant + resource only instead of
74
+ // failing — the document's `id` is a tenant-unique UUID, so no cross-org leak.
75
+ const orgFilter = resolveOrganizationScopeFilter(scope, auth)
76
+ const organizationId = orgFilter.rbacOrganizationId
76
77
 
77
78
  const requiredFeature = query.kind === 'order' ? 'sales.orders.view' : 'sales.quotes.view'
78
79
  const rbac = container.resolve('rbacService') as RbacService
@@ -92,7 +93,7 @@ export async function GET(req: Request) {
92
93
  const [actionLogList, notes] = await Promise.all([
93
94
  actionLogService.list({
94
95
  tenantId: auth.tenantId,
95
- organizationId,
96
+ organizationId: organizationId ?? undefined,
96
97
  resourceKind,
97
98
  resourceId: query.id,
98
99
  includeRelated: true,
@@ -107,11 +108,11 @@ export async function GET(req: Request) {
107
108
  contextType: query.kind,
108
109
  contextId: query.id,
109
110
  tenantId: auth.tenantId,
110
- organizationId,
111
+ ...orgFilter.where,
111
112
  deletedAt: null,
112
113
  },
113
114
  { orderBy: { createdAt: 'DESC' } },
114
- { tenantId: auth.tenantId, organizationId },
115
+ { tenantId: auth.tenantId, organizationId: organizationId ?? undefined },
115
116
  ),
116
117
  ])
117
118
  const logs = actionLogList.items as ActionLog[]
@@ -11,7 +11,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
11
11
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
12
12
  import { getCachedRateLimiterService } from '@open-mercato/core/bootstrap'
13
13
  import { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'
14
- import { checkRateLimit, getClientIp, rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'
14
+ import { checkRateLimit, getClientIp, RATE_LIMIT_FALLBACK_KEY, rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'
15
15
  import { validateSameOriginMutationRequest } from './originGuard'
16
16
  import { hashAuthToken } from '../../../../auth/lib/tokenHash'
17
17
  import { SalesOrder, SalesQuote } from '../../../data/entities'
@@ -52,11 +52,11 @@ export async function POST(req: Request) {
52
52
 
53
53
  const rateLimiterService = getCachedRateLimiterService()
54
54
  const clientIp = rateLimiterService ? getClientIp(req, rateLimiterService.trustProxyDepth) : null
55
- if (rateLimiterService && clientIp) {
55
+ if (rateLimiterService) {
56
56
  const rateLimitResponse = await checkRateLimit(
57
57
  rateLimiterService,
58
58
  quoteAcceptRateLimitConfig,
59
- clientIp,
59
+ clientIp ?? RATE_LIMIT_FALLBACK_KEY,
60
60
  translate('api.errors.rateLimit', 'Too many requests. Please try again later.'),
61
61
  )
62
62
  if (rateLimitResponse) return rateLimitResponse
@@ -5,6 +5,7 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
5
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
6
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
7
7
  import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
8
+ import { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'
8
9
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
10
  import { CrudHttpError, isCrudHttpError, notFound } from '@open-mercato/shared/lib/crud/errors'
10
11
  import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -40,20 +41,20 @@ export async function GET(req: Request, ctx: { params: { id: string } }) {
40
41
  }
41
42
 
42
43
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
43
- const organizationId = scope?.selectedId ?? auth.orgId ?? null
44
- if (!organizationId) {
45
- throw new CrudHttpError(400, {
46
- error: translate('sales.documents.errors.organization_required', 'Organization context is required'),
47
- })
48
- }
44
+ // Scope by the caller's visible organizations. Under "All organizations"
45
+ // (super-admin) `where` is empty and `rbacOrganizationId` is null, so the
46
+ // return is fetched by its tenant-unique id instead of 400-ing; restricted
47
+ // callers keep their `filterIds` `$in` guard.
48
+ const orgFilter = resolveOrganizationScopeFilter(scope, auth)
49
+ const decryptionOrganizationId = orgFilter.rbacOrganizationId ?? undefined
49
50
 
50
51
  const em = (container.resolve('em') as EntityManager).fork()
51
52
  const header = await findOneWithDecryption(
52
53
  em,
53
54
  SalesReturn,
54
- { id, deletedAt: null, tenantId: auth.tenantId, organizationId },
55
+ { id, deletedAt: null, tenantId: auth.tenantId, ...orgFilter.where },
55
56
  { populate: ['order'] },
56
- { tenantId: auth.tenantId, organizationId },
57
+ { tenantId: auth.tenantId, organizationId: decryptionOrganizationId },
57
58
  )
58
59
  if (!header || !header.order) {
59
60
  throw notFound(translate('sales.returns.notFound', 'Return not found.'))
@@ -64,7 +65,7 @@ export async function GET(req: Request, ctx: { params: { id: string } }) {
64
65
  SalesReturnLine,
65
66
  { salesReturn: header.id, deletedAt: null },
66
67
  { populate: ['orderLine'] },
67
- { tenantId: auth.tenantId, organizationId },
68
+ { tenantId: auth.tenantId, organizationId: decryptionOrganizationId },
68
69
  )
69
70
 
70
71
  const totals = lines.reduce(
@@ -27,6 +27,8 @@ import { ArrowRightLeft, Building2, CreditCard, Mail, Pencil, Plus, Send, Store,
27
27
  import { FormHeader, type ActionItem } from '@open-mercato/ui/backend/forms'
28
28
  import { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'
29
29
  import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
30
+ import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
31
+ import { hasFeature } from '@open-mercato/shared/security/features'
30
32
  import Link from 'next/link'
31
33
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
32
34
  import { apiCall, apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
@@ -3983,6 +3985,9 @@ export default function SalesDocumentDetailPage({
3983
3985
  []
3984
3986
  )
3985
3987
 
3988
+ const { payload: backendChromePayload, isReady: backendChromeReady } = useBackendChrome()
3989
+ const canComposeMessages = backendChromeReady && hasFeature(backendChromePayload?.grantedFeatures, 'messages.compose')
3990
+
3986
3991
  const tabInjectionSpotId = React.useMemo(() => `sales.document.detail.${kind}:tabs`, [kind])
3987
3992
  const { widgets: injectedTabWidgets } = useInjectionWidgets(tabInjectionSpotId, {
3988
3993
  context: detailInjectionContext,
@@ -4564,25 +4569,27 @@ export default function SalesDocumentDetailPage({
4564
4569
  backLabel={t('sales.documents.detail.back', 'Back to documents')}
4565
4570
  utilityActions={record ? (
4566
4571
  <>
4567
- <SendObjectMessageDialog
4568
- object={{
4569
- entityModule: 'sales',
4570
- entityType: kind,
4571
- entityId: record.id,
4572
- sourceEntityType: kind === 'order' ? 'sales.order' : 'sales.quote',
4573
- sourceEntityId: record.id,
4574
- previewData: {
4575
- title: number,
4576
- status: statusDisplay?.label ?? record?.status ?? undefined,
4577
- metadata: Object.keys(messagePreviewMetadata).length > 0 ? messagePreviewMetadata : undefined,
4578
- },
4579
- }}
4580
- viewHref={`/backend/sales/${kind === 'order' ? 'orders' : 'quotes'}/${record.id}`}
4581
- defaultValues={{
4582
- sourceEntityType: kind === 'order' ? 'sales.order' : 'sales.quote',
4583
- sourceEntityId: record.id,
4584
- }}
4585
- />
4572
+ {canComposeMessages ? (
4573
+ <SendObjectMessageDialog
4574
+ object={{
4575
+ entityModule: 'sales',
4576
+ entityType: kind,
4577
+ entityId: record.id,
4578
+ sourceEntityType: kind === 'order' ? 'sales.order' : 'sales.quote',
4579
+ sourceEntityId: record.id,
4580
+ previewData: {
4581
+ title: number,
4582
+ status: statusDisplay?.label ?? record?.status ?? undefined,
4583
+ metadata: Object.keys(messagePreviewMetadata).length > 0 ? messagePreviewMetadata : undefined,
4584
+ },
4585
+ }}
4586
+ viewHref={`/backend/sales/${kind === 'order' ? 'orders' : 'quotes'}/${record.id}`}
4587
+ defaultValues={{
4588
+ sourceEntityType: kind === 'order' ? 'sales.order' : 'sales.quote',
4589
+ sourceEntityId: record.id,
4590
+ }}
4591
+ />
4592
+ ) : null}
4586
4593
  <VersionHistoryAction
4587
4594
  config={{
4588
4595
  resourceKind: kind === 'order' ? 'sales.order' : 'sales.quote',
@@ -200,9 +200,13 @@ export function SalesDocumentAddressesSection({
200
200
  const [addressesLoading, setAddressesLoading] = React.useState(false)
201
201
  const [addressesError, setAddressesError] = React.useState<string | null>(null)
202
202
  const [addressFormat, setAddressFormat] = React.useState<AddressFormatStrategy>('line_first')
203
- const [useCustomShipping, setUseCustomShipping] = React.useState<boolean>(!!shippingAddressSnapshot)
203
+ const [useCustomShipping, setUseCustomShipping] = React.useState<boolean>(
204
+ !!shippingAddressSnapshot && !shippingAddressId
205
+ )
204
206
  const [useCustomBilling, setUseCustomBilling] = React.useState<boolean>(
205
- billingAddressSnapshot ? true : !!shippingAddressSnapshot
207
+ billingAddressSnapshot && !billingAddressId
208
+ ? true
209
+ : !!shippingAddressSnapshot && !shippingAddressId
206
210
  )
207
211
  const [sameAsShipping, setSameAsShipping] = React.useState<boolean>(() => {
208
212
  if (shippingAddressSnapshot || billingAddressSnapshot) {
@@ -366,8 +370,8 @@ export function SalesDocumentAddressesSection({
366
370
  }, [documentId, kind, resolveAddressSummary, t])
367
371
 
368
372
  React.useEffect(() => {
369
- const shippingCustom = !!shippingAddressSnapshot
370
- const billingCustom = !!billingAddressSnapshot
373
+ const shippingCustom = !!shippingAddressSnapshot && !shippingAddressId
374
+ const billingCustom = !!billingAddressSnapshot && !billingAddressId
371
375
  const nextSame = shippingAddressSnapshot || billingAddressSnapshot
372
376
  ? deepEqual(shippingAddressSnapshot, billingAddressSnapshot)
373
377
  : !billingAddressId || billingAddressId === shippingAddressId
@@ -12,9 +12,9 @@
12
12
  "sales.audit.credit_memos.create": "Utwórz notę kredytową",
13
13
  "sales.audit.credit_memos.delete": "Usuń notę kredytową",
14
14
  "sales.audit.credit_memos.update": "Zaktualizuj notę kredytową",
15
- "sales.audit.delivery-windows.create": "Create delivery window",
16
- "sales.audit.delivery-windows.delete": "Delete delivery window",
17
- "sales.audit.delivery-windows.update": "Update delivery window",
15
+ "sales.audit.delivery-windows.create": "Utworzenie okna dostawy",
16
+ "sales.audit.delivery-windows.delete": "Usunięcie okna dostawy",
17
+ "sales.audit.delivery-windows.update": "Aktualizacja okna dostawy",
18
18
  "sales.audit.document_addresses.create": "Dodaj adres dokumentu",
19
19
  "sales.audit.document_addresses.delete": "Usuń adres dokumentu",
20
20
  "sales.audit.document_addresses.update": "Zaktualizuj adres dokumentu",
@@ -31,9 +31,9 @@
31
31
  "sales.audit.orders.lines.delete": "Usuń pozycję zamówienia",
32
32
  "sales.audit.orders.lines.upsert": "Zaktualizuj pozycję zamówienia",
33
33
  "sales.audit.orders.update": "Aktualizuj zamówienie sprzedaży",
34
- "sales.audit.payment-methods.create": "Create payment method",
35
- "sales.audit.payment-methods.delete": "Delete payment method",
36
- "sales.audit.payment-methods.update": "Update payment method",
34
+ "sales.audit.payment-methods.create": "Utworzenie metody płatności",
35
+ "sales.audit.payment-methods.delete": "Usunięcie metody płatności",
36
+ "sales.audit.payment-methods.update": "Aktualizacja metody płatności",
37
37
  "sales.audit.payments.create": "Utwórz płatność",
38
38
  "sales.audit.payments.delete": "Usuń płatność",
39
39
  "sales.audit.payments.update": "Zaktualizuj płatność",
@@ -51,12 +51,12 @@
51
51
  "sales.audit.shipments.create": "Utwórz wysyłkę",
52
52
  "sales.audit.shipments.delete": "Usuń wysyłkę",
53
53
  "sales.audit.shipments.update": "Aktualizuj wysyłkę",
54
- "sales.audit.shipping-methods.create": "Create shipping method",
55
- "sales.audit.shipping-methods.delete": "Delete shipping method",
56
- "sales.audit.shipping-methods.update": "Update shipping method",
57
- "sales.audit.tax-rates.create": "Create tax rate",
58
- "sales.audit.tax-rates.delete": "Delete tax rate",
59
- "sales.audit.tax-rates.update": "Update tax rate",
54
+ "sales.audit.shipping-methods.create": "Utworzenie metody wysyłki",
55
+ "sales.audit.shipping-methods.delete": "Usunięcie metody wysyłki",
56
+ "sales.audit.shipping-methods.update": "Aktualizacja metody wysyłki",
57
+ "sales.audit.tax-rates.create": "Utworzenie stawki podatku",
58
+ "sales.audit.tax-rates.delete": "Usunięcie stawki podatku",
59
+ "sales.audit.tax-rates.update": "Aktualizacja stawki podatku",
60
60
  "sales.channels.actions.backToList": "Powrót do kanałów",
61
61
  "sales.channels.actions.create": "Dodaj kanał",
62
62
  "sales.channels.create": "Utwórz kanał sprzedaży",
@@ -1380,18 +1380,18 @@
1380
1380
  "sales.shipments.not_found": "Nie znaleziono wysyłki",
1381
1381
  "sales.shipments.payment_completed": "Nie można zmodyfikować wysyłki: płatność za zamówienie jest zakończona.",
1382
1382
  "sales.shipments.quantity_exceeded": "Nie można wysłać więcej niż pozostała ilość.",
1383
- "sales.stageBar.changeFailed": "Failed to update stage",
1384
- "sales.stageBar.changed": "Stage updated",
1385
- "sales.stageBar.closed.lost": "Lost",
1386
- "sales.stageBar.closed.won": "Won",
1387
- "sales.stageBar.closedLost": "Deal Lost",
1388
- "sales.stageBar.closedWon": "Deal Won",
1389
- "sales.stageBar.completed": "Completed",
1390
- "sales.stageBar.confirmChange": "Move deal to \"{{stage}}\"?",
1391
- "sales.stageBar.current": "Current",
1392
- "sales.stageBar.lost": "Lost",
1393
- "sales.stageBar.upcoming": "Upcoming",
1394
- "sales.stageBar.won": "Won",
1383
+ "sales.stageBar.changeFailed": "Nie udało się zmienić etapu",
1384
+ "sales.stageBar.changed": "Zmieniono etap",
1385
+ "sales.stageBar.closed.lost": "Przegrana",
1386
+ "sales.stageBar.closed.won": "Wygrana",
1387
+ "sales.stageBar.closedLost": "Szansa przegrana",
1388
+ "sales.stageBar.closedWon": "Szansa wygrana",
1389
+ "sales.stageBar.completed": "Zakończony",
1390
+ "sales.stageBar.confirmChange": "Przenieść szansę do etapu „{{stage}}”?",
1391
+ "sales.stageBar.current": "Bieżący",
1392
+ "sales.stageBar.lost": "Przegrana",
1393
+ "sales.stageBar.upcoming": "Nadchodzący",
1394
+ "sales.stageBar.won": "Wygrana",
1395
1395
  "sales.tax-rates.create": "Utwórz stawkę podatku",
1396
1396
  "sales.tax-rates.delete": "Usuń stawkę podatku",
1397
1397
  "sales.tax-rates.update": "Aktualizuj stawkę podatku",
@@ -174,7 +174,7 @@ const orderApproval = defineWorkflow({
174
174
  triggerId: 'order_approval_trigger',
175
175
  name: 'Order Approval Trigger',
176
176
  description: 'Triggers when a new sales order is created',
177
- eventPattern: 'sales.orders.created',
177
+ eventPattern: 'sales.order.created',
178
178
  config: { entityType: 'SalesOrder' },
179
179
  enabled: true,
180
180
  priority: 0,