@hartl-services/medusa-affiliate 0.5.0 → 0.7.0
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/.medusa/server/src/admin/index.js +291 -230
- package/.medusa/server/src/admin/index.mjs +291 -230
- package/.medusa/server/src/complete-cart-hooks.js +31 -0
- package/.medusa/server/src/jobs/affiliate-attribution-expiry.js +5 -6
- package/.medusa/server/src/jobs/affiliate-promotion-freshness.js +3 -6
- package/.medusa/server/src/modules/affiliate/domain/base-plugin-check.js +7 -4
- package/.medusa/server/src/modules/affiliate/domain/plugin-options.js +19 -2
- package/.medusa/server/src/modules/affiliate/service.js +23 -2
- package/.medusa/server/src/plugin-features.js +4 -2
- package/.medusa/server/src/subscribers/affiliate-product-eligibility-updated.js +4 -7
- package/.medusa/server/src/workflows/affiliate/finalize-affiliate-order-attribution.js +55 -3
- package/.medusa/server/src/workflows/affiliate/validate-complete-cart.js +62 -0
- package/.medusa/server/src/workflows/hooks/validate-complete-cart-affiliate-referral.js +1 -54
- package/README.md +55 -13
- package/package.json +4 -4
- package/.medusa/server/src/workflows/hooks/complete-cart-affiliate-attribution.js +0 -57
- package/.medusa/server/src/workflows/hooks/complete-cart-order-created-composition.js +0 -111
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const jsxRuntime = require("react/jsx-runtime");
|
|
3
3
|
const adminSdk = require("@medusajs/admin-sdk");
|
|
4
|
+
const adminComponents = require("@hartl-services/medusa-base/admin-components");
|
|
4
5
|
const ui = require("@medusajs/ui");
|
|
5
6
|
const react = require("react");
|
|
6
7
|
const reactQuery = require("@tanstack/react-query");
|
|
@@ -1143,7 +1144,7 @@ const AssignmentHistoryItem = ({
|
|
|
1143
1144
|
!assignment.active ? `, ${texts.assignment.endedReason}: ${formatAssignmentEndedReason(assignment.ended_reason)}` : ""
|
|
1144
1145
|
] })
|
|
1145
1146
|
] });
|
|
1146
|
-
const CustomerAffiliateWidget = ({ data }) => /* @__PURE__ */ jsxRuntime.jsx(CustomerAffiliateAssignmentPanel, { customerId: data.id });
|
|
1147
|
+
const CustomerAffiliateWidget = ({ data }) => /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureGate, { pluginKey: "affiliate", children: /* @__PURE__ */ jsxRuntime.jsx(CustomerAffiliateAssignmentPanel, { customerId: data.id }) });
|
|
1147
1148
|
adminSdk.defineWidgetConfig({
|
|
1148
1149
|
zone: "customer.details.side.after"
|
|
1149
1150
|
});
|
|
@@ -1229,7 +1230,7 @@ const OrderAffiliateAttributionPanel = ({ orderId }) => {
|
|
|
1229
1230
|
};
|
|
1230
1231
|
const OrderAffiliateAttributionWidget = ({
|
|
1231
1232
|
data
|
|
1232
|
-
}) => /* @__PURE__ */ jsxRuntime.jsx(OrderAffiliateAttributionPanel, { orderId: data.id });
|
|
1233
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureGate, { pluginKey: "affiliate", children: /* @__PURE__ */ jsxRuntime.jsx(OrderAffiliateAttributionPanel, { orderId: data.id }) });
|
|
1233
1234
|
adminSdk.defineWidgetConfig({
|
|
1234
1235
|
zone: "order.details.side.after"
|
|
1235
1236
|
});
|
|
@@ -1370,8 +1371,14 @@ const AffiliateListPage = () => {
|
|
|
1370
1371
|
] }) : null
|
|
1371
1372
|
] });
|
|
1372
1373
|
};
|
|
1373
|
-
const AffiliatesRoute = () =>
|
|
1374
|
-
const
|
|
1374
|
+
const AffiliatesRoute = () => {
|
|
1375
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled("affiliate");
|
|
1376
|
+
if (isPending) {
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(AffiliateListPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
1380
|
+
};
|
|
1381
|
+
const config$5 = adminSdk.defineRouteConfig({
|
|
1375
1382
|
label: texts.routes.affiliates,
|
|
1376
1383
|
rank: 81
|
|
1377
1384
|
});
|
|
@@ -2622,7 +2629,11 @@ const AffiliateFormFields = ({
|
|
|
2622
2629
|
};
|
|
2623
2630
|
const AffiliateEditRoute = () => {
|
|
2624
2631
|
const { id } = reactRouterDom.useParams();
|
|
2625
|
-
|
|
2632
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled("affiliate");
|
|
2633
|
+
if (isPending) {
|
|
2634
|
+
return null;
|
|
2635
|
+
}
|
|
2636
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(AffiliateFormPage, { mode: "edit", affiliateId: id }) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
2626
2637
|
};
|
|
2627
2638
|
const useAffiliateApplications = (params) => reactQuery.useQuery({
|
|
2628
2639
|
queryKey: affiliateKeys.applicationList(params),
|
|
@@ -2847,8 +2858,14 @@ const AffiliateApplicationsPage = () => {
|
|
|
2847
2858
|
] }) : null
|
|
2848
2859
|
] });
|
|
2849
2860
|
};
|
|
2850
|
-
const AffiliateApplicationsRoute = () =>
|
|
2851
|
-
const
|
|
2861
|
+
const AffiliateApplicationsRoute = () => {
|
|
2862
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled("affiliate");
|
|
2863
|
+
if (isPending) {
|
|
2864
|
+
return null;
|
|
2865
|
+
}
|
|
2866
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(AffiliateApplicationsPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
2867
|
+
};
|
|
2868
|
+
const config$4 = adminSdk.defineRouteConfig({
|
|
2852
2869
|
label: texts.routes.applications,
|
|
2853
2870
|
rank: 80
|
|
2854
2871
|
});
|
|
@@ -2890,8 +2907,17 @@ function ConsignmentReportsPage() {
|
|
|
2890
2907
|
] }) })
|
|
2891
2908
|
] });
|
|
2892
2909
|
}
|
|
2893
|
-
const ConsignmentReportsRoute = () =>
|
|
2894
|
-
const
|
|
2910
|
+
const ConsignmentReportsRoute = () => {
|
|
2911
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled(
|
|
2912
|
+
"affiliate",
|
|
2913
|
+
"consignment"
|
|
2914
|
+
);
|
|
2915
|
+
if (isPending) {
|
|
2916
|
+
return null;
|
|
2917
|
+
}
|
|
2918
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(ConsignmentReportsPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
2919
|
+
};
|
|
2920
|
+
const config$3 = adminSdk.defineRouteConfig({
|
|
2895
2921
|
label: "Kommissionsmeldungen",
|
|
2896
2922
|
rank: 83
|
|
2897
2923
|
});
|
|
@@ -3004,13 +3030,28 @@ function AffiliateDistributionReportPage() {
|
|
|
3004
3030
|
] }) })
|
|
3005
3031
|
] });
|
|
3006
3032
|
}
|
|
3007
|
-
const AffiliateDistributionReportRoute = () =>
|
|
3008
|
-
const
|
|
3033
|
+
const AffiliateDistributionReportRoute = () => {
|
|
3034
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled(
|
|
3035
|
+
"affiliate",
|
|
3036
|
+
"consignment"
|
|
3037
|
+
);
|
|
3038
|
+
if (isPending) {
|
|
3039
|
+
return null;
|
|
3040
|
+
}
|
|
3041
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(AffiliateDistributionReportPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
3042
|
+
};
|
|
3043
|
+
const config$2 = adminSdk.defineRouteConfig({
|
|
3009
3044
|
label: "Vertriebsreport",
|
|
3010
3045
|
rank: 85
|
|
3011
3046
|
});
|
|
3012
|
-
const NewAffiliateRoute = () =>
|
|
3013
|
-
const
|
|
3047
|
+
const NewAffiliateRoute = () => {
|
|
3048
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled("affiliate");
|
|
3049
|
+
if (isPending) {
|
|
3050
|
+
return null;
|
|
3051
|
+
}
|
|
3052
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(AffiliateFormPage, { mode: "create" }) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
3053
|
+
};
|
|
3054
|
+
const config$1 = adminSdk.defineRouteConfig({
|
|
3014
3055
|
label: texts.routes.newAffiliate
|
|
3015
3056
|
});
|
|
3016
3057
|
const useAffiliateReportOverview = (params) => reactQuery.useQuery({
|
|
@@ -3074,200 +3115,19 @@ const AffiliateReportsPage = () => {
|
|
|
3074
3115
|
renderBody()
|
|
3075
3116
|
] });
|
|
3076
3117
|
};
|
|
3077
|
-
const AffiliateReportsRoute = () =>
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
});
|
|
3082
|
-
function AffiliateProgramSettingsPage() {
|
|
3083
|
-
const queryClient = reactQuery.useQueryClient();
|
|
3084
|
-
const settings = reactQuery.useQuery({
|
|
3085
|
-
queryKey: ["affiliate", "program-settings"],
|
|
3086
|
-
queryFn: getAffiliateProgramSettings
|
|
3087
|
-
});
|
|
3088
|
-
const [commissionBase, setCommissionBase] = react.useState("after_discounts");
|
|
3089
|
-
const [referrerBase, setReferrerBase] = react.useState("order_base");
|
|
3090
|
-
const [stacking, setStacking] = react.useState(
|
|
3091
|
-
"allow_stacking"
|
|
3118
|
+
const AffiliateReportsRoute = () => {
|
|
3119
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled(
|
|
3120
|
+
"affiliate",
|
|
3121
|
+
"commissions"
|
|
3092
3122
|
);
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
const [referralWindowDays, setReferralWindowDays] = react.useState(30);
|
|
3096
|
-
react.useEffect(() => {
|
|
3097
|
-
const value = settings.data?.settings;
|
|
3098
|
-
if (!value) return;
|
|
3099
|
-
setCommissionBase(value.commission_base);
|
|
3100
|
-
setReferrerBase(value.referrer_base);
|
|
3101
|
-
setStacking(value.promotion_stacking);
|
|
3102
|
-
setDiscount(value.default_customer_discount_rate);
|
|
3103
|
-
setReferralOverwritePolicy(value.referral_overwrite_policy);
|
|
3104
|
-
setReferralWindowDays(value.referral_window_days);
|
|
3105
|
-
}, [settings.data]);
|
|
3106
|
-
const update = reactQuery.useMutation({
|
|
3107
|
-
mutationFn: () => {
|
|
3108
|
-
const current = settings.data?.settings;
|
|
3109
|
-
if (!current) throw new Error("Programmeinstellungen fehlen.");
|
|
3110
|
-
return updateAffiliateProgramSettings({
|
|
3111
|
-
expected_version: current.policy_version,
|
|
3112
|
-
commission_base: commissionBase,
|
|
3113
|
-
referrer_base: referrerBase,
|
|
3114
|
-
promotion_stacking: stacking,
|
|
3115
|
-
default_customer_discount_rate: discount,
|
|
3116
|
-
referral_overwrite_policy: referralOverwritePolicy,
|
|
3117
|
-
referral_window_days: referralWindowDays
|
|
3118
|
-
});
|
|
3119
|
-
},
|
|
3120
|
-
onSuccess: async (data) => {
|
|
3121
|
-
queryClient.setQueryData(["affiliate", "program-settings"], data);
|
|
3122
|
-
ui.toast.success("Programmeinstellungen wurden gespeichert.");
|
|
3123
|
-
},
|
|
3124
|
-
onError: (error) => ui.toast.error("Programmeinstellungen konnten nicht gespeichert werden.", {
|
|
3125
|
-
description: getErrorMessage(error)
|
|
3126
|
-
})
|
|
3127
|
-
});
|
|
3128
|
-
const submit = (event) => {
|
|
3129
|
-
event.preventDefault();
|
|
3130
|
-
update.mutate();
|
|
3131
|
-
};
|
|
3132
|
-
if (settings.isLoading) {
|
|
3133
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Einstellungen werden geladen …" }) });
|
|
3123
|
+
if (isPending) {
|
|
3124
|
+
return null;
|
|
3134
3125
|
}
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
}
|
|
3138
|
-
const previewOrderBase = commissionBase === "before_discounts" ? 100 : 90;
|
|
3139
|
-
const previewDirect = previewOrderBase * 0.1;
|
|
3140
|
-
const previewReferrerBase = referrerBase === "order_base" ? previewOrderBase : previewOrderBase - previewDirect;
|
|
3141
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-5", children: [
|
|
3142
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3143
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Affiliate-Programmeinstellungen" }),
|
|
3144
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
3145
|
-
"Globale Berechnungs- und Rabattstrategie · Version",
|
|
3146
|
-
" ",
|
|
3147
|
-
settings.data.settings.policy_version
|
|
3148
|
-
] })
|
|
3149
|
-
] }),
|
|
3150
|
-
/* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "grid gap-5", children: [
|
|
3151
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
3152
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3153
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "commission-base", children: "Provisionsbasis" }),
|
|
3154
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3155
|
-
"select",
|
|
3156
|
-
{
|
|
3157
|
-
id: "commission-base",
|
|
3158
|
-
className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
|
|
3159
|
-
value: commissionBase,
|
|
3160
|
-
onChange: (event) => setCommissionBase(
|
|
3161
|
-
event.target.value
|
|
3162
|
-
),
|
|
3163
|
-
children: [
|
|
3164
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "before_discounts", children: "Vor Rabatten" }),
|
|
3165
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "after_discounts", children: "Nach Rabatten" })
|
|
3166
|
-
]
|
|
3167
|
-
}
|
|
3168
|
-
)
|
|
3169
|
-
] }),
|
|
3170
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3171
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referral-overwrite-policy", children: "Referral-Regel" }),
|
|
3172
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3173
|
-
ui.Select,
|
|
3174
|
-
{
|
|
3175
|
-
value: referralOverwritePolicy,
|
|
3176
|
-
onValueChange: (value) => setReferralOverwritePolicy(value),
|
|
3177
|
-
children: [
|
|
3178
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { id: "referral-overwrite-policy", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {}) }),
|
|
3179
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
3180
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "first_touch", children: "First Touch" }),
|
|
3181
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "last_touch", children: "Last Touch" })
|
|
3182
|
-
] })
|
|
3183
|
-
]
|
|
3184
|
-
}
|
|
3185
|
-
),
|
|
3186
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: referralOverwritePolicy === "first_touch" ? "First Touch: Beispiel A-Link, danach B-Coupon; A bleibt." : "Last Touch: Beispiel A-Link, danach B-Coupon; B ersetzt A." })
|
|
3187
|
-
] }),
|
|
3188
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3189
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referral-window-days", children: "Referral-Frist in Tagen" }),
|
|
3190
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3191
|
-
ui.Input,
|
|
3192
|
-
{
|
|
3193
|
-
id: "referral-window-days",
|
|
3194
|
-
type: "number",
|
|
3195
|
-
min: 1,
|
|
3196
|
-
step: 1,
|
|
3197
|
-
required: true,
|
|
3198
|
-
value: referralWindowDays,
|
|
3199
|
-
onChange: (event) => setReferralWindowDays(Number(event.target.value))
|
|
3200
|
-
}
|
|
3201
|
-
),
|
|
3202
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Referral-Signale können innerhalb dieser positiven Frist ersetzt werden." })
|
|
3203
|
-
] }),
|
|
3204
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3205
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referrer-base", children: "Vermittlerbasis" }),
|
|
3206
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3207
|
-
"select",
|
|
3208
|
-
{
|
|
3209
|
-
id: "referrer-base",
|
|
3210
|
-
className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
|
|
3211
|
-
value: referrerBase,
|
|
3212
|
-
onChange: (event) => setReferrerBase(event.target.value),
|
|
3213
|
-
children: [
|
|
3214
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "order_base", children: "Order-Basis" }),
|
|
3215
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "after_direct_commission", children: "Nach direkter Provision" })
|
|
3216
|
-
]
|
|
3217
|
-
}
|
|
3218
|
-
)
|
|
3219
|
-
] }),
|
|
3220
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3221
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "promotion-stacking", children: "Promotion-Kombination" }),
|
|
3222
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3223
|
-
"select",
|
|
3224
|
-
{
|
|
3225
|
-
id: "promotion-stacking",
|
|
3226
|
-
className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
|
|
3227
|
-
value: stacking,
|
|
3228
|
-
onChange: (event) => setStacking(event.target.value),
|
|
3229
|
-
children: [
|
|
3230
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "allow_stacking", children: "Kombination erlauben" }),
|
|
3231
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "exclusive", children: "Exklusiv" })
|
|
3232
|
-
]
|
|
3233
|
-
}
|
|
3234
|
-
)
|
|
3235
|
-
] }),
|
|
3236
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3237
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "default-customer-discount", children: "Standard-Kundenrabatt %" }),
|
|
3238
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3239
|
-
ui.Input,
|
|
3240
|
-
{
|
|
3241
|
-
id: "default-customer-discount",
|
|
3242
|
-
type: "number",
|
|
3243
|
-
min: 0,
|
|
3244
|
-
max: 100,
|
|
3245
|
-
step: "0.01",
|
|
3246
|
-
value: discount,
|
|
3247
|
-
onChange: (event) => setDiscount(Number(event.target.value))
|
|
3248
|
-
}
|
|
3249
|
-
)
|
|
3250
|
-
] })
|
|
3251
|
-
] }),
|
|
3252
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
3253
|
-
"Vorschau bei 100 € Warenwert, 10 € Rabatt und 10 % Provision: Affiliate-Basis ",
|
|
3254
|
-
previewOrderBase.toFixed(2),
|
|
3255
|
-
" €, direkte Provision",
|
|
3256
|
-
" ",
|
|
3257
|
-
previewDirect.toFixed(2),
|
|
3258
|
-
" €, Vermittlerbasis",
|
|
3259
|
-
" ",
|
|
3260
|
-
previewReferrerBase.toFixed(2),
|
|
3261
|
-
" €."
|
|
3262
|
-
] }),
|
|
3263
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: update.isPending, children: update.isPending ? "Wird gespeichert …" : "Speichern" }) })
|
|
3264
|
-
] })
|
|
3265
|
-
] });
|
|
3266
|
-
}
|
|
3267
|
-
const AffiliateProgramSettingsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(AffiliateProgramSettingsPage, {});
|
|
3126
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(AffiliateReportsPage, {}) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
3127
|
+
};
|
|
3268
3128
|
const config = adminSdk.defineRouteConfig({
|
|
3269
|
-
label:
|
|
3270
|
-
rank:
|
|
3129
|
+
label: texts.routes.reports,
|
|
3130
|
+
rank: 82
|
|
3271
3131
|
});
|
|
3272
3132
|
function ConsignmentReportReviewPage({
|
|
3273
3133
|
reportId
|
|
@@ -3451,7 +3311,14 @@ function ConsignmentReportReviewPage({
|
|
|
3451
3311
|
}
|
|
3452
3312
|
const ConsignmentReportReviewRoute = () => {
|
|
3453
3313
|
const { id } = reactRouterDom.useParams();
|
|
3454
|
-
|
|
3314
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled(
|
|
3315
|
+
"affiliate",
|
|
3316
|
+
"consignment"
|
|
3317
|
+
);
|
|
3318
|
+
if (isPending) {
|
|
3319
|
+
return null;
|
|
3320
|
+
}
|
|
3321
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(ConsignmentReportReviewPage, { reportId: id }) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
3455
3322
|
};
|
|
3456
3323
|
const REPORT_ENTRIES_PAGE_SIZE = 100;
|
|
3457
3324
|
const AffiliateReportDetailPage = ({
|
|
@@ -3594,8 +3461,211 @@ const AffiliateReportDetailPage = ({
|
|
|
3594
3461
|
};
|
|
3595
3462
|
const AffiliateReportDetailRoute = () => {
|
|
3596
3463
|
const { id } = reactRouterDom.useParams();
|
|
3597
|
-
|
|
3464
|
+
const { enabled, isPending } = adminComponents.usePluginFeatureEnabled(
|
|
3465
|
+
"affiliate",
|
|
3466
|
+
"commissions"
|
|
3467
|
+
);
|
|
3468
|
+
if (isPending) {
|
|
3469
|
+
return null;
|
|
3470
|
+
}
|
|
3471
|
+
return enabled ? /* @__PURE__ */ jsxRuntime.jsx(AffiliateReportDetailPage, { affiliateId: id }) : /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" });
|
|
3598
3472
|
};
|
|
3473
|
+
function AffiliateProgramSettingsPage() {
|
|
3474
|
+
const queryClient = reactQuery.useQueryClient();
|
|
3475
|
+
const settings = reactQuery.useQuery({
|
|
3476
|
+
queryKey: ["affiliate", "program-settings"],
|
|
3477
|
+
queryFn: getAffiliateProgramSettings
|
|
3478
|
+
});
|
|
3479
|
+
const [commissionBase, setCommissionBase] = react.useState("after_discounts");
|
|
3480
|
+
const [referrerBase, setReferrerBase] = react.useState("order_base");
|
|
3481
|
+
const [stacking, setStacking] = react.useState(
|
|
3482
|
+
"allow_stacking"
|
|
3483
|
+
);
|
|
3484
|
+
const [discount, setDiscount] = react.useState(5);
|
|
3485
|
+
const [referralOverwritePolicy, setReferralOverwritePolicy] = react.useState("first_touch");
|
|
3486
|
+
const [referralWindowDays, setReferralWindowDays] = react.useState(30);
|
|
3487
|
+
react.useEffect(() => {
|
|
3488
|
+
const value = settings.data?.settings;
|
|
3489
|
+
if (!value) return;
|
|
3490
|
+
setCommissionBase(value.commission_base);
|
|
3491
|
+
setReferrerBase(value.referrer_base);
|
|
3492
|
+
setStacking(value.promotion_stacking);
|
|
3493
|
+
setDiscount(value.default_customer_discount_rate);
|
|
3494
|
+
setReferralOverwritePolicy(value.referral_overwrite_policy);
|
|
3495
|
+
setReferralWindowDays(value.referral_window_days);
|
|
3496
|
+
}, [settings.data]);
|
|
3497
|
+
const update = reactQuery.useMutation({
|
|
3498
|
+
mutationFn: () => {
|
|
3499
|
+
const current = settings.data?.settings;
|
|
3500
|
+
if (!current) throw new Error("Programmeinstellungen fehlen.");
|
|
3501
|
+
return updateAffiliateProgramSettings({
|
|
3502
|
+
expected_version: current.policy_version,
|
|
3503
|
+
commission_base: commissionBase,
|
|
3504
|
+
referrer_base: referrerBase,
|
|
3505
|
+
promotion_stacking: stacking,
|
|
3506
|
+
default_customer_discount_rate: discount,
|
|
3507
|
+
referral_overwrite_policy: referralOverwritePolicy,
|
|
3508
|
+
referral_window_days: referralWindowDays
|
|
3509
|
+
});
|
|
3510
|
+
},
|
|
3511
|
+
onSuccess: async (data) => {
|
|
3512
|
+
queryClient.setQueryData(["affiliate", "program-settings"], data);
|
|
3513
|
+
ui.toast.success("Programmeinstellungen wurden gespeichert.");
|
|
3514
|
+
},
|
|
3515
|
+
onError: (error) => ui.toast.error("Programmeinstellungen konnten nicht gespeichert werden.", {
|
|
3516
|
+
description: getErrorMessage(error)
|
|
3517
|
+
})
|
|
3518
|
+
});
|
|
3519
|
+
const submit = (event) => {
|
|
3520
|
+
event.preventDefault();
|
|
3521
|
+
update.mutate();
|
|
3522
|
+
};
|
|
3523
|
+
if (settings.isLoading) {
|
|
3524
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Programmeinstellungen werden geladen …" }) });
|
|
3525
|
+
}
|
|
3526
|
+
if (settings.isError || !settings.data) {
|
|
3527
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: getErrorMessage(settings.error) }) });
|
|
3528
|
+
}
|
|
3529
|
+
const previewOrderBase = commissionBase === "before_discounts" ? 100 : 90;
|
|
3530
|
+
const previewDirect = previewOrderBase * 0.1;
|
|
3531
|
+
const previewReferrerBase = referrerBase === "order_base" ? previewOrderBase : previewOrderBase - previewDirect;
|
|
3532
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "grid gap-5", children: [
|
|
3533
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3534
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Programmeinstellungen" }),
|
|
3535
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
3536
|
+
"Globale Berechnungs- und Rabattstrategie · Version",
|
|
3537
|
+
" ",
|
|
3538
|
+
settings.data.settings.policy_version
|
|
3539
|
+
] })
|
|
3540
|
+
] }),
|
|
3541
|
+
/* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, className: "grid gap-5", children: [
|
|
3542
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
3543
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3544
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "commission-base", children: "Provisionsbasis" }),
|
|
3545
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3546
|
+
"select",
|
|
3547
|
+
{
|
|
3548
|
+
id: "commission-base",
|
|
3549
|
+
className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
|
|
3550
|
+
value: commissionBase,
|
|
3551
|
+
onChange: (event) => setCommissionBase(
|
|
3552
|
+
event.target.value
|
|
3553
|
+
),
|
|
3554
|
+
children: [
|
|
3555
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "before_discounts", children: "Vor Rabatten" }),
|
|
3556
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "after_discounts", children: "Nach Rabatten" })
|
|
3557
|
+
]
|
|
3558
|
+
}
|
|
3559
|
+
)
|
|
3560
|
+
] }),
|
|
3561
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3562
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referral-overwrite-policy", children: "Referral-Regel" }),
|
|
3563
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3564
|
+
ui.Select,
|
|
3565
|
+
{
|
|
3566
|
+
value: referralOverwritePolicy,
|
|
3567
|
+
onValueChange: (value) => setReferralOverwritePolicy(value),
|
|
3568
|
+
children: [
|
|
3569
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { id: "referral-overwrite-policy", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {}) }),
|
|
3570
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
3571
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "first_touch", children: "First Touch" }),
|
|
3572
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "last_touch", children: "Last Touch" })
|
|
3573
|
+
] })
|
|
3574
|
+
]
|
|
3575
|
+
}
|
|
3576
|
+
),
|
|
3577
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: referralOverwritePolicy === "first_touch" ? "First Touch: Beispiel A-Link, danach B-Coupon; A bleibt." : "Last Touch: Beispiel A-Link, danach B-Coupon; B ersetzt A." })
|
|
3578
|
+
] }),
|
|
3579
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3580
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referral-window-days", children: "Referral-Frist in Tagen" }),
|
|
3581
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3582
|
+
ui.Input,
|
|
3583
|
+
{
|
|
3584
|
+
id: "referral-window-days",
|
|
3585
|
+
type: "number",
|
|
3586
|
+
min: 1,
|
|
3587
|
+
step: 1,
|
|
3588
|
+
required: true,
|
|
3589
|
+
value: referralWindowDays,
|
|
3590
|
+
onChange: (event) => setReferralWindowDays(Number(event.target.value))
|
|
3591
|
+
}
|
|
3592
|
+
),
|
|
3593
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Referral-Signale können innerhalb dieser positiven Frist ersetzt werden." })
|
|
3594
|
+
] }),
|
|
3595
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3596
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "referrer-base", children: "Vermittlerbasis" }),
|
|
3597
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3598
|
+
"select",
|
|
3599
|
+
{
|
|
3600
|
+
id: "referrer-base",
|
|
3601
|
+
className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
|
|
3602
|
+
value: referrerBase,
|
|
3603
|
+
onChange: (event) => setReferrerBase(event.target.value),
|
|
3604
|
+
children: [
|
|
3605
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "order_base", children: "Order-Basis" }),
|
|
3606
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "after_direct_commission", children: "Nach direkter Provision" })
|
|
3607
|
+
]
|
|
3608
|
+
}
|
|
3609
|
+
)
|
|
3610
|
+
] }),
|
|
3611
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3612
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "promotion-stacking", children: "Promotion-Kombination" }),
|
|
3613
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3614
|
+
"select",
|
|
3615
|
+
{
|
|
3616
|
+
id: "promotion-stacking",
|
|
3617
|
+
className: "bg-ui-bg-field border-ui-border-base rounded-md border px-3 py-2 text-sm",
|
|
3618
|
+
value: stacking,
|
|
3619
|
+
onChange: (event) => setStacking(event.target.value),
|
|
3620
|
+
children: [
|
|
3621
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "allow_stacking", children: "Kombination erlauben" }),
|
|
3622
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "exclusive", children: "Exklusiv" })
|
|
3623
|
+
]
|
|
3624
|
+
}
|
|
3625
|
+
)
|
|
3626
|
+
] }),
|
|
3627
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
3628
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "default-customer-discount", children: "Standard-Kundenrabatt %" }),
|
|
3629
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3630
|
+
ui.Input,
|
|
3631
|
+
{
|
|
3632
|
+
id: "default-customer-discount",
|
|
3633
|
+
type: "number",
|
|
3634
|
+
min: 0,
|
|
3635
|
+
max: 100,
|
|
3636
|
+
step: "0.01",
|
|
3637
|
+
value: discount,
|
|
3638
|
+
onChange: (event) => setDiscount(Number(event.target.value))
|
|
3639
|
+
}
|
|
3640
|
+
)
|
|
3641
|
+
] })
|
|
3642
|
+
] }),
|
|
3643
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
3644
|
+
"Vorschau bei 100 € Warenwert, 10 € Rabatt und 10 % Provision: Affiliate-Basis ",
|
|
3645
|
+
previewOrderBase.toFixed(2),
|
|
3646
|
+
" €, direkte Provision",
|
|
3647
|
+
" ",
|
|
3648
|
+
previewDirect.toFixed(2),
|
|
3649
|
+
" €, Vermittlerbasis",
|
|
3650
|
+
" ",
|
|
3651
|
+
previewReferrerBase.toFixed(2),
|
|
3652
|
+
" €."
|
|
3653
|
+
] }),
|
|
3654
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled: update.isPending, children: update.isPending ? "Wird gespeichert …" : "Speichern" }) })
|
|
3655
|
+
] })
|
|
3656
|
+
] });
|
|
3657
|
+
}
|
|
3658
|
+
const AffiliatePluginRoute = () => /* @__PURE__ */ jsxRuntime.jsxs(adminComponents.PluginPageShell, { pluginKey: "affiliate", children: [
|
|
3659
|
+
/* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureSections, { pluginKey: "affiliate" }),
|
|
3660
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3661
|
+
adminComponents.PluginFeatureGate,
|
|
3662
|
+
{
|
|
3663
|
+
pluginKey: "affiliate",
|
|
3664
|
+
fallback: /* @__PURE__ */ jsxRuntime.jsx(adminComponents.PluginFeatureDisabledNotice, { pluginKey: "affiliate" }),
|
|
3665
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AffiliateProgramSettingsPage, {})
|
|
3666
|
+
}
|
|
3667
|
+
)
|
|
3668
|
+
] });
|
|
3599
3669
|
const widgetModule = { widgets: [
|
|
3600
3670
|
{
|
|
3601
3671
|
Component: CustomerAffiliateWidget,
|
|
@@ -3613,7 +3683,7 @@ const routeModule = {
|
|
|
3613
3683
|
{
|
|
3614
3684
|
Component: AffiliatesRoute,
|
|
3615
3685
|
path: "/affiliates",
|
|
3616
|
-
handle: { label: config$
|
|
3686
|
+
handle: { label: config$5.label, translationNs: config$5.translationNs }
|
|
3617
3687
|
},
|
|
3618
3688
|
{
|
|
3619
3689
|
Component: AffiliateEditRoute,
|
|
@@ -3622,31 +3692,26 @@ const routeModule = {
|
|
|
3622
3692
|
{
|
|
3623
3693
|
Component: AffiliateApplicationsRoute,
|
|
3624
3694
|
path: "/affiliates/applications",
|
|
3625
|
-
handle: { label: config$
|
|
3695
|
+
handle: { label: config$4.label, translationNs: config$4.translationNs }
|
|
3626
3696
|
},
|
|
3627
3697
|
{
|
|
3628
3698
|
Component: ConsignmentReportsRoute,
|
|
3629
3699
|
path: "/affiliates/consignment-reports",
|
|
3630
|
-
handle: { label: config$
|
|
3700
|
+
handle: { label: config$3.label, translationNs: config$3.translationNs }
|
|
3631
3701
|
},
|
|
3632
3702
|
{
|
|
3633
3703
|
Component: AffiliateDistributionReportRoute,
|
|
3634
3704
|
path: "/affiliates/distribution-reports",
|
|
3635
|
-
handle: { label: config$
|
|
3705
|
+
handle: { label: config$2.label, translationNs: config$2.translationNs }
|
|
3636
3706
|
},
|
|
3637
3707
|
{
|
|
3638
3708
|
Component: NewAffiliateRoute,
|
|
3639
3709
|
path: "/affiliates/new",
|
|
3640
|
-
handle: { label: config$
|
|
3710
|
+
handle: { label: config$1.label, translationNs: config$1.translationNs }
|
|
3641
3711
|
},
|
|
3642
3712
|
{
|
|
3643
3713
|
Component: AffiliateReportsRoute,
|
|
3644
3714
|
path: "/affiliates/reports",
|
|
3645
|
-
handle: { label: config$1.label, translationNs: config$1.translationNs }
|
|
3646
|
-
},
|
|
3647
|
-
{
|
|
3648
|
-
Component: AffiliateProgramSettingsRoute,
|
|
3649
|
-
path: "/affiliates/settings",
|
|
3650
3715
|
handle: { label: config.label, translationNs: config.translationNs }
|
|
3651
3716
|
},
|
|
3652
3717
|
{
|
|
@@ -3656,13 +3721,17 @@ const routeModule = {
|
|
|
3656
3721
|
{
|
|
3657
3722
|
Component: AffiliateReportDetailRoute,
|
|
3658
3723
|
path: "/affiliates/reports/:id"
|
|
3724
|
+
},
|
|
3725
|
+
{
|
|
3726
|
+
Component: AffiliatePluginRoute,
|
|
3727
|
+
path: "/settings/hartl-services/affiliate"
|
|
3659
3728
|
}
|
|
3660
3729
|
]
|
|
3661
3730
|
};
|
|
3662
3731
|
const menuItemModule = {
|
|
3663
3732
|
menuItems: [
|
|
3664
3733
|
{
|
|
3665
|
-
label: config$
|
|
3734
|
+
label: config$5.label,
|
|
3666
3735
|
icon: void 0,
|
|
3667
3736
|
path: "/affiliates",
|
|
3668
3737
|
nested: void 0,
|
|
@@ -3670,7 +3739,7 @@ const menuItemModule = {
|
|
|
3670
3739
|
translationNs: void 0
|
|
3671
3740
|
},
|
|
3672
3741
|
{
|
|
3673
|
-
label: config$
|
|
3742
|
+
label: config$4.label,
|
|
3674
3743
|
icon: void 0,
|
|
3675
3744
|
path: "/affiliates/applications",
|
|
3676
3745
|
nested: void 0,
|
|
@@ -3678,7 +3747,7 @@ const menuItemModule = {
|
|
|
3678
3747
|
translationNs: void 0
|
|
3679
3748
|
},
|
|
3680
3749
|
{
|
|
3681
|
-
label: config$
|
|
3750
|
+
label: config$3.label,
|
|
3682
3751
|
icon: void 0,
|
|
3683
3752
|
path: "/affiliates/consignment-reports",
|
|
3684
3753
|
nested: void 0,
|
|
@@ -3686,7 +3755,7 @@ const menuItemModule = {
|
|
|
3686
3755
|
translationNs: void 0
|
|
3687
3756
|
},
|
|
3688
3757
|
{
|
|
3689
|
-
label: config$
|
|
3758
|
+
label: config$2.label,
|
|
3690
3759
|
icon: void 0,
|
|
3691
3760
|
path: "/affiliates/distribution-reports",
|
|
3692
3761
|
nested: void 0,
|
|
@@ -3694,7 +3763,7 @@ const menuItemModule = {
|
|
|
3694
3763
|
translationNs: void 0
|
|
3695
3764
|
},
|
|
3696
3765
|
{
|
|
3697
|
-
label: config$
|
|
3766
|
+
label: config$1.label,
|
|
3698
3767
|
icon: void 0,
|
|
3699
3768
|
path: "/affiliates/new",
|
|
3700
3769
|
nested: void 0,
|
|
@@ -3702,20 +3771,12 @@ const menuItemModule = {
|
|
|
3702
3771
|
translationNs: void 0
|
|
3703
3772
|
},
|
|
3704
3773
|
{
|
|
3705
|
-
label: config
|
|
3774
|
+
label: config.label,
|
|
3706
3775
|
icon: void 0,
|
|
3707
3776
|
path: "/affiliates/reports",
|
|
3708
3777
|
nested: void 0,
|
|
3709
3778
|
rank: 82,
|
|
3710
3779
|
translationNs: void 0
|
|
3711
|
-
},
|
|
3712
|
-
{
|
|
3713
|
-
label: config.label,
|
|
3714
|
-
icon: void 0,
|
|
3715
|
-
path: "/affiliates/settings",
|
|
3716
|
-
nested: void 0,
|
|
3717
|
-
rank: 84,
|
|
3718
|
-
translationNs: void 0
|
|
3719
3780
|
}
|
|
3720
3781
|
]
|
|
3721
3782
|
};
|