@open-mercato/core 0.6.4-develop.4000.1.450e315cec → 0.6.4-develop.4011.1.4f3ed9ae3e

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 (47) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/backend/users/[id]/edit/page.js +70 -57
  3. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  4. package/dist/modules/catalog/acl.js +30 -5
  5. package/dist/modules/catalog/acl.js.map +2 -2
  6. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +17 -5
  7. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  8. package/dist/modules/catalog/commands/offers.js +26 -7
  9. package/dist/modules/catalog/commands/offers.js.map +2 -2
  10. package/dist/modules/catalog/commands/prices.js +41 -26
  11. package/dist/modules/catalog/commands/prices.js.map +2 -2
  12. package/dist/modules/catalog/commands/productUnitConversions.js +7 -1
  13. package/dist/modules/catalog/commands/productUnitConversions.js.map +2 -2
  14. package/dist/modules/catalog/commands/products.js +2 -0
  15. package/dist/modules/catalog/commands/products.js.map +2 -2
  16. package/dist/modules/catalog/commands/shared.js +58 -11
  17. package/dist/modules/catalog/commands/shared.js.map +2 -2
  18. package/dist/modules/catalog/commands/variants.js +18 -5
  19. package/dist/modules/catalog/commands/variants.js.map +2 -2
  20. package/dist/modules/resources/backend/resources/resources/[id]/page.js +17 -2
  21. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  22. package/dist/modules/sales/backend/sales/documents/[id]/page.js +20 -1
  23. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  24. package/package.json +7 -7
  25. package/src/modules/auth/backend/users/[id]/edit/page.tsx +28 -6
  26. package/src/modules/auth/i18n/de.json +1 -0
  27. package/src/modules/auth/i18n/en.json +1 -0
  28. package/src/modules/auth/i18n/es.json +1 -0
  29. package/src/modules/auth/i18n/pl.json +1 -0
  30. package/src/modules/catalog/acl.ts +30 -5
  31. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -5
  32. package/src/modules/catalog/commands/offers.ts +26 -7
  33. package/src/modules/catalog/commands/prices.ts +41 -26
  34. package/src/modules/catalog/commands/productUnitConversions.ts +7 -1
  35. package/src/modules/catalog/commands/products.ts +2 -0
  36. package/src/modules/catalog/commands/shared.ts +70 -6
  37. package/src/modules/catalog/commands/variants.ts +18 -5
  38. package/src/modules/catalog/i18n/de.json +1 -0
  39. package/src/modules/catalog/i18n/en.json +1 -0
  40. package/src/modules/catalog/i18n/es.json +1 -0
  41. package/src/modules/catalog/i18n/pl.json +1 -0
  42. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +21 -2
  43. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +28 -1
  44. package/src/modules/sales/i18n/de.json +3 -0
  45. package/src/modules/sales/i18n/en.json +3 -0
  46. package/src/modules/sales/i18n/es.json +3 -0
  47. package/src/modules/sales/i18n/pl.json +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.4-develop.4000.1.450e315cec",
3
+ "version": "0.6.4-develop.4011.1.4f3ed9ae3e",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -243,16 +243,16 @@
243
243
  "zod": "^4.4.3"
244
244
  },
245
245
  "peerDependencies": {
246
- "@open-mercato/ai-assistant": "0.6.4-develop.4000.1.450e315cec",
247
- "@open-mercato/shared": "0.6.4-develop.4000.1.450e315cec",
248
- "@open-mercato/ui": "0.6.4-develop.4000.1.450e315cec",
246
+ "@open-mercato/ai-assistant": "0.6.4-develop.4011.1.4f3ed9ae3e",
247
+ "@open-mercato/shared": "0.6.4-develop.4011.1.4f3ed9ae3e",
248
+ "@open-mercato/ui": "0.6.4-develop.4011.1.4f3ed9ae3e",
249
249
  "react": "^19.0.0",
250
250
  "react-dom": "^19.0.0"
251
251
  },
