@open-mercato/core 0.6.6 → 0.6.7-develop.6552.1.5208a19369
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 +2 -2
- package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
- package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +12 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_keys/api/keys/route.js +30 -13
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/cli.js +2 -1
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +9 -7
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +21 -17
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
- package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
- package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
- package/dist/modules/attachments/lib/requestScope.js +10 -0
- package/dist/modules/attachments/lib/requestScope.js.map +7 -0
- package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
- package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
- package/dist/modules/auth/api/autologin.js +85 -0
- package/dist/modules/auth/api/autologin.js.map +7 -0
- package/dist/modules/auth/api/users/route.js +6 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +3 -2
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/autologin.js +19 -0
- package/dist/modules/auth/lib/autologin.js.map +7 -0
- package/dist/modules/configs/lib/upgrade-actions.js +22 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
- package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
- package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
- package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
- package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
- package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
- package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
- package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
- package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
- package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/cli.js +4 -3
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
- package/dist/modules/dictionaries/commands/entries.js +7 -14
- package/dist/modules/dictionaries/commands/entries.js.map +2 -2
- package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
- package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
- package/dist/modules/dictionaries/commands/factory.js +9 -3
- package/dist/modules/dictionaries/commands/factory.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +56 -41
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +10 -3
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +5 -4
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
- 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 +1 -0
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/cli.js +2 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
- package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
- package/dist/modules/feature_toggles/defaults.json +8 -0
- package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/data/entities.js +45 -0
- package/dist/modules/payment_gateways/data/entities.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
- package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
- package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
- package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
- package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/status.js +2 -8
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.js +6 -0
- package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
- package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +44 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +2 -27
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
- package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
- package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
- package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
- package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
- package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
- package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
- package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
- package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/dist/modules/sync_excel/lib/parser.js +185 -0
- package/dist/modules/sync_excel/lib/parser.js.map +2 -2
- package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
- package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
- package/generated/entities/gateway_session_initialization/index.ts +10 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +12 -0
- package/package.json +9 -8
- package/src/modules/api_keys/api/keys/route.ts +36 -13
- package/src/modules/api_keys/cli.ts +2 -3
- package/src/modules/attachments/api/file/[id]/route.ts +11 -7
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
- package/src/modules/attachments/api/route.ts +26 -21
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
- package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
- package/src/modules/attachments/lib/requestScope.ts +27 -0
- package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
- package/src/modules/auth/api/autologin.ts +100 -0
- package/src/modules/auth/api/users/route.ts +11 -2
- package/src/modules/auth/cli.ts +3 -2
- package/src/modules/auth/lib/autologin.ts +36 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +24 -0
- package/src/modules/customer_accounts/AGENTS.md +8 -6
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
- package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
- package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
- package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
- package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
- package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
- package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
- package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
- package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
- package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
- package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
- package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
- package/src/modules/dashboards/cli.ts +4 -9
- package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
- package/src/modules/dictionaries/commands/entries.ts +7 -16
- package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
- package/src/modules/dictionaries/commands/factory.ts +8 -2
- package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
- package/src/modules/dictionaries/i18n/de.json +2 -0
- package/src/modules/dictionaries/i18n/en.json +2 -0
- package/src/modules/dictionaries/i18n/es.json +2 -0
- package/src/modules/dictionaries/i18n/pl.json +2 -0
- package/src/modules/directory/utils/organizationScope.ts +36 -2
- package/src/modules/entities/api/records.ts +5 -7
- package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
- package/src/modules/entities/cli.ts +2 -1
- package/src/modules/entities/i18n/de.json +34 -0
- package/src/modules/entities/i18n/en.json +34 -0
- package/src/modules/entities/i18n/es.json +34 -0
- package/src/modules/entities/i18n/pl.json +34 -0
- package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
- package/src/modules/feature_toggles/defaults.json +8 -0
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
- package/src/modules/payment_gateways/data/entities.ts +39 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
- package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
- package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
- package/src/modules/query_index/api/status.ts +2 -8
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
- package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
- package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
- package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
- package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
- package/src/modules/sales/commands/documents.ts +65 -0
- package/src/modules/sales/commands/returns.ts +2 -32
- package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
- package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
- package/src/modules/sales/i18n/de.json +5 -0
- package/src/modules/sales/i18n/en.json +5 -0
- package/src/modules/sales/i18n/es.json +5 -0
- package/src/modules/sales/i18n/pl.json +5 -0
- package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
- package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
- package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
- package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
- package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
- package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
- package/src/modules/sync_excel/lib/parser.ts +245 -0
- package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import type { SectionAction } from '@open-mercato/ui/backend/detail'
|
|
5
5
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
|
+
import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
|
|
6
7
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
7
8
|
import { Plus } from 'lucide-react'
|
|
8
9
|
|
|
@@ -46,31 +47,19 @@ export function DetailTabsLayout<TId extends string = string>({
|
|
|
46
47
|
return (
|
|
47
48
|
<div className={cn('space-y-4', className)}>
|
|
48
49
|
<div className={cn('flex flex-wrap items-center justify-between gap-3', headerClassName)}>
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
<Tabs
|
|
51
|
+
value={activeTab}
|
|
52
|
+
onValueChange={(value) => handleTabChange(value as TId)}
|
|
53
|
+
variant="underline"
|
|
53
54
|
>
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
key={tab.id}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
onClick={() => handleTabChange(tab.id)}
|
|
63
|
-
className={cn(
|
|
64
|
-
'h-auto rounded-none border-b-2 px-0 py-1',
|
|
65
|
-
activeTab === tab.id
|
|
66
|
-
? 'border-accent-indigo text-foreground hover:bg-transparent'
|
|
67
|
-
: 'border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent'
|
|
68
|
-
)}
|
|
69
|
-
>
|
|
70
|
-
{tab.label}
|
|
71
|
-
</Button>
|
|
72
|
-
))}
|
|
73
|
-
</nav>
|
|
55
|
+
<TabsList className={cn('h-auto flex-wrap border-b-0', navClassName)} aria-label={navAriaLabel}>
|
|
56
|
+
{tabs.map((tab) => (
|
|
57
|
+
<TabsTrigger key={tab.id} value={tab.id}>
|
|
58
|
+
{tab.label}
|
|
59
|
+
</TabsTrigger>
|
|
60
|
+
))}
|
|
61
|
+
</TabsList>
|
|
62
|
+
</Tabs>
|
|
74
63
|
{sectionAction ? (
|
|
75
64
|
<Button
|
|
76
65
|
type="button"
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useRouter, useSearchParams } from 'next/navigation'
|
|
5
5
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
|
-
import {
|
|
7
|
-
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
|
+
import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
|
|
8
7
|
|
|
9
8
|
export type MobilePersonZone = 'details' | 'activity'
|
|
10
9
|
|
|
@@ -80,35 +79,30 @@ export function MobilePersonDetail({
|
|
|
80
79
|
|
|
81
80
|
return (
|
|
82
81
|
<div className="space-y-4">
|
|
83
|
-
<div
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
>
|
|
108
|
-
{t(entry.labelKey, entry.fallback)}
|
|
109
|
-
</Button>
|
|
110
|
-
)
|
|
111
|
-
})}
|
|
82
|
+
<div onKeyDown={handleKeyDown}>
|
|
83
|
+
<Tabs
|
|
84
|
+
value={zone}
|
|
85
|
+
onValueChange={(value) => handleZoneChange(value as MobilePersonZone)}
|
|
86
|
+
variant="underline"
|
|
87
|
+
>
|
|
88
|
+
<TabsList
|
|
89
|
+
aria-label={t(
|
|
90
|
+
'customers.people.mobile.zoneSwitcher.ariaLabel',
|
|
91
|
+
'Zone selector',
|
|
92
|
+
)}
|
|
93
|
+
className="w-full"
|
|
94
|
+
>
|
|
95
|
+
{ZONES.map((entry) => (
|
|
96
|
+
<TabsTrigger
|
|
97
|
+
key={entry.id}
|
|
98
|
+
value={entry.id}
|
|
99
|
+
className="flex-1 justify-center"
|
|
100
|
+
>
|
|
101
|
+
{t(entry.labelKey, entry.fallback)}
|
|
102
|
+
</TabsTrigger>
|
|
103
|
+
))}
|
|
104
|
+
</TabsList>
|
|
105
|
+
</Tabs>
|
|
112
106
|
</div>
|
|
113
107
|
<div
|
|
114
108
|
id={`mobile-person-zone-${zone}`}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import { cn } from '@open-mercato/shared/lib/utils'
|
|
5
4
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
5
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
|
+
import { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
|
|
7
7
|
import {
|
|
8
8
|
SquareCheckBig,
|
|
9
9
|
Mail,
|
|
@@ -32,7 +32,7 @@ type TabDef = {
|
|
|
32
32
|
id: PersonTabId
|
|
33
33
|
label: string
|
|
34
34
|
icon?: React.ReactNode
|
|
35
|
-
|
|
35
|
+
count?: React.ReactNode
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
type PersonDetailTabsProps = {
|
|
@@ -56,21 +56,9 @@ export function resolveLegacyTab(tab: string | null | undefined): PersonTabId {
|
|
|
56
56
|
return SUPPORTED_TAB_IDS.has(tab as PersonTabId) ? (tab as PersonTabId) : 'activities'
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function
|
|
60
|
-
if (count <= 0) return
|
|
61
|
-
return
|
|
62
|
-
<span className="ml-1 rounded-full bg-muted px-1.5 py-0.5 text-xs font-medium leading-none text-muted-foreground">
|
|
63
|
-
{count > 999 ? '999+' : count}
|
|
64
|
-
</span>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function NewBadge() {
|
|
69
|
-
return (
|
|
70
|
-
<span className="ml-1.5 rounded bg-foreground px-1.5 py-0.5 text-overline font-semibold leading-none text-background">
|
|
71
|
-
NEW
|
|
72
|
-
</span>
|
|
73
|
-
)
|
|
59
|
+
function formatTabCount(count: number): string | number | undefined {
|
|
60
|
+
if (count <= 0) return undefined
|
|
61
|
+
return count > 999 ? '999+' : count
|
|
74
62
|
}
|
|
75
63
|
|
|
76
64
|
export function PersonDetailTabs({
|
|
@@ -94,7 +82,7 @@ export function PersonDetailTabs({
|
|
|
94
82
|
id: 'activities',
|
|
95
83
|
label: t('customers.people.detail.tabs.activities', 'Activities'),
|
|
96
84
|
icon: <SquareCheckBig className="size-4" />,
|
|
97
|
-
|
|
85
|
+
count: formatTabCount(activitiesCount),
|
|
98
86
|
},
|
|
99
87
|
{
|
|
100
88
|
id: 'emails',
|
|
@@ -105,37 +93,37 @@ export function PersonDetailTabs({
|
|
|
105
93
|
id: 'deals',
|
|
106
94
|
label: t('customers.people.detail.tabs.deals', 'Deals'),
|
|
107
95
|
icon: <Briefcase className="size-4" />,
|
|
108
|
-
|
|
96
|
+
count: formatTabCount(dealsCount),
|
|
109
97
|
},
|
|
110
98
|
{
|
|
111
99
|
id: 'companies',
|
|
112
100
|
label: t('customers.people.detail.tabs.companies', 'Companies'),
|
|
113
101
|
icon: <Building2 className="size-4" />,
|
|
114
|
-
|
|
102
|
+
count: formatTabCount(companiesCount),
|
|
115
103
|
},
|
|
116
104
|
{
|
|
117
105
|
id: 'addresses',
|
|
118
106
|
label: t('customers.people.detail.tabs.addresses', 'Addresses'),
|
|
119
107
|
icon: <MapPin className="size-4" />,
|
|
120
|
-
|
|
108
|
+
count: formatTabCount(addressesCount),
|
|
121
109
|
},
|
|
122
110
|
{
|
|
123
111
|
id: 'tasks',
|
|
124
112
|
label: t('customers.people.detail.tabs.tasks', 'Tasks'),
|
|
125
113
|
icon: <Check className="size-4" />,
|
|
126
|
-
|
|
114
|
+
count: formatTabCount(tasksCount),
|
|
127
115
|
},
|
|
128
116
|
{
|
|
129
117
|
id: 'changelog',
|
|
130
118
|
label: t('customers.people.detail.tabs.changelog', 'Change log'),
|
|
131
119
|
icon: <History className="size-4" />,
|
|
132
|
-
|
|
120
|
+
count: 'NEW',
|
|
133
121
|
},
|
|
134
122
|
{
|
|
135
123
|
id: 'files',
|
|
136
124
|
label: t('customers.people.detail.tabs.files', 'Files'),
|
|
137
125
|
icon: <Paperclip className="size-4" />,
|
|
138
|
-
|
|
126
|
+
count: formatTabCount(filesCount),
|
|
139
127
|
},
|
|
140
128
|
],
|
|
141
129
|
[t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount],
|
|
@@ -155,33 +143,24 @@ export function PersonDetailTabs({
|
|
|
155
143
|
return (
|
|
156
144
|
<div>
|
|
157
145
|
{/* Tab navigation — full width above both zones */}
|
|
158
|
-
<div className="flex items-end justify-between gap-2 border-b"
|
|
159
|
-
<
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
className={cn(
|
|
172
|
-
'h-auto shrink-0 rounded-none border-b-2 px-3 py-2.5 hover:bg-transparent',
|
|
173
|
-
isActive
|
|
174
|
-
? 'border-foreground text-foreground font-semibold'
|
|
175
|
-
: 'border-transparent text-muted-foreground hover:text-foreground',
|
|
176
|
-
)}
|
|
177
|
-
>
|
|
178
|
-
{tab.icon && <span className="mr-1.5">{tab.icon}</span>}
|
|
146
|
+
<div className="flex items-end justify-between gap-2 border-b">
|
|
147
|
+
<Tabs
|
|
148
|
+
value={activeTab}
|
|
149
|
+
onValueChange={(value) => onTabChange(value as PersonTabId)}
|
|
150
|
+
variant="underline"
|
|
151
|
+
className="min-w-0 flex-1"
|
|
152
|
+
>
|
|
153
|
+
<TabsList
|
|
154
|
+
aria-label={t('customers.people.detail.tabs.label', 'Person detail sections')}
|
|
155
|
+
className="-mb-px w-full overflow-x-auto border-b-0 px-1"
|
|
156
|
+
>
|
|
157
|
+
{allTabs.map((tab) => (
|
|
158
|
+
<TabsTrigger key={tab.id} value={tab.id} leading={tab.icon} count={tab.count}>
|
|
179
159
|
{tab.label}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
</nav>
|
|
160
|
+
</TabsTrigger>
|
|
161
|
+
))}
|
|
162
|
+
</TabsList>
|
|
163
|
+
</Tabs>
|
|
185
164
|
{sectionAction ? (
|
|
186
165
|
<Button
|
|
187
166
|
type="button"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'
|
|
5
|
+
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
6
|
+
|
|
7
|
+
export type DealsAccess = {
|
|
8
|
+
canViewDeals: boolean
|
|
9
|
+
isReady: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useDealsAccess(): DealsAccess {
|
|
13
|
+
const { payload, isReady } = useBackendChrome()
|
|
14
|
+
const canViewDeals = React.useMemo(
|
|
15
|
+
() => hasFeature(payload?.grantedFeatures ?? [], 'customers.deals.view'),
|
|
16
|
+
[payload?.grantedFeatures],
|
|
17
|
+
)
|
|
18
|
+
return { canViewDeals, isReady }
|
|
19
|
+
}
|
|
@@ -6,7 +6,10 @@ 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 {
|
|
9
|
+
import {
|
|
10
|
+
resolveWidgetAssignmentReadScope,
|
|
11
|
+
resolveWidgetAssignmentTargetAccess,
|
|
12
|
+
} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
10
13
|
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
11
14
|
import {
|
|
12
15
|
runCrudMutationGuardAfterSuccess,
|
|
@@ -70,7 +73,15 @@ export async function GET(req: Request) {
|
|
|
70
73
|
})
|
|
71
74
|
|
|
72
75
|
const role = await em.findOne(Role, { id: roleId, deletedAt: null })
|
|
73
|
-
|
|
76
|
+
const access = resolveWidgetAssignmentTargetAccess({
|
|
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') {
|
|
74
85
|
return NextResponse.json({ error: 'Role not found' }, { status: 404 })
|
|
75
86
|
}
|
|
76
87
|
|
|
@@ -121,7 +132,15 @@ export async function PUT(req: Request) {
|
|
|
121
132
|
const organizationId = auth.orgId ?? null
|
|
122
133
|
|
|
123
134
|
const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null })
|
|
124
|
-
|
|
135
|
+
const access = resolveWidgetAssignmentTargetAccess({
|
|
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') {
|
|
125
144
|
return NextResponse.json({ error: 'Role not found' }, { status: 404 })
|
|
126
145
|
}
|
|
127
146
|
|
|
@@ -7,7 +7,10 @@ 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 {
|
|
10
|
+
import {
|
|
11
|
+
resolveWidgetAssignmentReadScope,
|
|
12
|
+
resolveWidgetAssignmentTargetAccess,
|
|
13
|
+
} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
11
14
|
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
12
15
|
import {
|
|
13
16
|
runCrudMutationGuardAfterSuccess,
|
|
@@ -53,7 +56,15 @@ export async function GET(req: Request) {
|
|
|
53
56
|
})
|
|
54
57
|
|
|
55
58
|
const targetUserForRead = await em.findOne(User, { id: userId, deletedAt: null })
|
|
56
|
-
|
|
59
|
+
const readAccess = resolveWidgetAssignmentTargetAccess({
|
|
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') {
|
|
57
68
|
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
|
58
69
|
}
|
|
59
70
|
|
|
@@ -121,7 +132,15 @@ export async function PUT(req: Request) {
|
|
|
121
132
|
const organizationId = auth.orgId ?? null
|
|
122
133
|
|
|
123
134
|
const targetUser = await em.findOne(User, { id: parsed.data.userId, deletedAt: null })
|
|
124
|
-
|
|
135
|
+
const writeAccess = resolveWidgetAssignmentTargetAccess({
|
|
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') {
|
|
125
144
|
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
|
126
145
|
}
|
|
127
146
|
|
|
@@ -6,6 +6,7 @@ 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'
|
|
9
10
|
|
|
10
11
|
type Args = Record<string, string>
|
|
11
12
|
|
|
@@ -108,10 +109,7 @@ const seedDefaults: ModuleCli = {
|
|
|
108
109
|
return
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
const roleNames = roleCsv
|
|
112
|
-
.split(',')
|
|
113
|
-
.map((name) => name.trim())
|
|
114
|
-
.filter(Boolean)
|
|
112
|
+
const roleNames = parseCommaSeparatedList(roleCsv)
|
|
115
113
|
|
|
116
114
|
if (!roleNames.length) {
|
|
117
115
|
console.log('No roles provided, nothing to seed.')
|
|
@@ -125,7 +123,7 @@ const seedDefaults: ModuleCli = {
|
|
|
125
123
|
tenantId,
|
|
126
124
|
organizationId,
|
|
127
125
|
roleNames,
|
|
128
|
-
widgetIds: widgetCsv ? widgetCsv
|
|
126
|
+
widgetIds: widgetCsv ? parseCommaSeparatedList(widgetCsv) : undefined,
|
|
129
127
|
logger: (message) => console.log(message),
|
|
130
128
|
})
|
|
131
129
|
},
|
|
@@ -143,10 +141,7 @@ const enableAnalyticsWidgets: ModuleCli = {
|
|
|
143
141
|
return
|
|
144
142
|
}
|
|
145
143
|
|
|
146
|
-
const roleNames = roleCsv
|
|
147
|
-
.split(',')
|
|
148
|
-
.map((name) => name.trim())
|
|
149
|
-
.filter(Boolean)
|
|
144
|
+
const roleNames = parseCommaSeparatedList(roleCsv)
|
|
150
145
|
|
|
151
146
|
if (!roleNames.length) {
|
|
152
147
|
console.log('No roles provided, nothing to update.')
|
|
@@ -8,6 +8,24 @@ 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
|
+
|
|
11
29
|
export function resolveWidgetAssignmentReadScope(params: {
|
|
12
30
|
auth: AuthScopeInput
|
|
13
31
|
isSuperAdmin: boolean
|
|
@@ -4,22 +4,13 @@ import {
|
|
|
4
4
|
dictionaryEntryCommandCreateSchema,
|
|
5
5
|
dictionaryEntryCommandUpdateSchema,
|
|
6
6
|
} from '@open-mercato/core/modules/dictionaries/data/validators'
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
ensureDictionaryEntryScope,
|
|
9
|
+
registerDictionaryEntryCommands,
|
|
10
|
+
} from '@open-mercato/core/modules/dictionaries/commands/factory'
|
|
9
11
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
12
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
11
13
|
|
|
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
|
-
|
|
23
14
|
async function ensureDictionary(em: EntityManager, id: string): Promise<Dictionary> {
|
|
24
15
|
const dictionary = await em.findOne(Dictionary, {
|
|
25
16
|
id,
|
|
@@ -37,12 +28,12 @@ registerDictionaryEntryCommands({
|
|
|
37
28
|
translationKeyPrefix: 'dictionaries.entries',
|
|
38
29
|
createSchema: dictionaryEntryCommandCreateSchema,
|
|
39
30
|
updateSchema: dictionaryEntryCommandUpdateSchema,
|
|
40
|
-
ensureScope,
|
|
31
|
+
ensureScope: ensureDictionaryEntryScope,
|
|
41
32
|
duplicateError: 'An entry with this value already exists.',
|
|
42
33
|
resolveDictionaryForCreate: async ({ em, ctx, parsed }) => {
|
|
43
34
|
const dictionary = await ensureDictionary(em, parsed.dictionaryId)
|
|
44
35
|
const scope = { tenantId: dictionary.tenantId, organizationId: dictionary.organizationId }
|
|
45
|
-
|
|
36
|
+
ensureDictionaryEntryScope(ctx, scope)
|
|
46
37
|
return { dictionary, scope }
|
|
47
38
|
},
|
|
48
39
|
resolveEntry: async ({ em, ctx, id }) => {
|
|
@@ -54,7 +45,7 @@ registerDictionaryEntryCommands({
|
|
|
54
45
|
throw new CrudHttpError(404, { error: 'Dictionary not found' })
|
|
55
46
|
}
|
|
56
47
|
const scope = { tenantId: entry.tenantId, organizationId: entry.organizationId }
|
|
57
|
-
|
|
48
|
+
ensureDictionaryEntryScope(ctx, scope)
|
|
58
49
|
return { entry, dictionary: entry.dictionary, scope }
|
|
59
50
|
},
|
|
60
51
|
ensureDictionaryForUndo: async ({ em, snapshot }) => {
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
type ReorderDictionaryEntriesCommandInput,
|
|
7
7
|
type SetDefaultDictionaryEntryCommandInput,
|
|
8
8
|
} from '@open-mercato/core/modules/dictionaries/data/validators'
|
|
9
|
-
import {
|
|
9
|
+
import { ensureDictionaryEntryScope } from '@open-mercato/core/modules/dictionaries/commands/factory'
|
|
10
|
+
import { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'
|
|
10
11
|
import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
11
12
|
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
12
13
|
import { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'
|
|
@@ -36,17 +37,6 @@ const dictionaryCrudEvents: CrudEventsConfig = {
|
|
|
36
37
|
}),
|
|
37
38
|
}
|
|
38
39
|
|
|
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
|
-
|
|
50
40
|
async function requireDictionary(
|
|
51
41
|
em: EntityManager,
|
|
52
42
|
id: string,
|
|
@@ -84,7 +74,7 @@ const reorderDictionaryEntriesCommand: CommandHandler<
|
|
|
84
74
|
async prepare(rawInput, ctx) {
|
|
85
75
|
const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)
|
|
86
76
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
87
|
-
|
|
77
|
+
ensureDictionaryEntryScope(ctx, scope)
|
|
88
78
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
89
79
|
const ids = parsed.entries.map((e) => e.id)
|
|
90
80
|
const existing = await findWithDecryption(
|
|
@@ -108,7 +98,7 @@ const reorderDictionaryEntriesCommand: CommandHandler<
|
|
|
108
98
|
async execute(rawInput, ctx) {
|
|
109
99
|
const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)
|
|
110
100
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
111
|
-
|
|
101
|
+
ensureDictionaryEntryScope(ctx, scope)
|
|
112
102
|
|
|
113
103
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
114
104
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)
|
|
@@ -201,7 +191,7 @@ const reorderDictionaryEntriesCommand: CommandHandler<
|
|
|
201
191
|
const undo = extractUndoPayload<ReorderUndoPayload>(logEntry)
|
|
202
192
|
const before = undo?.before ?? (logEntry?.snapshotBefore as ReorderSnapshot | null | undefined) ?? null
|
|
203
193
|
if (!before) return
|
|
204
|
-
|
|
194
|
+
ensureDictionaryEntryScope(ctx, before.scope)
|
|
205
195
|
|
|
206
196
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
207
197
|
const ids = before.positions.map((p) => p.id)
|
|
@@ -265,7 +255,7 @@ const setDefaultDictionaryEntryCommand: CommandHandler<
|
|
|
265
255
|
async prepare(rawInput, ctx) {
|
|
266
256
|
const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)
|
|
267
257
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
268
|
-
|
|
258
|
+
ensureDictionaryEntryScope(ctx, scope)
|
|
269
259
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
270
260
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)
|
|
271
261
|
const previousDefaults = await findWithDecryption(
|
|
@@ -291,7 +281,7 @@ const setDefaultDictionaryEntryCommand: CommandHandler<
|
|
|
291
281
|
async execute(rawInput, ctx) {
|
|
292
282
|
const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)
|
|
293
283
|
const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
|
|
294
|
-
|
|
284
|
+
ensureDictionaryEntryScope(ctx, scope)
|
|
295
285
|
|
|
296
286
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
297
287
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)
|
|
@@ -399,7 +389,7 @@ const setDefaultDictionaryEntryCommand: CommandHandler<
|
|
|
399
389
|
const undo = extractUndoPayload<DefaultUndoPayload>(logEntry)
|
|
400
390
|
const before = undo?.before ?? (logEntry?.snapshotBefore as DefaultSnapshot | null | undefined) ?? null
|
|
401
391
|
if (!before) return
|
|
402
|
-
|
|
392
|
+
ensureDictionaryEntryScope(ctx, before.scope)
|
|
403
393
|
|
|
404
394
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
405
395
|
const dictionary = await requireDictionary(em, before.dictionaryId, before.scope)
|
|
@@ -12,6 +12,7 @@ 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'
|
|
15
16
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
16
17
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
17
18
|
import type { z } from 'zod'
|
|
@@ -22,6 +23,11 @@ type DictionaryScope = {
|
|
|
22
23
|
organizationId: string
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
export function ensureDictionaryEntryScope(ctx: CommandRuntimeContext, scope: DictionaryScope): void {
|
|
27
|
+
ensureTenantScope(ctx, scope.tenantId)
|
|
28
|
+
ensureOrganizationScope(ctx, scope.organizationId)
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
export type DictionaryEntrySnapshot = {
|
|
26
32
|
id: string
|
|
27
33
|
dictionaryId: string
|
|
@@ -87,8 +93,8 @@ export type DictionaryEntryCommandConfig<TCreate, TUpdate> = {
|
|
|
87
93
|
labels?: DictionaryEntryCommandLabels
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
function toScopeEnsurer(fn: EnsureScopeFn | undefined): EnsureScopeFn {
|
|
91
|
-
return typeof fn === 'function' ? fn :
|
|
96
|
+
export function toScopeEnsurer(fn: EnsureScopeFn | undefined): EnsureScopeFn {
|
|
97
|
+
return typeof fn === 'function' ? fn : ensureDictionaryEntryScope
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
async function loadSnapshot(em: EntityManager, id: string): Promise<DictionaryEntrySnapshot | null> {
|