@open-mercato/core 0.6.6-develop.6542.1.fee3e68aa6 → 0.6.6-develop.6551.1.1511bfa890
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 +7 -4
- package/dist/modules/api_keys/api/keys/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/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/lib/autologin.js +19 -0
- package/dist/modules/auth/lib/autologin.js.map +7 -0
- 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/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 +27 -37
- 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/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/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/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/components/SalesChannelsDisabledNotice.js +15 -0
- package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +7 -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/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/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 +7 -7
- package/src/modules/api_keys/api/keys/route.ts +9 -4
- package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
- 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/lib/autologin.ts +36 -0
- 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/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +27 -49
- 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/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/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/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/components/SalesChannelsDisabledNotice.tsx +21 -0
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +7 -3
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
- package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
- package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
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,
|
|
@@ -20,12 +20,9 @@ function resolveLegacyTab(tab) {
|
|
|
20
20
|
if (!tab) return "activities";
|
|
21
21
|
return SUPPORTED_TAB_IDS.has(tab) ? tab : "activities";
|
|
22
22
|
}
|
|
23
|
-
function
|
|
24
|
-
if (count <= 0) return
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
function NewBadge() {
|
|
28
|
-
return /* @__PURE__ */ jsx("span", { className: "ml-1.5 rounded bg-foreground px-1.5 py-0.5 text-overline font-semibold leading-none text-background", children: "NEW" });
|
|
23
|
+
function formatTabCount(count) {
|
|
24
|
+
if (count <= 0) return void 0;
|
|
25
|
+
return count > 999 ? "999+" : count;
|
|
29
26
|
}
|
|
30
27
|
function PersonDetailTabs({
|
|
31
28
|
activeTab,
|
|
@@ -47,7 +44,7 @@ function PersonDetailTabs({
|
|
|
47
44
|
id: "activities",
|
|
48
45
|
label: t("customers.people.detail.tabs.activities", "Activities"),
|
|
49
46
|
icon: /* @__PURE__ */ jsx(SquareCheckBig, { className: "size-4" }),
|
|
50
|
-
|
|
47
|
+
count: formatTabCount(activitiesCount)
|
|
51
48
|
},
|
|
52
49
|
{
|
|
53
50
|
id: "emails",
|
|
@@ -58,37 +55,37 @@ function PersonDetailTabs({
|
|
|
58
55
|
id: "deals",
|
|
59
56
|
label: t("customers.people.detail.tabs.deals", "Deals"),
|
|
60
57
|
icon: /* @__PURE__ */ jsx(Briefcase, { className: "size-4" }),
|
|
61
|
-
|
|
58
|
+
count: formatTabCount(dealsCount)
|
|
62
59
|
},
|
|
63
60
|
{
|
|
64
61
|
id: "companies",
|
|
65
62
|
label: t("customers.people.detail.tabs.companies", "Companies"),
|
|
66
63
|
icon: /* @__PURE__ */ jsx(Building2, { className: "size-4" }),
|
|
67
|
-
|
|
64
|
+
count: formatTabCount(companiesCount)
|
|
68
65
|
},
|
|
69
66
|
{
|
|
70
67
|
id: "addresses",
|
|
71
68
|
label: t("customers.people.detail.tabs.addresses", "Addresses"),
|
|
72
69
|
icon: /* @__PURE__ */ jsx(MapPin, { className: "size-4" }),
|
|
73
|
-
|
|
70
|
+
count: formatTabCount(addressesCount)
|
|
74
71
|
},
|
|
75
72
|
{
|
|
76
73
|
id: "tasks",
|
|
77
74
|
label: t("customers.people.detail.tabs.tasks", "Tasks"),
|
|
78
75
|
icon: /* @__PURE__ */ jsx(Check, { className: "size-4" }),
|
|
79
|
-
|
|
76
|
+
count: formatTabCount(tasksCount)
|
|
80
77
|
},
|
|
81
78
|
{
|
|
82
79
|
id: "changelog",
|
|
83
80
|
label: t("customers.people.detail.tabs.changelog", "Change log"),
|
|
84
81
|
icon: /* @__PURE__ */ jsx(History, { className: "size-4" }),
|
|
85
|
-
|
|
82
|
+
count: "NEW"
|
|
86
83
|
},
|
|
87
84
|
{
|
|
88
85
|
id: "files",
|
|
89
86
|
label: t("customers.people.detail.tabs.files", "Files"),
|
|
90
87
|
icon: /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
|
|
91
|
-
|
|
88
|
+
count: formatTabCount(filesCount)
|
|
92
89
|
}
|
|
93
90
|
],
|
|
94
91
|
[t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount]
|
|
@@ -104,31 +101,24 @@ function PersonDetailTabs({
|
|
|
104
101
|
[builtInTabs, injectedTabs]
|
|
105
102
|
);
|
|
106
103
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
107
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-2 border-b",
|
|
108
|
-
/* @__PURE__ */ jsx(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
tab.label,
|
|
126
|
-
tab.badge
|
|
127
|
-
]
|
|
128
|
-
},
|
|
129
|
-
tab.id
|
|
130
|
-
);
|
|
131
|
-
}) }),
|
|
104
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-2 border-b", children: [
|
|
105
|
+
/* @__PURE__ */ jsx(
|
|
106
|
+
Tabs,
|
|
107
|
+
{
|
|
108
|
+
value: activeTab,
|
|
109
|
+
onValueChange: (value) => onTabChange(value),
|
|
110
|
+
variant: "underline",
|
|
111
|
+
className: "min-w-0 flex-1",
|
|
112
|
+
children: /* @__PURE__ */ jsx(
|
|
113
|
+
TabsList,
|
|
114
|
+
{
|
|
115
|
+
"aria-label": t("customers.people.detail.tabs.label", "Person detail sections"),
|
|
116
|
+
className: "-mb-px w-full overflow-x-auto border-b-0 px-1",
|
|
117
|
+
children: allTabs.map((tab) => /* @__PURE__ */ jsx(TabsTrigger, { value: tab.id, leading: tab.icon, count: tab.count, children: tab.label }, tab.id))
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
),
|
|
132
122
|
sectionAction ? /* @__PURE__ */ jsxs(
|
|
133
123
|
Button,
|
|
134
124
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/PersonDetailTabs.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport {\n SquareCheckBig,\n Mail,\n Briefcase,\n Building2,\n Check,\n History,\n Paperclip,\n Plus,\n MapPin,\n} from 'lucide-react'\nimport type { SectionAction } from '@open-mercato/ui/backend/detail'\n\nexport type PersonTabId =\n | 'activities'\n | 'emails'\n | 'deals'\n | 'companies'\n | 'addresses'\n | 'tasks'\n | 'changelog'\n | 'files'\n | string\n\ntype TabDef = {\n id: PersonTabId\n label: string\n icon?: React.ReactNode\n count?: React.ReactNode\n}\n\ntype PersonDetailTabsProps = {\n activeTab: PersonTabId\n onTabChange: (tab: PersonTabId) => void\n injectedTabs?: Array<{ id: string; label: string }>\n activitiesCount?: number\n dealsCount?: number\n companiesCount?: number\n addressesCount?: number\n tasksCount?: number\n filesCount?: number\n sectionAction?: SectionAction | null\n children: React.ReactNode\n}\n\nconst SUPPORTED_TAB_IDS = new Set<PersonTabId>(['activities', 'emails', 'deals', 'companies', 'addresses', 'tasks', 'changelog', 'files'])\n\nexport function resolveLegacyTab(tab: string | null | undefined): PersonTabId {\n if (!tab) return 'activities'\n return SUPPORTED_TAB_IDS.has(tab as PersonTabId) ? (tab as PersonTabId) : 'activities'\n}\n\nfunction formatTabCount(count: number): string | number | undefined {\n if (count <= 0) return undefined\n return count > 999 ? '999+' : count\n}\n\nexport function PersonDetailTabs({\n activeTab,\n onTabChange,\n injectedTabs = [],\n activitiesCount = 0,\n dealsCount = 0,\n companiesCount = 0,\n addressesCount = 0,\n tasksCount = 0,\n filesCount = 0,\n sectionAction = null,\n children,\n}: PersonDetailTabsProps) {\n const t = useT()\n\n const builtInTabs: TabDef[] = React.useMemo(\n () => [\n {\n id: 'activities',\n label: t('customers.people.detail.tabs.activities', 'Activities'),\n icon: <SquareCheckBig className=\"size-4\" />,\n count: formatTabCount(activitiesCount),\n },\n {\n id: 'emails',\n label: t('customers.people.detail.tabs.emails', 'Emails'),\n icon: <Mail className=\"size-4\" />,\n },\n {\n id: 'deals',\n label: t('customers.people.detail.tabs.deals', 'Deals'),\n icon: <Briefcase className=\"size-4\" />,\n count: formatTabCount(dealsCount),\n },\n {\n id: 'companies',\n label: t('customers.people.detail.tabs.companies', 'Companies'),\n icon: <Building2 className=\"size-4\" />,\n count: formatTabCount(companiesCount),\n },\n {\n id: 'addresses',\n label: t('customers.people.detail.tabs.addresses', 'Addresses'),\n icon: <MapPin className=\"size-4\" />,\n count: formatTabCount(addressesCount),\n },\n {\n id: 'tasks',\n label: t('customers.people.detail.tabs.tasks', 'Tasks'),\n icon: <Check className=\"size-4\" />,\n count: formatTabCount(tasksCount),\n },\n {\n id: 'changelog',\n label: t('customers.people.detail.tabs.changelog', 'Change log'),\n icon: <History className=\"size-4\" />,\n count: 'NEW',\n },\n {\n id: 'files',\n label: t('customers.people.detail.tabs.files', 'Files'),\n icon: <Paperclip className=\"size-4\" />,\n count: formatTabCount(filesCount),\n },\n ],\n [t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount],\n )\n\n const allTabs: TabDef[] = React.useMemo(\n () => [\n ...builtInTabs,\n ...injectedTabs.map((tab) => ({\n id: tab.id as PersonTabId,\n label: tab.label,\n })),\n ],\n [builtInTabs, injectedTabs],\n )\n\n return (\n <div>\n {/* Tab navigation \u2014 full width above both zones */}\n <div className=\"flex items-end justify-between gap-2 border-b\">\n <Tabs\n value={activeTab}\n onValueChange={(value) => onTabChange(value as PersonTabId)}\n variant=\"underline\"\n className=\"min-w-0 flex-1\"\n >\n <TabsList\n aria-label={t('customers.people.detail.tabs.label', 'Person detail sections')}\n className=\"-mb-px w-full overflow-x-auto border-b-0 px-1\"\n >\n {allTabs.map((tab) => (\n <TabsTrigger key={tab.id} value={tab.id} leading={tab.icon} count={tab.count}>\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n {sectionAction ? (\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={sectionAction.onClick}\n disabled={sectionAction.disabled}\n className=\"mb-1.5 mr-1 shrink-0\"\n >\n <Plus className=\"mr-1.5 h-4 w-4\" />\n {sectionAction.label}\n </Button>\n ) : null}\n </div>\n\n {/* Two-column content below tabs */}\n <div className=\"pt-6\">\n {children}\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,19 +3,12 @@ import {
|
|
|
3
3
|
dictionaryEntryCommandCreateSchema,
|
|
4
4
|
dictionaryEntryCommandUpdateSchema
|
|
5
5
|
} from "@open-mercato/core/modules/dictionaries/data/validators";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ensureDictionaryEntryScope,
|
|
8
|
+
registerDictionaryEntryCommands
|
|
9
|
+
} from "@open-mercato/core/modules/dictionaries/commands/factory";
|
|
7
10
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
8
11
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
9
|
-
function ensureScope(ctx, scope) {
|
|
10
|
-
const tenantId = ctx.auth?.tenantId ?? null;
|
|
11
|
-
if (tenantId && tenantId !== scope.tenantId) {
|
|
12
|
-
throw new CrudHttpError(403, { error: "Forbidden" });
|
|
13
|
-
}
|
|
14
|
-
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
15
|
-
if (organizationId && organizationId !== scope.organizationId) {
|
|
16
|
-
throw new CrudHttpError(403, { error: "Forbidden" });
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
12
|
async function ensureDictionary(em, id) {
|
|
20
13
|
const dictionary = await em.findOne(Dictionary, {
|
|
21
14
|
id,
|
|
@@ -32,12 +25,12 @@ registerDictionaryEntryCommands({
|
|
|
32
25
|
translationKeyPrefix: "dictionaries.entries",
|
|
33
26
|
createSchema: dictionaryEntryCommandCreateSchema,
|
|
34
27
|
updateSchema: dictionaryEntryCommandUpdateSchema,
|
|
35
|
-
ensureScope,
|
|
28
|
+
ensureScope: ensureDictionaryEntryScope,
|
|
36
29
|
duplicateError: "An entry with this value already exists.",
|
|
37
30
|
resolveDictionaryForCreate: async ({ em, ctx, parsed }) => {
|
|
38
31
|
const dictionary = await ensureDictionary(em, parsed.dictionaryId);
|
|
39
32
|
const scope = { tenantId: dictionary.tenantId, organizationId: dictionary.organizationId };
|
|
40
|
-
|
|
33
|
+
ensureDictionaryEntryScope(ctx, scope);
|
|
41
34
|
return { dictionary, scope };
|
|
42
35
|
},
|
|
43
36
|
resolveEntry: async ({ em, ctx, id }) => {
|
|
@@ -49,7 +42,7 @@ registerDictionaryEntryCommands({
|
|
|
49
42
|
throw new CrudHttpError(404, { error: "Dictionary not found" });
|
|
50
43
|
}
|
|
51
44
|
const scope = { tenantId: entry.tenantId, organizationId: entry.organizationId };
|
|
52
|
-
|
|
45
|
+
ensureDictionaryEntryScope(ctx, scope);
|
|
53
46
|
return { entry, dictionary: entry.dictionary, scope };
|
|
54
47
|
},
|
|
55
48
|
ensureDictionaryForUndo: async ({ em, snapshot }) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/dictionaries/commands/entries.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n dictionaryEntryCommandCreateSchema,\n dictionaryEntryCommandUpdateSchema,\n} from '@open-mercato/core/modules/dictionaries/data/validators'\nimport {
|
|
5
|
-
"mappings": "AACA,SAAS,YAAY,uBAAuB;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n dictionaryEntryCommandCreateSchema,\n dictionaryEntryCommandUpdateSchema,\n} from '@open-mercato/core/modules/dictionaries/data/validators'\nimport {\n ensureDictionaryEntryScope,\n registerDictionaryEntryCommands,\n} from '@open-mercato/core/modules/dictionaries/commands/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nasync function ensureDictionary(em: EntityManager, id: string): Promise<Dictionary> {\n const dictionary = await em.findOne(Dictionary, {\n id,\n deletedAt: null,\n })\n if (!dictionary) {\n throw new CrudHttpError(404, { error: 'Dictionary not found' })\n }\n return dictionary\n}\n\nregisterDictionaryEntryCommands({\n commandPrefix: 'dictionaries.entries',\n resourceKind: 'dictionaries.entry',\n translationKeyPrefix: 'dictionaries.entries',\n createSchema: dictionaryEntryCommandCreateSchema,\n updateSchema: dictionaryEntryCommandUpdateSchema,\n ensureScope: ensureDictionaryEntryScope,\n duplicateError: 'An entry with this value already exists.',\n resolveDictionaryForCreate: async ({ em, ctx, parsed }) => {\n const dictionary = await ensureDictionary(em, parsed.dictionaryId)\n const scope = { tenantId: dictionary.tenantId, organizationId: dictionary.organizationId }\n ensureDictionaryEntryScope(ctx, scope)\n return { dictionary, scope }\n },\n resolveEntry: async ({ em, ctx, id }) => {\n const entry = await findOneWithDecryption(em, DictionaryEntry, id, { populate: ['dictionary'] })\n if (!entry) {\n throw new CrudHttpError(404, { error: 'Dictionary entry not found' })\n }\n if (entry.dictionary.deletedAt) {\n throw new CrudHttpError(404, { error: 'Dictionary not found' })\n }\n const scope = { tenantId: entry.tenantId, organizationId: entry.organizationId }\n ensureDictionaryEntryScope(ctx, scope)\n return { entry, dictionary: entry.dictionary, scope }\n },\n ensureDictionaryForUndo: async ({ em, snapshot }) => {\n return em.findOne(Dictionary, snapshot.dictionaryId, { filters: false })\n },\n})\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,YAAY,uBAAuB;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AAEtC,eAAe,iBAAiB,IAAmB,IAAiC;AAClF,QAAM,aAAa,MAAM,GAAG,QAAQ,YAAY;AAAA,IAC9C;AAAA,IACA,WAAW;AAAA,EACb,CAAC;AACD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,uBAAuB,CAAC;AAAA,EAChE;AACA,SAAO;AACT;AAEA,gCAAgC;AAAA,EAC9B,eAAe;AAAA,EACf,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,cAAc;AAAA,EACd,cAAc;AAAA,EACd,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,4BAA4B,OAAO,EAAE,IAAI,KAAK,OAAO,MAAM;AACzD,UAAM,aAAa,MAAM,iBAAiB,IAAI,OAAO,YAAY;AACjE,UAAM,QAAQ,EAAE,UAAU,WAAW,UAAU,gBAAgB,WAAW,eAAe;AACzF,+BAA2B,KAAK,KAAK;AACrC,WAAO,EAAE,YAAY,MAAM;AAAA,EAC7B;AAAA,EACA,cAAc,OAAO,EAAE,IAAI,KAAK,GAAG,MAAM;AACvC,UAAM,QAAQ,MAAM,sBAAsB,IAAI,iBAAiB,IAAI,EAAE,UAAU,CAAC,YAAY,EAAE,CAAC;AAC/F,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,6BAA6B,CAAC;AAAA,IACtE;AACA,QAAI,MAAM,WAAW,WAAW;AAC9B,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,uBAAuB,CAAC;AAAA,IAChE;AACA,UAAM,QAAQ,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AAC/E,+BAA2B,KAAK,KAAK;AACrC,WAAO,EAAE,OAAO,YAAY,MAAM,YAAY,MAAM;AAAA,EACtD;AAAA,EACA,yBAAyB,OAAO,EAAE,IAAI,SAAS,MAAM;AACnD,WAAO,GAAG,QAAQ,YAAY,SAAS,cAAc,EAAE,SAAS,MAAM,CAAC;AAAA,EACzE;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
reorderDictionaryEntriesCommandSchema,
|
|
4
4
|
setDefaultDictionaryEntryCommandSchema
|
|
5
5
|
} from "@open-mercato/core/modules/dictionaries/data/validators";
|
|
6
|
+
import { ensureDictionaryEntryScope } from "@open-mercato/core/modules/dictionaries/commands/factory";
|
|
6
7
|
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
7
8
|
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
|
|
8
9
|
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
@@ -23,16 +24,6 @@ const dictionaryCrudEvents = {
|
|
|
23
24
|
...ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}
|
|
24
25
|
})
|
|
25
26
|
};
|
|
26
|
-
function ensureScope(ctx, scope) {
|
|
27
|
-
const tenantId = ctx.auth?.tenantId ?? null;
|
|
28
|
-
if (tenantId && tenantId !== scope.tenantId) {
|
|
29
|
-
throw new CrudHttpError(403, { error: "Forbidden" });
|
|
30
|
-
}
|
|
31
|
-
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
32
|
-
if (organizationId && organizationId !== scope.organizationId) {
|
|
33
|
-
throw new CrudHttpError(403, { error: "Forbidden" });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
27
|
async function requireDictionary(em, id, scope) {
|
|
37
28
|
const dictionary = await findOneWithDecryption(
|
|
38
29
|
em,
|
|
@@ -51,7 +42,7 @@ const reorderDictionaryEntriesCommand = {
|
|
|
51
42
|
async prepare(rawInput, ctx) {
|
|
52
43
|
const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput);
|
|
53
44
|
const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
|
|
54
|
-
|
|
45
|
+
ensureDictionaryEntryScope(ctx, scope);
|
|
55
46
|
const em = ctx.container.resolve("em").fork();
|
|
56
47
|
const ids = parsed.entries.map((e) => e.id);
|
|
57
48
|
const existing = await findWithDecryption(
|
|
@@ -75,7 +66,7 @@ const reorderDictionaryEntriesCommand = {
|
|
|
75
66
|
async execute(rawInput, ctx) {
|
|
76
67
|
const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput);
|
|
77
68
|
const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
|
|
78
|
-
|
|
69
|
+
ensureDictionaryEntryScope(ctx, scope);
|
|
79
70
|
const em = ctx.container.resolve("em").fork();
|
|
80
71
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope);
|
|
81
72
|
const ids = parsed.entries.map((e) => e.id);
|
|
@@ -164,7 +155,7 @@ const reorderDictionaryEntriesCommand = {
|
|
|
164
155
|
const undo = extractUndoPayload(logEntry);
|
|
165
156
|
const before = undo?.before ?? logEntry?.snapshotBefore ?? null;
|
|
166
157
|
if (!before) return;
|
|
167
|
-
|
|
158
|
+
ensureDictionaryEntryScope(ctx, before.scope);
|
|
168
159
|
const em = ctx.container.resolve("em").fork();
|
|
169
160
|
const ids = before.positions.map((p) => p.id);
|
|
170
161
|
const entries = await findWithDecryption(
|
|
@@ -209,7 +200,7 @@ const setDefaultDictionaryEntryCommand = {
|
|
|
209
200
|
async prepare(rawInput, ctx) {
|
|
210
201
|
const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput);
|
|
211
202
|
const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
|
|
212
|
-
|
|
203
|
+
ensureDictionaryEntryScope(ctx, scope);
|
|
213
204
|
const em = ctx.container.resolve("em").fork();
|
|
214
205
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope);
|
|
215
206
|
const previousDefaults = await findWithDecryption(
|
|
@@ -235,7 +226,7 @@ const setDefaultDictionaryEntryCommand = {
|
|
|
235
226
|
async execute(rawInput, ctx) {
|
|
236
227
|
const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput);
|
|
237
228
|
const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
|
|
238
|
-
|
|
229
|
+
ensureDictionaryEntryScope(ctx, scope);
|
|
239
230
|
const em = ctx.container.resolve("em").fork();
|
|
240
231
|
const dictionary = await requireDictionary(em, parsed.dictionaryId, scope);
|
|
241
232
|
const targetEntry = await findOneWithDecryption(
|
|
@@ -330,7 +321,7 @@ const setDefaultDictionaryEntryCommand = {
|
|
|
330
321
|
const undo = extractUndoPayload(logEntry);
|
|
331
322
|
const before = undo?.before ?? logEntry?.snapshotBefore ?? null;
|
|
332
323
|
if (!before) return;
|
|
333
|
-
|
|
324
|
+
ensureDictionaryEntryScope(ctx, before.scope);
|
|
334
325
|
const em = ctx.container.resolve("em").fork();
|
|
335
326
|
const dictionary = await requireDictionary(em, before.dictionaryId, before.scope);
|
|
336
327
|
const newDefault = before.newDefaultId ? await findOneWithDecryption(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/dictionaries/commands/entry-operations.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n reorderDictionaryEntriesCommandSchema,\n setDefaultDictionaryEntryCommandSchema,\n type ReorderDictionaryEntriesCommandInput,\n type SetDefaultDictionaryEntryCommandInput,\n} from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { registerCommand, type CommandHandler, type CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nconst RESOURCE_KIND = 'dictionaries.dictionary'\nconst INDEXER_ENTITY_TYPE = 'dictionaries:entry'\n\ntype DictionaryScope = {\n tenantId: string\n organizationId: string\n}\n\nconst dictionaryCrudEvents: CrudEventsConfig = {\n module: 'dictionaries',\n entity: 'entry',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n ...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),\n }),\n}\n\nfunction ensureScope(ctx: CommandRuntimeContext, scope: DictionaryScope): void {\n const tenantId = ctx.auth?.tenantId ?? null\n if (tenantId && tenantId !== scope.tenantId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n if (organizationId && organizationId !== scope.organizationId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n}\n\nasync function requireDictionary(\n em: EntityManager,\n id: string,\n scope: DictionaryScope,\n): Promise<Dictionary> {\n const dictionary = await findOneWithDecryption(\n em,\n Dictionary,\n { id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },\n undefined,\n scope,\n )\n if (!dictionary) {\n throw new CrudHttpError(404, { error: 'Dictionary not found' })\n }\n return dictionary\n}\n\ntype ReorderSnapshot = {\n dictionaryId: string\n scope: DictionaryScope\n positions: Array<{ id: string; position: number }>\n}\n\ntype ReorderUndoPayload = {\n before?: ReorderSnapshot | null\n after?: ReorderSnapshot | null\n}\n\nconst reorderDictionaryEntriesCommand: CommandHandler<\n ReorderDictionaryEntriesCommandInput,\n { dictionaryId: string; updatedIds: string[] }\n> = {\n id: 'dictionaries.entries.reorder',\n async prepare(rawInput, ctx) {\n const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureScope(ctx, scope)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ids = parsed.entries.map((e) => e.id)\n const existing = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const before: ReorderSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n positions: existing.map((entry) => ({ id: entry.id, position: entry.position ?? 0 })),\n }\n return { before }\n },\n async execute(rawInput, ctx) {\n const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureScope(ctx, scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)\n const ids = parsed.entries.map((e) => e.id)\n const entries = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const entryMap = new Map<string, DictionaryEntry>()\n for (const entry of entries) entryMap.set(entry.id, entry)\n\n const updatedIds: string[] = []\n await withAtomicFlush(em, [\n () => {\n for (const { id, position } of parsed.entries) {\n const entry = entryMap.get(id)\n if (!entry) continue\n entry.position = position\n entry.updatedAt = new Date()\n updatedIds.push(id)\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const entry of entries) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n\n return { dictionaryId: parsed.dictionaryId, updatedIds }\n },\n captureAfter: async (rawInput, _result, ctx) => {\n const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)\n const em = ctx.container.resolve('em') as EntityManager\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n const ids = parsed.entries.map((e) => e.id)\n const current = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const after: ReorderSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n positions: current.map((entry) => ({ id: entry.id, position: entry.position ?? 0 })),\n }\n return after\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ReorderSnapshot | undefined\n const after = snapshots.after as ReorderSnapshot | undefined\n if (!before || !after) return null\n return {\n actionLabel: translate('dictionaries.entries.audit.reorder', 'Reorder dictionary entries'),\n resourceKind: RESOURCE_KIND,\n resourceId: result.dictionaryId,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: {\n undo: { before, after } satisfies ReorderUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<ReorderUndoPayload>(logEntry)\n const before = undo?.before ?? (logEntry?.snapshotBefore as ReorderSnapshot | null | undefined) ?? null\n if (!before) return\n ensureScope(ctx, before.scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ids = before.positions.map((p) => p.id)\n const entries = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n before.scope,\n )\n const entryMap = new Map<string, DictionaryEntry>()\n for (const entry of entries) entryMap.set(entry.id, entry)\n\n await withAtomicFlush(em, [\n () => {\n for (const { id, position } of before.positions) {\n const entry = entryMap.get(id)\n if (!entry) continue\n entry.position = position\n entry.updatedAt = new Date()\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const entry of entries) {\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n },\n}\n\ntype DefaultSnapshot = {\n dictionaryId: string\n scope: DictionaryScope\n previousDefaultIds: string[]\n newDefaultId: string\n}\n\ntype DefaultUndoPayload = {\n before?: DefaultSnapshot | null\n after?: DefaultSnapshot | null\n}\n\nconst setDefaultDictionaryEntryCommand: CommandHandler<\n SetDefaultDictionaryEntryCommandInput,\n { dictionaryId: string; entryId: string; clearedIds: string[] }\n> = {\n id: 'dictionaries.entries.set_default',\n async prepare(rawInput, ctx) {\n const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureScope(ctx, scope)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)\n const previousDefaults = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isDefault: true,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const before: DefaultSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n previousDefaultIds: previousDefaults.map((entry) => entry.id),\n newDefaultId: parsed.entryId,\n }\n return { before }\n },\n async execute(rawInput, ctx) {\n const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureScope(ctx, scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)\n const targetEntry = await findOneWithDecryption(\n em,\n DictionaryEntry,\n {\n id: parsed.entryId,\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n if (!targetEntry) {\n throw new CrudHttpError(404, { error: 'Dictionary entry not found' })\n }\n\n const existingDefaults = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isDefault: true,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const clearedIds: string[] = []\n\n // The partial unique index `dictionary_entries_one_default_per_dict`\n // (dictionary_id, organization_id, tenant_id) WHERE is_default = true forbids\n // two default entries in the same dictionary. PostgreSQL checks partial unique\n // indexes per-statement (no deferral) and MikroORM does not guarantee that the\n // is_default=false UPDATEs run before the is_default=true UPDATE within a single\n // flush, so clearing and setting in one flush races to a 23505. Clear the prior\n // default(s) in their own flush first, then set the new default in a second\n // flush, so Postgres never observes two default rows at once.\n await withAtomicFlush(em, [\n () => {\n for (const entry of existingDefaults) {\n if (entry.id === targetEntry.id) continue\n entry.isDefault = false\n entry.updatedAt = new Date()\n clearedIds.push(entry.id)\n }\n },\n ], { transaction: true })\n await withAtomicFlush(em, [\n () => {\n targetEntry.isDefault = true\n targetEntry.updatedAt = new Date()\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const touched = [targetEntry, ...existingDefaults.filter((entry) => entry.id !== targetEntry.id)]\n for (const entry of touched) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n\n return { dictionaryId: parsed.dictionaryId, entryId: targetEntry.id, clearedIds }\n },\n captureAfter: async (rawInput, result, _ctx) => {\n const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n const after: DefaultSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n previousDefaultIds: result.clearedIds,\n newDefaultId: result.entryId,\n }\n return after\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as DefaultSnapshot | undefined\n const after = snapshots.after as DefaultSnapshot | undefined\n if (!before || !after) return null\n return {\n actionLabel: translate('dictionaries.entries.audit.set_default', 'Set default dictionary entry'),\n resourceKind: RESOURCE_KIND,\n resourceId: result.dictionaryId,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: {\n undo: { before, after } satisfies DefaultUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<DefaultUndoPayload>(logEntry)\n const before = undo?.before ?? (logEntry?.snapshotBefore as DefaultSnapshot | null | undefined) ?? null\n if (!before) return\n ensureScope(ctx, before.scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, before.dictionaryId, before.scope)\n\n const newDefault = before.newDefaultId\n ? await findOneWithDecryption(\n em,\n DictionaryEntry,\n {\n id: before.newDefaultId,\n dictionary,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n before.scope,\n )\n : null\n const previousDefaults = before.previousDefaultIds.length > 0\n ? await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: before.previousDefaultIds },\n dictionary,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n before.scope,\n )\n : []\n\n // Same partial-unique-index ordering constraint as `execute`: clear the\n // current default in its own flush before restoring the previous default(s),\n // so Postgres never sees two is_default=true rows in the dictionary at once.\n await withAtomicFlush(em, [\n () => {\n if (newDefault) {\n newDefault.isDefault = false\n newDefault.updatedAt = new Date()\n }\n },\n ], { transaction: true })\n await withAtomicFlush(em, [\n () => {\n for (const entry of previousDefaults) {\n entry.isDefault = true\n entry.updatedAt = new Date()\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const touched = [\n ...(newDefault ? [newDefault] : []),\n ...previousDefaults,\n ]\n for (const entry of touched) {\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n },\n}\n\nregisterCommand(reorderDictionaryEntriesCommand)\nregisterCommand(setDefaultDictionaryEntryCommand)\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,YAAY,uBAAuB;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n reorderDictionaryEntriesCommandSchema,\n setDefaultDictionaryEntryCommandSchema,\n type ReorderDictionaryEntriesCommandInput,\n type SetDefaultDictionaryEntryCommandInput,\n} from '@open-mercato/core/modules/dictionaries/data/validators'\nimport { ensureDictionaryEntryScope } from '@open-mercato/core/modules/dictionaries/commands/factory'\nimport { registerCommand, type CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects } from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nconst RESOURCE_KIND = 'dictionaries.dictionary'\nconst INDEXER_ENTITY_TYPE = 'dictionaries:entry'\n\ntype DictionaryScope = {\n tenantId: string\n organizationId: string\n}\n\nconst dictionaryCrudEvents: CrudEventsConfig = {\n module: 'dictionaries',\n entity: 'entry',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n ...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),\n }),\n}\n\nasync function requireDictionary(\n em: EntityManager,\n id: string,\n scope: DictionaryScope,\n): Promise<Dictionary> {\n const dictionary = await findOneWithDecryption(\n em,\n Dictionary,\n { id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },\n undefined,\n scope,\n )\n if (!dictionary) {\n throw new CrudHttpError(404, { error: 'Dictionary not found' })\n }\n return dictionary\n}\n\ntype ReorderSnapshot = {\n dictionaryId: string\n scope: DictionaryScope\n positions: Array<{ id: string; position: number }>\n}\n\ntype ReorderUndoPayload = {\n before?: ReorderSnapshot | null\n after?: ReorderSnapshot | null\n}\n\nconst reorderDictionaryEntriesCommand: CommandHandler<\n ReorderDictionaryEntriesCommandInput,\n { dictionaryId: string; updatedIds: string[] }\n> = {\n id: 'dictionaries.entries.reorder',\n async prepare(rawInput, ctx) {\n const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureDictionaryEntryScope(ctx, scope)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ids = parsed.entries.map((e) => e.id)\n const existing = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const before: ReorderSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n positions: existing.map((entry) => ({ id: entry.id, position: entry.position ?? 0 })),\n }\n return { before }\n },\n async execute(rawInput, ctx) {\n const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureDictionaryEntryScope(ctx, scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)\n const ids = parsed.entries.map((e) => e.id)\n const entries = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const entryMap = new Map<string, DictionaryEntry>()\n for (const entry of entries) entryMap.set(entry.id, entry)\n\n const updatedIds: string[] = []\n await withAtomicFlush(em, [\n () => {\n for (const { id, position } of parsed.entries) {\n const entry = entryMap.get(id)\n if (!entry) continue\n entry.position = position\n entry.updatedAt = new Date()\n updatedIds.push(id)\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const entry of entries) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n\n return { dictionaryId: parsed.dictionaryId, updatedIds }\n },\n captureAfter: async (rawInput, _result, ctx) => {\n const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput)\n const em = ctx.container.resolve('em') as EntityManager\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n const ids = parsed.entries.map((e) => e.id)\n const current = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const after: ReorderSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n positions: current.map((entry) => ({ id: entry.id, position: entry.position ?? 0 })),\n }\n return after\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ReorderSnapshot | undefined\n const after = snapshots.after as ReorderSnapshot | undefined\n if (!before || !after) return null\n return {\n actionLabel: translate('dictionaries.entries.audit.reorder', 'Reorder dictionary entries'),\n resourceKind: RESOURCE_KIND,\n resourceId: result.dictionaryId,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: {\n undo: { before, after } satisfies ReorderUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<ReorderUndoPayload>(logEntry)\n const before = undo?.before ?? (logEntry?.snapshotBefore as ReorderSnapshot | null | undefined) ?? null\n if (!before) return\n ensureDictionaryEntryScope(ctx, before.scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ids = before.positions.map((p) => p.id)\n const entries = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: ids },\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n before.scope,\n )\n const entryMap = new Map<string, DictionaryEntry>()\n for (const entry of entries) entryMap.set(entry.id, entry)\n\n await withAtomicFlush(em, [\n () => {\n for (const { id, position } of before.positions) {\n const entry = entryMap.get(id)\n if (!entry) continue\n entry.position = position\n entry.updatedAt = new Date()\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n for (const entry of entries) {\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n },\n}\n\ntype DefaultSnapshot = {\n dictionaryId: string\n scope: DictionaryScope\n previousDefaultIds: string[]\n newDefaultId: string\n}\n\ntype DefaultUndoPayload = {\n before?: DefaultSnapshot | null\n after?: DefaultSnapshot | null\n}\n\nconst setDefaultDictionaryEntryCommand: CommandHandler<\n SetDefaultDictionaryEntryCommandInput,\n { dictionaryId: string; entryId: string; clearedIds: string[] }\n> = {\n id: 'dictionaries.entries.set_default',\n async prepare(rawInput, ctx) {\n const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureDictionaryEntryScope(ctx, scope)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)\n const previousDefaults = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isDefault: true,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const before: DefaultSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n previousDefaultIds: previousDefaults.map((entry) => entry.id),\n newDefaultId: parsed.entryId,\n }\n return { before }\n },\n async execute(rawInput, ctx) {\n const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n ensureDictionaryEntryScope(ctx, scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, parsed.dictionaryId, scope)\n const targetEntry = await findOneWithDecryption(\n em,\n DictionaryEntry,\n {\n id: parsed.entryId,\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n if (!targetEntry) {\n throw new CrudHttpError(404, { error: 'Dictionary entry not found' })\n }\n\n const existingDefaults = await findWithDecryption(\n em,\n DictionaryEntry,\n {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isDefault: true,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n scope,\n )\n const clearedIds: string[] = []\n\n // The partial unique index `dictionary_entries_one_default_per_dict`\n // (dictionary_id, organization_id, tenant_id) WHERE is_default = true forbids\n // two default entries in the same dictionary. PostgreSQL checks partial unique\n // indexes per-statement (no deferral) and MikroORM does not guarantee that the\n // is_default=false UPDATEs run before the is_default=true UPDATE within a single\n // flush, so clearing and setting in one flush races to a 23505. Clear the prior\n // default(s) in their own flush first, then set the new default in a second\n // flush, so Postgres never observes two default rows at once.\n await withAtomicFlush(em, [\n () => {\n for (const entry of existingDefaults) {\n if (entry.id === targetEntry.id) continue\n entry.isDefault = false\n entry.updatedAt = new Date()\n clearedIds.push(entry.id)\n }\n },\n ], { transaction: true })\n await withAtomicFlush(em, [\n () => {\n targetEntry.isDefault = true\n targetEntry.updatedAt = new Date()\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const touched = [targetEntry, ...existingDefaults.filter((entry) => entry.id !== targetEntry.id)]\n for (const entry of touched) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n\n return { dictionaryId: parsed.dictionaryId, entryId: targetEntry.id, clearedIds }\n },\n captureAfter: async (rawInput, result, _ctx) => {\n const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput)\n const scope: DictionaryScope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }\n const after: DefaultSnapshot = {\n dictionaryId: parsed.dictionaryId,\n scope,\n previousDefaultIds: result.clearedIds,\n newDefaultId: result.entryId,\n }\n return after\n },\n buildLog: async ({ snapshots, result }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as DefaultSnapshot | undefined\n const after = snapshots.after as DefaultSnapshot | undefined\n if (!before || !after) return null\n return {\n actionLabel: translate('dictionaries.entries.audit.set_default', 'Set default dictionary entry'),\n resourceKind: RESOURCE_KIND,\n resourceId: result.dictionaryId,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: {\n undo: { before, after } satisfies DefaultUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const undo = extractUndoPayload<DefaultUndoPayload>(logEntry)\n const before = undo?.before ?? (logEntry?.snapshotBefore as DefaultSnapshot | null | undefined) ?? null\n if (!before) return\n ensureDictionaryEntryScope(ctx, before.scope)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const dictionary = await requireDictionary(em, before.dictionaryId, before.scope)\n\n const newDefault = before.newDefaultId\n ? await findOneWithDecryption(\n em,\n DictionaryEntry,\n {\n id: before.newDefaultId,\n dictionary,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n before.scope,\n )\n : null\n const previousDefaults = before.previousDefaultIds.length > 0\n ? await findWithDecryption(\n em,\n DictionaryEntry,\n {\n id: { $in: before.previousDefaultIds },\n dictionary,\n tenantId: before.scope.tenantId,\n organizationId: before.scope.organizationId,\n } as FilterQuery<DictionaryEntry>,\n undefined,\n before.scope,\n )\n : []\n\n // Same partial-unique-index ordering constraint as `execute`: clear the\n // current default in its own flush before restoring the previous default(s),\n // so Postgres never sees two is_default=true rows in the dictionary at once.\n await withAtomicFlush(em, [\n () => {\n if (newDefault) {\n newDefault.isDefault = false\n newDefault.updatedAt = new Date()\n }\n },\n ], { transaction: true })\n await withAtomicFlush(em, [\n () => {\n for (const entry of previousDefaults) {\n entry.isDefault = true\n entry.updatedAt = new Date()\n }\n },\n ], { transaction: true })\n\n const dataEngine = ctx.container.resolve('dataEngine') as DataEngine\n const touched = [\n ...(newDefault ? [newDefault] : []),\n ...previousDefaults,\n ]\n for (const entry of touched) {\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: entry,\n identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },\n syncOrigin: ctx.syncOrigin,\n events: dictionaryCrudEvents,\n indexer: { entityType: INDEXER_ENTITY_TYPE },\n })\n }\n },\n}\n\nregisterCommand(reorderDictionaryEntriesCommand)\nregisterCommand(setDefaultDictionaryEntryCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -9,13 +9,17 @@ import {
|
|
|
9
9
|
} from "@open-mercato/shared/lib/commands";
|
|
10
10
|
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
|
|
11
11
|
import { buildChanges, requireId } from "@open-mercato/shared/lib/commands/helpers";
|
|
12
|
+
import { ensureOrganizationScope, ensureTenantScope } from "@open-mercato/shared/lib/commands/scope";
|
|
12
13
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
13
14
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
14
15
|
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
16
|
+
function ensureDictionaryEntryScope(ctx, scope) {
|
|
17
|
+
ensureTenantScope(ctx, scope.tenantId);
|
|
18
|
+
ensureOrganizationScope(ctx, scope.organizationId);
|
|
19
|
+
}
|
|
15
20
|
const ENTRY_CHANGE_KEYS = ["value", "label", "color", "icon", "position", "isDefault"];
|
|
16
21
|
function toScopeEnsurer(fn) {
|
|
17
|
-
return typeof fn === "function" ? fn :
|
|
18
|
-
};
|
|
22
|
+
return typeof fn === "function" ? fn : ensureDictionaryEntryScope;
|
|
19
23
|
}
|
|
20
24
|
async function loadSnapshot(em, id) {
|
|
21
25
|
const entry = await findOneWithDecryption(em, DictionaryEntry, id, { populate: ["dictionary"] });
|
|
@@ -365,6 +369,8 @@ function registerDictionaryEntryCommands(config) {
|
|
|
365
369
|
registerCommand(deleteCommand);
|
|
366
370
|
}
|
|
367
371
|
export {
|
|
368
|
-
|
|
372
|
+
ensureDictionaryEntryScope,
|
|
373
|
+
registerDictionaryEntryCommands,
|
|
374
|
+
toScopeEnsurer
|
|
369
375
|
};
|
|
370
376
|
//# sourceMappingURL=factory.js.map
|