@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js +70 -57
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/acl.js +30 -5
- package/dist/modules/catalog/acl.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +17 -5
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/commands/offers.js +26 -7
- package/dist/modules/catalog/commands/offers.js.map +2 -2
- package/dist/modules/catalog/commands/prices.js +41 -26
- package/dist/modules/catalog/commands/prices.js.map +2 -2
- package/dist/modules/catalog/commands/productUnitConversions.js +7 -1
- package/dist/modules/catalog/commands/productUnitConversions.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +2 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/shared.js +58 -11
- package/dist/modules/catalog/commands/shared.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +18 -5
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +17 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +20 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +28 -6
- package/src/modules/auth/i18n/de.json +1 -0
- package/src/modules/auth/i18n/en.json +1 -0
- package/src/modules/auth/i18n/es.json +1 -0
- package/src/modules/auth/i18n/pl.json +1 -0
- package/src/modules/catalog/acl.ts +30 -5
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -5
- package/src/modules/catalog/commands/offers.ts +26 -7
- package/src/modules/catalog/commands/prices.ts +41 -26
- package/src/modules/catalog/commands/productUnitConversions.ts +7 -1
- package/src/modules/catalog/commands/products.ts +2 -0
- package/src/modules/catalog/commands/shared.ts +70 -6
- package/src/modules/catalog/commands/variants.ts +18 -5
- package/src/modules/catalog/i18n/de.json +1 -0
- package/src/modules/catalog/i18n/en.json +1 -0
- package/src/modules/catalog/i18n/es.json +1 -0
- package/src/modules/catalog/i18n/pl.json +1 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +21 -2
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +28 -1
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- 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.
|
|
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.
|
|
247
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
248
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
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.
|
|
254
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
255
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
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
|
-
|
|
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
|
-
{
|
|
3
|
-
|
|
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
|
-
{
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
572
|
-
|
|
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
|
|
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(
|
|
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);
|