@hartl-services/medusa-base 2.9.0 → 2.11.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.
Files changed (37) hide show
  1. package/.medusa/server/src/admin/index.js +5 -1
  2. package/.medusa/server/src/admin/index.mjs +5 -1
  3. package/.medusa/server/src/documents/templates/financial-document.js +23 -4
  4. package/.medusa/server/src/email/application/deliver-ephemeral-auth-mail.js +2 -4
  5. package/.medusa/server/src/email/application/handle-password-reset-event.js +75 -27
  6. package/.medusa/server/src/email/rendering/default-templates.js +2 -1
  7. package/.medusa/server/src/email/rendering/render-email.js +2 -1
  8. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +5 -1
  9. package/.medusa/server/src/email/templates/blocks/dynamic-blocks.js +5 -2
  10. package/.medusa/server/src/email/templates/defaults/documents.js +8 -1
  11. package/.medusa/server/src/email/templates/defaults/subjects.js +3 -1
  12. package/.medusa/server/src/email/templates/defaults/texts.js +4 -2
  13. package/.medusa/server/src/email/templates/render.js +2 -1
  14. package/.medusa/server/src/email/templates/sample-data.js +5 -1
  15. package/.medusa/server/src/email/types/email-kinds.js +3 -1
  16. package/.medusa/server/src/email/types/template-data.js +8 -1
  17. package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +4 -1
  18. package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +103 -29
  19. package/.medusa/server/src/modules/email/migrations/Migration20261013120000.js +20 -0
  20. package/.medusa/server/src/shared/lib/index.js +2 -1
  21. package/.medusa/server/src/shared/lib/shipping-tax.js +12 -0
  22. package/.medusa/server/src/shop-config/build-food-shop-config.js +7 -1
  23. package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +44 -33
  24. package/README.md +43 -9
  25. package/dist/documents/templates/financial-document.js +22 -3
  26. package/dist/documents/types/documents.d.ts +13 -0
  27. package/dist/email/types/email-kinds.d.ts +3 -2
  28. package/dist/email/types/email-kinds.js +2 -0
  29. package/dist/email/types/shop-configuration.d.ts +8 -0
  30. package/dist/email/types/template-data.d.ts +6 -1
  31. package/dist/email/types/template-data.js +7 -0
  32. package/dist/shared/lib/index.d.ts +1 -0
  33. package/dist/shared/lib/index.js +1 -0
  34. package/dist/shared/lib/shipping-tax.d.ts +8 -0
  35. package/dist/shared/lib/shipping-tax.js +11 -0
  36. package/dist/shop-config/build-food-shop-config.js +6 -0
  37. package/package.json +3 -3
package/README.md CHANGED
@@ -1436,6 +1436,11 @@ Options of the `email` namespace:
1436
1436
  `orderNotificationEmail` mailbox. `verifyAccountUrl` (optional builder,
1437
1437
  like `passwordResetUrl`; the mail appends `#token=`) is the storefront page
1438
1438
  that confirms an account, needed while `customer_verification` is on.
1439
+ `adminPasswordResetUrl` (optional builder, receives `{ token }`) is the
1440
+ Medusa Admin's "choose a new password" page; without it an Admin user's
1441
+ password reset fails with `render_failed`. `buildFoodShopConfig` sets
1442
+ `<MEDUSA_BACKEND_URL>/app/reset-password?token=<token>` (the dashboard reads
1443
+ the token from the query).
1439
1444
  `adminAffiliateApplicationsUrl` (optional builder, receives
1440
1445
  `{ applicationId }`) is the Admin page of the partner applications; the
1441
1446
  notification about a new application shows its "Im Admin prüfen" button
@@ -1539,7 +1544,7 @@ feature is switched on again.
1539
1544
  | --------------------- | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
1540
1545
  | (plugin switch) | E-Mail | on | every route under `/admin/email/*` (mail log, resends, attachments; 404); jobs `email-reconcile-attempts`, `email-reconcile-pending-messages`, `email-retry-messages`; every feature below | checkout and orders; already queued messages stay stored |
