@open-mercato/core 0.6.8-develop.7043.1.b0199a62c6 → 0.6.8-develop.7047.1.32ab5fcb07

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.8-develop.7043.1.b0199a62c6",
3
+ "version": "0.6.8-develop.7047.1.32ab5fcb07",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -252,16 +252,16 @@
252
252
  "zod": "^4.4.3"
253
253
  },
254
254
  "peerDependencies": {
255
- "@open-mercato/ai-assistant": "0.6.8-develop.7043.1.b0199a62c6",
256
- "@open-mercato/shared": "0.6.8-develop.7043.1.b0199a62c6",
257
- "@open-mercato/ui": "0.6.8-develop.7043.1.b0199a62c6",
255
+ "@open-mercato/ai-assistant": "0.6.8-develop.7047.1.32ab5fcb07",
256
+ "@open-mercato/shared": "0.6.8-develop.7047.1.32ab5fcb07",
257
+ "@open-mercato/ui": "0.6.8-develop.7047.1.32ab5fcb07",
258
258
  "react": "^19.0.0",
259
259
  "react-dom": "^19.0.0"
260
260
  },
261
261
  "devDependencies": {
262
- "@open-mercato/ai-assistant": "0.6.8-develop.7043.1.b0199a62c6",
263
- "@open-mercato/shared": "0.6.8-develop.7043.1.b0199a62c6",
264
- "@open-mercato/ui": "0.6.8-develop.7043.1.b0199a62c6",
262
+ "@open-mercato/ai-assistant": "0.6.8-develop.7047.1.32ab5fcb07",
263
+ "@open-mercato/shared": "0.6.8-develop.7047.1.32ab5fcb07",
264
+ "@open-mercato/ui": "0.6.8-develop.7047.1.32ab5fcb07",
265
265
  "@testing-library/dom": "^10.4.1",
266
266
  "@testing-library/jest-dom": "^7.0.0",
267
267
  "@testing-library/react": "^16.3.1",
@@ -148,21 +148,25 @@ export async function postForm(
148
148
  * The `request` fixture keeps a cookie jar, and `/api/auth/login` sets `auth_token`
149
149
  * on it. Every later call through that fixture therefore carries the LAST logged-in
150
150
  * user's session, even one that deliberately sends no Authorization header or an
151
- * `ApiKey` one. Whether that cookie is stored at all depends on the deployment:
152
- * the login route marks it `secure` only when `NODE_ENV === 'production'`, so a lane
153
- * that serves the app with any other NODE_ENV over http keeps it while a production
154
- * lane silently drops it.
151
+ * `ApiKey` one.
155
152
  *
156
153
  * A spec asserting "no credentials are rejected" or "this API key alone decides
157
- * access" must not depend on which lane it happens to run in — it must issue the
158
- * request from a jar that never saw a login. TC-DOCUMENTS-009 and TC-DOCUMENTS-018
159
- * both failed in the standalone lane for exactly that reason while passing in the
160
- * ephemeral one.
154
+ * access" must therefore issue the request from a jar that never saw a login,
155
+ * rather than assuming the fixture's jar is empty. TC-DOCUMENTS-009 and
156
+ * TC-DOCUMENTS-018 both observably failed in the standalone lane — with the
157
+ * replayed cookie visible in the trace — while passing in the ephemeral one, and
158
+ * routing them through this helper fixed both.
161
159
  */
162
160
  export async function withCredentialIsolatedRequest<T>(
163
161
  use: (request: APIRequestContext) => Promise<T>,
164
162
  ): Promise<T> {
165
- const context = await playwrightRequest.newContext(BASE_URL ? { baseURL: BASE_URL } : {});
163
+ // A hand-built context does not inherit the project's `use.baseURL` the way the
164
+ // `request` fixture does, so it has to repeat the config's own resolution — otherwise
165
+ // a relative path throws here on any run that leaves BASE_URL unset, while the same
166
+ // path works through the fixture.
167
+ const context = await playwrightRequest.newContext({
168
+ baseURL: BASE_URL ?? 'http://localhost:3000',
169
+ });
166
170
  try {
167
171
  return await use(context);
168
172
  } finally {
@@ -14,6 +14,7 @@ import {
14
14
  import { registerDomainSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'
15
15
  import {
16
16
  DomainMappingService,
17
+ DomainMappingOrgScopeError,
17
18
  type ResolveResult,
18
19
  } from '@open-mercato/core/modules/customer_accounts/services/domainMappingService'
19
20
  import { DomainMapping } from '@open-mercato/core/modules/customer_accounts/data/entities'
@@ -142,6 +143,12 @@ export async function POST(req: Request) {
142
143
  { status: 409 },
143
144
  )
144
145
  }
146
+ if (err instanceof DomainMappingOrgScopeError) {
147
+ return NextResponse.json(
148
+ { ok: false, error: 'Organization was not found in the current tenant.' },
149
+ { status: 400 },
150
+ )
151
+ }
145
152
  const message = err instanceof Error ? err.message : 'Failed to register domain'
146
153
  return NextResponse.json({ ok: false, error: message }, { status: 500 })
147
154
  }
@@ -9,9 +9,17 @@ import {
9
9
  import { Organization } from '@open-mercato/core/modules/directory/data/entities'
10
10
  import { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'
11
11
  import { normalizeHostname, tryNormalizeHostname } from '@open-mercato/core/modules/customer_accounts/lib/hostname'
12
+ import { findOrganizationInTenant } from '@open-mercato/core/modules/customer_accounts/lib/organizationLookup'
12
13
  import { platformDomains } from '@open-mercato/core/modules/customer_accounts/lib/platformDomains'
13
14
  import { detectProxy, isInKnownProxyRange } from '@open-mercato/core/modules/customer_accounts/lib/proxyRanges'
14
15
 
16
+ export class DomainMappingOrgScopeError extends Error {
17
+ constructor(organizationId: string) {
18
+ super(`[internal] organizationId ${organizationId} does not belong to the caller's tenant`)
19
+ this.name = 'DomainMappingOrgScopeError'
20
+ }
21
+ }
22
+
15
23
  const DOMAIN_ROUTING_TAG = 'domain_routing'
16
24
  const RESOLVE_KEY_PREFIX = 'domain_routing:resolve'
17
25
  const ACTIVE_BY_ORG_KEY_PREFIX = 'domain_routing:active-by-org'
@@ -269,6 +277,8 @@ export class DomainMappingService {
269
277
 
270
278
  async register(input: RegisterInput): Promise<DomainMapping> {
271
279
  const hostname = normalizeHostname(input.hostname)
280
+ const organization = await findOrganizationInTenant(this.em, input.organizationId, input.tenantId)
281
+ if (!organization) throw new DomainMappingOrgScopeError(input.organizationId)
272
282
 
273
283
  let replacesDomain: DomainMapping | null = null
274
284
  if (input.replacesDomainId) {
@@ -26,7 +26,6 @@ import { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'
26
26
  import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
27
27
  import { parseIdsParam } from '@open-mercato/shared/lib/crud/ids'
28
28
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
29
- import { recalculateOrderTotalsForDisplay } from '../../commands/returns'
30
29
  import { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'
31
30
 
32
31
  type DocumentKind = 'order' | 'quote'
@@ -616,46 +615,6 @@ export function buildDocumentCrudOptions(binding: DocumentBinding) {
616
615
  afterList: async (payload: any, ctx: CrudCtx) => {
617
616
  await attachTags(payload, { ...ctx, bindingKind: binding.kind })
618
617
  await attachChannelNames(payload, ctx)
619
- if (binding.kind === 'order' && Array.isArray(payload?.items) && payload.items.length === 1) {
620
- const item = payload.items[0] as Record<string, unknown>
621
- const orderId = typeof item?.id === 'string' ? item.id : null
622
- const tenantId = typeof item?.tenantId === 'string' ? item.tenantId : ctx?.auth?.tenantId ?? null
623
- const organizationId =
624
- typeof item?.organizationId === 'string' ? item.organizationId : ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null
625
- if (orderId && tenantId && organizationId) {
626
- const requestEm = ctx?.container?.resolve?.('em') as import('@mikro-orm/postgresql').EntityManager | undefined
627
- // Display-only totals recalculation: run on a forked EntityManager so
628
- // the order/line/adjustment entities loaded here never enter the
629
- // request's Unit of Work. This guarantees a GET can never flush an
630
- // UPDATE (and thus never advance `updated_at`), which would otherwise
631
- // surface as a spurious optimistic-lock 409 in another tab.
632
- const em = requestEm?.fork()
633
- if (em) {
634
- const totals = await recalculateOrderTotalsForDisplay(
635
- em,
636
- ctx.container,
637
- orderId,
638
- { tenantId, organizationId },
639
- )
640
- if (totals) {
641
- Object.assign(item, {
642
- subtotalNetAmount: totals.subtotalNetAmount,
643
- subtotalGrossAmount: totals.subtotalGrossAmount,
644
- discountTotalAmount: totals.discountTotalAmount,
645
- taxTotalAmount: totals.taxTotalAmount,
646
- shippingNetAmount: totals.shippingNetAmount,
647
- shippingGrossAmount: totals.shippingGrossAmount,
648
- surchargeTotalAmount: totals.surchargeTotalAmount,
649
- grandTotalNetAmount: totals.grandTotalNetAmount,
650
- grandTotalGrossAmount: totals.grandTotalGrossAmount,
651
- paidTotalAmount: totals.paidTotalAmount,
652
- refundedTotalAmount: totals.refundedTotalAmount,
653
- outstandingAmount: totals.outstandingAmount,
654
- })
655
- }
656
- }
657
- }
658
- }
659
618
  },
660
619
  },
661
620
  }
@@ -198,8 +198,10 @@ function buildCalculationContext(order: SalesOrder) {
198
198
  }
199
199
 
200
200
  /**
201
- * Recalculates order totals (including line-scoped return adjustments) for display.
202
- * Returns the totals object to merge into an order API response, or null if order not found.
201
+ * Recalculates order totals (including line-scoped return adjustments) from lines and
202
+ * adjustments. Do not merge this into GET /api/sales/orders responses: provider
203
+ * calculators re-emit shipping/payment fees on top of already-persisted adjustments
204
+ * and make list vs detail totals diverge (#5438). Persist via commands instead.
203
205
  */
204
206
  export async function recalculateOrderTotalsForDisplay(
205
207
  em: EntityManager,