252
252
  "devDependencies": {
253
- "@open-mercato/ai-assistant": "0.6.4-develop.4000.1.450e315cec",
254
- "@open-mercato/shared": "0.6.4-develop.4000.1.450e315cec",
255
- "@open-mercato/ui": "0.6.4-develop.4000.1.450e315cec",
253
+ "@open-mercato/ai-assistant": "0.6.4-develop.4011.1.4f3ed9ae3e",
254
+ "@open-mercato/shared": "0.6.4-develop.4011.1.4f3ed9ae3e",
255
+ "@open-mercato/ui": "0.6.4-develop.4011.1.4f3ed9ae3e",
256
256
  "@testing-library/dom": "^10.4.1",
257
257
  "@testing-library/jest-dom": "^6.9.1",
258
258
  "@testing-library/react": "^16.3.1",
@@ -17,6 +17,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
17
17
  import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
18
18
  import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
19
19
  import { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'
20
+ import { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'
20
21
  import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
21
22
 
22
23
  type EditUserFormValues = {
@@ -119,6 +120,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
119
120
  const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)
120
121
  const [loading, setLoading] = React.useState(true)
121
122
  const [error, setError] = React.useState<string | null>(null)
123
+ const [isNotFound, setIsNotFound] = React.useState(false)
122
124
  const [canEditOrgs, setCanEditOrgs] = React.useState(false)
123
125
  const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })
124
126
  const [customFieldValues, setCustomFieldValues] = React.useState<Record<string, unknown>>({})
@@ -171,6 +173,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
171
173
  async function load() {
172
174
  setLoading(true)
173
175
  setError(null)
176
+ setIsNotFound(false)
174
177
  setCustomFieldValues({})
175
178
  try {
176
179
  const { ok, result } = await apiCall<UserListResponse>(
@@ -182,7 +185,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
182
185
  setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))
183
186
  setActorResolved(true)
184
187
  if (!item) {
185
- setError(tRef.current('auth.users.form.errors.notFound', 'User not found'))
188
+ setIsNotFound(true)
186
189
  setCustomFieldValues({})
187
190
  setInitialUser(null)
188
191
  setSelectedTenantId(null)
@@ -404,14 +407,33 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
404
407
  }
405
408
  }, [initialUser, customFieldValues, selectedTenantId])
406
409
 