1541
1546
  | `order_mails` | Bestell-Mails | on | subscribers `order-placed`, `email-shipment-created`, `email-order-canceled`, `return-requested`, `return-received`, `documents-shipment-invoice-assignment-decided`, `documents-order-cancellation-ready`, `documents-refund-ready`; route `/admin/draft-orders/:id/order-confirmation` (404) and its draft-order widget | jobs, Admin routes, already created messages |
1542
- | `account_mails` | Konto-Mails | on | subscribers `password-reset`, `user-invite`, `customer-verification` (the last one only with `customer_verification` on as well); not the login code mail, which depends on neither this feature nor the plugin switch (see "Kunden-Login") | jobs, Admin routes, already created messages |
1547
+ | `account_mails` | Konto-Mails | on | subscribers `password-reset` (customers and Admin users), `user-invite`, `customer-verification` (the last one only with `customer_verification` on as well); not the login code mail, which depends on neither this feature nor the plugin switch (see "Kunden-Login") | jobs, Admin routes, already created messages |
1543
1548
  | `customer_verification` | Kontobestätigung per E-Mail | off | Medusa's e-mail verification of customer logins (see "Kontobestätigung per E-Mail"), route `POST /store/customer-verification/resend` (404 while off), subscriber `customer-verification`; child of `account_mails` | checkout and orders; confirmed accounts stay confirmed, already created messages |
1544
1549
  | `affiliate_reminders` | Affiliate-Mails | on | subscribers `affiliate-consignment-reminder-due` (reminders to affiliates) and `affiliate-application-submitted` (notification to the shop about a new partner application) | jobs, Admin routes, already created messages |
1545
1550
 
@@ -1826,9 +1831,10 @@ has at most 35 characters. Email runtime-validates every contract before use.
1826
1831
 
1827
1832
  - The accepted kinds are exactly `order.received`, `order.received.admin`, `order.shipped`,
1828
1833
  `order.canceled`, `order.return_requested`, `order.return_received`,
1829
- `order.refunded`, `auth.password_reset`, `auth.verification`,
1830
- `auth.login_code`, `affiliate.consignment_report_due`,
1831
- `affiliate.application_submitted.admin` and `user.invite`.
1834
+ `order.refunded`, `auth.password_reset`, `auth.admin_password_reset`,
1835
+ `auth.verification`, `auth.login_code`, `affiliate.consignment_report_due`,
1836
+ `affiliate.application_submitted.admin`, `user.invite` and
1837
+ `system.credential_expiry.admin`.
1832
1838
  - Every automatic message has a deterministic business key.
1833
1839
  - Every placed Order creates independent customer and Shop intents. The customer
1834
1840
  intent honors `no_notification`; the Shop intent always targets the static
@@ -1914,7 +1920,7 @@ an info box lists number, customer, e-mail and order time, followed by an
1914
1920
  button is left out without it) and `timeZone` (IANA; the order time is left
1915
1921
  out without it). `buildFoodShopConfig` sets both (`<MEDUSA_BACKEND_URL>/app/
1916
1922
  orders/<id>` and its `timeZone` option). The layout receives `audience`
1917
- (`"shop"` for this kind, `affiliate.application_submitted.admin` and `user.invite`, `"customer"` for all others) and
1923
+ (`"shop"` for this kind, `affiliate.application_submitted.admin`, `system.credential_expiry.admin`, `user.invite` and `auth.admin_password_reset`, `"customer"` for all others) and
1918
1924
  shows the contact line only to customers.
1919
1925
 
1920
1926
  The shipping confirmation (`order.shipped`, one per fulfillment) receives a
@@ -1978,6 +1984,20 @@ without `iat`) and a note that the mail can be ignored. The lifetime is
1978
1984
  passed as the ephemeral `valid_minutes` next to `reset_url` and is never
1979
1985
  persisted.
1980
1986
 
