@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
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
CatalogOptionSchemaTemplate,
|
|
6
6
|
CatalogPriceKind,
|
|
7
7
|
} from '../data/entities'
|
|
8
|
-
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
8
|
+
import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
|
|
9
9
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
10
|
import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
11
11
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
@@ -73,12 +73,42 @@ export function toNumericString(value: number | null | undefined): string | null
|
|
|
73
73
|
return value.toString()
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export type RequireScope = {
|
|
77
|
+
tenantId: string | null
|
|
78
|
+
organizationId: string | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Derives the actor's effective tenant/org scope for entry-point lookups, mirroring
|
|
82
|
+
// the bypass semantics of ensureTenantScope/ensureOrganizationScope: tenant is always
|
|
83
|
+
// strict, organization is left unrestricted for super-admins and global-org actors.
|
|
84
|
+
export function commandActorScope(ctx: CommandRuntimeContext): RequireScope {
|
|
85
|
+
const orgUnrestricted = ctx.auth?.isSuperAdmin === true || ctx.organizationScope?.allowedIds === null
|
|
86
|
+
return {
|
|
87
|
+
tenantId: ctx.auth?.tenantId ?? null,
|
|
88
|
+
organizationId: orgUnrestricted ? null : (ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function applyScopeToWhere(where: Record<string, unknown>, scope: RequireScope): void {
|
|
93
|
+
if (scope.tenantId != null) where.tenantId = scope.tenantId
|
|
94
|
+
if (scope.organizationId != null) where.organizationId = scope.organizationId
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
export async function requireProduct(
|
|
77
98
|
em: EntityManager,
|
|
78
99
|
id: string,
|
|
100
|
+
scope: RequireScope,
|
|
79
101
|
message = 'Catalog product not found'
|
|
80
102
|
): Promise<CatalogProduct> {
|
|
81
|
-
const
|
|
103
|
+
const where: Record<string, unknown> = { id, deletedAt: null }
|
|
104
|
+
applyScopeToWhere(where, scope)
|
|
105
|
+
const product = await findOneWithDecryption(
|
|
106
|
+
em,
|
|
107
|
+
CatalogProduct,
|
|
108
|
+
where as FilterQuery<CatalogProduct>,
|
|
109
|
+
undefined,
|
|
110
|
+
{ tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
111
|
+
)
|
|
82
112
|
if (!product) throw new CrudHttpError(404, { error: message })
|
|
83
113
|
return product
|
|
84
114
|
}
|
|
@@ -86,13 +116,17 @@ export async function requireProduct(
|
|
|
86
116
|
export async function requireVariant(
|
|
87
117
|
em: EntityManager,
|
|
88
118
|
id: string,
|
|
119
|
+
scope: RequireScope,
|
|
89
120
|
message = 'Catalog variant not found'
|
|
90
121
|
): Promise<CatalogProductVariant> {
|
|
122
|
+
const where: Record<string, unknown> = { id, deletedAt: null }
|
|
123
|
+
applyScopeToWhere(where, scope)
|
|
91
124
|
const variant = await findOneWithDecryption(
|
|
92
125
|
em,
|
|
93
126
|
CatalogProductVariant,
|
|
94
|
-
|
|
127
|
+
where as FilterQuery<CatalogProductVariant>,
|
|
95
128
|
{ populate: ['product'] },
|
|
129
|
+
{ tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
96
130
|
)
|
|
97
131
|
if (!variant) throw new CrudHttpError(404, { error: message })
|
|
98
132
|
return variant
|
|
@@ -101,9 +135,18 @@ export async function requireVariant(
|
|
|
101
135
|
export async function requireOffer(
|
|
102
136
|
em: EntityManager,
|
|
103
137
|
id: string,
|
|
138
|
+
scope: RequireScope,
|
|
104
139
|
message = 'Catalog offer not found'
|
|
105
140
|
): Promise<CatalogOffer> {
|
|
106
|
-
const
|
|
141
|
+
const where: Record<string, unknown> = { id }
|
|
142
|
+
applyScopeToWhere(where, scope)
|
|
143
|
+
const offer = await findOneWithDecryption(
|
|
144
|
+
em,
|
|
145
|
+
CatalogOffer,
|
|
146
|
+
where as FilterQuery<CatalogOffer>,
|
|
147
|
+
undefined,
|
|
148
|
+
{ tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
149
|
+
)
|
|
107
150
|
if (!offer) throw new CrudHttpError(404, { error: message })
|
|
108
151
|
return offer
|
|
109
152
|
}
|
|
@@ -111,9 +154,21 @@ export async function requireOffer(
|
|
|
111
154
|
export async function requirePriceKind(
|
|
112
155
|
em: EntityManager,
|
|
113
156
|
id: string,
|
|
157
|
+
scope: RequireScope,
|
|
114
158
|
message = 'Catalog price kind not found'
|
|
115
159
|
): Promise<CatalogPriceKind> {
|
|
116
|
-
|
|
160
|
+
// Price kinds are tenant-global: organization_id is always null and the unique key is
|
|
161
|
+
// (tenant_id, code). Scope by tenant only — applying a concrete org would never match the
|
|
162
|
+
// null row. Tenant scoping still closes the cross-tenant read hole this helper guards.
|
|
163
|
+
const where: Record<string, unknown> = { id, deletedAt: null }
|
|
164
|
+
applyScopeToWhere(where, { tenantId: scope.tenantId, organizationId: null })
|
|
165
|
+
const priceKind = await findOneWithDecryption(
|
|
166
|
+
em,
|
|
167
|
+
CatalogPriceKind,
|
|
168
|
+
where as FilterQuery<CatalogPriceKind>,
|
|
169
|
+
undefined,
|
|
170
|
+
{ tenantId: scope.tenantId, organizationId: null },
|
|
171
|
+
)
|
|
117
172
|
if (!priceKind) throw new CrudHttpError(404, { error: message })
|
|
118
173
|
return priceKind
|
|
119
174
|
}
|
|
@@ -121,9 +176,18 @@ export async function requirePriceKind(
|
|
|
121
176
|
export async function requireOptionSchemaTemplate(
|
|
122
177
|
em: EntityManager,
|
|
123
178
|
id: string,
|
|
179
|
+
scope: RequireScope,
|
|
124
180
|
message = 'Option schema not found'
|
|
125
181
|
): Promise<CatalogOptionSchemaTemplate> {
|
|
126
|
-
const
|
|
182
|
+
const where: Record<string, unknown> = { id, deletedAt: null }
|
|
183
|
+
applyScopeToWhere(where, scope)
|
|
184
|
+
const schema = await findOneWithDecryption(
|
|
185
|
+
em,
|
|
186
|
+
CatalogOptionSchemaTemplate,
|
|
187
|
+
where as FilterQuery<CatalogOptionSchemaTemplate>,
|
|
188
|
+
undefined,
|
|
189
|
+
{ tenantId: scope.tenantId, organizationId: scope.organizationId },
|
|
190
|
+
)
|
|
127
191
|
if (!schema) throw new CrudHttpError(404, { error: message })
|
|
128
192
|
return schema
|
|
129
193
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from '../data/validators'
|
|
26
26
|
import {
|
|
27
27
|
cloneJson,
|
|
28
|
+
commandActorScope,
|
|
28
29
|
ensureOrganizationScope,
|
|
29
30
|
ensureTenantScope,
|
|
30
31
|
emitCatalogQueryIndexEvent,
|
|
@@ -318,7 +319,10 @@ async function restoreVariantPricesFromSnapshots(
|
|
|
318
319
|
if (!snapshots.length) return
|
|
319
320
|
const productRef =
|
|
320
321
|
typeof variant.product === 'string'
|
|
321
|
-
? await requireProduct(em, variant.product
|
|
322
|
+
? await requireProduct(em, variant.product, {
|
|
323
|
+
tenantId: variant.tenantId,
|
|
324
|
+
organizationId: variant.organizationId,
|
|
325
|
+
})
|
|
322
326
|
: variant.product
|
|
323
327
|
for (const snapshot of snapshots) {
|
|
324
328
|
const product =
|
|
@@ -547,7 +551,7 @@ const createVariantCommand: CommandHandler<VariantCreateInput, { variantId: stri
|
|
|
547
551
|
async execute(rawInput, ctx) {
|
|
548
552
|
const { parsed, custom } = parseWithCustomFields(variantCreateSchema, rawInput)
|
|
549
553
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
550
|
-
const product = await requireProduct(em, parsed.productId)
|
|
554
|
+
const product = await requireProduct(em, parsed.productId, commandActorScope(ctx))
|
|
551
555
|
ensureTenantScope(ctx, product.tenantId)
|
|
552
556
|
ensureOrganizationScope(ctx, product.organizationId)
|
|
553
557
|
const { taxRateId, taxRate } = await resolveVariantTaxRate(
|
|
@@ -705,7 +709,10 @@ const updateVariantCommand: CommandHandler<VariantUpdateInput, { variantId: stri
|
|
|
705
709
|
if (!record) throw new CrudHttpError(404, { error: 'Catalog variant not found' })
|
|
706
710
|
ensureTenantScope(ctx, record.tenantId)
|
|
707
711
|
ensureOrganizationScope(ctx, record.organizationId)
|
|
708
|
-
const product = await requireProduct(em, record.product.id
|
|
712
|
+
const product = await requireProduct(em, record.product.id, {
|
|
713
|
+
tenantId: record.tenantId,
|
|
714
|
+
organizationId: record.organizationId,
|
|
715
|
+
})
|
|
709
716
|
|
|
710
717
|
if (!product) throw new CrudHttpError(400, { error: 'Variant product missing' })
|
|
711
718
|
|
|
@@ -827,7 +834,10 @@ const updateVariantCommand: CommandHandler<VariantUpdateInput, { variantId: stri
|
|
|
827
834
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
828
835
|
let record = await em.findOne(CatalogProductVariant, { id: before.id })
|
|
829
836
|
if (!record) {
|
|
830
|
-
const product = await requireProduct(em, before.productId
|
|
837
|
+
const product = await requireProduct(em, before.productId, {
|
|
838
|
+
tenantId: before.tenantId,
|
|
839
|
+
organizationId: before.organizationId,
|
|
840
|
+
})
|
|
831
841
|
record = em.create(CatalogProductVariant, {
|
|
832
842
|
id: before.id,
|
|
833
843
|
product,
|
|
@@ -994,7 +1004,10 @@ const deleteVariantCommand: CommandHandler<
|
|
|
994
1004
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
995
1005
|
let record = await em.findOne(CatalogProductVariant, { id: before.id })
|
|
996
1006
|
if (!record) {
|
|
997
|
-
const product = await requireProduct(em, before.productId
|
|
1007
|
+
const product = await requireProduct(em, before.productId, {
|
|
1008
|
+
tenantId: before.tenantId,
|
|
1009
|
+
organizationId: before.organizationId,
|
|
1010
|
+
})
|
|
998
1011
|
record = em.create(CatalogProductVariant, {
|
|
999
1012
|
id: before.id,
|
|
1000
1013
|
product,
|
|
@@ -361,6 +361,7 @@
|
|
|
361
361
|
"catalog.products.create.variantsBuilder.vatColumn": "Steuerklasse",
|
|
362
362
|
"catalog.products.create.variantsBuilder.vatOptionDefault": "Produkt-Steuerklasse verwenden ({{label}})",
|
|
363
363
|
"catalog.products.create.variantsBuilder.vatOptionNone": "Keine Steuerklasse",
|
|
364
|
+
"catalog.products.edit.actions.backToList": "Zurück zu Produkten",
|
|
364
365
|
"catalog.products.edit.custom.title": "Benutzerdefinierte Attribute",
|
|
365
366
|
"catalog.products.edit.dimensions": "Maße & Gewicht",
|
|
366
367
|
"catalog.products.edit.dimensions.depth": "Tiefe",
|
|
@@ -361,6 +361,7 @@
|
|
|
361
361
|
"catalog.products.create.variantsBuilder.vatColumn": "Tax class",
|
|
362
362
|
"catalog.products.create.variantsBuilder.vatOptionDefault": "Use product tax class ({{label}})",
|
|
363
363
|
"catalog.products.create.variantsBuilder.vatOptionNone": "No tax class",
|
|
364
|
+
"catalog.products.edit.actions.backToList": "Back to products",
|
|
364
365
|
"catalog.products.edit.custom.title": "Custom attributes",
|
|
365
366
|
"catalog.products.edit.dimensions": "Dimensions & weight",
|
|
366
367
|
"catalog.products.edit.dimensions.depth": "Depth",
|
|
@@ -361,6 +361,7 @@
|
|
|
361
361
|
"catalog.products.create.variantsBuilder.vatColumn": "Clase de impuesto",
|
|
362
362
|
"catalog.products.create.variantsBuilder.vatOptionDefault": "Usar clase de impuesto del producto ({{label}})",
|
|
363
363
|
"catalog.products.create.variantsBuilder.vatOptionNone": "Sin clase de impuesto",
|
|
364
|
+
"catalog.products.edit.actions.backToList": "Volver a productos",
|
|
364
365
|
"catalog.products.edit.custom.title": "Atributos personalizados",
|
|
365
366
|
"catalog.products.edit.dimensions": "Dimensiones y peso",
|
|
366
367
|
"catalog.products.edit.dimensions.depth": "Profundidad",
|
|
@@ -361,6 +361,7 @@
|
|
|
361
361
|
"catalog.products.create.variantsBuilder.vatColumn": "Klasa podatkowa",
|
|
362
362
|
"catalog.products.create.variantsBuilder.vatOptionDefault": "Użyj klasy podatkowej produktu ({{label}})",
|
|
363
363
|
"catalog.products.create.variantsBuilder.vatOptionNone": "Brak klasy podatkowej",
|
|
364
|
+
"catalog.products.edit.actions.backToList": "Powrót do listy produktów",
|
|
364
365
|
"catalog.products.edit.custom.title": "Atrybuty niestandardowe",
|
|
365
366
|
"catalog.products.edit.dimensions": "Wymiary i waga",
|
|
366
367
|
"catalog.products.edit.dimensions.depth": "Głębokość",
|
|
@@ -10,7 +10,7 @@ import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customF
|
|
|
10
10
|
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
11
11
|
import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
12
12
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
13
|
-
import { ActivitiesSection, NotesSection, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'
|
|
13
|
+
import { ActivitiesSection, NotesSection, RecordNotFoundState, type SectionAction, type TagOption } from '@open-mercato/ui/backend/detail'
|
|
14
14
|
import { VersionHistoryAction } from '@open-mercato/ui/backend/version-history'
|
|
15
15
|
import { SendObjectMessageDialog } from '@open-mercato/ui/backend/messages'
|
|
16
16
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -83,6 +83,7 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
83
83
|
const router = useRouter()
|
|
84
84
|
const searchParams = useSearchParams()
|
|
85
85
|
const [initialValues, setInitialValues] = React.useState<Record<string, unknown> | null>(null)
|
|
86
|
+
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
86
87
|
const [tags, setTags] = React.useState<TagOption[]>([])
|
|
87
88
|
const [activeTab, setActiveTab] = React.useState<'details' | 'availability'>('details')
|
|
88
89
|
const [activeDetailTab, setActiveDetailTab] = React.useState<'notes' | 'activities'>('notes')
|
|
@@ -411,6 +412,7 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
411
412
|
|
|
412
413
|
React.useEffect(() => {
|
|
413
414
|
if (!resourceId || !resourceTypesLoaded) return
|
|
415
|
+
setIsNotFound(false)
|
|
414
416
|
let cancelled = false
|
|
415
417
|
async function loadResource() {
|
|
416
418
|
try {
|
|
@@ -421,7 +423,10 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
421
423
|
const record = await readApiResultOrThrow<ResourceResponse>(`/api/resources/resources?${params.toString()}`)
|
|
422
424
|
const resourceRaw = Array.isArray(record?.items) ? record.items[0] : null
|
|
423
425
|
const resource = resourceRaw ? normalizeResourceRecord(resourceRaw) : null
|
|
424
|
-
if (!resource)
|
|
426
|
+
if (!resource) {
|
|
427
|
+
if (!cancelled) setIsNotFound(true)
|
|
428
|
+
return
|
|
429
|
+
}
|
|
425
430
|
if (!cancelled) {
|
|
426
431
|
const customValues = extractCustomFieldEntries(resource)
|
|
427
432
|
setTags(Array.isArray(resource.tags) ? resource.tags : [])
|
|
@@ -479,6 +484,20 @@ export default function ResourcesResourceDetailPage({ params }: { params?: { id?
|
|
|
479
484
|
? initialValues.name.trim()
|
|
480
485
|
: t('resources.resources.detail.untitled', 'Unnamed resource')
|
|
481
486
|
|
|
487
|
+
if (isNotFound) {
|
|
488
|
+
return (
|
|
489
|
+
<Page>
|
|
490
|
+
<PageBody>
|
|
491
|
+
<RecordNotFoundState
|
|
492
|
+
label={t('resources.resources.form.errors.notFound', 'Resource not found.')}
|
|
493
|
+
backHref="/backend/resources/resources"
|
|
494
|
+
backLabel={t('resources.resources.detail.back', 'Back to resources')}
|
|
495
|
+
/>
|
|
496
|
+
</PageBody>
|
|
497
|
+
</Page>
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
|
|
482
501
|
return (
|
|
483
502
|
<Page>
|
|
484
503
|
<PageBody>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ErrorMessage,
|
|
12
12
|
InlineTextEditor,
|
|
13
13
|
LoadingMessage,
|
|
14
|
+
RecordNotFoundState,
|
|
14
15
|
TabEmptyState,
|
|
15
16
|
TagsSection,
|
|
16
17
|
type TagOption,
|
|
@@ -1876,6 +1877,7 @@ export default function SalesDocumentDetailPage({
|
|
|
1876
1877
|
const searchParams = useSearchParams()
|
|
1877
1878
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
1878
1879
|
const [loading, setLoading] = React.useState(true)
|
|
1880
|
+
const [isNotFound, setIsNotFound] = React.useState(false)
|
|
1879
1881
|
const [record, setRecord] = React.useState<DocumentRecord | null>(null)
|
|
1880
1882
|
const [tags, setTags] = React.useState<TagOption[]>([])
|
|
1881
1883
|
const [kind, setKind] = React.useState<'order' | 'quote'>('quote')
|
|
@@ -2483,6 +2485,7 @@ export default function SalesDocumentDetailPage({
|
|
|
2483
2485
|
async function load() {
|
|
2484
2486
|
setLoading(true)
|
|
2485
2487
|
setError(null)
|
|
2488
|
+
setIsNotFound(false)
|
|
2486
2489
|
const requestedKind = searchParams.get('kind')
|
|
2487
2490
|
const preferredKind = requestedKind === 'order' ? 'order' : requestedKind === 'quote' ? 'quote' : initialKind ?? null
|
|
2488
2491
|
const kindsToTry: Array<'order' | 'quote'> = preferredKind
|
|
@@ -2505,7 +2508,11 @@ export default function SalesDocumentDetailPage({
|
|
|
2505
2508
|
}
|
|
2506
2509
|
if (!cancelled) {
|
|
2507
2510
|
setLoading(false)
|
|
2508
|
-
|
|
2511
|
+
if (lastError) {
|
|
2512
|
+
setError(lastError)
|
|
2513
|
+
} else {
|
|
2514
|
+
setIsNotFound(true)
|
|
2515
|
+
}
|
|
2509
2516
|
}
|
|
2510
2517
|
}
|
|
2511
2518
|
load().catch((err) => {
|
|
@@ -4435,6 +4442,26 @@ export default function SalesDocumentDetailPage({
|
|
|
4435
4442
|
)
|
|
4436
4443
|
}
|
|
4437
4444
|
|
|
4445
|
+
if (isNotFound) {
|
|
4446
|
+
const backHref = (searchParams.get('kind') === 'order' || initialKind === 'order')
|
|
4447
|
+
? '/backend/sales/orders'
|
|
4448
|
+
: '/backend/sales/quotes'
|
|
4449
|
+
const backLabel = (searchParams.get('kind') === 'order' || initialKind === 'order')
|
|
4450
|
+
? t('sales.documents.detail.backToOrders', 'Back to orders')
|
|
4451
|
+
: t('sales.documents.detail.backToQuotes', 'Back to quotes')
|
|
4452
|
+
return (
|
|
4453
|
+
<Page>
|
|
4454
|
+
<PageBody>
|
|
4455
|
+
<RecordNotFoundState
|
|
4456
|
+
label={t('sales.documents.detail.notFound', 'Document not found.')}
|
|
4457
|
+
backHref={backHref}
|
|
4458
|
+
backLabel={backLabel}
|
|
4459
|
+
/>
|
|
4460
|
+
</PageBody>
|
|
4461
|
+
</Page>
|
|
4462
|
+
)
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4438
4465
|
if (error) {
|
|
4439
4466
|
return (
|
|
4440
4467
|
<Page>
|
|
@@ -552,6 +552,8 @@
|
|
|
552
552
|
"sales.documents.detail.adjustments.updated": "Anpassung aktualisiert.",
|
|
553
553
|
"sales.documents.detail.back": "Zurück zu Sales",
|
|
554
554
|
"sales.documents.detail.backToCreate": "Neues Dokument erstellen",
|
|
555
|
+
"sales.documents.detail.backToOrders": "Zurück zu Bestellungen",
|
|
556
|
+
"sales.documents.detail.backToQuotes": "Zurück zu Angeboten",
|
|
555
557
|
"sales.documents.detail.billing": "Rechnungsadresse",
|
|
556
558
|
"sales.documents.detail.channel": "Kanal",
|
|
557
559
|
"sales.documents.detail.channelInvalid": "Ausgewählter Kanal wurde nicht gefunden.",
|
|
@@ -679,6 +681,7 @@
|
|
|
679
681
|
"sales.documents.detail.items.variant": "Variante",
|
|
680
682
|
"sales.documents.detail.items.variantSearch": "Variante suchen",
|
|
681
683
|
"sales.documents.detail.loading": "Dokument wird geladen…",
|
|
684
|
+
"sales.documents.detail.notFound": "Dokument nicht gefunden.",
|
|
682
685
|
"sales.documents.detail.number": "Dokumentnummer",
|
|
683
686
|
"sales.documents.detail.numberEditForbidden": "Dokumentennummer kann nicht bearbeitet werden.",
|
|
684
687
|
"sales.documents.detail.numberEmpty": "Noch keine Nummer",
|
|
@@ -552,6 +552,8 @@
|
|
|
552
552
|
"sales.documents.detail.adjustments.updated": "Adjustment updated.",
|
|
553
553
|
"sales.documents.detail.back": "Back to Sales",
|
|
554
554
|
"sales.documents.detail.backToCreate": "Create a new document",
|
|
555
|
+
"sales.documents.detail.backToOrders": "Back to orders",
|
|
556
|
+
"sales.documents.detail.backToQuotes": "Back to quotes",
|
|
555
557
|
"sales.documents.detail.billing": "Billing address",
|
|
556
558
|
"sales.documents.detail.channel": "Channel",
|
|
557
559
|
"sales.documents.detail.channelInvalid": "Selected channel could not be found.",
|
|
@@ -679,6 +681,7 @@
|
|
|
679
681
|
"sales.documents.detail.items.variant": "Variant",
|
|
680
682
|
"sales.documents.detail.items.variantSearch": "Search variant",
|
|
681
683
|
"sales.documents.detail.loading": "Loading document…",
|
|
684
|
+
"sales.documents.detail.notFound": "Document not found.",
|
|
682
685
|
"sales.documents.detail.number": "Document number",
|
|
683
686
|
"sales.documents.detail.numberEditForbidden": "Document number cannot be edited.",
|
|
684
687
|
"sales.documents.detail.numberEmpty": "No number yet",
|
|
@@ -552,6 +552,8 @@
|
|
|
552
552
|
"sales.documents.detail.adjustments.updated": "Ajuste actualizado.",
|
|
553
553
|
"sales.documents.detail.back": "Volver a Ventas",
|
|
554
554
|
"sales.documents.detail.backToCreate": "Crear un nuevo documento",
|
|
555
|
+
"sales.documents.detail.backToOrders": "Volver a pedidos",
|
|
556
|
+
"sales.documents.detail.backToQuotes": "Volver a cotizaciones",
|
|
555
557
|
"sales.documents.detail.billing": "Dirección de facturación",
|
|
556
558
|
"sales.documents.detail.channel": "Canal",
|
|
557
559
|
"sales.documents.detail.channelInvalid": "No se pudo encontrar el canal seleccionado.",
|
|
@@ -679,6 +681,7 @@
|
|
|
679
681
|
"sales.documents.detail.items.variant": "Variante",
|
|
680
682
|
"sales.documents.detail.items.variantSearch": "Buscar variante",
|
|
681
683
|
"sales.documents.detail.loading": "Cargando documento…",
|
|
684
|
+
"sales.documents.detail.notFound": "Documento no encontrado.",
|
|
682
685
|
"sales.documents.detail.number": "Número de documento",
|
|
683
686
|
"sales.documents.detail.numberEditForbidden": "El número de documento no se puede editar.",
|
|
684
687
|
"sales.documents.detail.numberEmpty": "Sin número aún",
|
|
@@ -552,6 +552,8 @@
|
|
|
552
552
|
"sales.documents.detail.adjustments.updated": "Korekta zaktualizowana.",
|
|
553
553
|
"sales.documents.detail.back": "Wróć do sprzedaży",
|
|
554
554
|
"sales.documents.detail.backToCreate": "Utwórz nowy dokument",
|
|
555
|
+
"sales.documents.detail.backToOrders": "Powrót do zamówień",
|
|
556
|
+
"sales.documents.detail.backToQuotes": "Powrót do ofert",
|
|
555
557
|
"sales.documents.detail.billing": "Adres rozliczeniowy",
|
|
556
558
|
"sales.documents.detail.channel": "Kanał",
|
|
557
559
|
"sales.documents.detail.channelInvalid": "Nie znaleziono wybranego kanału.",
|
|
@@ -679,6 +681,7 @@
|
|
|
679
681
|
"sales.documents.detail.items.variant": "Wariant",
|
|
680
682
|
"sales.documents.detail.items.variantSearch": "Szukaj wariantu",
|
|
681
683
|
"sales.documents.detail.loading": "Ładowanie dokumentu…",
|
|
684
|
+
"sales.documents.detail.notFound": "Nie znaleziono dokumentu.",
|
|
682
685
|
"sales.documents.detail.number": "Numer dokumentu",
|
|
683
686
|
"sales.documents.detail.numberEditForbidden": "Numer dokumentu nie może być edytowany.",
|
|
684
687
|
"sales.documents.detail.numberEmpty": "Brak numeru",
|