@open-mercato/core 0.10.1-develop.7841.1.af87ac614d → 0.10.1-develop.7847.1.7d83dc5794
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/api_keys/api/keys/route.js +15 -1
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +16 -1
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +13 -2
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +4 -2
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +6 -9
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/branding/page.js +15 -3
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/wms/api/listEnrichers.js +3 -1
- package/dist/modules/wms/api/listEnrichers.js.map +2 -2
- package/dist/modules/wms/index.js +1 -1
- package/dist/modules/wms/index.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/api_keys/api/keys/route.ts +15 -1
- package/src/modules/api_keys/i18n/de.json +1 -0
- package/src/modules/api_keys/i18n/en.json +1 -0
- package/src/modules/api_keys/i18n/es.json +1 -0
- package/src/modules/api_keys/i18n/ko.json +1 -0
- package/src/modules/api_keys/i18n/pl.json +1 -0
- package/src/modules/auth/api/users/route.ts +18 -1
- package/src/modules/auth/backend/users/page.tsx +14 -2
- 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/ko.json +1 -0
- package/src/modules/auth/i18n/pl.json +1 -0
- package/src/modules/catalog/commands/products.ts +4 -1
- package/src/modules/directory/api/organization-branding/route.ts +5 -8
- package/src/modules/directory/backend/directory/branding/page.tsx +15 -3
- package/src/modules/wms/api/listEnrichers.ts +5 -2
- package/src/modules/wms/index.ts +1 -1
|
@@ -21,6 +21,7 @@ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/u
|
|
|
21
21
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
22
22
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
23
23
|
import { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'
|
|
24
|
+
import { ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
24
25
|
|
|
25
26
|
type Row = {
|
|
26
27
|
id: string
|
|
@@ -348,7 +349,7 @@ export default function UsersListPage() {
|
|
|
348
349
|
return p.toString()
|
|
349
350
|
}, [filterValues.name, page, search, organizationId, normalizedRoleIds])
|
|
350
351
|
|
|
351
|
-
const { data: usersData, isLoading } = useQuery({
|
|
352
|
+
const { data: usersData, isLoading, isPending, isFetching, isError, errorUpdatedAt, refetch } = useQuery({
|
|
352
353
|
queryKey: ['users', params, scopeVersion],
|
|
353
354
|
queryFn: async () => {
|
|
354
355
|
const call = await apiCall<{ items: Row[]; total: number; totalPages: number; totalIsCapped?: boolean; isSuperAdmin?: boolean }>(
|
|
@@ -361,6 +362,7 @@ export default function UsersListPage() {
|
|
|
361
362
|
},
|
|
362
363
|
})
|
|
363
364
|
|
|
365
|
+
const hasLoadError = isError || (isPending && errorUpdatedAt > 0)
|
|
364
366
|
const rows = usersData?.items || []
|
|
365
367
|
const total = usersData?.total || 0
|
|
366
368
|
const totalPages = usersData?.totalPages || 1
|
|
@@ -459,7 +461,17 @@ export default function UsersListPage() {
|
|
|
459
461
|
]} />
|
|
460
462
|
)}
|
|
461
463
|
pagination={{ page, pageSize: 50, total, totalPages, totalIsCapped, onPageChange: setPage }}
|
|
462
|
-
isLoading={isLoading}
|
|
464
|
+
isLoading={isLoading && !hasLoadError}
|
|
465
|
+
error={hasLoadError ? (
|
|
466
|
+
<ErrorMessage
|
|
467
|
+
label={t('auth.users.list.error.load', 'Failed to load users')}
|
|
468
|
+
action={(
|
|
469
|
+
<Button type="button" variant="outline" disabled={isFetching} onClick={() => { void refetch() }}>
|
|
470
|
+
{t('auth.users.list.actions.retry', 'Retry')}
|
|
471
|
+
</Button>
|
|
472
|
+
)}
|
|
473
|
+
/>
|
|
474
|
+
) : null}
|
|
463
475
|
/>
|
|
464
476
|
</PageBody>
|
|
465
477
|
{ConfirmDialogElement}
|
|
@@ -710,6 +710,7 @@
|
|
|
710
710
|
"auth.users.form.loading": "Benutzerdaten werden geladen...",
|
|
711
711
|
"auth.users.form.title.create": "Benutzer erstellen",
|
|
712
712
|
"auth.users.form.title.edit": "Benutzer bearbeiten",
|
|
713
|
+
"auth.users.list.actions.retry": "Erneut versuchen",
|
|
713
714
|
"auth.users.list.columns.email": "E-Mail",
|
|
714
715
|
"auth.users.list.columns.name": "Anzeigename",
|
|
715
716
|
"auth.users.list.columns.organization": "Organisation",
|
|
@@ -710,6 +710,7 @@
|
|
|
710
710
|
"auth.users.form.loading": "Loading user data...",
|
|
711
711
|
"auth.users.form.title.create": "Create User",
|
|
712
712
|
"auth.users.form.title.edit": "Edit User",
|
|
713
|
+
"auth.users.list.actions.retry": "Retry",
|
|
713
714
|
"auth.users.list.columns.email": "Email",
|
|
714
715
|
"auth.users.list.columns.name": "Display name",
|
|
715
716
|
"auth.users.list.columns.organization": "Organization",
|
|
@@ -710,6 +710,7 @@
|
|
|
710
710
|
"auth.users.form.loading": "Cargando datos del usuario...",
|
|
711
711
|
"auth.users.form.title.create": "Crear usuario",
|
|
712
712
|
"auth.users.form.title.edit": "Editar usuario",
|
|
713
|
+
"auth.users.list.actions.retry": "Reintentar",
|
|
713
714
|
"auth.users.list.columns.email": "Correo electrónico",
|
|
714
715
|
"auth.users.list.columns.name": "Nombre visible",
|
|
715
716
|
"auth.users.list.columns.organization": "Organización",
|
|
@@ -710,6 +710,7 @@
|
|
|
710
710
|
"auth.users.form.loading": "사용자 데이터를 불러오는 중...",
|
|
711
711
|
"auth.users.form.title.create": "사용자 생성",
|
|
712
712
|
"auth.users.form.title.edit": "사용자 편집",
|
|
713
|
+
"auth.users.list.actions.retry": "다시 시도",
|
|
713
714
|
"auth.users.list.columns.email": "이메일",
|
|
714
715
|
"auth.users.list.columns.name": "표시 이름",
|
|
715
716
|
"auth.users.list.columns.organization": "조직",
|
|
@@ -710,6 +710,7 @@
|
|
|
710
710
|
"auth.users.form.loading": "Ładowanie danych użytkownika...",
|
|
711
711
|
"auth.users.form.title.create": "Utwórz użytkownika",
|
|
712
712
|
"auth.users.form.title.edit": "Edytuj użytkownika",
|
|
713
|
+
"auth.users.list.actions.retry": "Spróbuj ponownie",
|
|
713
714
|
"auth.users.list.columns.email": "Email",
|
|
714
715
|
"auth.users.list.columns.name": "Nazwa wyświetlana",
|
|
715
716
|
"auth.users.list.columns.organization": "Organizacja",
|
|
@@ -688,6 +688,7 @@ function ensureSchemaName(
|
|
|
688
688
|
|
|
689
689
|
async function assignOptionSchemaTemplate(
|
|
690
690
|
em: EntityManager,
|
|
691
|
+
lookupEm: EntityManager,
|
|
691
692
|
product: CatalogProduct,
|
|
692
693
|
schema: CatalogProductOptionSchema,
|
|
693
694
|
preferredName?: string | null,
|
|
@@ -703,7 +704,7 @@ async function assignOptionSchemaTemplate(
|
|
|
703
704
|
});
|
|
704
705
|
let template = product.optionSchemaTemplate ?? null;
|
|
705
706
|
if (!template) {
|
|
706
|
-
template = await
|
|
707
|
+
template = await lookupEm.findOne(CatalogOptionSchemaTemplate, {
|
|
707
708
|
organizationId: product.organizationId,
|
|
708
709
|
tenantId: product.tenantId,
|
|
709
710
|
code: templateCode,
|
|
@@ -1606,6 +1607,7 @@ const createProductCommand: CommandHandler<
|
|
|
1606
1607
|
record.optionSchemaTemplate = optionSchemaTemplate;
|
|
1607
1608
|
} else if (optionSchemaDefinition) {
|
|
1608
1609
|
optionSchemaTemplate = await assignOptionSchemaTemplate(
|
|
1610
|
+
em,
|
|
1609
1611
|
em,
|
|
1610
1612
|
record,
|
|
1611
1613
|
optionSchemaDefinition,
|
|
@@ -2029,6 +2031,7 @@ const updateProductCommand: CommandHandler<
|
|
|
2029
2031
|
if (optionSchemaDefinition) {
|
|
2030
2032
|
await assignOptionSchemaTemplate(
|
|
2031
2033
|
em,
|
|
2034
|
+
lookupEm,
|
|
2032
2035
|
record,
|
|
2033
2036
|
optionSchemaDefinition,
|
|
2034
2037
|
optionSchemaDefinition.name ?? parsed.title ?? record.title,
|
|
@@ -17,7 +17,7 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
|
17
17
|
import { Organization } from '@open-mercato/core/modules/directory/data/entities'
|
|
18
18
|
import { organizationUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'
|
|
19
19
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
20
|
-
import {
|
|
20
|
+
import { isExplicitlyEmptyOrganizationScope } from '@open-mercato/shared/lib/auth/organizationScope'
|
|
21
21
|
import '@open-mercato/core/modules/directory/commands/organizations'
|
|
22
22
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
23
23
|
import { getCommandInterceptorHttpRejection } from '@open-mercato/shared/lib/commands/errors'
|
|
@@ -82,15 +82,12 @@ async function resolveCurrentOrganization(req: Request) {
|
|
|
82
82
|
|
|
83
83
|
const container = await createRequestContainer()
|
|
84
84
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (isCrudHttpError(error)) {
|
|
90
|
-
return { response: NextResponse.json(error.body, { status: error.status }) }
|
|
85
|
+
if (isExplicitlyEmptyOrganizationScope(scope)) {
|
|
86
|
+
return {
|
|
87
|
+
response: NextResponse.json({ error: translate('api.errors.forbidden', 'Forbidden') }, { status: 403 }),
|
|
91
88
|
}
|
|
92
|
-
throw error
|
|
93
89
|
}
|
|
90
|
+
const organizationId = scope.selectedId
|
|
94
91
|
const tenantId = scope.tenantId ?? auth.tenantId ?? null
|
|
95
92
|
if (!organizationId || !tenantId) {
|
|
96
93
|
return {
|
|
@@ -15,7 +15,8 @@ import { Button } from '@open-mercato/ui/primitives/button'
|
|
|
15
15
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
16
16
|
import { SwitchField } from '@open-mercato/ui/primitives/switch-field'
|
|
17
17
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
18
|
-
import { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
18
|
+
import { useOrganizationScopeDetail, useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
19
|
+
import { getCurrentOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/organizationEvents'
|
|
19
20
|
|
|
20
21
|
type BrandingPayload = {
|
|
21
22
|
organizationId: string
|
|
@@ -42,6 +43,7 @@ export default function OrganizationBrandingPage() {
|
|
|
42
43
|
const t = useT()
|
|
43
44
|
const queryClient = useQueryClient()
|
|
44
45
|
const { organizationId, tenantId } = useOrganizationScopeDetail()
|
|
46
|
+
const scopeVersion = useOrganizationScopeVersion()
|
|
45
47
|
const [logoUrl, setLogoUrl] = React.useState('')
|
|
46
48
|
const [logoPreserveAspectRatio, setLogoPreserveAspectRatio] = React.useState(false)
|
|
47
49
|
const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
|
|
@@ -67,7 +69,8 @@ export default function OrganizationBrandingPage() {
|
|
|
67
69
|
setLogoUrl(data?.logoUrl ?? '')
|
|
68
70
|
setLogoPreserveAspectRatio(data?.logoPreserveAspectRatio ?? false)
|
|
69
71
|
setSelectedFile(null)
|
|
70
|
-
|
|
72
|
+
if (fileInputRef.current) fileInputRef.current.value = ''
|
|
73
|
+
}, [scopeVersion, organizationId, tenantId, data?.organizationId, data?.logoPreserveAspectRatio, data?.logoUrl])
|
|
71
74
|
|
|
72
75
|
React.useEffect(() => {
|
|
73
76
|
if (!selectedFile || typeof URL === 'undefined') {
|
|
@@ -111,7 +114,13 @@ export default function OrganizationBrandingPage() {
|
|
|
111
114
|
try {
|
|
112
115
|
await runMutation({
|
|
113
116
|
operation: async () => {
|
|
117
|
+
if (getCurrentOrganizationScopeVersion() !== scopeVersion) {
|
|
118
|
+
throw new Error('[internal] Organization scope changed during branding save')
|
|
119
|
+
}
|
|
114
120
|
const uploadedLogoUrl = shouldUpload ? await uploadLogo(data.organizationId) : null
|
|
121
|
+
if (getCurrentOrganizationScopeVersion() !== scopeVersion) {
|
|
122
|
+
throw new Error('[internal] Organization scope changed during branding save')
|
|
123
|
+
}
|
|
115
124
|
const resolvedLogoUrl = uploadedLogoUrl ?? nextLogoUrl ?? logoUrl.trim()
|
|
116
125
|
const response = await withScopedApiRequestHeaders(
|
|
117
126
|
buildOptimisticLockHeader(data.updatedAt),
|
|
@@ -142,12 +151,15 @@ export default function OrganizationBrandingPage() {
|
|
|
142
151
|
hasUpload: shouldUpload,
|
|
143
152
|
},
|
|
144
153
|
})
|
|
154
|
+
if (getCurrentOrganizationScopeVersion() !== scopeVersion) return
|
|
145
155
|
await queryClient.invalidateQueries({ queryKey: ['directory-organization-branding'] })
|
|
156
|
+
if (getCurrentOrganizationScopeVersion() !== scopeVersion) return
|
|
146
157
|
window.dispatchEvent(new Event('om:refresh-sidebar'))
|
|
147
158
|
setSelectedFile(null)
|
|
148
159
|
if (fileInputRef.current) fileInputRef.current.value = ''
|
|
149
160
|
flash(t('directory.branding.flash.saved', 'Organization branding updated'), 'success')
|
|
150
161
|
} catch (err: unknown) {
|
|
162
|
+
if (getCurrentOrganizationScopeVersion() !== scopeVersion) return
|
|
151
163
|
if (surfaceRecordConflict(err, t)) return
|
|
152
164
|
const fallback = t('directory.branding.errors.save', 'Failed to update organization branding')
|
|
153
165
|
const message = err instanceof Error ? err.message : fallback
|
|
@@ -155,7 +167,7 @@ export default function OrganizationBrandingPage() {
|
|
|
155
167
|
} finally {
|
|
156
168
|
setSaving(false)
|
|
157
169
|
}
|
|
158
|
-
}, [data, logoPreserveAspectRatio, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo])
|
|
170
|
+
}, [data, logoPreserveAspectRatio, logoUrl, queryClient, runMutation, scopeVersion, selectedFile, t, uploadLogo])
|
|
159
171
|
|
|
160
172
|
const handleSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {
|
|
161
173
|
event.preventDefault()
|
|
@@ -277,7 +277,8 @@ const CUSTOMER_FIELDS = ['id', 'display_name']
|
|
|
277
277
|
/**
|
|
278
278
|
* Decorate list items with `vendor_name` when they carry a `vendor_id` column
|
|
279
279
|
* referencing `customers.customer_entity` by UUID only (no ORM cross-module
|
|
280
|
-
* relations).
|
|
280
|
+
* relations). No-op when the optional `customers` module is not enabled.
|
|
281
|
+
* Mutates `payload.items` in place.
|
|
281
282
|
*/
|
|
282
283
|
export async function attachVendorLabelsToListItems(
|
|
283
284
|
payload: AnyListPayload,
|
|
@@ -285,11 +286,13 @@ export async function attachVendorLabelsToListItems(
|
|
|
285
286
|
): Promise<void> {
|
|
286
287
|
const items = Array.isArray(payload?.items) ? payload.items : []
|
|
287
288
|
if (items.length === 0) return
|
|
289
|
+
const customerEntityId = E.customers?.customer_entity
|
|
290
|
+
if (!customerEntityId) return
|
|
288
291
|
const ids = uniqueIdsFromKeys(items, ['vendor_id'])
|
|
289
292
|
if (ids.length === 0) return
|
|
290
293
|
const map = (await batchLoadById(
|
|
291
294
|
ctx,
|
|
292
|
-
|
|
295
|
+
customerEntityId,
|
|
293
296
|
ids,
|
|
294
297
|
CUSTOMER_FIELDS,
|
|
295
298
|
'vendor',
|
package/src/modules/wms/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const metadata: ModuleInfo = {
|
|
|
9
9
|
author: 'Open Mercato Team',
|
|
10
10
|
license: 'MIT',
|
|
11
11
|
ejectable: true,
|
|
12
|
-
requires: ['catalog', 'sales', 'feature_toggles'],
|
|
12
|
+
requires: ['catalog', 'customers', 'sales', 'feature_toggles'],
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export { features } from './acl'
|