1987
+ The same Medusa event with `actor_type: "user"` (the Admin's "Passwort
1988
+ zurücksetzen" at `/app/reset-password`) becomes its own kind,
1989
+ `auth.admin_password_reset`, with its own Admin template: subject "Neues
1990
+ Passwort für den Admin-Bereich von <displayName>", the same button, link text,
1991
+ lifetime and ignore note, and the link from `adminPasswordResetUrl` (the token
1992
+ in the query). It is sent only when exactly one Admin user has the address:
1993
+ the `emailpass` identity is shared by all actor types, so a customer could ask
1994
+ for an Admin reset of their own identity, and that request is ignored
1995
+ (`no_admin_user`). A token whose `actor_type` differs from the event's is
1996
+ ignored as `invalid_token`; every other actor type stays `invalid_event`. Like
1997
+ the customer reset it is gated by `account_mails`, ephemeral (never resent or
1998
+ retried by the generic job) and stored with `resource_type` `"user"`; its
1999
+ layout audience is `"shop"`.
2000
+
1981
2001
  The account confirmation (`auth.verification`) has the subject "Bestätige
1982
2002
  deine E-Mail-Adresse", a "E-Mail-Adresse bestätigen" button, the link as
1983
2003
  copyable text, a note that the link is only valid for a limited time (a new
@@ -2374,10 +2394,24 @@ separate documents package since 1.0.0.
2374
2394
  persists the customer company, customer UID and exemption wording in that
2375
2395
  same snapshot. B2C and non-eligible evidence add no tax-proof statement.
2376
2396
  - Freezes a tax breakdown (`totals.tax_breakdown`) grouped by the order's
2377
- item and shipping tax-line name and rate, as configured on the Medusa tax
2378
- region. Amounts are rounded to the currency and add up exactly to the
2379
- tax total; cancellations scale it proportionally. Snapshots issued before
2380
- this field existed read as an empty breakdown.
2397
+ item tax-line name and rate, as configured on the Medusa tax region, with
2398
+ the tax and the net amount (`taxable_amount`, after discounts) per rate.
2399
+ Shipping follows the items: a shipping tax line with the code
2400
+ `APPORTIONED_SHIPPING_TAX_CODE` (`/lib`; Tax Compliance's "Anteilige
2401
+ Versandsteuer") is split back onto the item rates by the items' net
2402
+ amount after discounts, the key Tax Compliance weights it by, so the
2403
+ invoice never prints a weighted rate. Other shipping tax joins the items'
2404
+ row when the cart has one item rate equal to it, otherwise it keeps its
2405
+ own row.
2406
+ Amounts are rounded to the currency and add up exactly to the tax total;
2407
+ cancellations scale them proportionally. Snapshots issued before these
2408
+ fields existed read as an empty breakdown or without net amounts.
2409
+ - Prints net amounts before discounts: line unit price and "Summe",
2410
+ "Zwischensumme" (items), "Versand" (before a free-shipping promotion),
2411
+ then all promotions as one net "Rabatt" (only when there is a discount),
2412
+ the tax rows and the gross "Gesamt"; the rows add up to the total. With
2413
+ several tax rates each tax row also names its net amount ("AT Reduziert
2414
+ 10 % auf € 55,00").
2381
2415
  - Persists one immutable decision per shipped Fulfillment. A leased, Order-
2382
2416
  unique coordinator chooses exactly one invoice carrier without assuming
2383
2417
  event delivery order; all other shipments become `non_carrier`.
@@ -63,7 +63,7 @@ const renderLines = (input, timeZone) => input.lines
63
63
  </td>
64
64
  <td class="number quantity">${(0, lib_1.escapeHtml)(line.quantity)}</td>
65
65
  <td class="number">${(0, html_utils_1.renderMoney)(line.unit_price)}</td>
66
- <td class="number">${(0, html_utils_1.renderMoney)(line.total)}</td>
66
+ <td class="number">${(0, html_utils_1.renderMoney)(line.subtotal)}</td>
67
67
  </tr>`)
68
68
  .join("");
69
69
  const isExemptIntraCommunitySupply = (input) => input.order.tax_compliance?.exemption_wording ===
@@ -93,11 +93,30 @@ const formatTaxRate = (rate) => new Intl.NumberFormat(format_1.DEFAULT_FORMAT_LO
93
93
  }).format(rate / 100);
94
94
  /** One totals row below the Menge/Einzelpreis columns; the amount sits under "Summe". */
95
95
  const renderTotalRow = (label, value, className) => `<tr${className ? ` class="${className}"` : ""}><td class="spacer"></td><td class="label" colspan="2">${(0, lib_1.escapeHtml)(label)}</td><td class="number">${value}</td></tr>`;
96
+ /** No discount, no row. */
97
+ const renderDiscountRow = (input) => Number(input.totals.discount_total.amount) === 0
98
+ ? ""
99
+ : renderTotalRow("Rabatt", renderDiscountTotal(input));
100
+ /**
101
+ * One row per tax rate. With several rates each row also names its net
102
+ * amount, since an invoice states the amount taxed at every rate.
103
+ */
96
104
  const renderTaxRows = (input) => {
97
105
  const { tax_breakdown: breakdown, tax_total: taxTotal } = input.totals;
106
+ const withNetAmount = breakdown.length > 1;
98
107
  const rows = breakdown.length > 0
99
108
  ? breakdown.map((entry) => ({
100
- label: `${entry.name} ${formatTaxRate(entry.rate)}`,
109
+ label: [
110
+ `${entry.name} ${formatTaxRate(entry.rate)}`,
111
+ withNetAmount && entry.taxable_amount
112
+ ? `auf ${(0, format_1.formatMoney)({
113
+ amount: entry.taxable_amount.amount,
114
+ currencyCode: entry.taxable_amount.currency_code,
115
+ })}`
116
+ : null,
117
+ ]
118
+ .filter((part) => Boolean(part))
119
+ .join(" "),
101
120
  money: entry.tax_total,
102
121
  }))
103
122
  : [{ label: "Umsatzsteuer", money: taxTotal }];
@@ -141,7 +160,7 @@ const renderFinancialDocument = (input, layout, timeZone) => {
141
160
  <tbody class="totals">
142
161
  ${renderTotalRow("Zwischensumme", (0, html_utils_1.renderMoney)(totals.subtotal))}
143
162
  ${renderTotalRow("Versand", (0, html_utils_1.renderMoney)(totals.shipping_total))}
144
- ${renderTotalRow("Rabatt", renderDiscountTotal(input))}
163
+ ${renderDiscountRow(input)}
145
164
  ${renderTaxRows(input)}
146
165
  ${renderTotalRow("Gesamt", (0, html_utils_1.renderMoney)(totals.total), "grand-total")}
147
166
  </tbody>
@@ -40,13 +40,20 @@ export type DocumentTaxBreakdownEntry = {
40
40
  code: string | null;
41
41
  rate: number;
42
42
  tax_total: DocumentMoney;
43
+ /**
44
+ * Net amount taxed at this rate (items and their share of shipping, after
45
+ * discounts). Missing in snapshots issued before it was recorded.
46
+ */
47
+ taxable_amount?: DocumentMoney;
43
48
  };
44
49
  export type DocumentTemplateLine = {
45
50
  id: string;
46
51
  title: string;
47
52
  sku: string | null;
48
53
  quantity: number;
54
+ /** Net, before discounts (older invoice snapshots: Medusa's unit price). */
49
55
  unit_price: DocumentMoney;
56
+ /** Net, before discounts; printed under "Summe". */
50
57
  subtotal: DocumentMoney;
51
58
  tax_total: DocumentMoney;
52
59
  total: DocumentMoney;
@@ -74,6 +81,12 @@ export type DocumentTemplateInput = {
74
81
  tax_compliance: DocumentTaxComplianceDTO | null;
75
82
  };
76
83
  lines: DocumentTemplateLine[];
84
+ /**
85
+ * Subtotal (items) and shipping are net amounts before discounts, the
86
+ * discount is net, so they add up with the tax to the total. Older invoice
87
+ * snapshots carry Medusa's order subtotal (items plus shipping) and gross
88
+ * shipping and discount totals.
89
+ */
77
90
  totals: {
78
91
  subtotal: DocumentMoney;
79
92
  tax_total: DocumentMoney;
@@ -1,5 +1,5 @@
1
1
  import { z } from "@medusajs/framework/zod";
2
- export declare const EMAIL_KINDS: readonly ["order.received", "order.received.admin", "order.shipped", "order.canceled", "order.return_requested", "order.return_received", "order.refunded", "auth.password_reset", "affiliate.consignment_report_due", "affiliate.application_submitted.admin", "user.invite", "auth.verification", "auth.login_code", "system.credential_expiry.admin"];
2
+ export declare const EMAIL_KINDS: readonly ["order.received", "order.received.admin", "order.shipped", "order.canceled", "order.return_requested", "order.return_received", "order.refunded", "auth.password_reset", "affiliate.consignment_report_due", "affiliate.application_submitted.admin", "user.invite", "auth.verification", "auth.login_code", "system.credential_expiry.admin", "auth.admin_password_reset"];
3
3
  export declare const EmailKindSchema: z.ZodEnum<{
4
4
  "order.received": "order.received";
5
5
  "order.received.admin": "order.received.admin";
@@ -15,6 +15,7 @@ export declare const EmailKindSchema: z.ZodEnum<{
15
15
  "auth.verification": "auth.verification";
16
16
  "auth.login_code": "auth.login_code";
17
17
  "system.credential_expiry.admin": "system.credential_expiry.admin";
18
+ "auth.admin_password_reset": "auth.admin_password_reset";
18
19
  }>;
19
20
  export type EmailKind = (typeof EMAIL_KINDS)[number];
20
21
  /**
@@ -22,5 +23,5 @@ export type EmailKind = (typeof EMAIL_KINDS)[number];
22
23
  * subscriber builds the mail from the event, so neither the generic retry
23
24
  * job, the automatic dispatch nor an Admin resend may touch them.
24
25
  */
25
- export declare const EPHEMERAL_EMAIL_KINDS: readonly ["auth.password_reset", "auth.verification", "auth.login_code"];
26
+ export declare const EPHEMERAL_EMAIL_KINDS: readonly ["auth.password_reset", "auth.admin_password_reset", "auth.verification", "auth.login_code"];
26
27
  export declare const isEphemeralEmailKind: (kind: EmailKind) => boolean;
@@ -17,6 +17,7 @@ exports.EMAIL_KINDS = [
17
17
  "auth.verification",
18
18
  "auth.login_code",
19
19
  "system.credential_expiry.admin",
20
+ "auth.admin_password_reset",
20
21
  ];
21
22
  exports.EmailKindSchema = zod_1.z.enum(exports.EMAIL_KINDS);
22
23
  /**
@@ -26,6 +27,7 @@ exports.EmailKindSchema = zod_1.z.enum(exports.EMAIL_KINDS);
26
27
  */
27
28
  exports.EPHEMERAL_EMAIL_KINDS = [
28
29
  "auth.password_reset",
30
+ "auth.admin_password_reset",
29
31
  "auth.verification",
30
32
  "auth.login_code",
31
33
  ];
@@ -48,6 +48,14 @@ export type ShopEmailConfiguration = Readonly<{
48
48
  adminAffiliateApplicationsUrl?: (input: Readonly<{
49
49
  applicationId: string;
50
50
  }>) => string;
51
+ /**
52
+ * The Medusa Admin's "choose a new password" page with the reset token
53
+ * (`/app/reset-password?token=<token>`). Without it an Admin user's
54
+ * password reset sends no mail.
55
+ */
56
+ adminPasswordResetUrl?: (input: Readonly<{
57
+ token: string;
58
+ }>) => string;
51
59
  affiliatePortalUrl: string;
52
60
  displayName: string;
53
61
  inviteUrl: (input: Readonly<{
@@ -381,6 +381,7 @@ export declare const EmailPersistedDataSchemas: {
381
381
  client_id: z.ZodString;
382
382
  expires_on: z.ZodString;
383
383
  }, z.core.$strict>;
384
+ readonly "auth.admin_password_reset": z.ZodObject<{}, z.core.$strict>;
384
385
  };
385
386
  export declare const EmailRenderDataSchemas: {
386
387
  readonly "auth.password_reset": z.ZodObject<{
@@ -388,6 +389,10 @@ export declare const EmailRenderDataSchemas: {
388
389
  reset_url: z.ZodString;
389
390
  valid_minutes: z.ZodOptional<z.ZodNumber>;
390
391
  }, z.core.$strict>;
392
+ readonly "auth.admin_password_reset": z.ZodObject<{
393
+ reset_url: z.ZodString;
394
+ valid_minutes: z.ZodOptional<z.ZodNumber>;
395
+ }, z.core.$strict>;
391
396
  readonly "user.invite": z.ZodObject<{
392
397
  invite_url: z.ZodString;
393
398
  expires_at: z.ZodOptional<z.ZodString>;
@@ -635,7 +640,7 @@ export type EmailPersistedDataMap = {
635
640
  };
636
641
  type EmptyRenderContext = Readonly<Record<never, never>>;
637
642
  export type EmailEphemeralRenderContextMap = {
638
- readonly [Kind in EmailKind]: Kind extends "auth.password_reset" ? Readonly<{
643
+ readonly [Kind in EmailKind]: Kind extends "auth.password_reset" | "auth.admin_password_reset" ? Readonly<{
639
644
  reset_url: string;
640
645
  valid_minutes?: number;
641
646
  }> : Kind extends "user.invite" ? Readonly<{
@@ -240,6 +240,8 @@ exports.EmailPersistedDataSchemas = {
240
240
  expires_on: affiliate_1.CalendarDateSchema,
241
241
  })
242
242
  .strict(),
243
+ // The Admin dashboard is German only, so no locale; the link is ephemeral.
244
+ "auth.admin_password_reset": zod_1.z.object({}).strict(),
243
245
  };
244
246
  exports.EmailRenderDataSchemas = {
245
247
  ...exports.EmailPersistedDataSchemas,
@@ -248,6 +250,11 @@ exports.EmailRenderDataSchemas = {
248
250
  /** How long the reset link is valid, from the token; `undefined` if unknown. */
249
251
  valid_minutes: zod_1.z.number().int().positive().optional(),
250
252
  }),
253
+ "auth.admin_password_reset": exports.EmailPersistedDataSchemas["auth.admin_password_reset"].extend({
254
+ reset_url: zod_1.z.string().url(),
255
+ /** How long the reset link is valid, from the token; `undefined` if unknown. */
256
+ valid_minutes: zod_1.z.number().int().positive().optional(),
257
+ }),
251
258
  "user.invite": exports.EmailPersistedDataSchemas["user.invite"].extend({
252
259
  invite_url: zod_1.z.string().url(),
253
260
  /** When the invite expires (ISO 8601), from the current invite. */
@@ -6,6 +6,7 @@ export * from "./hash";
6
6
  export * from "./logging";
7
7
  export * from "./money";
8
8
  export * from "./refreshing-snapshot";
9
+ export * from "./shipping-tax";
9
10
  export * from "./text";
10
11
  export * from "./transaction";
11
12
  export * from "./wire-response";
@@ -24,6 +24,7 @@ __exportStar(require("./hash"), exports);
24
24
  __exportStar(require("./logging"), exports);
25
25
  __exportStar(require("./money"), exports);
26
26
  __exportStar(require("./refreshing-snapshot"), exports);
27
+ __exportStar(require("./shipping-tax"), exports);
27
28
  __exportStar(require("./text"), exports);
28
29
  __exportStar(require("./transaction"), exports);
29
30
  __exportStar(require("./wire-response"), exports);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Code of a shipping tax line whose rate is the item rates weighted by the
3
+ * items' net amount after discounts (shipping follows the main supplies,
4
+ * split by their Entgelt). Tax Compliance writes it; Documents splits such a
5
+ * line back onto the item tax rates by the same key, because an invoice
6
+ * states the tax per actual rate.
7
+ */
8
+ export declare const APPORTIONED_SHIPPING_TAX_CODE = "shipping_blended";
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APPORTIONED_SHIPPING_TAX_CODE = void 0;
4
+ /**
5
+ * Code of a shipping tax line whose rate is the item rates weighted by the
6
+ * items' net amount after discounts (shipping follows the main supplies,
7
+ * split by their Entgelt). Tax Compliance writes it; Documents splits such a
8
+ * line back onto the item tax rates by the same key, because an invoice
9
+ * states the tax per actual rate.
10
+ */
11
+ exports.APPORTIONED_SHIPPING_TAX_CODE = "shipping_blended";
@@ -172,6 +172,12 @@ function buildFoodShopConfig(options, environment = process.env) {
172
172
  adminAffiliateApplicationsUrl: () => new URL("/app/affiliates/applications", backendOrigin).toString(),
173
173
  // The Admin dashboard mounts under admin.path ("/app").
174
174
  adminOrderUrl: ({ orderId }) => new URL(`/app/orders/${encodeURIComponent(orderId)}`, backendOrigin).toString(),
175
+ // The Admin dashboard reads the token from the query, not the hash.
176
+ adminPasswordResetUrl: ({ token }) => {
177
+ const url = new URL("/app/reset-password", backendOrigin);
178
+ url.searchParams.set("token", token);
179
+ return url.toString();
180
+ },
175
181
  displayName: options.displayName,
176
182
  affiliatePortalUrl: (0, config_1.shopUrl)(storefrontOrigin, `${storefrontPath}/partnerportal`),
177
183
  // The Admin dashboard mounts "/invite" under admin.path ("/app").
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hartl-services/medusa-base",
3
- "version": "2.9.0",
3
+ "version": "2.11.0",
4
4
  "description": "Base plugin for Hartl Services Medusa shops: per-plugin feature toggles with a generic Admin settings page, a Store endpoint for storefronts, enforcement helpers, the complete-cart hook composition, the root redirect, cart-promotion link repair, the built-in E-Mail, Documents and S3 file storage modules, config helpers with the turnkey food-shop preset, and the Sentry logger wrapper and instrumentation.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -101,7 +101,7 @@
101
101
  "mime-types": "3.0.1",
102
102
  "nodemailer": "6.10.1",
103
103
  "ulid": "^2.4.0",
104
- "@hartl-services/medusa-food-supplements-contracts": "^0.29.0"
104
+ "@hartl-services/medusa-food-supplements-contracts": "^0.31.0"
105
105
  },
106
106
  "devDependencies": {
107
107
  "@medusajs/admin-sdk": "2.19.0",
@@ -153,7 +153,7 @@
153
153
  "dev": "medusa plugin:develop",
154
154
  "typecheck": "tsc --noEmit && tsc -p tsconfig.type-tests.json --noEmit && tsc -p tsconfig.component-tests.json",
155
155
  "pretest:unit": "pnpm --filter @hartl-services/medusa-food-supplements-contracts build",
156
- "test:unit": "jest",
156
+ "test:unit": "sh ../../scripts/dev-capped.sh jest",
157
157
  "pretest:contract": "pnpm --filter @hartl-services/medusa-food-supplements-contracts build",
158
158
  "test:contract": "jest --config jest.contract.config.js --runInBand",
159
159
  "test:integration:modules": "TEST_TYPE=integration:modules NODE_OPTIONS=--experimental-vm-modules jest --config jest.integration.config.js --silent=false --runInBand --forceExit"