@hartl-services/medusa-free-shipping 0.1.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 -0
- package/.medusa/server/src/admin/index.mjs +290 -0
- package/.medusa/server/src/api/admin/free-shipping-settings/route.js +18 -0
- package/.medusa/server/src/api/middlewares.js +22 -0
- package/.medusa/server/src/api/wire-response.js +10 -0
- package/.medusa/server/src/jobs/free-shipping-promotion-sync.js +21 -0
- package/.medusa/server/src/modules/free-shipping/index.js +13 -0
- package/.medusa/server/src/modules/free-shipping/migrations/Migration20260911114515.js +16 -0
- package/.medusa/server/src/modules/free-shipping/models/free-shipping-settings.js +27 -0
- package/.medusa/server/src/modules/free-shipping/service.js +131 -0
- package/.medusa/server/src/utils/resolve-services.js +7 -0
- package/.medusa/server/src/workflows/free-shipping/sync-free-shipping-promotions.js +124 -0
- package/.medusa/server/src/workflows/free-shipping/update-free-shipping-settings.js +20 -0
- package/.medusa/server/src/workflows/hooks/set-free-shipping-promotion-context.js +71 -0
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/package.json +75 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const adminSdk = require("@medusajs/admin-sdk");
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const ui = require("@medusajs/ui");
|
|
5
|
+
const reactQuery = require("@tanstack/react-query");
|
|
6
|
+
const react = require("react");
|
|
7
|
+
const freeShipping = require("@hartl-services/medusa-food-supplements-contracts/paths/free-shipping");
|
|
8
|
+
const Medusa = require("@medusajs/js-sdk");
|
|
9
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
10
|
+
const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
|
|
11
|
+
const texts = {
|
|
12
|
+
menu: {
|
|
13
|
+
label: "Versandkosten"
|
|
14
|
+
},
|
|
15
|
+
page: {
|
|
16
|
+
title: "Versandkostenfreie Lieferung",
|
|
17
|
+
description: "Ab welchem Bestellwert die Lieferung kostenlos ist - mit einer eigenen, höheren Schwelle für Affiliate-Bestellungen."
|
|
18
|
+
},
|
|
19
|
+
fields: {
|
|
20
|
+
enabled: "Versandkostenfreie Lieferung aktiv",
|
|
21
|
+
defaultThreshold: "Kostenlose Lieferung ab (€)",
|
|
22
|
+
affiliateThreshold: "Für Affiliates ab (€)",
|
|
23
|
+
affiliateAppliesToAccountHolders: "Gilt, wenn der Kunde selbst Affiliate ist",
|
|
24
|
+
affiliateAppliesToReferredCustomers: "Gilt, wenn der Warenkorb über einen Affiliate-Link/-Code zugeordnet wurde"
|
|
25
|
+
},
|
|
26
|
+
actions: {
|
|
27
|
+
save: "Speichern",
|
|
28
|
+
saving: "Wird gespeichert…"
|
|
29
|
+
},
|
|
30
|
+
states: {
|
|
31
|
+
loading: "Einstellungen werden geladen…",
|
|
32
|
+
error: "Einstellungen konnten nicht geladen werden."
|
|
33
|
+
},
|
|
34
|
+
toasts: {
|
|
35
|
+
saved: "Einstellungen wurden gespeichert.",
|
|
36
|
+
saveFailed: "Einstellungen konnten nicht gespeichert werden."
|
|
37
|
+
},
|
|
38
|
+
errors: {
|
|
39
|
+
unknown: "Unbekannter Fehler"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const getErrorMessage = (error) => {
|
|
43
|
+
if (error instanceof Error && error.message) {
|
|
44
|
+
return error.message;
|
|
45
|
+
}
|
|
46
|
+
if (typeof error !== "object" || error === null) {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
const candidate = error;
|
|
50
|
+
for (const value of [
|
|
51
|
+
candidate.response?.data?.message,
|
|
52
|
+
candidate.response?.data?.error,
|
|
53
|
+
candidate.body?.message,
|
|
54
|
+
candidate.body?.error,
|
|
55
|
+
candidate.message,
|
|
56
|
+
candidate.statusText
|
|
57
|
+
]) {
|
|
58
|
+
if (typeof value === "string" && value.trim()) {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return void 0;
|
|
63
|
+
};
|
|
64
|
+
const sdk = new Medusa__default.default({
|
|
65
|
+
baseUrl: __BACKEND_URL__ ?? "/",
|
|
66
|
+
auth: {
|
|
67
|
+
type: __AUTH_TYPE__ ?? "session",
|
|
68
|
+
jwtTokenStorageKey: __JWT_TOKEN_STORAGE_KEY__ || void 0
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const getFreeShippingSettings = () => {
|
|
72
|
+
return sdk.client.fetch(
|
|
73
|
+
freeShipping.freeShippingAdminPaths.get.retrieveSettings
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
const updateFreeShippingSettings = (payload) => {
|
|
77
|
+
return sdk.client.fetch(
|
|
78
|
+
freeShipping.freeShippingAdminPaths.post.updateSettings,
|
|
79
|
+
{
|
|
80
|
+
method: "POST",
|
|
81
|
+
body: payload
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
const SETTINGS_QUERY_KEY = ["free-shipping", "settings"];
|
|
86
|
+
function useFreeShippingSettingsForm() {
|
|
87
|
+
const queryClient = reactQuery.useQueryClient();
|
|
88
|
+
const settingsQuery = reactQuery.useQuery({
|
|
89
|
+
queryKey: SETTINGS_QUERY_KEY,
|
|
90
|
+
queryFn: getFreeShippingSettings
|
|
91
|
+
});
|
|
92
|
+
const [enabled, setEnabled] = react.useState(true);
|
|
93
|
+
const [defaultThreshold, setDefaultThreshold] = react.useState(50);
|
|
94
|
+
const [affiliateThreshold, setAffiliateThreshold] = react.useState(100);
|
|
95
|
+
const [
|
|
96
|
+
affiliateAppliesToAccountHolders,
|
|
97
|
+
setAffiliateAppliesToAccountHolders
|
|
98
|
+
] = react.useState(true);
|
|
99
|
+
const [
|
|
100
|
+
affiliateAppliesToReferredCustomers,
|
|
101
|
+
setAffiliateAppliesToReferredCustomers
|
|
102
|
+
] = react.useState(false);
|
|
103
|
+
react.useEffect(() => {
|
|
104
|
+
const settings = settingsQuery.data?.settings;
|
|
105
|
+
if (!settings) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
setEnabled(settings.enabled);
|
|
109
|
+
setDefaultThreshold(settings.default_threshold);
|
|
110
|
+
setAffiliateThreshold(settings.affiliate_threshold);
|
|
111
|
+
setAffiliateAppliesToAccountHolders(
|
|
112
|
+
settings.affiliate_applies_to_account_holders
|
|
113
|
+
);
|
|
114
|
+
setAffiliateAppliesToReferredCustomers(
|
|
115
|
+
settings.affiliate_applies_to_referred_customers
|
|
116
|
+
);
|
|
117
|
+
}, [settingsQuery.data]);
|
|
118
|
+
const mutation = reactQuery.useMutation({
|
|
119
|
+
mutationFn: () => {
|
|
120
|
+
const current = settingsQuery.data?.settings;
|
|
121
|
+
if (!current) {
|
|
122
|
+
throw new Error(texts.states.error);
|
|
123
|
+
}
|
|
124
|
+
return updateFreeShippingSettings({
|
|
125
|
+
expected_version: current.policy_version,
|
|
126
|
+
enabled,
|
|
127
|
+
default_threshold: defaultThreshold,
|
|
128
|
+
affiliate_threshold: affiliateThreshold,
|
|
129
|
+
affiliate_applies_to_account_holders: affiliateAppliesToAccountHolders,
|
|
130
|
+
affiliate_applies_to_referred_customers: affiliateAppliesToReferredCustomers
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
onSuccess: (data) => {
|
|
134
|
+
queryClient.setQueryData(SETTINGS_QUERY_KEY, data);
|
|
135
|
+
ui.toast.success(texts.toasts.saved);
|
|
136
|
+
},
|
|
137
|
+
onError: async (error) => {
|
|
138
|
+
ui.toast.error(texts.toasts.saveFailed, {
|
|
139
|
+
description: getErrorMessage(error) ?? texts.errors.unknown
|
|
140
|
+
});
|
|
141
|
+
await queryClient.invalidateQueries({ queryKey: SETTINGS_QUERY_KEY });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
const submit = (event) => {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
mutation.mutate();
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
settingsQuery,
|
|
150
|
+
enabled,
|
|
151
|
+
setEnabled,
|
|
152
|
+
defaultThreshold,
|
|
153
|
+
setDefaultThreshold,
|
|
154
|
+
affiliateThreshold,
|
|
155
|
+
setAffiliateThreshold,
|
|
156
|
+
affiliateAppliesToAccountHolders,
|
|
157
|
+
setAffiliateAppliesToAccountHolders,
|
|
158
|
+
affiliateAppliesToReferredCustomers,
|
|
159
|
+
setAffiliateAppliesToReferredCustomers,
|
|
160
|
+
submit,
|
|
161
|
+
mutation
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const FreeShippingSettingsPage = () => {
|
|
165
|
+
const form = useFreeShippingSettingsForm();
|
|
166
|
+
if (form.settingsQuery.isPending) {
|
|
167
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.loading }) });
|
|
168
|
+
}
|
|
169
|
+
if (form.settingsQuery.isError || !form.settingsQuery.data) {
|
|
170
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: texts.states.error }) });
|
|
171
|
+
}
|
|
172
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
173
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
174
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.page.title }),
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.page.description })
|
|
176
|
+
] }),
|
|
177
|
+
/* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: form.submit, className: "grid gap-5 px-6 py-5", children: [
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
180
|
+
ui.Switch,
|
|
181
|
+
{
|
|
182
|
+
checked: form.enabled,
|
|
183
|
+
id: "free-shipping-enabled",
|
|
184
|
+
onCheckedChange: (checked) => form.setEnabled(checked === true)
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "free-shipping-enabled", children: texts.fields.enabled })
|
|
188
|
+
] }),
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
190
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
191
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "default-threshold", children: texts.fields.defaultThreshold }),
|
|
192
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
193
|
+
ui.Input,
|
|
194
|
+
{
|
|
195
|
+
id: "default-threshold",
|
|
196
|
+
min: 0,
|
|
197
|
+
required: true,
|
|
198
|
+
step: "0.01",
|
|
199
|
+
type: "number",
|
|
200
|
+
value: form.defaultThreshold,
|
|
201
|
+
onChange: (event) => form.setDefaultThreshold(Number(event.target.value))
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
] }),
|
|
205
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
|
|
206
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "affiliate-threshold", children: texts.fields.affiliateThreshold }),
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
208
|
+
ui.Input,
|
|
209
|
+
{
|
|
210
|
+
id: "affiliate-threshold",
|
|
211
|
+
min: 0,
|
|
212
|
+
required: true,
|
|
213
|
+
step: "0.01",
|
|
214
|
+
type: "number",
|
|
215
|
+
value: form.affiliateThreshold,
|
|
216
|
+
onChange: (event) => form.setAffiliateThreshold(Number(event.target.value))
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
] })
|
|
220
|
+
] }),
|
|
221
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3", children: [
|
|
222
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
223
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
224
|
+
ui.Checkbox,
|
|
225
|
+
{
|
|
226
|
+
checked: form.affiliateAppliesToAccountHolders,
|
|
227
|
+
id: "affiliate-applies-account-holders",
|
|
228
|
+
onCheckedChange: (checked) => form.setAffiliateAppliesToAccountHolders(checked === true)
|
|
229
|
+
}
|
|
230
|
+
),
|
|
231
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "affiliate-applies-account-holders", children: texts.fields.affiliateAppliesToAccountHolders })
|
|
232
|
+
] }),
|
|
233
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
235
|
+
ui.Checkbox,
|
|
236
|
+
{
|
|
237
|
+
checked: form.affiliateAppliesToReferredCustomers,
|
|
238
|
+
id: "affiliate-applies-referred-customers",
|
|
239
|
+
onCheckedChange: (checked) => form.setAffiliateAppliesToReferredCustomers(checked === true)
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "affiliate-applies-referred-customers", children: texts.fields.affiliateAppliesToReferredCustomers })
|
|
243
|
+
] })
|
|
244
|
+
] }),
|
|
245
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { disabled: form.mutation.isPending, type: "submit", children: form.mutation.isPending ? texts.actions.saving : texts.actions.save }) })
|
|
246
|
+
] })
|
|
247
|
+
] });
|
|
248
|
+
};
|
|
249
|
+
const config = adminSdk.defineRouteConfig({
|
|
250
|
+
label: texts.menu.label
|
|
251
|
+
});
|
|
252
|
+
const widgetModule = { widgets: [] };
|
|
253
|
+
const routeModule = {
|
|
254
|
+
routes: [
|
|
255
|
+
{
|
|
256
|
+
Component: FreeShippingSettingsPage,
|
|
257
|
+
path: "/settings/free-shipping",
|
|
258
|
+
handle: { label: config.label, translationNs: config.translationNs }
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
};
|
|
262
|
+
const menuItemModule = {
|
|
263
|
+
menuItems: [
|
|
264
|
+
{
|
|
265
|
+
label: config.label,
|
|
266
|
+
icon: void 0,
|
|
267
|
+
path: "/settings/free-shipping",
|
|
268
|
+
nested: void 0,
|
|
269
|
+
rank: void 0,
|
|
270
|
+
translationNs: void 0
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
};
|
|
274
|
+
const formModule = { customFields: {} };
|
|
275
|
+
const displayModule = {
|
|
276
|
+
displays: {}
|
|
277
|
+
};
|
|
278
|
+
const i18nModule = { resources: {} };
|
|
279
|
+
const cellRendererModule = {};
|
|
280
|
+
const layoutModule = { layouts: [] };
|
|
281
|
+
const plugin = {
|
|
282
|
+
widgetModule,
|
|
283
|
+
routeModule,
|
|
284
|
+
menuItemModule,
|
|
285
|
+
formModule,
|
|
286
|
+
displayModule,
|
|
287
|
+
i18nModule,
|
|
288
|
+
cellRendererModule,
|
|
289
|
+
layoutModule
|
|
290
|
+
};
|
|
291
|
+
module.exports = plugin;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { toast, Container, Text, Heading, Switch, Label, Input, Checkbox, Button } from "@medusajs/ui";
|
|
4
|
+
import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query";
|
|
5
|
+
import { useState, useEffect } from "react";
|
|
6
|
+
import { freeShippingAdminPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/free-shipping";
|
|
7
|
+
import Medusa from "@medusajs/js-sdk";
|
|
8
|
+
const texts = {
|
|
9
|
+
menu: {
|
|
10
|
+
label: "Versandkosten"
|
|
11
|
+
},
|
|
12
|
+
page: {
|
|
13
|
+
title: "Versandkostenfreie Lieferung",
|
|
14
|
+
description: "Ab welchem Bestellwert die Lieferung kostenlos ist - mit einer eigenen, höheren Schwelle für Affiliate-Bestellungen."
|
|
15
|
+
},
|
|
16
|
+
fields: {
|
|
17
|
+
enabled: "Versandkostenfreie Lieferung aktiv",
|
|
18
|
+
defaultThreshold: "Kostenlose Lieferung ab (€)",
|
|
19
|
+
affiliateThreshold: "Für Affiliates ab (€)",
|
|
20
|
+
affiliateAppliesToAccountHolders: "Gilt, wenn der Kunde selbst Affiliate ist",
|
|
21
|
+
affiliateAppliesToReferredCustomers: "Gilt, wenn der Warenkorb über einen Affiliate-Link/-Code zugeordnet wurde"
|
|
22
|
+
},
|
|
23
|
+
actions: {
|
|
24
|
+
save: "Speichern",
|
|
25
|
+
saving: "Wird gespeichert…"
|
|
26
|
+
},
|
|
27
|
+
states: {
|
|
28
|
+
loading: "Einstellungen werden geladen…",
|
|
29
|
+
error: "Einstellungen konnten nicht geladen werden."
|
|
30
|
+
},
|
|
31
|
+
toasts: {
|
|
32
|
+
saved: "Einstellungen wurden gespeichert.",
|
|
33
|
+
saveFailed: "Einstellungen konnten nicht gespeichert werden."
|
|
34
|
+
},
|
|
35
|
+
errors: {
|
|
36
|
+
unknown: "Unbekannter Fehler"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const getErrorMessage = (error) => {
|
|
40
|
+
if (error instanceof Error && error.message) {
|
|
41
|
+
return error.message;
|
|
42
|
+
}
|
|
43
|
+
if (typeof error !== "object" || error === null) {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
const candidate = error;
|
|
47
|
+
for (const value of [
|
|
48
|
+
candidate.response?.data?.message,
|
|
49
|
+
candidate.response?.data?.error,
|
|
50
|
+
candidate.body?.message,
|
|
51
|
+
candidate.body?.error,
|
|
52
|
+
candidate.message,
|
|
53
|
+
candidate.statusText
|
|
54
|
+
]) {
|
|
55
|
+
if (typeof value === "string" && value.trim()) {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
};
|
|
61
|
+
const sdk = new Medusa({
|
|
62
|
+
baseUrl: __BACKEND_URL__ ?? "/",
|
|
63
|
+
auth: {
|
|
64
|
+
type: __AUTH_TYPE__ ?? "session",
|
|
65
|
+
jwtTokenStorageKey: __JWT_TOKEN_STORAGE_KEY__ || void 0
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const getFreeShippingSettings = () => {
|
|
69
|
+
return sdk.client.fetch(
|
|
70
|
+
freeShippingAdminPaths.get.retrieveSettings
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
const updateFreeShippingSettings = (payload) => {
|
|
74
|
+
return sdk.client.fetch(
|
|
75
|
+
freeShippingAdminPaths.post.updateSettings,
|
|
76
|
+
{
|
|
77
|
+
method: "POST",
|
|
78
|
+
body: payload
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
const SETTINGS_QUERY_KEY = ["free-shipping", "settings"];
|
|
83
|
+
function useFreeShippingSettingsForm() {
|
|
84
|
+
const queryClient = useQueryClient();
|
|
85
|
+
const settingsQuery = useQuery({
|
|
86
|
+
queryKey: SETTINGS_QUERY_KEY,
|
|
87
|
+
queryFn: getFreeShippingSettings
|
|
88
|
+
});
|
|
89
|
+
const [enabled, setEnabled] = useState(true);
|
|
90
|
+
const [defaultThreshold, setDefaultThreshold] = useState(50);
|
|
91
|
+
const [affiliateThreshold, setAffiliateThreshold] = useState(100);
|
|
92
|
+
const [
|
|
93
|
+
affiliateAppliesToAccountHolders,
|
|
94
|
+
setAffiliateAppliesToAccountHolders
|
|
95
|
+
] = useState(true);
|
|
96
|
+
const [
|
|
97
|
+
affiliateAppliesToReferredCustomers,
|
|
98
|
+
setAffiliateAppliesToReferredCustomers
|
|
99
|
+
] = useState(false);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const settings = settingsQuery.data?.settings;
|
|
102
|
+
if (!settings) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
setEnabled(settings.enabled);
|
|
106
|
+
setDefaultThreshold(settings.default_threshold);
|
|
107
|
+
setAffiliateThreshold(settings.affiliate_threshold);
|
|
108
|
+
setAffiliateAppliesToAccountHolders(
|
|
109
|
+
settings.affiliate_applies_to_account_holders
|
|
110
|
+
);
|
|
111
|
+
setAffiliateAppliesToReferredCustomers(
|
|
112
|
+
settings.affiliate_applies_to_referred_customers
|
|
113
|
+
);
|
|
114
|
+
}, [settingsQuery.data]);
|
|
115
|
+
const mutation = useMutation({
|
|
116
|
+
mutationFn: () => {
|
|
117
|
+
const current = settingsQuery.data?.settings;
|
|
118
|
+
if (!current) {
|
|
119
|
+
throw new Error(texts.states.error);
|
|
120
|
+
}
|
|
121
|
+
return updateFreeShippingSettings({
|
|
122
|
+
expected_version: current.policy_version,
|
|
123
|
+
enabled,
|
|
124
|
+
default_threshold: defaultThreshold,
|
|
125
|
+
affiliate_threshold: affiliateThreshold,
|
|
126
|
+
affiliate_applies_to_account_holders: affiliateAppliesToAccountHolders,
|
|
127
|
+
affiliate_applies_to_referred_customers: affiliateAppliesToReferredCustomers
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
onSuccess: (data) => {
|
|
131
|
+
queryClient.setQueryData(SETTINGS_QUERY_KEY, data);
|
|
132
|
+
toast.success(texts.toasts.saved);
|
|
133
|
+
},
|
|
134
|
+
onError: async (error) => {
|
|
135
|
+
toast.error(texts.toasts.saveFailed, {
|
|
136
|
+
description: getErrorMessage(error) ?? texts.errors.unknown
|
|
137
|
+
});
|
|
138
|
+
await queryClient.invalidateQueries({ queryKey: SETTINGS_QUERY_KEY });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const submit = (event) => {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
mutation.mutate();
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
settingsQuery,
|
|
147
|
+
enabled,
|
|
148
|
+
setEnabled,
|
|
149
|
+
defaultThreshold,
|
|
150
|
+
setDefaultThreshold,
|
|
151
|
+
affiliateThreshold,
|
|
152
|
+
setAffiliateThreshold,
|
|
153
|
+
affiliateAppliesToAccountHolders,
|
|
154
|
+
setAffiliateAppliesToAccountHolders,
|
|
155
|
+
affiliateAppliesToReferredCustomers,
|
|
156
|
+
setAffiliateAppliesToReferredCustomers,
|
|
157
|
+
submit,
|
|
158
|
+
mutation
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const FreeShippingSettingsPage = () => {
|
|
162
|
+
const form = useFreeShippingSettingsForm();
|
|
163
|
+
if (form.settingsQuery.isPending) {
|
|
164
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: texts.states.loading }) });
|
|
165
|
+
}
|
|
166
|
+
if (form.settingsQuery.isError || !form.settingsQuery.data) {
|
|
167
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", children: texts.states.error }) });
|
|
168
|
+
}
|
|
169
|
+
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
171
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: texts.page.title }),
|
|
172
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: texts.page.description })
|
|
173
|
+
] }),
|
|
174
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: form.submit, className: "grid gap-5 px-6 py-5", children: [
|
|
175
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
176
|
+
/* @__PURE__ */ jsx(
|
|
177
|
+
Switch,
|
|
178
|
+
{
|
|
179
|
+
checked: form.enabled,
|
|
180
|
+
id: "free-shipping-enabled",
|
|
181
|
+
onCheckedChange: (checked) => form.setEnabled(checked === true)
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "free-shipping-enabled", children: texts.fields.enabled })
|
|
185
|
+
] }),
|
|
186
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
187
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
188
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "default-threshold", children: texts.fields.defaultThreshold }),
|
|
189
|
+
/* @__PURE__ */ jsx(
|
|
190
|
+
Input,
|
|
191
|
+
{
|
|
192
|
+
id: "default-threshold",
|
|
193
|
+
min: 0,
|
|
194
|
+
required: true,
|
|
195
|
+
step: "0.01",
|
|
196
|
+
type: "number",
|
|
197
|
+
value: form.defaultThreshold,
|
|
198
|
+
onChange: (event) => form.setDefaultThreshold(Number(event.target.value))
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
203
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "affiliate-threshold", children: texts.fields.affiliateThreshold }),
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
Input,
|
|
206
|
+
{
|
|
207
|
+
id: "affiliate-threshold",
|
|
208
|
+
min: 0,
|
|
209
|
+
required: true,
|
|
210
|
+
step: "0.01",
|
|
211
|
+
type: "number",
|
|
212
|
+
value: form.affiliateThreshold,
|
|
213
|
+
onChange: (event) => form.setAffiliateThreshold(Number(event.target.value))
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
] })
|
|
217
|
+
] }),
|
|
218
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
219
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
220
|
+
/* @__PURE__ */ jsx(
|
|
221
|
+
Checkbox,
|
|
222
|
+
{
|
|
223
|
+
checked: form.affiliateAppliesToAccountHolders,
|
|
224
|
+
id: "affiliate-applies-account-holders",
|
|
225
|
+
onCheckedChange: (checked) => form.setAffiliateAppliesToAccountHolders(checked === true)
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "affiliate-applies-account-holders", children: texts.fields.affiliateAppliesToAccountHolders })
|
|
229
|
+
] }),
|
|
230
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
231
|
+
/* @__PURE__ */ jsx(
|
|
232
|
+
Checkbox,
|
|
233
|
+
{
|
|
234
|
+
checked: form.affiliateAppliesToReferredCustomers,
|
|
235
|
+
id: "affiliate-applies-referred-customers",
|
|
236
|
+
onCheckedChange: (checked) => form.setAffiliateAppliesToReferredCustomers(checked === true)
|
|
237
|
+
}
|
|
238
|
+
),
|
|
239
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "affiliate-applies-referred-customers", children: texts.fields.affiliateAppliesToReferredCustomers })
|
|
240
|
+
] })
|
|
241
|
+
] }),
|
|
242
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(Button, { disabled: form.mutation.isPending, type: "submit", children: form.mutation.isPending ? texts.actions.saving : texts.actions.save }) })
|
|
243
|
+
] })
|
|
244
|
+
] });
|
|
245
|
+
};
|
|
246
|
+
const config = defineRouteConfig({
|
|
247
|
+
label: texts.menu.label
|
|
248
|
+
});
|
|
249
|
+
const widgetModule = { widgets: [] };
|
|
250
|
+
const routeModule = {
|
|
251
|
+
routes: [
|
|
252
|
+
{
|
|
253
|
+
Component: FreeShippingSettingsPage,
|
|
254
|
+
path: "/settings/free-shipping",
|
|
255
|
+
handle: { label: config.label, translationNs: config.translationNs }
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
};
|
|
259
|
+
const menuItemModule = {
|
|
260
|
+
menuItems: [
|
|
261
|
+
{
|
|
262
|
+
label: config.label,
|
|
263
|
+
icon: void 0,
|
|
264
|
+
path: "/settings/free-shipping",
|
|
265
|
+
nested: void 0,
|
|
266
|
+
rank: void 0,
|
|
267
|
+
translationNs: void 0
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
const formModule = { customFields: {} };
|
|
272
|
+
const displayModule = {
|
|
273
|
+
displays: {}
|
|
274
|
+
};
|
|
275
|
+
const i18nModule = { resources: {} };
|
|
276
|
+
const cellRendererModule = {};
|
|
277
|
+
const layoutModule = { layouts: [] };
|
|
278
|
+
const plugin = {
|
|
279
|
+
widgetModule,
|
|
280
|
+
routeModule,
|
|
281
|
+
menuItemModule,
|
|
282
|
+
formModule,
|
|
283
|
+
displayModule,
|
|
284
|
+
i18nModule,
|
|
285
|
+
cellRendererModule,
|
|
286
|
+
layoutModule
|
|
287
|
+
};
|
|
288
|
+
export {
|
|
289
|
+
plugin as default
|
|
290
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POST = exports.GET = void 0;
|
|
4
|
+
const free_shipping_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/free-shipping");
|
|
5
|
+
const resolve_services_1 = require("../../../utils/resolve-services");
|
|
6
|
+
const update_free_shipping_settings_1 = require("../../../workflows/free-shipping/update-free-shipping-settings");
|
|
7
|
+
const wire_response_1 = require("../../wire-response");
|
|
8
|
+
const GET = async (req, res) => {
|
|
9
|
+
const settings = await (0, resolve_services_1.resolveFreeShippingService)(req.scope).retrieveSettings();
|
|
10
|
+
res.json((0, wire_response_1.toWireResponse)(free_shipping_1.FreeShippingSettingsResponseSchema, { settings }));
|
|
11
|
+
};
|
|
12
|
+
exports.GET = GET;
|
|
13
|
+
const POST = async (req, res) => {
|
|
14
|
+
const settings = await (0, update_free_shipping_settings_1.updateFreeShippingSettingsWithPromotionSync)(req.scope, req.validatedBody);
|
|
15
|
+
res.json((0, wire_response_1.toWireResponse)(free_shipping_1.FreeShippingSettingsResponseSchema, { settings }));
|
|
16
|
+
};
|
|
17
|
+
exports.POST = POST;
|
|
18
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL2ZyZWUtc2hpcHBpbmctc2V0dGluZ3Mvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBS0EsMkdBR2dGO0FBR2hGLHNFQUE0RTtBQUM1RSxrSEFBNEg7QUFDNUgsdURBQW9EO0FBSTdDLE1BQU0sR0FBRyxHQUFHLEtBQUssRUFDdEIsR0FBK0IsRUFDL0IsR0FBaUQsRUFDakQsRUFBRTtJQUNGLE1BQU0sUUFBUSxHQUNaLE1BQU0sSUFBQSw2Q0FBMEIsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQTtJQUNoRSxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUEsOEJBQWMsRUFBQyxrREFBa0MsRUFBRSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQTtBQUM1RSxDQUFDLENBQUE7QUFQWSxRQUFBLEdBQUcsT0FPZjtBQUVNLE1BQU0sSUFBSSxHQUFHLEtBQUssRUFDdkIsR0FBMkMsRUFDM0MsR0FBaUQsRUFDakQsRUFBRTtJQUNGLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBQSwyRUFBMkMsRUFDaEUsR0FBRyxDQUFDLEtBQUssRUFDVCxHQUFHLENBQUMsYUFBYSxDQUNsQixDQUFBO0lBQ0QsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFBLDhCQUFjLEVBQUMsa0RBQWtDLEVBQUUsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUE7QUFDNUUsQ0FBQyxDQUFBO0FBVFksUUFBQSxJQUFJLFFBU2hCIn0=
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const http_1 = require("@medusajs/framework/http");
|
|
4
|
+
const free_shipping_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/free-shipping");
|
|
5
|
+
exports.default = (0, http_1.defineMiddlewares)({
|
|
6
|
+
routes: [
|
|
7
|
+
{
|
|
8
|
+
matcher: "/admin/free-shipping-settings",
|
|
9
|
+
methods: ["GET"],
|
|
10
|
+
middlewares: [(0, http_1.authenticate)("user", ["session", "bearer"])],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
matcher: "/admin/free-shipping-settings",
|
|
14
|
+
methods: ["POST"],
|
|
15
|
+
middlewares: [
|
|
16
|
+
(0, http_1.authenticate)("user", ["session", "bearer"]),
|
|
17
|
+
(0, http_1.validateAndTransformBody)(free_shipping_1.UpdateFreeShippingSettingsSchema),
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWlkZGxld2FyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBpL21pZGRsZXdhcmVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsbURBSWlDO0FBQ2pDLDJHQUEwSDtBQUUxSCxrQkFBZSxJQUFBLHdCQUFpQixFQUFDO0lBQy9CLE1BQU0sRUFBRTtRQUNOO1lBQ0UsT0FBTyxFQUFFLCtCQUErQjtZQUN4QyxPQUFPLEVBQUUsQ0FBQyxLQUFLLENBQUM7WUFDaEIsV0FBVyxFQUFFLENBQUMsSUFBQSxtQkFBWSxFQUFDLE1BQU0sRUFBRSxDQUFDLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO1NBQzNEO1FBQ0Q7WUFDRSxPQUFPLEVBQUUsK0JBQStCO1lBQ3hDLE9BQU8sRUFBRSxDQUFDLE1BQU0sQ0FBQztZQUNqQixXQUFXLEVBQUU7Z0JBQ1gsSUFBQSxtQkFBWSxFQUFDLE1BQU0sRUFBRSxDQUFDLFNBQVMsRUFBRSxRQUFRLENBQUMsQ0FBQztnQkFDM0MsSUFBQSwrQkFBd0IsRUFBQyxnREFBZ0MsQ0FBQzthQUMzRDtTQUNGO0tBQ0Y7Q0FDRixDQUFDLENBQUEifQ==
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toWireResponse = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Serializes module values exactly as HTTP JSON does, then validates and strips
|
|
6
|
+
* fields that aren't part of the deliberate public response contract.
|
|
7
|
+
*/
|
|
8
|
+
const toWireResponse = (schema, value) => schema.parse(JSON.parse(JSON.stringify(value)));
|
|
9
|
+
exports.toWireResponse = toWireResponse;
|
|
10
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2lyZS1yZXNwb25zZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hcGkvd2lyZS1yZXNwb25zZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFFQTs7O0dBR0c7QUFDSSxNQUFNLGNBQWMsR0FBRyxDQUM1QixNQUFlLEVBQ2YsS0FBYyxFQUNJLEVBQUUsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFIekQsUUFBQSxjQUFjLGtCQUcyQyJ9
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
exports.default = freeShippingPromotionSyncJob;
|
|
5
|
+
const resolve_services_1 = require("../utils/resolve-services");
|
|
6
|
+
const sync_free_shipping_promotions_1 = require("../workflows/free-shipping/sync-free-shipping-promotions");
|
|
7
|
+
/**
|
|
8
|
+
* Keeps the two automatic promotions in line with the saved settings, also
|
|
9
|
+
* after a deploy/migration where nobody has saved the settings page. The sync
|
|
10
|
+
* writes only differences, so a run against in-sync promotions is read-only.
|
|
11
|
+
* A failure is logged by Medusa's job runner and retried on the next run.
|
|
12
|
+
*/
|
|
13
|
+
async function freeShippingPromotionSyncJob(container) {
|
|
14
|
+
const settings = await (0, resolve_services_1.resolveFreeShippingService)(container).retrieveSettings();
|
|
15
|
+
await (0, sync_free_shipping_promotions_1.syncFreeShippingPromotions)(container, settings);
|
|
16
|
+
}
|
|
17
|
+
exports.config = {
|
|
18
|
+
name: "free-shipping-promotion-sync",
|
|
19
|
+
schedule: "*/15 * * * *",
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnJlZS1zaGlwcGluZy1wcm9tb3Rpb24tc3luYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9qb2JzL2ZyZWUtc2hpcHBpbmctcHJvbW90aW9uLXN5bmMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBV0EsK0NBS0M7QUFkRCxnRUFBc0U7QUFDdEUsNEdBQXFHO0FBRXJHOzs7OztHQUtHO0FBQ1ksS0FBSyxVQUFVLDRCQUE0QixDQUN4RCxTQUEwQjtJQUUxQixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUEsNkNBQTBCLEVBQUMsU0FBUyxDQUFDLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQTtJQUMvRSxNQUFNLElBQUEsMERBQTBCLEVBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFBO0FBQ3ZELENBQUM7QUFFWSxRQUFBLE1BQU0sR0FBRztJQUNwQixJQUFJLEVBQUUsOEJBQThCO0lBQ3BDLFFBQVEsRUFBRSxjQUFjO0NBQ3pCLENBQUEifQ==
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FREE_SHIPPING_MODULE = void 0;
|
|
7
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
8
|
+
const service_1 = __importDefault(require("./service"));
|
|
9
|
+
exports.FREE_SHIPPING_MODULE = "free_shipping";
|
|
10
|
+
exports.default = (0, utils_1.Module)(exports.FREE_SHIPPING_MODULE, {
|
|
11
|
+
service: service_1.default,
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9mcmVlLXNoaXBwaW5nL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLHFEQUFrRDtBQUVsRCx3REFBaUQ7QUFFcEMsUUFBQSxvQkFBb0IsR0FBRyxlQUFlLENBQUE7QUFFbkQsa0JBQWUsSUFBQSxjQUFNLEVBQUMsNEJBQW9CLEVBQUU7SUFDMUMsT0FBTyxFQUFFLGlCQUF5QjtDQUNuQyxDQUFDLENBQUEifQ==
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20260911114515 = void 0;
|
|
4
|
+
const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
|
|
5
|
+
class Migration20260911114515 extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`create table if not exists "free_shipping_settings" ("id" text not null, "scope" text not null default 'global', "enabled" boolean not null default true, "default_threshold" real not null default 50, "affiliate_threshold" real not null default 100, "affiliate_applies_to_account_holders" boolean not null default true, "affiliate_applies_to_referred_customers" boolean not null default false, "policy_version" integer not null default 1, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "free_shipping_settings_pkey" primary key ("id"));`);
|
|
8
|
+
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_free_shipping_settings_deleted_at" ON "free_shipping_settings" ("deleted_at") WHERE deleted_at IS NULL;`);
|
|
9
|
+
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_free_shipping_settings_global" ON "free_shipping_settings" ("scope") WHERE deleted_at IS NULL;`);
|
|
10
|
+
}
|
|
11
|
+
async down() {
|
|
12
|
+
this.addSql(`drop table if exists "free_shipping_settings" cascade;`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.Migration20260911114515 = Migration20260911114515;
|
|
16
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNjA5MTExMTQ1MTUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9mcmVlLXNoaXBwaW5nL21pZ3JhdGlvbnMvTWlncmF0aW9uMjAyNjA5MTExMTQ1MTUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEseUVBQXFFO0FBRXJFLE1BQWEsdUJBQXdCLFNBQVEsc0JBQVM7SUFFM0MsS0FBSyxDQUFDLEVBQUU7UUFDZixJQUFJLENBQUMsTUFBTSxDQUFDLHNuQkFBc25CLENBQUMsQ0FBQztRQUNwb0IsSUFBSSxDQUFDLE1BQU0sQ0FBQyx5SUFBeUksQ0FBQyxDQUFDO1FBQ3ZKLElBQUksQ0FBQyxNQUFNLENBQUMsdUlBQXVJLENBQUMsQ0FBQztJQUN2SixDQUFDO0lBRVEsS0FBSyxDQUFDLElBQUk7UUFDakIsSUFBSSxDQUFDLE1BQU0sQ0FBQyx3REFBd0QsQ0FBQyxDQUFDO0lBQ3hFLENBQUM7Q0FFRjtBQVpELDBEQVlDIn0=
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
4
|
+
const FreeShippingSettings = utils_1.model
|
|
5
|
+
.define({
|
|
6
|
+
name: "free_shipping_settings",
|
|
7
|
+
tableName: "free_shipping_settings",
|
|
8
|
+
}, {
|
|
9
|
+
id: utils_1.model.id({ prefix: "freeship" }).primaryKey(),
|
|
10
|
+
scope: utils_1.model.text().default("global"),
|
|
11
|
+
enabled: utils_1.model.boolean().default(true),
|
|
12
|
+
default_threshold: utils_1.model.float().default(50),
|
|
13
|
+
affiliate_threshold: utils_1.model.float().default(100),
|
|
14
|
+
affiliate_applies_to_account_holders: utils_1.model.boolean().default(true),
|
|
15
|
+
affiliate_applies_to_referred_customers: utils_1.model.boolean().default(false),
|
|
16
|
+
policy_version: utils_1.model.number().default(1),
|
|
17
|
+
})
|
|
18
|
+
.indexes([
|
|
19
|
+
{
|
|
20
|
+
name: "IDX_free_shipping_settings_global",
|
|
21
|
+
on: ["scope"],
|
|
22
|
+
unique: true,
|
|
23
|
+
where: "deleted_at IS NULL",
|
|
24
|
+
},
|
|
25
|
+
]);
|
|
26
|
+
exports.default = FreeShippingSettings;
|
|
27
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnJlZS1zaGlwcGluZy1zZXR0aW5ncy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL2ZyZWUtc2hpcHBpbmcvbW9kZWxzL2ZyZWUtc2hpcHBpbmctc2V0dGluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxREFBaUQ7QUFFakQsTUFBTSxvQkFBb0IsR0FBRyxhQUFLO0tBQy9CLE1BQU0sQ0FDTDtJQUNFLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsU0FBUyxFQUFFLHdCQUF3QjtDQUNwQyxFQUNEO0lBQ0UsRUFBRSxFQUFFLGFBQUssQ0FBQyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQyxVQUFVLEVBQUU7SUFDakQsS0FBSyxFQUFFLGFBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDO0lBQ3JDLE9BQU8sRUFBRSxhQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztJQUN0QyxpQkFBaUIsRUFBRSxhQUFLLENBQUMsS0FBSyxFQUFFLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztJQUM1QyxtQkFBbUIsRUFBRSxhQUFLLENBQUMsS0FBSyxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQztJQUMvQyxvQ0FBb0MsRUFBRSxhQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQztJQUNuRSx1Q0FBdUMsRUFBRSxhQUFLLENBQUMsT0FBTyxFQUFFLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztJQUN2RSxjQUFjLEVBQUUsYUFBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7Q0FDMUMsQ0FDRjtLQUNBLE9BQU8sQ0FBQztJQUNQO1FBQ0UsSUFBSSxFQUFFLG1DQUFtQztRQUN6QyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUM7UUFDYixNQUFNLEVBQUUsSUFBSTtRQUNaLEtBQUssRUFBRSxvQkFBb0I7S0FDNUI7Q0FDRixDQUFDLENBQUE7QUFFSixrQkFBZSxvQkFBb0IsQ0FBQSJ9
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
19
|
+
const free_shipping_settings_1 = __importDefault(require("./models/free-shipping-settings"));
|
|
20
|
+
/**
|
|
21
|
+
* Singleton shop-wide settings, modeled directly on
|
|
22
|
+
* `plugins/affiliate/src/modules/affiliate/models/affiliate-program-settings.ts`
|
|
23
|
+
* and its service methods: a lazily self-healing `scope = "global"` row plus
|
|
24
|
+
* optimistic-concurrency updates keyed on `policy_version`.
|
|
25
|
+
*/
|
|
26
|
+
class FreeShippingModuleService extends (0, utils_1.MedusaService)({
|
|
27
|
+
FreeShippingSettings: free_shipping_settings_1.default,
|
|
28
|
+
}) {
|
|
29
|
+
async retrieveSettings(sharedContext = {}) {
|
|
30
|
+
const settings = await this.listFreeShippingSettings({ scope: "global" }, { take: 1 }, sharedContext);
|
|
31
|
+
const current = settings[0];
|
|
32
|
+
if (current) {
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
35
|
+
const manager = sharedContext.transactionManager;
|
|
36
|
+
if (!manager) {
|
|
37
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, "Für die Initialisierung der Versandkosten-Einstellungen fehlt die Transaktion.");
|
|
38
|
+
}
|
|
39
|
+
// The migration seeds this singleton; module-test cleanup, an
|
|
40
|
+
// interrupted seed, or an accidental soft-delete can remove it. The
|
|
41
|
+
// partial unique scope index is authoritative: concurrent creators race
|
|
42
|
+
// on the INSERT, one wins and every loser re-reads the committed row.
|
|
43
|
+
await manager.execute(`insert into "free_shipping_settings"
|
|
44
|
+
("id", "scope", "enabled", "default_threshold", "affiliate_threshold",
|
|
45
|
+
"affiliate_applies_to_account_holders",
|
|
46
|
+
"affiliate_applies_to_referred_customers",
|
|
47
|
+
"policy_version", "created_at", "updated_at")
|
|
48
|
+
values (
|
|
49
|
+
'freeship_' || substr(md5(random()::text || clock_timestamp()::text), 1, 26),
|
|
50
|
+
'global', true, 50, 100, true, false, 1, now(), now()
|
|
51
|
+
)
|
|
52
|
+
on conflict do nothing`, [], "run");
|
|
53
|
+
const restored = await this.listFreeShippingSettings({ scope: "global" }, { take: 1 }, sharedContext);
|
|
54
|
+
if (!restored[0]) {
|
|
55
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, "Die globalen Versandkosten-Einstellungen konnten nicht initialisiert werden.");
|
|
56
|
+
}
|
|
57
|
+
return restored[0];
|
|
58
|
+
}
|
|
59
|
+
async updateSettings(input, sharedContext = {}) {
|
|
60
|
+
this.validateSettingsInput(input);
|
|
61
|
+
const manager = sharedContext.transactionManager;
|
|
62
|
+
if (!manager) {
|
|
63
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, "Für die Aktualisierung der Versandkosten-Einstellungen fehlt die Transaktion.");
|
|
64
|
+
}
|
|
65
|
+
const rows = await manager.execute(`update "free_shipping_settings"
|
|
66
|
+
set "enabled" = ?,
|
|
67
|
+
"default_threshold" = ?,
|
|
68
|
+
"affiliate_threshold" = ?,
|
|
69
|
+
"affiliate_applies_to_account_holders" = ?,
|
|
70
|
+
"affiliate_applies_to_referred_customers" = ?,
|
|
71
|
+
"policy_version" = ?,
|
|
72
|
+
"updated_at" = now()
|
|
73
|
+
where "scope" = 'global'
|
|
74
|
+
and "policy_version" = ?
|
|
75
|
+
and "deleted_at" is null
|
|
76
|
+
returning "id", "enabled", "default_threshold", "affiliate_threshold",
|
|
77
|
+
"affiliate_applies_to_account_holders",
|
|
78
|
+
"affiliate_applies_to_referred_customers",
|
|
79
|
+
"policy_version", "created_at", "updated_at"`, [
|
|
80
|
+
input.enabled,
|
|
81
|
+
input.default_threshold,
|
|
82
|
+
input.affiliate_threshold,
|
|
83
|
+
input.affiliate_applies_to_account_holders,
|
|
84
|
+
input.affiliate_applies_to_referred_customers,
|
|
85
|
+
input.expected_version + 1,
|
|
86
|
+
input.expected_version,
|
|
87
|
+
], "all");
|
|
88
|
+
const updated = rows[0];
|
|
89
|
+
if (!updated) {
|
|
90
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.CONFLICT, "Die Versandkosten-Einstellungen wurden zwischenzeitlich geändert.");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
id: updated.id,
|
|
94
|
+
scope: "global",
|
|
95
|
+
enabled: updated.enabled,
|
|
96
|
+
default_threshold: Number(updated.default_threshold),
|
|
97
|
+
affiliate_threshold: Number(updated.affiliate_threshold),
|
|
98
|
+
affiliate_applies_to_account_holders: updated.affiliate_applies_to_account_holders,
|
|
99
|
+
affiliate_applies_to_referred_customers: updated.affiliate_applies_to_referred_customers,
|
|
100
|
+
policy_version: Number(updated.policy_version),
|
|
101
|
+
created_at: updated.created_at,
|
|
102
|
+
updated_at: updated.updated_at,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
validateSettingsInput(input) {
|
|
106
|
+
if (!Number.isFinite(input.default_threshold) ||
|
|
107
|
+
input.default_threshold < 0) {
|
|
108
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Der Standard-Schwellenwert muss eine nicht-negative Zahl sein.");
|
|
109
|
+
}
|
|
110
|
+
if (!Number.isFinite(input.affiliate_threshold) ||
|
|
111
|
+
input.affiliate_threshold < 0) {
|
|
112
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Der Affiliate-Schwellenwert muss eine nicht-negative Zahl sein.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
__decorate([
|
|
117
|
+
(0, utils_1.InjectTransactionManager)(),
|
|
118
|
+
__param(0, (0, utils_1.MedusaContext)()),
|
|
119
|
+
__metadata("design:type", Function),
|
|
120
|
+
__metadata("design:paramtypes", [Object]),
|
|
121
|
+
__metadata("design:returntype", Promise)
|
|
122
|
+
], FreeShippingModuleService.prototype, "retrieveSettings", null);
|
|
123
|
+
__decorate([
|
|
124
|
+
(0, utils_1.InjectTransactionManager)(),
|
|
125
|
+
__param(1, (0, utils_1.MedusaContext)()),
|
|
126
|
+
__metadata("design:type", Function),
|
|
127
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
128
|
+
__metadata("design:returntype", Promise)
|
|
129
|
+
], FreeShippingModuleService.prototype, "updateSettings", null);
|
|
130
|
+
exports.default = FreeShippingModuleService;
|
|
131
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL2ZyZWUtc2hpcHBpbmcvc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHFEQUtrQztBQUdsQyw2RkFBa0U7QUE0Q2xFOzs7OztHQUtHO0FBQ0gsTUFBTSx5QkFBMEIsU0FBUSxJQUFBLHFCQUFhLEVBQUM7SUFDcEQsb0JBQW9CLEVBQXBCLGdDQUFvQjtDQUNyQixDQUFDO0lBRU0sQUFBTixLQUFLLENBQUMsZ0JBQWdCLENBRXBCLGdCQUF5RCxFQUFFO1FBRTNELE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUNsRCxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsRUFDbkIsRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLEVBQ1gsYUFBYSxDQUNkLENBQUE7UUFDRCxNQUFNLE9BQU8sR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFFM0IsSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUNaLE9BQU8sT0FBNkMsQ0FBQTtRQUN0RCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQUcsYUFBYSxDQUFDLGtCQUFrQixDQUFBO1FBQ2hELElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNiLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFDbEMsZ0ZBQWdGLENBQ2pGLENBQUE7UUFDSCxDQUFDO1FBRUQsOERBQThEO1FBQzlELG9FQUFvRTtRQUNwRSx3RUFBd0U7UUFDeEUsc0VBQXNFO1FBQ3RFLE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FDbkI7Ozs7Ozs7Ozs4QkFTd0IsRUFDeEIsRUFBRSxFQUNGLEtBQUssQ0FDTixDQUFBO1FBRUQsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQ2xELEVBQUUsS0FBSyxFQUFFLFFB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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveFreeShippingService = void 0;
|
|
4
|
+
const free_shipping_1 = require("../modules/free-shipping");
|
|
5
|
+
const resolveFreeShippingService = (container) => container.resolve(free_shipping_1.FREE_SHIPPING_MODULE);
|
|
6
|
+
exports.resolveFreeShippingService = resolveFreeShippingService;
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1zZXJ2aWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy91dGlscy9yZXNvbHZlLXNlcnZpY2VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBLDREQUErRDtBQUd4RCxNQUFNLDBCQUEwQixHQUFHLENBQ3hDLFNBQTBCLEVBQ0MsRUFBRSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsb0NBQW9CLENBQUMsQ0FBQTtBQUYxRCxRQUFBLDBCQUEwQiw4QkFFZ0MifQ==
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.syncFreeShippingPromotions = exports.AFFILIATE_FREE_SHIPPING_PROMOTION_CODE = exports.STANDARD_FREE_SHIPPING_PROMOTION_CODE = exports.AFFILIATE_QUALIFICATION_ATTRIBUTE = void 0;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const core_flows_1 = require("@medusajs/medusa/core-flows");
|
|
6
|
+
/**
|
|
7
|
+
* Context attribute the `set-free-shipping-promotion-context` hook injects
|
|
8
|
+
* into every cart-promotion computation. Both promotions below key off it so
|
|
9
|
+
* a cart between the two thresholds can't get free shipping through the
|
|
10
|
+
* standard promotion just because it also satisfies `item_total >= X`.
|
|
11
|
+
*/
|
|
12
|
+
exports.AFFILIATE_QUALIFICATION_ATTRIBUTE = "qualifies_for_affiliate_shipping_threshold";
|
|
13
|
+
exports.STANDARD_FREE_SHIPPING_PROMOTION_CODE = "AUTO-FREE-SHIPPING-STANDARD";
|
|
14
|
+
exports.AFFILIATE_FREE_SHIPPING_PROMOTION_CODE = "AUTO-FREE-SHIPPING-AFFILIATE";
|
|
15
|
+
const buildRules = (threshold, qualifiesValue) => [
|
|
16
|
+
{ attribute: "item_total", operator: "gte", values: [String(threshold)] },
|
|
17
|
+
{
|
|
18
|
+
attribute: exports.AFFILIATE_QUALIFICATION_ATTRIBUTE,
|
|
19
|
+
operator: "eq",
|
|
20
|
+
values: [qualifiesValue],
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const ruleKey = (rule) => JSON.stringify([rule.attribute, rule.operator, [...rule.values].sort()]);
|
|
24
|
+
const ensurePromotion = async (container, promotionService, code, status, threshold, qualifiesValue) => {
|
|
25
|
+
const rules = buildRules(threshold, qualifiesValue);
|
|
26
|
+
const existing = await promotionService.listPromotions({ code: [code] }, { relations: ["rules"] });
|
|
27
|
+
if (!existing[0]) {
|
|
28
|
+
try {
|
|
29
|
+
await (0, core_flows_1.createPromotionsWorkflow)(container).run({
|
|
30
|
+
input: {
|
|
31
|
+
promotionsData: [
|
|
32
|
+
{
|
|
33
|
+
code,
|
|
34
|
+
type: "standard",
|
|
35
|
+
status,
|
|
36
|
+
is_automatic: true,
|
|
37
|
+
application_method: {
|
|
38
|
+
type: "percentage",
|
|
39
|
+
value: 100,
|
|
40
|
+
target_type: "shipping_methods",
|
|
41
|
+
allocation: "across",
|
|
42
|
+
},
|
|
43
|
+
rules,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Concurrent boot across server/worker processes can race on the
|
|
52
|
+
// unique promotion code. Whoever loses the race falls through to the
|
|
53
|
+
// update path below, which is idempotent either way.
|
|
54
|
+
const createdByAnotherProcess = await promotionService.listPromotions({ code: [code] }, { relations: ["rules"] });
|
|
55
|
+
if (!createdByAnotherProcess[0]) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const current = (await promotionService.listPromotions({ code: [code] }, { relations: ["application_method", "rules", "rules.values"] }))[0];
|
|
61
|
+
if (!current) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const desiredKeys = new Set(rules.map(ruleKey));
|
|
65
|
+
const currentRules = (current.rules ?? []).map((rule) => ({
|
|
66
|
+
id: rule.id,
|
|
67
|
+
key: ruleKey({
|
|
68
|
+
attribute: rule.attribute,
|
|
69
|
+
operator: rule.operator,
|
|
70
|
+
values: (rule.values ?? []).map(({ value }) => String(value)),
|
|
71
|
+
}),
|
|
72
|
+
}));
|
|
73
|
+
const currentKeys = new Set(currentRules.map(({ key }) => key));
|
|
74
|
+
const rulesToCreate = rules.filter((rule) => !currentKeys.has(ruleKey(rule)));
|
|
75
|
+
const ruleIdsToDelete = currentRules
|
|
76
|
+
.filter(({ key }) => !desiredKeys.has(key))
|
|
77
|
+
.map(({ id }) => id);
|
|
78
|
+
// Create before delete: meanwhile the old and new rules apply together,
|
|
79
|
+
// which can only narrow which carts qualify. Deleting first would briefly
|
|
80
|
+
// leave an automatic promotion without rules - free shipping for every cart.
|
|
81
|
+
if (rulesToCreate.length) {
|
|
82
|
+
await (0, core_flows_1.batchPromotionRulesWorkflow)(container).run({
|
|
83
|
+
input: { id: current.id, rule_type: utils_1.RuleType.RULES, create: rulesToCreate },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (ruleIdsToDelete.length) {
|
|
87
|
+
await (0, core_flows_1.batchPromotionRulesWorkflow)(container).run({
|
|
88
|
+
input: {
|
|
89
|
+
id: current.id,
|
|
90
|
+
rule_type: utils_1.RuleType.RULES,
|
|
91
|
+
delete: ruleIdsToDelete,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (current.status !== status ||
|
|
96
|
+
Number(current.application_method?.value) !== 100) {
|
|
97
|
+
await (0, core_flows_1.updatePromotionsWorkflow)(container).run({
|
|
98
|
+
input: {
|
|
99
|
+
promotionsData: [
|
|
100
|
+
{
|
|
101
|
+
id: current.id,
|
|
102
|
+
status,
|
|
103
|
+
application_method: { value: 100 },
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Keeps the two automatic free-shipping promotions in sync with the current
|
|
112
|
+
* settings row. Called after every settings update and once at backend boot
|
|
113
|
+
* (see `../../loaders/sync-free-shipping-promotions.ts`) so the promotions
|
|
114
|
+
* reflect the saved thresholds even without an admin visiting the settings
|
|
115
|
+
* page after a deploy/migration.
|
|
116
|
+
*/
|
|
117
|
+
const syncFreeShippingPromotions = async (container, settings) => {
|
|
118
|
+
const promotionService = container.resolve(utils_1.Modules.PROMOTION);
|
|
119
|
+
const status = settings.enabled ? "active" : "inactive";
|
|
120
|
+
await ensurePromotion(container, promotionService, exports.STANDARD_FREE_SHIPPING_PROMOTION_CODE, status, settings.default_threshold, "false");
|
|
121
|
+
await ensurePromotion(container, promotionService, exports.AFFILIATE_FREE_SHIPPING_PROMOTION_CODE, status, settings.affiliate_threshold, "true");
|
|
122
|
+
};
|
|
123
|
+
exports.syncFreeShippingPromotions = syncFreeShippingPromotions;
|
|
124
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1mcmVlLXNoaXBwaW5nLXByb21vdGlvbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL2ZyZWUtc2hpcHBpbmcvc3luYy1mcmVlLXNoaXBwaW5nLXByb21vdGlvbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscURBQTZEO0FBQzdELDREQUlvQztBQUlwQzs7Ozs7R0FLRztBQUNVLFFBQUEsaUNBQWlDLEdBQzVDLDRDQUE0QyxDQUFBO0FBRWpDLFFBQUEscUNBQXFDLEdBQ2hELDZCQUE2QixDQUFBO0FBQ2xCLFFBQUEsc0NBQXNDLEdBQ2pELDhCQUE4QixDQUFBO0FBUWhDLE1BQU0sVUFBVSxHQUFHLENBQ2pCLFNBQWlCLEVBQ2pCLGNBQWdDLEVBQ3BCLEVBQUUsQ0FBQztJQUNmLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxFQUFFO0lBQ3pFO1FBQ0UsU0FBUyxFQUFFLHlDQUFpQztRQUM1QyxRQUFRLEVBQUUsSUFBSTtRQUNkLE1BQU0sRUFBRSxDQUFDLGNBQWMsQ0FBQztLQUN6QjtDQUNGLENBQUE7QUFFRCxNQUFNLE9BQU8sR0FBRyxDQUFDLElBSWhCLEVBQVUsRUFBRSxDQUNYLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUE7QUFFMUUsTUFBTSxlQUFlLEdBQUcsS0FBSyxFQUMzQixTQUEwQixFQUMxQixnQkFBeUMsRUFDekMsSUFBWSxFQUNaLE1BQTZCLEVBQzdCLFNBQWlCLEVBQ2pCLGNBQWdDLEVBQ2pCLEVBQUU7SUFDakIsTUFBTSxLQUFLLEdBQUcsVUFBVSxDQUFDLFNBQVMs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
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateFreeShippingSettingsWithPromotionSync = void 0;
|
|
4
|
+
const resolve_services_1 = require("../../utils/resolve-services");
|
|
5
|
+
const sync_free_shipping_promotions_1 = require("./sync-free-shipping-promotions");
|
|
6
|
+
/**
|
|
7
|
+
* Updates the settings row, then keeps the two automatic promotions in sync
|
|
8
|
+
* with the saved thresholds. Not a Medusa `createWorkflow` - the plugin only
|
|
9
|
+
* needs a single, non-compensatable sequence of two calls, matching the
|
|
10
|
+
* plain-function shape already used for
|
|
11
|
+
* `updateProgramSettingsWithCouponDiscountReconciliation` in the affiliate
|
|
12
|
+
* plugin.
|
|
13
|
+
*/
|
|
14
|
+
const updateFreeShippingSettingsWithPromotionSync = async (container, input) => {
|
|
15
|
+
const settings = await (0, resolve_services_1.resolveFreeShippingService)(container).updateSettings(input);
|
|
16
|
+
await (0, sync_free_shipping_promotions_1.syncFreeShippingPromotions)(container, settings);
|
|
17
|
+
return settings;
|
|
18
|
+
};
|
|
19
|
+
exports.updateFreeShippingSettingsWithPromotionSync = updateFreeShippingSettingsWithPromotionSync;
|
|
20
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBkYXRlLWZyZWUtc2hpcHBpbmctc2V0dGluZ3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL2ZyZWUtc2hpcHBpbmcvdXBkYXRlLWZyZWUtc2hpcHBpbmctc2V0dGluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBTUEsbUVBQXlFO0FBQ3pFLG1GQUE0RTtBQUU1RTs7Ozs7OztHQU9HO0FBQ0ksTUFBTSwyQ0FBMkMsR0FBRyxLQUFLLEVBQzlELFNBQTBCLEVBQzFCLEtBQXNDLEVBQ0osRUFBRTtJQUNwQyxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUEsNkNBQTBCLEVBQUMsU0FBUyxDQUFDLENBQUMsY0FBYyxDQUN6RSxLQUFLLENBQ04sQ0FBQTtJQUNELE1BQU0sSUFBQSwwREFBMEIsRUFBQyxTQUFTLEVBQUUsUUFBUSxDQUFDLENBQUE7SUFDckQsT0FBTyxRQUFRLENBQUE7QUFDakIsQ0FBQyxDQUFBO0FBVFksUUFBQSwyQ0FBMkMsK0NBU3ZEIn0=
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setFreeShippingPromotionContext = void 0;
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
6
|
+
const core_flows_1 = require("@medusajs/medusa/core-flows");
|
|
7
|
+
const resolve_services_1 = require("../../utils/resolve-services");
|
|
8
|
+
const sync_free_shipping_promotions_1 = require("../free-shipping/sync-free-shipping-promotions");
|
|
9
|
+
// Mirrors `tryResolve` in
|
|
10
|
+
// plugins/affiliate/src/workflows/affiliate/submit-consignment-report.ts:
|
|
11
|
+
// resolves an optionally-installed module without throwing when it's absent.
|
|
12
|
+
const tryResolve = (container, name) => {
|
|
13
|
+
try {
|
|
14
|
+
return container.resolve(name);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error instanceof Error &&
|
|
18
|
+
error.message.includes("Missing registration")) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Deliberately duplicated, simplified email normalization (trim+lowercase)
|
|
26
|
+
* for the referred-customer check - see
|
|
27
|
+
* `plugins/affiliate/src/modules/affiliate/domain/email-normalization.ts`
|
|
28
|
+
* for the affiliate plugin's own version. Unlike that version, a
|
|
29
|
+
* missing/invalid email here just means "no email", not a thrown error:
|
|
30
|
+
* this context computation must never fail a cart's promotion recalculation.
|
|
31
|
+
*/
|
|
32
|
+
const normalizeEmailOrNull = (value) => {
|
|
33
|
+
const normalized = value?.trim().toLowerCase();
|
|
34
|
+
return normalized && normalized.includes("@") ? normalized : null;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Computes whether the cart qualifies for the affiliate free-shipping
|
|
38
|
+
* threshold, so the two automatic promotions (whose rules are static, see
|
|
39
|
+
* `sync-free-shipping-promotions.ts`) can tell them apart at evaluation
|
|
40
|
+
* time. Which criteria count as "affiliate" is controlled entirely by the
|
|
41
|
+
* two settings toggles - no promotion resync is needed when they change,
|
|
42
|
+
* only this computation.
|
|
43
|
+
*/
|
|
44
|
+
const setFreeShippingPromotionContext = async ({ cart }, { container }) => {
|
|
45
|
+
const typedCart = cart;
|
|
46
|
+
let qualifies = false;
|
|
47
|
+
if (typedCart.customer_id) {
|
|
48
|
+
const affiliateService = tryResolve(container, "affiliate");
|
|
49
|
+
if (affiliateService) {
|
|
50
|
+
const settings = await (0, resolve_services_1.resolveFreeShippingService)(container).retrieveSettings();
|
|
51
|
+
if (settings.affiliate_applies_to_account_holders) {
|
|
52
|
+
qualifies = await affiliateService.isCustomerAffiliate(typedCart.customer_id);
|
|
53
|
+
}
|
|
54
|
+
if (!qualifies && settings.affiliate_applies_to_referred_customers) {
|
|
55
|
+
const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
56
|
+
const state = await affiliateService.inspectAffiliateCartReferralForCheckout({
|
|
57
|
+
cart_id: typedCart.id,
|
|
58
|
+
customer_id: typedCart.customer_id,
|
|
59
|
+
normalized_email: normalizeEmailOrNull(typedCart.email),
|
|
60
|
+
}, query);
|
|
61
|
+
qualifies = state.assignment !== null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return new workflows_sdk_1.StepResponse({
|
|
66
|
+
[sync_free_shipping_promotions_1.AFFILIATE_QUALIFICATION_ATTRIBUTE]: qualifies ? "true" : "false",
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
exports.setFreeShippingPromotionContext = setFreeShippingPromotionContext;
|
|
70
|
+
core_flows_1.updateCartPromotionsWorkflow.hooks.setPromotionContext(exports.setFreeShippingPromotionContext);
|
|
71
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2V0LWZyZWUtc2hpcHBpbmctcHJvbW90aW9uLWNvbnRleHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL2hvb2tzL3NldC1mcmVlLXNoaXBwaW5nLXByb21vdGlvbi1jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFFQUFnRTtBQUNoRSxxREFBcUU7QUFDckUsNERBQTBFO0FBRTFFLG1FQUF5RTtBQUN6RSxrR0FBa0c7QUFxQmxHLDBCQUEwQjtBQUMxQiwwRUFBMEU7QUFDMUUsNkVBQTZFO0FBQzdFLE1BQU0sVUFBVSxHQUFHLENBQ2pCLFNBQThELEVBQzlELElBQVksRUFDRixFQUFFO0lBQ1osSUFBSSxDQUFDO1FBQ0gsT0FBTyxTQUFTLENBQUMsT0FBTyxDQUFJLElBQUksQ0FBQyxDQUFBO0lBQ25DLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsSUFDRSxLQUFLLFlBQVksS0FBSztZQUN0QixLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxFQUM5QyxDQUFDO1lBQ0QsT0FBTyxJQUFJLENBQUE7UUFDYixDQUFDO1FBQ0QsTUFBTSxLQUFLLENBQUE7SUFDYixDQUFDO0FBQ0gsQ0FBQyxDQUFBO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sb0JBQW9CLEdBQUcsQ0FDM0IsS0FBZ0MsRUFDakIsRUFBRTtJQUNqQixNQUFNLFVBQVUsR0FBRyxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUE7SUFDOUMsT0FBTyxVQUFVLElBQUksVUFBVSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7QUFDbkUsQ0FBQyxDQUFBO0FBUUQ7Ozs7Ozs7R0FPRztBQUNJLE1BQU0sK0JBQStCLEdBRXJDLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxFQUFFLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUN2QyxNQUFNLFNBQVMsR0FBRyxJQUFxQixDQUFBO0lBQ3ZDLElBQUksU0FBUyxHQUFHLEtBQUssQ0FBQTtJQUVyQixJQUFJLFNBQVMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUMxQixNQUFNLGdCQUFnQixHQUFHLFVBQVUsQ0FDakMsU0FBUyxFQUNULFdBQVcsQ0FDWixDQUFBO1FBRUQsSUFBSSxnQkFBZ0IsRUFBRSxDQUFDO1lBQ3JCLE1BQU0sUUFBUSxHQUNaLE1BQU0sSUFBQSw2Q0FBMEIsRUFBQyxTQUFTLENBQUMsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFBO1lBRWhFLElBQUksUUFBUSxDQUFDLG9DQUFvQyxFQUFFLENBQUM7Z0JBQ2xELFNBQVMsR0FBRyxNQUFNLGdCQUFnQixDQUFDLG1CQUFtQixDQUNwRCxTQUFTLENBQUMsV0FBVyxDQUN0QixDQUFBO1lBQ0gsQ0FBQztZQUVELElBQUksQ0FBQyxTQUFTLElBQUksUUFBUSxDQUFDLHVDQUF1QyxFQUFFLENBQUM7Z0JBQ25FLE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsS0FBSyxDQUFDLENBQUE7Z0JBQ2hFLE1BQU0sS0FBSyxHQUNULE1BQU0sZ0JBQWdCLENBQUMsdUNBQXVDLENBQzVEO29CQUNFLE9BQU8sRUFBRSxTQUFTLENBQUMsRUFBRTtvQkFDckIsV0FBVyxFQUFFLFNBQVMsQ0FBQyxXQUFXO29CQUNsQyxnQkFBZ0IsRUFBRSxvQkFBb0IsQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDO2lCQUN4RCxFQUNELEtBQUssQ0FDTixDQUFBO2dCQUNILFNBQVMsR0FBRyxLQUFLLENBQUMsVUFBVSxLQUFLLElBQUksQ0FBQTtZQUN2QyxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCxPQUFPLElBQUksNEJBQVksQ0FBQztRQUN0QixDQUFDLGlFQUFpQyxDQUFDLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU87S0FDbEUsQ0FBQyxDQUFBO0FBQ0osQ0FBQyxDQUFBO0FBekNZLFFBQUEsK0JBQStCLG1DQXlDM0M7QUFFRCx5Q0FBNEIsQ0FBQyxLQUFLLENBQUMsbUJBQW1CLENBQ3BELHVDQUErQixDQUNoQyxDQUFBIn0=
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hartl Services GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Medusa Free Shipping
|
|
2
|
+
|
|
3
|
+
Medusa v2.19 plugin for a shop-wide free-shipping threshold, maintained in the
|
|
4
|
+
Admin under **Settings → Free shipping**.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
- Stores one global settings row: enabled flag, default threshold and a
|
|
9
|
+
separate affiliate threshold (versioned, optimistic-concurrency updates).
|
|
10
|
+
- Keeps two automatic Medusa promotions in sync with those settings: on save
|
|
11
|
+
and every 15 minutes through the `free-shipping-promotion-sync` scheduled
|
|
12
|
+
job (worker instances only). The sync writes only differences, so a run
|
|
13
|
+
against in-sync promotions is read-only. Changed rules are created before
|
|
14
|
+
outdated ones are deleted, so an automatic promotion never exists without
|
|
15
|
+
its rules (which would grant free shipping to every cart).
|
|
16
|
+
- Adds the cart's affiliate qualification to the promotion context. This uses
|
|
17
|
+
the `affiliate` module only if it is registered; without the Affiliate
|
|
18
|
+
plugin, only the default threshold applies.
|
|
19
|
+
- Exposes `GET`/`POST /admin/free-shipping-settings` (authenticated Admin).
|
|
20
|
+
DTOs, schemas and paths are exported from
|
|
21
|
+
`@hartl-services/medusa-food-supplements-contracts` (`free-shipping`
|
|
22
|
+
subpaths).
|
|
23
|
+
|
|
24
|
+
## Installation in a shop
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @hartl-services/medusa-free-shipping @hartl-services/medusa-food-supplements-contracts
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// medusa-config.ts
|
|
32
|
+
plugins: [
|
|
33
|
+
{
|
|
34
|
+
resolve: "@hartl-services/medusa-free-shipping",
|
|
35
|
+
options: {},
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The plugin has no options. Run `medusa db:migrate` after installing it.
|
|
41
|
+
|
|
42
|
+
Peer dependencies are the host's Medusa 2.19 packages (`@medusajs/framework`,
|
|
43
|
+
`@medusajs/medusa`, `@medusajs/admin-sdk`, `@medusajs/js-sdk`, `@medusajs/ui`)
|
|
44
|
+
plus React, React Router and TanStack Query in the versions the Medusa 2.19
|
|
45
|
+
dashboard uses.
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hartl-services/medusa-free-shipping",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Configurable free-shipping threshold plugin for Medusa.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
".medusa/server",
|
|
8
|
+
"README.md",
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://gitlab.com/hartl-services-gmbh/services/food-supplements-e-commerce.git",
|
|
14
|
+
"directory": "plugins/free-shipping"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": [
|
|
21
|
+
"./.medusa/**"
|
|
22
|
+
],
|
|
23
|
+
"exports": {
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
"./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
|
|
26
|
+
"./.medusa/server/src/*": "./.medusa/server/src/*",
|
|
27
|
+
"./admin": {
|
|
28
|
+
"import": "./.medusa/server/src/admin/index.mjs",
|
|
29
|
+
"require": "./.medusa/server/src/admin/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@medusajs/admin-sdk": "^2.19.0",
|
|
34
|
+
"@medusajs/framework": "^2.19.0",
|
|
35
|
+
"@medusajs/js-sdk": "^2.19.0",
|
|
36
|
+
"@medusajs/medusa": "^2.19.0",
|
|
37
|
+
"@medusajs/ui": "^4.2.1",
|
|
38
|
+
"@tanstack/react-query": "^5.64.2",
|
|
39
|
+
"react": "^18.3.1",
|
|
40
|
+
"react-router-dom": "^7.18.2"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@hartl-services/medusa-food-supplements-contracts": "^0.9.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@medusajs/admin-sdk": "2.19.0",
|
|
47
|
+
"@medusajs/cli": "2.19.0",
|
|
48
|
+
"@medusajs/framework": "2.19.0",
|
|
49
|
+
"@medusajs/js-sdk": "2.19.0",
|
|
50
|
+
"@medusajs/medusa": "2.19.0",
|
|
51
|
+
"@medusajs/ui": "4.2.1",
|
|
52
|
+
"@swc/core": "^1.7.28",
|
|
53
|
+
"@swc/jest": "^0.2.39",
|
|
54
|
+
"@tanstack/react-query": "5.64.2",
|
|
55
|
+
"@types/jest": "^29.5.14",
|
|
56
|
+
"@types/node": "^20.12.11",
|
|
57
|
+
"@types/react": "^18.3.2",
|
|
58
|
+
"@types/react-dom": "^18.2.25",
|
|
59
|
+
"jest": "^29.7.0",
|
|
60
|
+
"react": "^18.3.1",
|
|
61
|
+
"react-router-dom": "7.18.2",
|
|
62
|
+
"typescript": "^5.6.2",
|
|
63
|
+
"vite": "7.3.6",
|
|
64
|
+
"yalc": "^1.0.0-pre.53"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=20"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build": "medusa plugin:build",
|
|
71
|
+
"dev": "medusa plugin:develop",
|
|
72
|
+
"typecheck": "tsc --noEmit",
|
|
73
|
+
"test:unit": "jest"
|
|
74
|
+
}
|
|
75
|
+
}
|