410
+ if (isNotFound) {
411
+ return (
412
+ <Page>
413
+ <PageBody>
414
+ <RecordNotFoundState
415
+ label={t('auth.users.form.errors.notFound', 'User not found')}
416
+ backHref="/backend/users"
417
+ backLabel={t('auth.users.form.actions.backToList', 'Back to users')}
418
+ />
419
+ </PageBody>
420
+ </Page>
421
+ )
422
+ }
423
+
424
+ if (error && !loading) {
425
+ return (
426
+ <Page>
427
+ <PageBody>
428
+ <ErrorMessage label={error} />
429
+ </PageBody>
430
+ </Page>
431
+ )
432
+ }
433
+
407
434
  return (
408
435
  <Page>
409
436
  <PageBody>
410
- {error && (
411
- <div className="p-4 mb-4 bg-red-50 border border-red-200 rounded text-red-800">
412
- {error}
413
- </div>
414
- )}
415
437
  <CrudForm<EditUserFormValues>
416
438
  title={t('auth.users.form.title.edit', 'Edit User')}
417
439
  backHref="/backend/users"
@@ -172,6 +172,7 @@
172
172
  "auth.users.form.action.resendInvite": "Einladung erneut senden",
173
173
  "auth.users.form.action.resendingInvite": "Wird gesendet...",
174
174
  "auth.users.form.action.save": "Speichern",
175
+ "auth.users.form.actions.backToList": "Zurück zu Benutzern",
175
176
  "auth.users.form.errors.aclUpdate": "Aktualisierung der Benutzerberechtigungen fehlgeschlagen",
176
177
  "auth.users.form.errors.delete": "Benutzer konnte nicht gelöscht werden",
177
178
  "auth.users.form.errors.inviteResend": "Einladungs-E-Mail konnte nicht gesendet werden",
@@ -172,6 +172,7 @@
172
172
  "auth.users.form.action.resendInvite": "Resend Invite",
173
173
  "auth.users.form.action.resendingInvite": "Sending...",
174
174
  "auth.users.form.action.save": "Save",
175
+ "auth.users.form.actions.backToList": "Back to users",
175
176
  "auth.users.form.errors.aclUpdate": "Failed to update user access control",
176
177
  "auth.users.form.errors.delete": "Failed to delete user",
177
178
  "auth.users.form.errors.inviteResend": "Failed to send invitation email",
@@ -172,6 +172,7 @@
172
172
  "auth.users.form.action.resendInvite": "Reenviar invitación",
173
173
  "auth.users.form.action.resendingInvite": "Enviando...",
174
174
  "auth.users.form.action.save": "Guardar",
175
+ "auth.users.form.actions.backToList": "Volver a usuarios",
175
176
  "auth.users.form.errors.aclUpdate": "No se pudo actualizar el control de acceso del usuario",
176
177
  "auth.users.form.errors.delete": "No se pudo eliminar el usuario",
177
178
  "auth.users.form.errors.inviteResend": "No se pudo enviar el correo de invitación",
@@ -172,6 +172,7 @@
172
172
  "auth.users.form.action.resendInvite": "Wyślij zaproszenie ponownie",
173
173
  "auth.users.form.action.resendingInvite": "Wysyłanie...",
174
174
  "auth.users.form.action.save": "Zapisz",
175
+ "auth.users.form.actions.backToList": "Powrót do listy użytkowników",
175
176
  "auth.users.form.errors.aclUpdate": "Nie udało się zaktualizować uprawnień użytkownika",
176
177
  "auth.users.form.errors.delete": "Nie udało się usunąć użytkownika",
177
178
  "auth.users.form.errors.inviteResend": "Nie udało się wysłać e-maila z zaproszeniem",
@@ -1,10 +1,35 @@
1
1
  export const features = [
2
- { id: 'catalog.products.view', title: 'View catalog products', module: 'catalog' },
3
- { id: 'catalog.products.manage', title: 'Manage catalog products', module: 'catalog' },
2
+ {
3
+ id: 'catalog.products.view',
4
+ title: 'View catalog products',
5
+ module: 'catalog',
6
+ dependsOn: ['currencies.view', 'dictionaries.view'],
7
+ },
8
+ {
9
+ id: 'catalog.products.manage',
10
+ title: 'Manage catalog products',
11
+ module: 'catalog',
12
+ dependsOn: ['catalog.products.view'],
13
+ },
4
14
  { id: 'catalog.categories.view', title: 'View catalog categories', module: 'catalog' },
5
- { id: 'catalog.categories.manage', title: 'Manage catalog categories', module: 'catalog' },
6
- { id: 'catalog.variants.manage', title: 'Manage catalog variants', module: 'catalog' },
7
- { id: 'catalog.pricing.manage', title: 'Manage catalog pricing', module: 'catalog' },
15
+ {
16
+ id: 'catalog.categories.manage',
17
+ title: 'Manage catalog categories',
18
+ module: 'catalog',
19
+ dependsOn: ['catalog.categories.view'],
20
+ },
21
+ {
22
+ id: 'catalog.variants.manage',
23
+ title: 'Manage catalog variants',
24
+ module: 'catalog',
25
+ dependsOn: ['catalog.products.view'],
26
+ },
27
+ {
28
+ id: 'catalog.pricing.manage',
29
+ title: 'Manage catalog pricing',
30
+ module: 'catalog',
31
+ dependsOn: ['catalog.products.view', 'currencies.view'],
32
+ },
8
33
  { id: 'catalog.settings.manage', title: 'Manage catalog settings', module: 'catalog' },
9
34
  ]
10
35
 
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import Link from "next/link";
5
5
  import dynamic from "next/dynamic";
6
6
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
- import { ErrorMessage } from "@open-mercato/ui/backend/detail";
7
+ import { ErrorMessage, RecordNotFoundState } from "@open-mercato/ui/backend/detail";
8
8
  import {
9
9
  CrudForm,
10
10
  type CrudFormGroup,
@@ -327,6 +327,7 @@ export default function EditCatalogProductPage({
327
327
  React.useState<Partial<ProductFormValues> | null>(null);
328
328
  const [loading, setLoading] = React.useState(true);
329
329
  const [error, setError] = React.useState<string | null>(null);
330
+ const [isNotFound, setIsNotFound] = React.useState(false);
330
331
  const offerSnapshotsRef = React.useRef<OfferSnapshot[]>([]);
331
332
  const initialConversionsRef = React.useRef<ProductUnitConversionDraft[]>([]);
332
333
  const [categorizeOptions, setCategorizeOptions] = React.useState<{
@@ -552,6 +553,7 @@ export default function EditCatalogProductPage({
552
553
  async function loadProduct() {
553
554
  setLoading(true);
554
555
  setError(null);
556
+ setIsNotFound(false);
555
557
  try {
556
558
  const productRes = await apiCall<ProductResponse>(
557
559
  `/api/catalog/products?id=${encodeURIComponent(productId!)}&page=1&pageSize=1&withDeleted=false`,
@@ -567,10 +569,10 @@ export default function EditCatalogProductPage({
567
569
  const record = Array.isArray(productRes.result?.items)
568
570
  ? productRes.result?.items?.[0]
569
571
  : undefined;
570
- if (!record)
571
- throw new Error(
572
- t("catalog.products.edit.errors.notFound", "Product not found."),
573
- );
572
+ if (!record) {
573
+ if (!cancelled) setIsNotFound(true);
574
+ return;
575
+ }
574
576
  const rawMetadata = isRecord(record.metadata)
575
577
  ? (record.metadata as Record<string, unknown>)
576
578
  : null;
@@ -1341,6 +1343,20 @@ export default function EditCatalogProductPage({
1341
1343
  );
1342
1344
  }
1343
1345
 
1346
+ if (isNotFound && !loading) {
1347
+ return (
1348
+ <Page>
1349
+ <PageBody>
1350
+ <RecordNotFoundState
1351
+ label={t("catalog.products.edit.errors.notFound", "Product not found.")}
1352
+ backHref="/backend/catalog/products"
1353
+ backLabel={t("catalog.products.edit.actions.backToList", "Back to products")}
1354
+ />
1355
+ </PageBody>
1356
+ </Page>
1357
+ );
1358
+ }
1359
+
1344
1360
  if (error && !loading) {
1345
1361
  return (
1346
1362
  <Page>
@@ -14,6 +14,7 @@ import {
14
14
  } from '../data/validators'
15
15
  import {
16
16
  cloneJson,
17
+ commandActorScope,
17
18
  ensureOrganizationScope,
18
19
  ensureSameScope,
19
20
  ensureTenantScope,
@@ -98,7 +99,10 @@ const createOfferCommand: CommandHandler<OfferCreateInput, { offerId: string }>
98
99
  ensureTenantScope(ctx, parsed.tenantId)
99
100
  ensureOrganizationScope(ctx, parsed.organizationId)
100
101
  const em = (ctx.container.resolve('em') as EntityManager).fork()
101
- const product = await requireProduct(em, parsed.productId)
102
+ const product = await requireProduct(em, parsed.productId, {
103
+ tenantId: parsed.tenantId,
104
+ organizationId: parsed.organizationId,
105
+ })
102
106
  if (
103
107
  product.organizationId !== parsed.organizationId ||
104
108
  product.tenantId !== parsed.tenantId
@@ -224,10 +228,16 @@ const updateOfferCommand: CommandHandler<OfferUpdateInput, { offerId: string }>
224
228
  ensureOrganizationScope(ctx, record.organizationId)
225
229
  let productEntity =
226
230
  typeof record.product === 'string'
227
- ? await requireProduct(em, record.product)
231
+ ? await requireProduct(em, record.product, {
232
+ tenantId: record.tenantId,
233
+ organizationId: record.organizationId,
234
+ })
228
235
  : record.product
229
236
  if (parsed.productId && parsed.productId !== record.product.id) {
230
- const nextProduct = await requireProduct(em, parsed.productId)
237
+ const nextProduct = await requireProduct(em, parsed.productId, {
238
+ tenantId: record.tenantId,
239
+ organizationId: record.organizationId,
240
+ })
231
241
  ensureSameScope(nextProduct, record.organizationId, record.tenantId)
232
242
  productEntity = nextProduct
233
243
  }
@@ -322,9 +332,15 @@ const updateOfferCommand: CommandHandler<OfferUpdateInput, { offerId: string }>
322
332
  const before = payload?.before
323
333
  if (!before) return
324
334
  const em = (ctx.container.resolve('em') as EntityManager).fork()
325
- const record = await requireOffer(em, before.id).catch(() => null)
335
+ const record = await requireOffer(em, before.id, {
336
+ tenantId: before.tenantId,
337
+ organizationId: before.organizationId,
338
+ }).catch(() => null)
326
339
  if (!record) {
327
- const product = await requireProduct(em, before.productId)
340
+ const product = await requireProduct(em, before.productId, {
341
+ tenantId: before.tenantId,
342
+ organizationId: before.organizationId,
343
+ })
328
344
  ensureSameScope(product, before.organizationId, before.tenantId)
329
345
  const restored = em.create(CatalogOffer, {
330
346
  id: before.id,
@@ -384,7 +400,7 @@ const deleteOfferCommand: CommandHandler<{ id?: string }, { offerId: string }> =
384
400
  async execute(input, ctx) {
385
401
  const parsed = { id: requireId(input, 'Offer id is required.') }
386
402
  const em = (ctx.container.resolve('em') as EntityManager).fork()
387
- const record = await requireOffer(em, parsed.id)
403
+ const record = await requireOffer(em, parsed.id, commandActorScope(ctx))
388
404
  ensureTenantScope(ctx, record.tenantId)
389
405
  ensureOrganizationScope(ctx, record.organizationId)
390
406
  const baseEm = ctx.container.resolve('em') as EntityManager
@@ -436,7 +452,10 @@ const deleteOfferCommand: CommandHandler<{ id?: string }, { offerId: string }> =
436
452
  const em = (ctx.container.resolve('em') as EntityManager).fork()
437
453
  const existing = await em.findOne(CatalogOffer, { id: before.id })
438
454
  if (existing) return
439
- const product = await requireProduct(em, before.productId)
455
+ const product = await requireProduct(em, before.productId, {
456
+ tenantId: before.tenantId,
457
+ organizationId: before.organizationId,
458
+ })
440
459
  ensureSameScope(product, before.organizationId, before.tenantId)
441
460
  const restored = em.create(CatalogOffer, {
442
461
  id: before.id,
@@ -16,6 +16,7 @@ import {
16
16
  import type { TaxCalculationService } from '@open-mercato/core/modules/sales/services/taxCalculationService'
17
17
  import {
18
18
  cloneJson,
19
+ commandActorScope,
19
20
  ensureOrganizationScope,
20
21
  ensureSameScope,
21
22
  ensureSameTenant,
@@ -105,17 +106,18 @@ async function resolveSnapshotAssociations(
105
106
  product: CatalogProduct
106
107
  offer: CatalogOffer | null
107
108
  }> {
109
+ const scope = { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId }
108
110
  let variant: CatalogProductVariant | null = null
109
111
  if (snapshot.variantId) {
110
- variant = await requireVariant(em, snapshot.variantId)
112
+ variant = await requireVariant(em, snapshot.variantId, scope)
111
113
  }
112
114
  let product: CatalogProduct | null = null
113
115
  if (snapshot.productId) {
114
- product = await requireProduct(em, snapshot.productId)
116
+ product = await requireProduct(em, snapshot.productId, scope)
115
117
  } else if (variant) {
116
118
  product =
117
119
  typeof variant.product === 'string'
118
- ? await requireProduct(em, variant.product)
120
+ ? await requireProduct(em, variant.product, scope)
119
121
  : variant.product
120
122
  }
121
123
  if (!product) {
@@ -123,7 +125,7 @@ async function resolveSnapshotAssociations(
123
125
  }
124
126
  let offer: CatalogOffer | null = null
125
127
  if (snapshot.offerId) {
126
- offer = await requireOffer(em, snapshot.offerId)
128
+ offer = await requireOffer(em, snapshot.offerId, scope)
127
129
  }
128
130
  return { variant, product, offer }
129
131
  }
@@ -262,17 +264,21 @@ const createPriceCommand: CommandHandler<PriceCreateInput, { priceId: string }>
262
264
  async execute(rawInput, ctx) {
263
265
  const { parsed, custom } = parseWithCustomFields(priceCreateSchema, rawInput)
264
266
  const em = (ctx.container.resolve('em') as EntityManager).fork()
267
+ const actorScope = commandActorScope(ctx)
265
268
  let variant: CatalogProductVariant | null = null
266
269
  let product: CatalogProduct | null = null
267
270
  if (parsed.variantId) {
268
- variant = await requireVariant(em, parsed.variantId)
271
+ variant = await requireVariant(em, parsed.variantId, actorScope)
269
272
  product =
270
273
  typeof variant.product === 'string'
271
- ? await requireProduct(em, variant.product)
274
+ ? await requireProduct(em, variant.product, {
275
+ tenantId: variant.tenantId,
276
+ organizationId: variant.organizationId,
277
+ })
272
278
  : variant.product
273
279
  }
274
280
  if (parsed.productId) {
275
- const explicitProduct = await requireProduct(em, parsed.productId)
281
+ const explicitProduct = await requireProduct(em, parsed.productId, actorScope)
276
282
  if (product && explicitProduct.id !== product.id) {
277
283
  throw new CrudHttpError(400, { error: 'Variant does not belong to the provided product.' })
278
284
  }
@@ -284,17 +290,24 @@ const createPriceCommand: CommandHandler<PriceCreateInput, { priceId: string }>
284
290
  const scopeSource = variant ?? product!
285
291
  ensureTenantScope(ctx, scopeSource.tenantId)
286
292
  ensureOrganizationScope(ctx, scopeSource.organizationId)
293
+ const scopeSourceScope = {
294
+ tenantId: scopeSource.tenantId,
295
+ organizationId: scopeSource.organizationId,
296
+ }
287
297
 
288
- const priceKind = await requirePriceKind(em, parsed.priceKindId)
298
+ const priceKind = await requirePriceKind(em, parsed.priceKindId, scopeSourceScope)
289
299
  ensureSameTenant(priceKind, scopeSource.tenantId)
290
300
 
291
301
  let offer: CatalogOffer | null = null
292
302
  if (parsed.offerId) {
293
- offer = await requireOffer(em, parsed.offerId)
303
+ offer = await requireOffer(em, parsed.offerId, scopeSourceScope)
294
304
  ensureSameScope(offer, scopeSource.organizationId, scopeSource.tenantId)
295
305
  const offerProduct =
296
306
  typeof offer.product === 'string'
297
- ? await requireProduct(em, offer.product)
307
+ ? await requireProduct(em, offer.product, {
308
+ tenantId: offer.tenantId,
309
+ organizationId: offer.organizationId,
310
+ })
298
311
  : offer.product
299
312
  if (product && offerProduct.id !== product.id) {
300
313
  throw new CrudHttpError(400, { error: 'Offer does not belong to the selected product.' })
@@ -445,17 +458,18 @@ const updatePriceCommand: CommandHandler<PriceUpdateInput, { priceId: string }>
445
458
  { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
446
459
  )
447
460
  if (!record) throw new CrudHttpError(404, { error: 'Catalog price not found' })
461
+ const recordScope = { tenantId: record.tenantId, organizationId: record.organizationId }
448
462
  const currentVariantRef = record.variant
449
463
  let targetVariant: CatalogProductVariant | null = null
450
464
  if (typeof currentVariantRef === 'string') {
451
- targetVariant = await requireVariant(em, currentVariantRef)
465
+ targetVariant = await requireVariant(em, currentVariantRef, recordScope)
452
466
  } else if (currentVariantRef) {
453
467
  targetVariant = currentVariantRef
454
468
  }
455
469
  const currentProductRef = record.product ?? (targetVariant ? targetVariant.product : null)
456
470
  let targetProduct: CatalogProduct | null = null
457
471
  if (typeof currentProductRef === 'string') {
458
- targetProduct = await requireProduct(em, currentProductRef)
472
+ targetProduct = await requireProduct(em, currentProductRef, recordScope)
459
473
  } else if (currentProductRef) {
460
474
  targetProduct = currentProductRef
461
475
  }
@@ -464,23 +478,23 @@ const updatePriceCommand: CommandHandler<PriceUpdateInput, { priceId: string }>
464
478
  if (!parsed.variantId) {
465
479
  targetVariant = null
466
480
  } else {
467
- targetVariant = await requireVariant(em, parsed.variantId)
481
+ targetVariant = await requireVariant(em, parsed.variantId, recordScope)
468
482
  targetProduct =
469
483
  typeof targetVariant.product === 'string'
470
- ? await requireProduct(em, targetVariant.product)
484
+ ? await requireProduct(em, targetVariant.product, recordScope)
471
485
  : targetVariant.product
472
486
  }
473
487
  }
474
488
 
475
489
  if (targetVariant && (targetVariant as CatalogProductVariant | null)?.product === undefined) {
476
- targetVariant = await requireVariant(em, targetVariant.id)
490
+ targetVariant = await requireVariant(em, targetVariant.id, recordScope)
477
491
  }
478
492
 
479
493
  if (parsed.productId !== undefined) {
480
494
  if (!parsed.productId) {
481
495
  targetProduct = null
482
496
  } else {
483
- const explicitProduct = await requireProduct(em, parsed.productId)
497
+ const explicitProduct = await requireProduct(em, parsed.productId, recordScope)
484
498
  if (targetVariant) {
485
499
  const variantProductId =
486
500
  typeof targetVariant.product === 'string'
@@ -500,7 +514,7 @@ const updatePriceCommand: CommandHandler<PriceUpdateInput, { priceId: string }>
500
514
  if (!targetProduct && targetVariant) {
501
515
  targetProduct =
502
516
  typeof targetVariant.product === 'string'
503
- ? await requireProduct(em, targetVariant.product)
517
+ ? await requireProduct(em, targetVariant.product, recordScope)
504
518
  : targetVariant.product
505
519
  }
506
520
  if (!targetProduct) {
@@ -511,14 +525,14 @@ const updatePriceCommand: CommandHandler<PriceUpdateInput, { priceId: string }>
511
525
  if (record.offer) {
512
526
  targetOffer =
513
527
  typeof record.offer === 'string'
514
- ? await requireOffer(em, record.offer)
528
+ ? await requireOffer(em, record.offer, recordScope)
515
529
  : record.offer
516
530
  }
517
531
  if (parsed.offerId !== undefined) {
518
532
  if (!parsed.offerId) {
519
533
  targetOffer = null
520
534
  } else {
521
- const explicitOffer = await requireOffer(em, parsed.offerId)
535
+ const explicitOffer = await requireOffer(em, parsed.offerId, recordScope)
522
536
  ensureSameScope(explicitOffer, targetProduct.organizationId, targetProduct.tenantId)
523
537
  const offerProductId =
524
538
  typeof explicitOffer.product === 'string'
@@ -538,14 +552,14 @@ const updatePriceCommand: CommandHandler<PriceUpdateInput, { priceId: string }>
538
552
  if (record.priceKind) {
539
553
  targetPriceKind =
540
554
  typeof record.priceKind === 'string'
541
- ? await requirePriceKind(em, record.priceKind)
555
+ ? await requirePriceKind(em, record.priceKind, recordScope)
542
556
  : record.priceKind
543
557
  }
544
558
  if (parsed.priceKindId !== undefined) {
545
559
  if (!parsed.priceKindId) {
546
560
  throw new CrudHttpError(400, { error: 'Price kind is required.' })
547
561
  }
548
- targetPriceKind = await requirePriceKind(em, parsed.priceKindId)
562
+ targetPriceKind = await requirePriceKind(em, parsed.priceKindId, recordScope)
549
563
  }
550
564
  if (!targetPriceKind) {
551
565
  throw new CrudHttpError(400, { error: 'Price kind is required.' })
@@ -881,15 +895,16 @@ async function resolvePriceRecordAssociations(
881
895
  em: EntityManager,
882
896
  record: CatalogProductPrice,
883
897
  ): Promise<{ product: CatalogProduct; variant: CatalogProductVariant | null }> {
898
+ const scope = { tenantId: record.tenantId, organizationId: record.organizationId }
884
899
  const variant = record.variant
885
900
  ? (typeof record.variant === 'string'
886
- ? await requireVariant(em, record.variant)
901
+ ? await requireVariant(em, record.variant, scope)
887
902
  : record.variant)
888
903
  : null
889
904
  if (record.product) {
890
905
  const product =
891
906
  typeof record.product === 'string'
892
- ? await requireProduct(em, record.product)
907
+ ? await requireProduct(em, record.product, scope)
893
908
  : record.product
894
909
  return { product, variant }
895
910
  }
@@ -897,20 +912,20 @@ async function resolvePriceRecordAssociations(
897
912
  const productRef = variant.product
898
913
  const product =
899
914
  typeof productRef === 'string'
900
- ? await requireProduct(em, productRef)
915
+ ? await requireProduct(em, productRef, scope)
901
916
  : productRef
902
917
  return { product, variant }
903
918
  }
904
919
  if (record.offer) {
905
920
  const offer =
906
921
  typeof record.offer === 'string'
907
- ? await requireOffer(em, record.offer)
922
+ ? await requireOffer(em, record.offer, scope)
908
923
  : record.offer
909
924
  const productRef = offer?.product ?? null
910
925
  if (productRef) {
911
926
  const product =
912
927
  typeof productRef === 'string'
913
- ? await requireProduct(em, productRef)
928
+ ? await requireProduct(em, productRef, scope)
914
929
  : productRef
915
930
  return { product, variant }
916
931
  }
@@ -247,6 +247,7 @@ const createProductUnitConversionCommand: CommandHandler<
247
247
  const product = await requireProduct(
248
248
  em,
249
249
  parsed.productId,
250
+ { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
250
251
  translate("catalog.errors.productNotFound", "Catalog product not found"),
251
252
  );
252
253
  ensureSameScope(product, parsed.organizationId, parsed.tenantId);
@@ -373,7 +374,12 @@ const updateProductUnitConversionCommand: CommandHandler<
373
374
  });
374
375
  const product =
375
376
  typeof record.product === "string"
376
- ? await requireProduct(em, record.product, translate("catalog.errors.productNotFound", "Catalog product not found"))
377
+ ? await requireProduct(
378
+ em,
379
+ record.product,
380
+ { tenantId: record.tenantId, organizationId: record.organizationId },
381
+ translate("catalog.errors.productNotFound", "Catalog product not found"),
382
+ )
377
383
  : record.product;
378
384
  ensureTenantScope(ctx, record.tenantId);
379
385
  ensureOrganizationScope(ctx, record.organizationId);
@@ -1288,6 +1288,7 @@ const createProductCommand: CommandHandler<
1288
1288
  optionSchemaTemplate = await requireOptionSchemaTemplate(
1289
1289
  em,
1290
1290
  parsed.optionSchemaId,
1291
+ { tenantId: parsed.tenantId, organizationId: parsed.organizationId },
1291
1292
  translate("catalog.errors.optionSchemaNotFound", "Option schema not found"),
1292
1293
  );
1293
1294
  ensureSameScope(
@@ -1591,6 +1592,7 @@ const updateProductCommand: CommandHandler<
1591
1592
  const optionTemplate = await requireOptionSchemaTemplate(
1592
1593
  lookupEm,
1593
1594
  parsed.optionSchemaId,
1595
+ { tenantId, organizationId },
1594
1596
  translate("catalog.errors.optionSchemaNotFound", "Option schema not found"),
1595
1597
  );
1596
1598
  ensureSameScope(optionTemplate, organizationId, tenantId);