@open-mercato/core 0.6.6-develop.6543.1.49fadb4c31 → 0.6.6
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 +9 -23
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/cli.js +1 -2
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +7 -9
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -9
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +17 -21
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +2 -6
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +2 -3
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +0 -22
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +2 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -7
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +7 -11
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/dashboards/api/roles/widgets/route.js +3 -22
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +3 -22
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/cli.js +3 -4
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js +1 -9
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
- package/dist/modules/dictionaries/commands/entries.js +14 -7
- package/dist/modules/dictionaries/commands/entries.js.map +2 -2
- package/dist/modules/dictionaries/commands/entry-operations.js +16 -7
- package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
- package/dist/modules/dictionaries/commands/factory.js +3 -9
- package/dist/modules/dictionaries/commands/factory.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +41 -56
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +3 -10
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +4 -5
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +0 -1
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +0 -1
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/cli.js +1 -2
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/query_index/api/status.js +8 -2
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +0 -44
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +27 -2
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +2 -6
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +0 -1
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +0 -9
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +0 -44
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +38 -59
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/lib/shipments/snapshots.js +1 -27
- package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js +8 -1
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +1 -1
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/find-definition.js +18 -2
- package/dist/modules/workflows/lib/find-definition.js.map +2 -2
- package/dist/modules/workflows/lib/signal-handler.js +3 -2
- package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
- package/dist/modules/workflows/lib/step-handler.js +4 -8
- package/dist/modules/workflows/lib/step-handler.js.map +2 -2
- package/dist/modules/workflows/lib/task-handler.js +3 -5
- package/dist/modules/workflows/lib/task-handler.js.map +2 -2
- package/dist/modules/workflows/lib/timer-handler.js +2 -1
- package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
- package/dist/modules/workflows/lib/transition-handler.js +5 -13
- package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
- package/dist/modules/workflows/lib/workflow-executor.js +11 -9
- package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20260715120000.js +68 -0
- package/dist/modules/workflows/migrations/Migration20260715120000.js.map +7 -0
- package/dist/modules/workflows/migrations/Migration20260716120000.js +24 -0
- package/dist/modules/workflows/migrations/Migration20260716120000.js.map +7 -0
- package/dist/modules/workflows/workflows.js +2 -0
- package/dist/modules/workflows/workflows.js.map +2 -2
- package/package.json +8 -9
- package/src/modules/api_keys/api/keys/route.ts +9 -27
- package/src/modules/api_keys/cli.ts +3 -2
- package/src/modules/attachments/api/file/[id]/route.ts +7 -11
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -11
- package/src/modules/attachments/api/route.ts +21 -26
- package/src/modules/auth/api/users/route.ts +2 -11
- package/src/modules/auth/cli.ts +2 -3
- package/src/modules/configs/i18n/de.json +1 -5
- package/src/modules/configs/i18n/en.json +1 -5
- package/src/modules/configs/i18n/es.json +1 -5
- package/src/modules/configs/i18n/pl.json +1 -5
- package/src/modules/configs/lib/upgrade-actions.ts +0 -24
- package/src/modules/customer_accounts/AGENTS.md +6 -8
- package/src/modules/customer_accounts/widgets/injection-table.ts +2 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -11
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +7 -13
- package/src/modules/dashboards/api/roles/widgets/route.ts +3 -22
- package/src/modules/dashboards/api/users/widgets/route.ts +3 -22
- package/src/modules/dashboards/cli.ts +9 -4
- package/src/modules/dashboards/lib/widgetAssignmentScope.ts +0 -18
- package/src/modules/dictionaries/commands/entries.ts +16 -7
- package/src/modules/dictionaries/commands/entry-operations.ts +18 -8
- package/src/modules/dictionaries/commands/factory.ts +2 -8
- package/src/modules/dictionaries/fields/dictionary.tsx +32 -55
- package/src/modules/dictionaries/i18n/de.json +0 -2
- package/src/modules/dictionaries/i18n/en.json +0 -2
- package/src/modules/dictionaries/i18n/es.json +0 -2
- package/src/modules/dictionaries/i18n/pl.json +0 -2
- package/src/modules/directory/utils/organizationScope.ts +2 -36
- package/src/modules/entities/api/records.ts +7 -5
- package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +0 -1
- package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +0 -1
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +49 -51
- package/src/modules/entities/cli.ts +1 -2
- package/src/modules/entities/i18n/de.json +0 -34
- package/src/modules/entities/i18n/en.json +0 -34
- package/src/modules/entities/i18n/es.json +0 -34
- package/src/modules/entities/i18n/pl.json +0 -34
- package/src/modules/query_index/api/status.ts +8 -2
- package/src/modules/sales/commands/documents.ts +0 -65
- package/src/modules/sales/commands/returns.ts +32 -2
- package/src/modules/sales/components/documents/AddressesSection.tsx +2 -6
- package/src/modules/sales/components/documents/ItemsSection.tsx +0 -5
- package/src/modules/sales/components/documents/LineItemDialog.tsx +0 -10
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +0 -46
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +40 -65
- package/src/modules/sales/i18n/de.json +0 -4
- package/src/modules/sales/i18n/en.json +0 -4
- package/src/modules/sales/i18n/es.json +0 -4
- package/src/modules/sales/i18n/pl.json +0 -4
- package/src/modules/sales/lib/shipments/snapshots.ts +0 -30
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +12 -1
- package/src/modules/workflows/lib/activity-executor.ts +22 -2
- package/src/modules/workflows/lib/find-definition.ts +53 -1
- package/src/modules/workflows/lib/signal-handler.ts +5 -4
- package/src/modules/workflows/lib/step-handler.ts +4 -8
- package/src/modules/workflows/lib/task-handler.ts +2 -4
- package/src/modules/workflows/lib/timer-handler.ts +3 -2
- package/src/modules/workflows/lib/transition-handler.ts +5 -13
- package/src/modules/workflows/lib/workflow-executor.ts +11 -8
- package/src/modules/workflows/migrations/Migration20260715120000.ts +114 -0
- package/src/modules/workflows/migrations/Migration20260716120000.ts +57 -0
- package/src/modules/workflows/workflows.ts +2 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js +0 -110
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +0 -7
- package/dist/modules/attachments/lib/requestScope.js +0 -10
- package/dist/modules/attachments/lib/requestScope.js.map +0 -7
- package/dist/modules/customers/components/detail/useDealsAccess.js +0 -16
- package/dist/modules/customers/components/detail/useDealsAccess.js.map +0 -7
- package/src/modules/attachments/lib/reconcileOrganization.ts +0 -188
- package/src/modules/attachments/lib/requestScope.ts +0 -27
- package/src/modules/customers/components/detail/useDealsAccess.ts +0 -19
|
@@ -19,8 +19,10 @@ const companyUsersWidget = {
|
|
|
19
19
|
export const injectionTable: ModuleInjectionTable = {
|
|
20
20
|
'customers.person': [accountStatusWidget],
|
|
21
21
|
'crud-form:customers.person': [accountStatusWidget],
|
|
22
|
+
'crud-form:customers:customer_person_profile:fields': [accountStatusWidget],
|
|
22
23
|
'customers.company': [companyUsersWidget],
|
|
23
24
|
'crud-form:customers.company': [companyUsersWidget],
|
|
25
|
+
'crud-form:customers:customer_company_profile:fields': [companyUsersWidget],
|
|
24
26
|
// Step 4.10 — Portal AiChat injection example.
|
|
25
27
|
// Mapped to the portal profile page's `pageAfter('profile')` spot;
|
|
26
28
|
// third-party modules targeting other portal pages can copy this entry.
|
|
@@ -31,7 +31,6 @@ import type { TagsSectionController } from '@open-mercato/ui/backend/detail'
|
|
|
31
31
|
import { coerceDisplayName } from '../../../../lib/displayName'
|
|
32
32
|
import { CompanyDetailHeader } from '../../../../components/detail/CompanyDetailHeader'
|
|
33
33
|
import { CompanyDetailTabs, resolveLegacyTab, type CompanyTabId } from '../../../../components/detail/CompanyDetailTabs'
|
|
34
|
-
import { useDealsAccess } from '../../../../components/detail/useDealsAccess'
|
|
35
34
|
import { CompanyKpiBar } from '../../../../components/detail/CompanyKpiBar'
|
|
36
35
|
import { ScheduleActivityDialog, type ScheduleActivityEditData } from '../../../../components/detail/ScheduleActivityDialog'
|
|
37
36
|
import { ChangelogTab } from '../../../../components/detail/ChangelogTab'
|
|
@@ -70,7 +69,6 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
70
69
|
}, [searchParams])
|
|
71
70
|
const [activeTab, setActiveTab] = React.useState<CompanyTabId>(initialTab)
|
|
72
71
|
const [sectionAction, setSectionAction] = React.useState<SectionAction | null>(null)
|
|
73
|
-
const { canViewDeals, isReady: isDealsAccessReady } = useDealsAccess()
|
|
74
72
|
|
|
75
73
|
// Form state
|
|
76
74
|
const [isDirty, setIsDirty] = React.useState(false)
|
|
@@ -330,14 +328,6 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
330
328
|
setSectionAction(null)
|
|
331
329
|
}, [activeTab])
|
|
332
330
|
|
|
333
|
-
// A `?tab=deals` deep link must not strand users without `customers.deals.view`
|
|
334
|
-
// on a tab that no longer exists for them. Wait for the granted features to load
|
|
335
|
-
// so a permitted user is never bounced off the tab mid-fetch.
|
|
336
|
-
React.useEffect(() => {
|
|
337
|
-
if (!isDealsAccessReady || canViewDeals) return
|
|
338
|
-
setActiveTab((current) => (current === 'deals' ? 'people' : current))
|
|
339
|
-
}, [isDealsAccessReady, canViewDeals])
|
|
340
|
-
|
|
341
331
|
// Deals scope
|
|
342
332
|
const dealsScope = React.useMemo(
|
|
343
333
|
() => (currentCompanyId ? ({ kind: 'company', entityId: currentCompanyId } as const) : null),
|
|
@@ -554,7 +544,7 @@ export default function CompanyDetailV2Page({ params }: { params?: { id?: string
|
|
|
554
544
|
/>
|
|
555
545
|
)}
|
|
556
546
|
|
|
557
|
-
{activeTab === 'deals' &&
|
|
547
|
+
{activeTab === 'deals' && (
|
|
558
548
|
<DealsSection
|
|
559
549
|
scope={dealsScope}
|
|
560
550
|
emptyLabel={t('customers.companies.detail.empty.deals', 'No deals linked to this company.')}
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
Plus,
|
|
15
15
|
} from 'lucide-react'
|
|
16
16
|
import type { SectionAction } from '@open-mercato/ui/backend/detail'
|
|
17
|
-
import { useDealsAccess } from './useDealsAccess'
|
|
18
17
|
|
|
19
18
|
export type CompanyTabId =
|
|
20
19
|
| 'people'
|
|
@@ -88,7 +87,6 @@ export function CompanyDetailTabs({
|
|
|
88
87
|
children,
|
|
89
88
|
}: CompanyDetailTabsProps) {
|
|
90
89
|
const t = useT()
|
|
91
|
-
const { canViewDeals } = useDealsAccess()
|
|
92
90
|
|
|
93
91
|
const builtInTabs: TabDef[] = React.useMemo(
|
|
94
92
|
() => [
|
|
@@ -98,16 +96,12 @@ export function CompanyDetailTabs({
|
|
|
98
96
|
icon: <Users className="size-4" />,
|
|
99
97
|
badge: <CountBadge count={peopleCount} />,
|
|
100
98
|
},
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
badge: <CountBadge count={dealsCount} />,
|
|
108
|
-
},
|
|
109
|
-
]
|
|
110
|
-
: []),
|
|
99
|
+
{
|
|
100
|
+
id: 'deals',
|
|
101
|
+
label: t('customers.companies.detail.tabs.deals', 'Deals'),
|
|
102
|
+
icon: <Handshake className="size-4" />,
|
|
103
|
+
badge: <CountBadge count={dealsCount} />,
|
|
104
|
+
},
|
|
111
105
|
{
|
|
112
106
|
id: 'activity-log',
|
|
113
107
|
label: t('customers.companies.detail.tabs.activityLog', 'Activity log'),
|
|
@@ -127,7 +121,7 @@ export function CompanyDetailTabs({
|
|
|
127
121
|
badge: <CountBadge count={filesCount} />,
|
|
128
122
|
},
|
|
129
123
|
],
|
|
130
|
-
[t,
|
|
124
|
+
[t, peopleCount, dealsCount, activitiesCount, filesCount],
|
|
131
125
|
)
|
|
132
126
|
|
|
133
127
|
const allTabs: TabDef[] = React.useMemo(
|
|
@@ -6,10 +6,7 @@ import { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data
|
|
|
6
6
|
import { Role } from '@open-mercato/core/modules/auth/data/entities'
|
|
7
7
|
import { roleWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'
|
|
8
8
|
import { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'
|
|
9
|
-
import {
|
|
10
|
-
resolveWidgetAssignmentReadScope,
|
|
11
|
-
resolveWidgetAssignmentTargetAccess,
|
|
12
|
-
} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
9
|
+
import { resolveWidgetAssignmentReadScope } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
13
10
|
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
14
11
|
import {
|
|
15
12
|
runCrudMutationGuardAfterSuccess,
|
|
@@ -73,15 +70,7 @@ export async function GET(req: Request) {
|
|
|
73
70
|
})
|
|
74
71
|
|
|
75
72
|
const role = await em.findOne(Role, { id: roleId, deletedAt: null })
|
|
76
|
-
|
|
77
|
-
isSuperAdmin: !!acl.isSuperAdmin,
|
|
78
|
-
scopeTenantId: tenantId,
|
|
79
|
-
target: role,
|
|
80
|
-
})
|
|
81
|
-
if (access === 'forbidden') {
|
|
82
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
83
|
-
}
|
|
84
|
-
if (access === 'not-found') {
|
|
73
|
+
if (!role || (tenantId && role.tenantId !== tenantId)) {
|
|
85
74
|
return NextResponse.json({ error: 'Role not found' }, { status: 404 })
|
|
86
75
|
}
|
|
87
76
|
|
|
@@ -132,15 +121,7 @@ export async function PUT(req: Request) {
|
|
|
132
121
|
const organizationId = auth.orgId ?? null
|
|
133
122
|
|
|
134
123
|
const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null })
|
|
135
|
-
|
|
136
|
-
isSuperAdmin: !!acl.isSuperAdmin,
|
|
137
|
-
scopeTenantId: tenantId,
|
|
138
|
-
target: role,
|
|
139
|
-
})
|
|
140
|
-
if (access === 'forbidden') {
|
|
141
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
142
|
-
}
|
|
143
|
-
if (access === 'not-found') {
|
|
124
|
+
if (!role || (tenantId && role.tenantId !== tenantId)) {
|
|
144
125
|
return NextResponse.json({ error: 'Role not found' }, { status: 404 })
|
|
145
126
|
}
|
|
146
127
|
|
|
@@ -7,10 +7,7 @@ import { User } from '@open-mercato/core/modules/auth/data/entities'
|
|
|
7
7
|
import { userWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'
|
|
8
8
|
import { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'
|
|
9
9
|
import { resolveAllowedWidgetIds } from '@open-mercato/core/modules/dashboards/lib/access'
|
|
10
|
-
import {
|
|
11
|
-
resolveWidgetAssignmentReadScope,
|
|
12
|
-
resolveWidgetAssignmentTargetAccess,
|
|
13
|
-
} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
10
|
+
import { resolveWidgetAssignmentReadScope } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
14
11
|
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
15
12
|
import {
|
|
16
13
|
runCrudMutationGuardAfterSuccess,
|
|
@@ -56,15 +53,7 @@ export async function GET(req: Request) {
|
|
|
56
53
|
})
|
|
57
54
|
|
|
58
55
|
const targetUserForRead = await em.findOne(User, { id: userId, deletedAt: null })
|
|
59
|
-
|
|
60
|
-
isSuperAdmin: !!acl.isSuperAdmin,
|
|
61
|
-
scopeTenantId: tenantId,
|
|
62
|
-
target: targetUserForRead,
|
|
63
|
-
})
|
|
64
|
-
if (readAccess === 'forbidden') {
|
|
65
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
66
|
-
}
|
|
67
|
-
if (readAccess === 'not-found') {
|
|
56
|
+
if (!targetUserForRead || (tenantId && (targetUserForRead.tenantId ?? null) !== tenantId)) {
|
|
68
57
|
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
|
69
58
|
}
|
|
70
59
|
|
|
@@ -132,15 +121,7 @@ export async function PUT(req: Request) {
|
|
|
132
121
|
const organizationId = auth.orgId ?? null
|
|
133
122
|
|
|
134
123
|
const targetUser = await em.findOne(User, { id: parsed.data.userId, deletedAt: null })
|
|
135
|
-
|
|
136
|
-
isSuperAdmin: !!acl.isSuperAdmin,
|
|
137
|
-
scopeTenantId: tenantId,
|
|
138
|
-
target: targetUser,
|
|
139
|
-
})
|
|
140
|
-
if (writeAccess === 'forbidden') {
|
|
141
|
-
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
142
|
-
}
|
|
143
|
-
if (writeAccess === 'not-found') {
|
|
124
|
+
if (!targetUser || (tenantId && (targetUser.tenantId ?? null) !== tenantId)) {
|
|
144
125
|
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
|
145
126
|
}
|
|
146
127
|
|
|
@@ -6,7 +6,6 @@ import { Role } from '@open-mercato/core/modules/auth/data/entities'
|
|
|
6
6
|
import { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'
|
|
7
7
|
import { appendWidgetsToRoles, resolveAnalyticsWidgetIds } from '@open-mercato/core/modules/dashboards/lib/role-widgets'
|
|
8
8
|
import { seedAnalyticsData } from './seed/analytics'
|
|
9
|
-
import { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'
|
|
10
9
|
|
|
11
10
|
type Args = Record<string, string>
|
|
12
11
|
|
|
@@ -109,7 +108,10 @@ const seedDefaults: ModuleCli = {
|
|
|
109
108
|
return
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
const roleNames =
|
|
111
|
+
const roleNames = roleCsv
|
|
112
|
+
.split(',')
|
|
113
|
+
.map((name) => name.trim())
|
|
114
|
+
.filter(Boolean)
|
|
113
115
|
|
|
114
116
|
if (!roleNames.length) {
|
|
115
117
|
console.log('No roles provided, nothing to seed.')
|
|
@@ -123,7 +125,7 @@ const seedDefaults: ModuleCli = {
|
|
|
123
125
|
tenantId,
|
|
124
126
|
organizationId,
|
|
125
127
|
roleNames,
|
|
126
|
-
widgetIds: widgetCsv ?
|
|
128
|
+
widgetIds: widgetCsv ? widgetCsv.split(',').map((id) => id.trim()).filter(Boolean) : undefined,
|
|
127
129
|
logger: (message) => console.log(message),
|
|
128
130
|
})
|
|
129
131
|
},
|
|
@@ -141,7 +143,10 @@ const enableAnalyticsWidgets: ModuleCli = {
|
|
|
141
143
|
return
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
const roleNames =
|
|
146
|
+
const roleNames = roleCsv
|
|
147
|
+
.split(',')
|
|
148
|
+
.map((name) => name.trim())
|
|
149
|
+
.filter(Boolean)
|
|
145
150
|
|
|
146
151
|
if (!roleNames.length) {
|
|
147
152
|
console.log('No roles provided, nothing to update.')
|
|
@@ -8,24 +8,6 @@ export type ResolvedAssignmentScope = {
|
|
|
8
8
|
organizationId: string | null
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export type WidgetAssignmentTarget = {
|
|
12
|
-
tenantId?: string | null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type WidgetAssignmentTargetAccess = 'allowed' | 'forbidden' | 'not-found'
|
|
16
|
-
|
|
17
|
-
export function resolveWidgetAssignmentTargetAccess(params: {
|
|
18
|
-
isSuperAdmin: boolean
|
|
19
|
-
scopeTenantId: string | null
|
|
20
|
-
target: WidgetAssignmentTarget | null | undefined
|
|
21
|
-
}): WidgetAssignmentTargetAccess {
|
|
22
|
-
const { isSuperAdmin, scopeTenantId, target } = params
|
|
23
|
-
if (!isSuperAdmin && !scopeTenantId) return 'forbidden'
|
|
24
|
-
if (!target) return 'not-found'
|
|
25
|
-
if (scopeTenantId && (target.tenantId ?? null) !== scopeTenantId) return 'not-found'
|
|
26
|
-
return 'allowed'
|
|
27
|
-
}
|
|
28
|
-
|
|
29
11
|
export function resolveWidgetAssignmentReadScope(params: {
|
|
30
12
|
auth: AuthScopeInput
|
|
31
13
|
isSuperAdmin: boolean
|
|
@@ -4,13 +4,22 @@ import {
|
|
|
4
4
|
dictionaryEntryCommandCreateSchema,
|
|
5
5
|
dictionaryEntryCommandUpdateSchema,
|
|
6
6
|
} from '@open-mercato/core/modules/dictionaries/data/validators'
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
registerDictionaryEntryCommands,
|
|
10
|
-
} from '@open-mercato/core/modules/dictionaries/commands/factory'
|
|
7
|
+
import { registerDictionaryEntryCommands } from '@open-mercato/core/modules/dictionaries/commands/factory'
|
|
8
|
+
import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
11
9
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
12
10
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
13
11
|
|
|
12
|
+
function ensureScope(ctx: CommandRuntimeContext, scope: { tenantId: string; organizationId: string }): void {
|
|
13
|
+
const tenantId = ctx.auth?.tenantId ?? null
|
|
14
|
+
if (tenantId && tenantId !== scope.tenantId) {
|
|
15
|
+
throw new CrudHttpError(403, { error: 'Forbidden' })
|
|
16
|
+
}
|
|
17
|
+
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
|
|
18
|
+
if (organizationId && organizationId !== scope.organizationId) {
|
|
19
|
+
throw new CrudHttpError(403, { error: 'Forbidden' })
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
async function ensureDictionary(em: EntityManager, id: string): Promise<Dictionary> {
|
|
15
24
|
const dictionary = await em.findOne(Dictionary, {
|
|
16
25
|
id,
|
|
@@ -28,12 +37,12 @@ registerDictionaryEntryCommands({
|
|
|
28
37
|
translationKeyPrefix: 'dictionaries.entries',
|
|
29
38
|
createSchema: dictionaryEntryCommandCreateSchema,
|
|
30
39
|
updateSchema: dictionaryEntryCommandUpdateSchema,
|
|
31
|
-
ensureScope
|
|
40
|
+
ensureScope,
|
|
32
41
|
duplicateError: 'An entry with this value already exists.',
|
|
33
42
|
resolveDictionaryForCreate: async ({ em, ctx, parsed }) => {
|
|
34
43
|
const dictionary = await ensureDictionary(em, parsed.dictionaryId)
|
|
35
44
|
const scope = { tenantId: dictionary.tenantId, organizationId: dictionary.organizationId }
|
|
36
|
-
|
|
45
|
+
ensureScope(ctx, scope)
|
|
37
46
|
return { dictionary, scope }
|
|
38
47
|
},
|
|
39
48
|
resolveEntry: async ({ em, ctx, id }) => {
|
|
@@ -45,7 +54,7 @@ registerDictionaryEntryCommands({
|
|
|
45
54
|
throw new CrudHttpError(404, { error: 'Dictionary not found' })
|
|
46
55
|
}
|
|
47
56
|
const scope = { tenantId: entry.tenantId, organizationId: entry.organizationId }
|
|
48
|
-
|
|
57
|
+
ensureScope(ctx, scope)
|
|
49
58
|
return { entry, dictionary: entry.dictionary, scope }
|
|
50
59
|
},
|
|
51
60
|
ensureDictionaryForUndo: async ({ em, snapshot }) => {
|
|
@@ -6,8 +6,7 @@ import {
|
|
|
6
6
|
type ReorderDictionaryEntriesCommandInput,
|
|
7
7
|
type SetDefaultDictionaryEntryCommandInput,
|
|
8
8
|
} from '@open-mercato/core/modules/dictionaries/data/validators'
|
|
9
|
-
import {
|
|
10
|
-
import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
9
|
+
import { registerCommand, type CommandHandler, type CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
11
10
|
import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
12
11
|
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
13
12
|
import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
|
|
@@ -37,6 +36,17 @@ const dictionaryCrudEvents: CrudEventsConfig = {
|
|
|
37
36
|
}),
|
|
38
37
|
}
|
|
39
38
|
|
|
39
|
+
function ensureScope(ctx: CommandRuntimeContext, scope: DictionaryScope): void {
|
|
40
|
+
const tenantId = ctx.auth?.tenantId ?? null
|
|
41
|
+
if (tenantId && tenantId !== scope.tenantId) {
|
|
42
|
+
throw new CrudHttpError(403, { error: 'Forbidden' })
|
|
43
|
+
}
|
|
44
|
+
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
|
|
45
|
+
if (organizationId && organizationId !== scope.organizationId) {
|
|
46
|
+
throw new CrudHttpError(403, { error: 'Forbidden' })
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
async function requireDictionary(
|
|
41
51
|
em: EntityManager,
|
|
42
52
|
id: string,
|
|
@@ -74,7 +84,7 @@ const reorderDictionaryEntriesCommand: CommandHandler<
|
|
|
74
84
|
async prepare(rawInput, ctx) {
|
|
75
85
|
const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)
|
|
76
86
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
77
|
-
|
|
87
|
+
ensureScope(ctx, scope)
|
|
78
88
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
79
89
|
const ids = parsed.entries.map((e) => e.id)
|
|
80
90
|
const existing = await findWithDecryption(
|
|
@@ -98,7 +108,7 @@ const reorderDictionaryEntriesCommand: CommandHandler<
|
|
|
98
108
|
async execute(rawInput, ctx) {
|
|
99
109
|
const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)
|
|
100
110
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
101
|
-
|
|
111
|
+
ensureScope(ctx, scope)
|
|
102
112
|
|
|
103
113
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
104
114
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)
|
|
@@ -191,7 +201,7 @@ const reorderDictionaryEntriesCommand: CommandHandler<
|
|
|
191
201
|
const undo = extractUndoPayload<ReorderUndoPayload>(logEntry)
|
|
192
202
|
const before = undo?.before ?? (logEntry?.snapshotBefore as ReorderSnapshot | null | undefined) ?? null
|
|
193
203
|
if (!before) return
|
|
194
|
-
|
|
204
|
+
ensureScope(ctx, before.scope)
|
|
195
205
|
|
|
196
206
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
197
207
|
const ids = before.positions.map((p) => p.id)
|
|
@@ -255,7 +265,7 @@ const setDefaultDictionaryEntryCommand: CommandHandler<
|
|
|
255
265
|
async prepare(rawInput, ctx) {
|
|
256
266
|
const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)
|
|
257
267
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
258
|
-
|
|
268
|
+
ensureScope(ctx, scope)
|
|
259
269
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
260
270
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)
|
|
261
271
|
const previousDefaults = await findWithDecryption(
|
|
@@ -281,7 +291,7 @@ const setDefaultDictionaryEntryCommand: CommandHandler<
|
|
|
281
291
|
async execute(rawInput, ctx) {
|
|
282
292
|
const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)
|
|
283
293
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
284
|
-
|
|
294
|
+
ensureScope(ctx, scope)
|
|
285
295
|
|
|
286
296
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
287
297
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)
|
|
@@ -389,7 +399,7 @@ const setDefaultDictionaryEntryCommand: CommandHandler<
|
|
|
389
399
|
const undo = extractUndoPayload<DefaultUndoPayload>(logEntry)
|
|
390
400
|
const before = undo?.before ?? (logEntry?.snapshotBefore as DefaultSnapshot | null | undefined) ?? null
|
|
391
401
|
if (!before) return
|
|
392
|
-
|
|
402
|
+
ensureScope(ctx, before.scope)
|
|
393
403
|
|
|
394
404
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
395
405
|
const dictionary = await requireDictionary(em, before.dictionaryId, before.scope)
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from '@open-mercato/shared/lib/commands'
|
|
13
13
|
import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
14
14
|
import { buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'
|
|
15
|
-
import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
|
|
16
15
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
17
16
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
18
17
|
import type { z } from 'zod'
|
|
@@ -23,11 +22,6 @@ type DictionaryScope = {
|
|
|
23
22
|
organizationId: string
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
export function ensureDictionaryEntryScope(ctx: CommandRuntimeContext, scope: DictionaryScope): void {
|
|
27
|
-
ensureTenantScope(ctx, scope.tenantId)
|
|
28
|
-
ensureOrganizationScope(ctx, scope.organizationId)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
25
|
export type DictionaryEntrySnapshot = {
|
|
32
26
|
id: string
|
|
33
27
|
dictionaryId: string
|
|
@@ -93,8 +87,8 @@ export type DictionaryEntryCommandConfig<TCreate, TUpdate> = {
|
|
|
93
87
|
labels?: DictionaryEntryCommandLabels
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
return typeof fn === 'function' ? fn :
|
|
90
|
+
function toScopeEnsurer(fn: EnsureScopeFn | undefined): EnsureScopeFn {
|
|
91
|
+
return typeof fn === 'function' ? fn : () => {}
|
|
98
92
|
}
|
|
99
93
|
|
|
100
94
|
async function loadSnapshot(em: EntityManager, id: string): Promise<DictionaryEntrySnapshot | null> {
|
|
@@ -90,12 +90,9 @@ function DictionaryFieldDefEditor({ def, onChange }: { def: { configJson?: Dicti
|
|
|
90
90
|
const [error, setError] = React.useState<string | null>(null)
|
|
91
91
|
const multiId = React.useId()
|
|
92
92
|
const inlineCreateId = React.useId()
|
|
93
|
-
const inlineCreateHintId = React.useId()
|
|
94
|
-
const defaultValueHintId = React.useId()
|
|
95
93
|
const selectedId = typeof def?.configJson?.dictionaryId === 'string' ? def?.configJson?.dictionaryId : ''
|
|
96
94
|
const inlineCreate = def?.configJson?.dictionaryInlineCreate !== false
|
|
97
95
|
const isMulti = def?.configJson?.multi === true
|
|
98
|
-
const inlineCreateDisabled = !selectedId || isMulti
|
|
99
96
|
const errorLoadLabel = t('dictionaries.customFields.errorLoad', 'Failed to load dictionaries.')
|
|
100
97
|
|
|
101
98
|
React.useEffect(() => {
|
|
@@ -184,65 +181,45 @@ function DictionaryFieldDefEditor({ def, onChange }: { def: { configJson?: Dicti
|
|
|
184
181
|
</a>
|
|
185
182
|
</div>
|
|
186
183
|
) : null}
|
|
187
|
-
<div className="
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
{t('dictionaries.customFields.allowMultiple', 'Allow selecting multiple entries')}
|
|
204
|
-
</label>
|
|
205
|
-
</div>
|
|
206
|
-
<div className="inline-flex items-center gap-2 text-xs">
|
|
207
|
-
<Checkbox
|
|
208
|
-
id={inlineCreateId}
|
|
209
|
-
checked={!isMulti && inlineCreate}
|
|
210
|
-
onCheckedChange={(checked) => onChange({ dictionaryInlineCreate: checked === true })}
|
|
211
|
-
disabled={inlineCreateDisabled}
|
|
212
|
-
aria-describedby={isMulti ? inlineCreateHintId : undefined}
|
|
213
|
-
/>
|
|
214
|
-
<label
|
|
215
|
-
htmlFor={inlineCreateId}
|
|
216
|
-
className={!inlineCreateDisabled ? 'cursor-pointer select-none' : 'cursor-not-allowed opacity-60'}
|
|
217
|
-
>
|
|
218
|
-
{t('dictionaries.customFields.allowInlineCreate', 'Allow inline creation inside forms')}
|
|
219
|
-
</label>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
{isMulti ? (
|
|
223
|
-
<p id={inlineCreateHintId} className="text-xs text-muted-foreground">
|
|
224
|
-
{t(
|
|
225
|
-
'dictionaries.customFields.inlineCreateSingleOnly',
|
|
226
|
-
'Inline creation is available for single-entry dictionary fields only.',
|
|
227
|
-
)}
|
|
228
|
-
</p>
|
|
229
|
-
) : null}
|
|
184
|
+
<div className="inline-flex items-center gap-2 text-xs">
|
|
185
|
+
<Checkbox
|
|
186
|
+
id={multiId}
|
|
187
|
+
checked={isMulti}
|
|
188
|
+
onCheckedChange={(checked) => {
|
|
189
|
+
const enabled = checked === true
|
|
190
|
+
onChange({ multi: enabled, defaultValue: enabled ? undefined : def?.configJson?.defaultValue })
|
|
191
|
+
}}
|
|
192
|
+
disabled={!selectedId}
|
|
193
|
+
/>
|
|
194
|
+
<label
|
|
195
|
+
htmlFor={multiId}
|
|
196
|
+
className={selectedId ? 'cursor-pointer select-none' : 'cursor-not-allowed opacity-60'}
|
|
197
|
+
>
|
|
198
|
+
{t('dictionaries.customFields.allowMultiple', 'Allow selecting multiple entries')}
|
|
199
|
+
</label>
|
|
230
200
|
</div>
|
|
201
|
+
{!isMulti ? (
|
|
202
|
+
<div className="inline-flex items-center gap-2 text-xs">
|
|
203
|
+
<Checkbox
|
|
204
|
+
id={inlineCreateId}
|
|
205
|
+
checked={inlineCreate}
|
|
206
|
+
onCheckedChange={(checked) => onChange({ dictionaryInlineCreate: checked === true })}
|
|
207
|
+
disabled={!selectedId}
|
|
208
|
+
/>
|
|
209
|
+
<label
|
|
210
|
+
htmlFor={inlineCreateId}
|
|
211
|
+
className={selectedId ? 'cursor-pointer select-none' : 'cursor-not-allowed opacity-60'}
|
|
212
|
+
>
|
|
213
|
+
{t('dictionaries.customFields.allowInlineCreate', 'Allow inline creation inside forms')}
|
|
214
|
+
</label>
|
|
215
|
+
</div>
|
|
216
|
+
) : null}
|
|
231
217
|
{selectedId && !isMulti ? (
|
|
232
218
|
<DictionaryDefaultSelector
|
|
233
219
|
dictionaryId={selectedId}
|
|
234
220
|
defaultValue={typeof def?.configJson?.defaultValue === 'string' ? def.configJson.defaultValue : ''}
|
|
235
221
|
onChange={(value) => onChange({ defaultValue: value || undefined })}
|
|
236
222
|
/>
|
|
237
|
-
) : selectedId && isMulti ? (
|
|
238
|
-
<div className="inline-flex items-center gap-2 text-xs">
|
|
239
|
-
<p id={defaultValueHintId} className="text-xs text-muted-foreground">
|
|
240
|
-
{t(
|
|
241
|
-
'dictionaries.customFields.defaultValueMultiUnavailable',
|
|
242
|
-
'Default values are not available for multi-select dictionary fields.',
|
|
243
|
-
)}
|
|
244
|
-
</p>
|
|
245
|
-
</div>
|
|
246
223
|
) : null}
|
|
247
224
|
</div>
|
|
248
225
|
)
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Inline-Erstellung in Formularen erlauben",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Mehrere Einträge auswählen erlauben",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Standardwert",
|
|
85
|
-
"dictionaries.customFields.defaultValueMultiUnavailable": "Standardwerte sind für Wörterbuchfelder mit Mehrfachauswahl nicht verfügbar.",
|
|
86
85
|
"dictionaries.customFields.defaultValueNone": "Kein Standardwert",
|
|
87
86
|
"dictionaries.customFields.defaultValueStale": "Standardeintrag nicht gefunden — er wurde möglicherweise gelöscht oder umbenannt.",
|
|
88
87
|
"dictionaries.customFields.dictionaryLabel": "Quelle des Wörterbuchs",
|
|
@@ -90,7 +89,6 @@
|
|
|
90
89
|
"dictionaries.customFields.empty": "Noch keine Wörterbücher verfügbar. Erstellen Sie zuerst eines.",
|
|
91
90
|
"dictionaries.customFields.errorLoad": "Wörterbücher konnten nicht geladen werden.",
|
|
92
91
|
"dictionaries.customFields.inactive": "inaktiv",
|
|
93
|
-
"dictionaries.customFields.inlineCreateSingleOnly": "Inline-Erstellung ist nur für Wörterbuchfelder mit Einzelauswahl verfügbar.",
|
|
94
92
|
"dictionaries.customFields.loading": "Wörterbücher werden geladen…",
|
|
95
93
|
"dictionaries.customFields.manageLink": "Wörterbücher verwalten",
|
|
96
94
|
"dictionaries.customFields.selectedHint": "Einträge aus diesem Wörterbuch füllen das Feld.",
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Allow inline creation inside forms",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Allow selecting multiple entries",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Default value",
|
|
85
|
-
"dictionaries.customFields.defaultValueMultiUnavailable": "Default values are not available for multi-select dictionary fields.",
|
|
86
85
|
"dictionaries.customFields.defaultValueNone": "No default",
|
|
87
86
|
"dictionaries.customFields.defaultValueStale": "Default entry not found — it may have been deleted or renamed.",
|
|
88
87
|
"dictionaries.customFields.dictionaryLabel": "Dictionary source",
|
|
@@ -90,7 +89,6 @@
|
|
|
90
89
|
"dictionaries.customFields.empty": "No dictionaries available yet. Create one first.",
|
|
91
90
|
"dictionaries.customFields.errorLoad": "Failed to load dictionaries.",
|
|
92
91
|
"dictionaries.customFields.inactive": "inactive",
|
|
93
|
-
"dictionaries.customFields.inlineCreateSingleOnly": "Inline creation is available for single-entry dictionary fields only.",
|
|
94
92
|
"dictionaries.customFields.loading": "Loading dictionaries…",
|
|
95
93
|
"dictionaries.customFields.manageLink": "Manage dictionaries",
|
|
96
94
|
"dictionaries.customFields.selectedHint": "Entries from this dictionary populate the field.",
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Permitir creación en línea dentro de los formularios",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Permitir seleccionar varias entradas",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Valor predeterminado",
|
|
85
|
-
"dictionaries.customFields.defaultValueMultiUnavailable": "Los valores predeterminados no están disponibles para campos de diccionario con selección múltiple.",
|
|
86
85
|
"dictionaries.customFields.defaultValueNone": "Sin valor predeterminado",
|
|
87
86
|
"dictionaries.customFields.defaultValueStale": "No se encontró la entrada predeterminada; puede haber sido eliminada o renombrada.",
|
|
88
87
|
"dictionaries.customFields.dictionaryLabel": "Fuente del diccionario",
|
|
@@ -90,7 +89,6 @@
|
|
|
90
89
|
"dictionaries.customFields.empty": "Aún no hay diccionarios disponibles. Crea uno primero.",
|
|
91
90
|
"dictionaries.customFields.errorLoad": "No se pudieron cargar los diccionarios.",
|
|
92
91
|
"dictionaries.customFields.inactive": "inactivo",
|
|
93
|
-
"dictionaries.customFields.inlineCreateSingleOnly": "La creación en línea solo está disponible para campos de diccionario de una sola entrada.",
|
|
94
92
|
"dictionaries.customFields.loading": "Cargando diccionarios…",
|
|
95
93
|
"dictionaries.customFields.manageLink": "Administrar diccionarios",
|
|
96
94
|
"dictionaries.customFields.selectedHint": "Las entradas de este diccionario rellenarán el campo.",
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
"dictionaries.customFields.allowInlineCreate": "Zezwól na tworzenie w formularzach",
|
|
83
83
|
"dictionaries.customFields.allowMultiple": "Zezwól na wybór wielu wpisów",
|
|
84
84
|
"dictionaries.customFields.defaultValue": "Wartość domyślna",
|
|
85
|
-
"dictionaries.customFields.defaultValueMultiUnavailable": "Wartości domyślne nie są dostępne dla wielokrotnego wyboru wpisów słownika.",
|
|
86
85
|
"dictionaries.customFields.defaultValueNone": "Brak wartości domyślnej",
|
|
87
86
|
"dictionaries.customFields.defaultValueStale": "Nie znaleziono domyślnego wpisu — mógł zostać usunięty albo przemianowany.",
|
|
88
87
|
"dictionaries.customFields.dictionaryLabel": "Źródło słownika",
|
|
@@ -90,7 +89,6 @@
|
|
|
90
89
|
"dictionaries.customFields.empty": "Brak dostępnych słowników. Najpierw utwórz jeden.",
|
|
91
90
|
"dictionaries.customFields.errorLoad": "Nie udało się wczytać słowników.",
|
|
92
91
|
"dictionaries.customFields.inactive": "nieaktywne",
|
|
93
|
-
"dictionaries.customFields.inlineCreateSingleOnly": "Tworzenie wpisów w formularzu jest dostępne tylko dla pól z pojedynczym wyborem.",
|
|
94
92
|
"dictionaries.customFields.loading": "Ładowanie słowników…",
|
|
95
93
|
"dictionaries.customFields.manageLink": "Zarządzaj słownikami",
|
|
96
94
|
"dictionaries.customFields.selectedHint": "Wpisy z tego słownika uzupełnią pole.",
|