@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.
@@ -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
+ }