@hartl-services/medusa-base 1.0.0 → 2.0.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 (79) hide show
  1. package/.medusa/server/src/admin/index.js +117 -1
  2. package/.medusa/server/src/admin/index.mjs +118 -2
  3. package/.medusa/server/src/documents/plugin-features.js +15 -1
  4. package/.medusa/server/src/documents/templates/financial-document.js +69 -51
  5. package/.medusa/server/src/documents/templates/format.js +44 -2
  6. package/.medusa/server/src/documents/templates/html-factory.js +7 -5
  7. package/.medusa/server/src/documents/templates/html-utils.js +12 -2
  8. package/.medusa/server/src/documents/templates/layout.js +115 -70
  9. package/.medusa/server/src/documents/templates/packing-list.js +10 -8
  10. package/.medusa/server/src/documents/utils/document-merchant.js +5 -5
  11. package/.medusa/server/src/documents/utils/document-render-settings.js +59 -0
  12. package/.medusa/server/src/email/application/handle-invite-event.js +8 -2
  13. package/.medusa/server/src/email/application/handle-password-reset-event.js +16 -4
  14. package/.medusa/server/src/email/domain/order-summary.js +103 -0
  15. package/.medusa/server/src/email/domain/refund-summary.js +43 -0
  16. package/.medusa/server/src/email/domain/return-summary.js +118 -0
  17. package/.medusa/server/src/email/domain/shipment-summary.js +74 -0
  18. package/.medusa/server/src/email/plugin-features.js +9 -1
  19. package/.medusa/server/src/email/rendering/default-templates.js +693 -65
  20. package/.medusa/server/src/email/rendering/load-email-shop-details.js +36 -0
  21. package/.medusa/server/src/email/rendering/render-email.js +16 -3
  22. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +20 -1
  23. package/.medusa/server/src/email/types/template-data.js +155 -2
  24. package/.medusa/server/src/modules/documents/domain/merchant.js +2 -33
  25. package/.medusa/server/src/modules/documents/domain/options.js +1 -7
  26. package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +4 -3
  27. package/.medusa/server/src/public/descriptor.js +3 -1
  28. package/.medusa/server/src/public/shop-master-data.js +7 -7
  29. package/.medusa/server/src/shop-config/build-food-shop-config.js +6 -6
  30. package/.medusa/server/src/shop-config/options.js +5 -15
  31. package/.medusa/server/src/workflows/documents/issue-cancellation-after-refund.js +2 -3
  32. package/.medusa/server/src/workflows/documents/issue-document.js +6 -5
  33. package/.medusa/server/src/workflows/documents/issue-packing-list.js +9 -3
  34. package/.medusa/server/src/workflows/email/dispatch-email-message.js +4 -2
  35. package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +42 -4
  36. package/.medusa/server/src/workflows/email/prepare-order-received-email.js +31 -5
  37. package/.medusa/server/src/workflows/email/prepare-refund-email.js +25 -1
  38. package/.medusa/server/src/workflows/email/prepare-return-email.js +64 -3
  39. package/.medusa/server/src/workflows/email/prepare-shipment-email.js +36 -3
  40. package/README.md +180 -43
  41. package/dist/admin-components/features/plugin-features/components/ImageSettingInput.d.ts +15 -0
  42. package/dist/admin-components/features/plugin-features/components/ImageSettingInput.js +42 -0
  43. package/dist/admin-components/features/plugin-features/components/PluginSettingsForm.js +4 -2
  44. package/dist/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +1 -1
  45. package/dist/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +4 -4
  46. package/dist/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +1 -1
  47. package/dist/admin-components/features/plugin-features/lib/image-upload.d.ts +6 -0
  48. package/dist/admin-components/features/plugin-features/lib/image-upload.js +17 -0
  49. package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +4 -4
  50. package/dist/admin-components/i18n/texts.d.ts +6 -0
  51. package/dist/admin-components/i18n/texts.js +6 -0
  52. package/dist/documents/templates/financial-document.d.ts +3 -3
  53. package/dist/documents/templates/financial-document.js +68 -50
  54. package/dist/documents/templates/format.d.ts +14 -0
  55. package/dist/documents/templates/format.js +43 -1
  56. package/dist/documents/templates/html-factory.d.ts +6 -2
  57. package/dist/documents/templates/html-factory.js +6 -4
  58. package/dist/documents/templates/html-utils.d.ts +6 -0
  59. package/dist/documents/templates/html-utils.js +11 -1
  60. package/dist/documents/templates/layout.d.ts +21 -3
  61. package/dist/documents/templates/layout.js +114 -69
  62. package/dist/documents/templates/packing-list.js +9 -7
  63. package/dist/documents/types/documents.d.ts +11 -16
  64. package/dist/documents/types/html-factory.d.ts +1 -0
  65. package/dist/email/types/shop-configuration.d.ts +25 -0
  66. package/dist/email/types/template-data.d.ts +509 -0
  67. package/dist/email/types/template-data.js +154 -1
  68. package/dist/modules/documents/domain/options.d.ts +1 -9
  69. package/dist/modules/documents/domain/options.js +0 -6
  70. package/dist/public/descriptor.js +2 -0
  71. package/dist/public/shop-master-data.d.ts +5 -6
  72. package/dist/public/shop-master-data.js +6 -6
  73. package/dist/public/types.d.ts +2 -1
  74. package/dist/shop-config/build-food-shop-config.js +5 -5
  75. package/dist/shop-config/options.d.ts +13 -8
  76. package/dist/shop-config/options.js +4 -14
  77. package/package.json +2 -2
  78. package/dist/modules/documents/domain/merchant.d.ts +0 -22
  79. package/dist/modules/documents/domain/merchant.js +0 -85
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EmailRenderDataSchemas = exports.EmailPersistedDataSchemas = void 0;
3
+ exports.EmailRenderDataSchemas = exports.EmailPersistedDataSchemas = exports.RefundSummarySchema = exports.ReturnReceivedSummarySchema = exports.ReturnSummarySchema = exports.ShipmentSummarySchema = exports.OrderSummarySchema = void 0;
4
4
  const zod_1 = require("@medusajs/framework/zod");
5
5
  const affiliate_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/affiliate");
6
6
  const NonEmptyStringSchema = zod_1.z.string().trim().min(1);
@@ -9,15 +9,146 @@ const DocumentsFactFingerprintSchema = zod_1.z
9
9
  .string()
10
10
  .regex(/^[a-f0-9]{64}$/)
11
11
  .optional();
12
+ const AmountSchema = zod_1.z.string().regex(/^-?\d+(\.\d+)?$/);
13
+ const OptionalTextSchema = zod_1.z.string().nullable();
14
+ const OrderSummaryAddressSchema = zod_1.z
15
+ .object({
16
+ first_name: OptionalTextSchema,
17
+ last_name: OptionalTextSchema,
18
+ company: OptionalTextSchema,
19
+ address_1: OptionalTextSchema,
20
+ address_2: OptionalTextSchema,
21
+ postal_code: OptionalTextSchema,
22
+ city: OptionalTextSchema,
23
+ country_code: OptionalTextSchema,
24
+ })
25
+ .strict();
26
+ /**
27
+ * Snapshot of an order when it was placed. Amounts are Medusa's gross
28
+ * decimal strings: item and shipping rows before discounts, so
29
+ * `original_item_total + original_shipping_total - discount_total` is the
30
+ * `total`, which already contains `tax_total`.
31
+ */
32
+ exports.OrderSummarySchema = zod_1.z
33
+ .object({
34
+ display_id: zod_1.z.number().int().nullable(),
35
+ /** The order's e-mail address; `null` when the order has none. */
36
+ email: OptionalTextSchema,
37
+ /** When the order was placed (ISO 8601). */
38
+ placed_at: zod_1.z.string().datetime(),
39
+ currency_code: NonEmptyStringSchema,
40
+ items: zod_1.z.array(zod_1.z
41
+ .object({
42
+ title: NonEmptyStringSchema,
43
+ variant_title: OptionalTextSchema,
44
+ quantity: zod_1.z.number().positive(),
45
+ original_total: AmountSchema,
46
+ })
47
+ .strict()),
48
+ shipping_method_names: zod_1.z.array(NonEmptyStringSchema),
49
+ original_item_total: AmountSchema,
50
+ original_shipping_total: AmountSchema,
51
+ discount_total: AmountSchema,
52
+ tax_total: AmountSchema,
53
+ total: AmountSchema,
54
+ billing_address: OrderSummaryAddressSchema.nullable(),
55
+ shipping_address: OrderSummaryAddressSchema.nullable(),
56
+ })
57
+ .strict();
58
+ /**
59
+ * Snapshot of one shipment when it was shipped: the parcel's items (title
60
+ * of the order line, shipped quantity), tracking numbers and links as
61
+ * entered at shipping, and whether more of the order is still to be shipped.
62
+ */
63
+ exports.ShipmentSummarySchema = zod_1.z
64
+ .object({
65
+ display_id: zod_1.z.number().int().nullable(),
66
+ shipping_method_name: OptionalTextSchema,
67
+ tracking: zod_1.z.array(zod_1.z
68
+ .object({
69
+ number: OptionalTextSchema,
70
+ url: OptionalTextSchema,
71
+ })
72
+ .strict()),
73
+ items: zod_1.z.array(zod_1.z
74
+ .object({
75
+ title: NonEmptyStringSchema,
76
+ variant_title: OptionalTextSchema,
77
+ quantity: zod_1.z.number().positive(),
78
+ })
79
+ .strict()),
80
+ delivery_address: OrderSummaryAddressSchema.nullable(),
81
+ more_to_follow: zod_1.z.boolean(),
82
+ })
83
+ .strict();
84
+ /**
85
+ * Snapshot of a return when it was requested: the return and order numbers,
86
+ * the items to send back (title of the order line, quantity, reason label),
87
+ * the return shipping option and where to send the parcel.
88
+ */
89
+ exports.ReturnSummarySchema = zod_1.z
90
+ .object({
91
+ display_id: zod_1.z.number().int().nullable(),
92
+ order_display_id: zod_1.z.number().int().nullable(),
93
+ shipping_method_name: OptionalTextSchema,
94
+ items: zod_1.z.array(zod_1.z
95
+ .object({
96
+ title: NonEmptyStringSchema,
97
+ variant_title: OptionalTextSchema,
98
+ quantity: zod_1.z.number().positive(),
99
+ reason: OptionalTextSchema,
100
+ })
101
+ .strict()),
102
+ return_address: OrderSummaryAddressSchema.nullable(),
103
+ })
104
+ .strict();
105
+ /**
106
+ * Snapshot of a return when it was completely received: the return and
107
+ * order numbers, when it arrived and the received items (title of the order
108
+ * line, received quantity and how much of it arrived damaged).
109
+ */
110
+ exports.ReturnReceivedSummarySchema = zod_1.z
111
+ .object({
112
+ display_id: zod_1.z.number().int().nullable(),
113
+ order_display_id: zod_1.z.number().int().nullable(),
114
+ received_at: zod_1.z.string().datetime().nullable(),
115
+ items: zod_1.z.array(zod_1.z
116
+ .object({
117
+ title: NonEmptyStringSchema,
118
+ variant_title: OptionalTextSchema,
119
+ quantity: zod_1.z.number().positive(),
120
+ damaged_quantity: zod_1.z.number().nonnegative(),
121
+ })
122
+ .strict()),
123
+ })
124
+ .strict();
125
+ /**
126
+ * Snapshot of one refund when its Documents fact arrived: the order number,
127
+ * the refunded gross amount, when it was refunded and the refund reason
128
+ * chosen in the Admin.
129
+ */
130
+ exports.RefundSummarySchema = zod_1.z
131
+ .object({
132
+ order_display_id: zod_1.z.number().int().nullable(),
133
+ amount: AmountSchema,
134
+ currency_code: NonEmptyStringSchema,
135
+ refunded_at: zod_1.z.string().datetime().nullable(),
136
+ reason: OptionalTextSchema,
137
+ })
138
+ .strict();
12
139
  exports.EmailPersistedDataSchemas = {
13
140
  "order.received": zod_1.z
14
141
  .object({
15
142
  order_id: NonEmptyStringSchema,
143
+ // Optional: messages stored before the summary existed have none.
144
+ summary: exports.OrderSummarySchema.optional(),
16
145
  })
17
146
  .strict(),
18
147
  "order.received.admin": zod_1.z
19
148
  .object({
20
149
  order_id: NonEmptyStringSchema,
150
+ // Optional: messages stored before the summary existed have none.
151
+ summary: exports.OrderSummarySchema.optional(),
21
152
  })
22
153
  .strict(),
23
154
  "order.shipped": zod_1.z
@@ -25,24 +156,38 @@ exports.EmailPersistedDataSchemas = {
25
156
  order_id: NonEmptyStringSchema,
26
157
  fulfillment_id: NonEmptyStringSchema,
27
158
  documents_fact_fingerprint: DocumentsFactFingerprintSchema,
159
+ // Optional: messages stored before the snapshot existed have none.
160
+ shipment: exports.ShipmentSummarySchema.optional(),
161
+ /** The invoice PDF is attached (Documents chose this shipment). */
162
+ invoice_attached: zod_1.z.boolean().optional(),
28
163
  })
29
164
  .strict(),
30
165
  "order.canceled": zod_1.z
31
166
  .object({
32
167
  order_id: NonEmptyStringSchema,
33
168
  documents_fact_fingerprint: DocumentsFactFingerprintSchema,
169
+ // Optional: messages stored before the snapshot existed have none.
170
+ summary: exports.OrderSummarySchema.optional(),
171
+ /** When the order was canceled (ISO 8601). */
172
+ canceled_at: zod_1.z.string().datetime().optional(),
173
+ /** A cancellation document PDF is attached (from Documents). */
174
+ cancellation_attached: zod_1.z.boolean().optional(),
34
175
  })
35
176
  .strict(),
36
177
  "order.return_requested": zod_1.z
37
178
  .object({
38
179
  order_id: NonEmptyStringSchema,
39
180
  return_id: NonEmptyStringSchema,
181
+ // Optional: messages stored before the snapshot existed have none.
182
+ summary: exports.ReturnSummarySchema.optional(),
40
183
  })
41
184
  .strict(),
42
185
  "order.return_received": zod_1.z
43
186
  .object({
44
187
  order_id: NonEmptyStringSchema,
45
188
  return_id: NonEmptyStringSchema,
189
+ // Optional: messages stored before the snapshot existed have none.
190
+ summary: exports.ReturnReceivedSummarySchema.optional(),
46
191
  })
47
192
  .strict(),
48
193
  "order.refunded": zod_1.z
@@ -50,6 +195,10 @@ exports.EmailPersistedDataSchemas = {
50
195
  order_id: NonEmptyStringSchema,
51
196
  refund_id: NonEmptyStringSchema,
52
197
  documents_fact_fingerprint: DocumentsFactFingerprintSchema,
198
+ // Optional: messages stored before the snapshot existed have none.
199
+ summary: exports.RefundSummarySchema.optional(),
200
+ /** A cancellation document PDF is attached (from Documents). */
201
+ cancellation_attached: zod_1.z.boolean().optional(),
53
202
  })
54
203
  .strict(),
55
204
  "auth.password_reset": zod_1.z
@@ -71,8 +220,12 @@ exports.EmailRenderDataSchemas = {
71
220
  ...exports.EmailPersistedDataSchemas,
72
221
  "auth.password_reset": exports.EmailPersistedDataSchemas["auth.password_reset"].extend({
73
222
  reset_url: zod_1.z.string().url(),
223
+ /** How long the reset link is valid, from the token; `undefined` if unknown. */
224
+ valid_minutes: zod_1.z.number().int().positive().optional(),
74
225
  }),
75
226
  "user.invite": exports.EmailPersistedDataSchemas["user.invite"].extend({
76
227
  invite_url: zod_1.z.string().url(),
228
+ /** When the invite expires (ISO 8601), from the current invite. */
229
+ expires_at: zod_1.z.string().datetime().optional(),
77
230
  }),
78
231
  };
@@ -1,6 +1,5 @@
1
1
  import type { DocumentHtmlFactory } from "../../../documents/types/html-factory";
2
- import type { DocumentMerchantOption, DocumentPdfOptions } from "../../../documents/types/documents";
3
- import { type DocumentMerchantValues } from "./merchant";
2
+ import type { DocumentPdfOptions } from "../../../documents/types/documents";
4
3
  /** Used when the `documents.numbering.timeZone` option is not given. */
5
4
  export declare const DEFAULT_NUMBERING_TIME_ZONE = "Europe/Vienna";
6
5
  /** The `documents` namespace of the base plugin options. */
@@ -10,11 +9,6 @@ export type DocumentPluginOptions = {
10
9
  * `createDefaultDocumentHtmlFactory` with the `numbering.timeZone`.
11
10
  */
12
11
  htmlFactory?: DocumentHtmlFactory;
13
- /**
14
- * Fallback for the shop master data of the base plugin: used per field
15
- * only where no value is stored there.
16
- */
17
- merchant?: DocumentMerchantOption;
18
12
  numbering?: {
19
13
  timeZone?: string;
20
14
  };
@@ -22,8 +16,6 @@ export type DocumentPluginOptions = {
22
16
  };
23
17
  export type ResolvedDocumentPluginOptions = {
24
18
  htmlFactory: DocumentHtmlFactory;
25
- /** The `merchant` option keyed by shop master data field. */
26
- merchantFallback: DocumentMerchantValues;
27
19
  numbering: {
28
20
  timeZone: string;
29
21
  };
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveDocumentPluginOptions = exports.DEFAULT_NUMBERING_TIME_ZONE = void 0;
4
4
  const utils_1 = require("@medusajs/framework/utils");
5
5
  const html_factory_1 = require("../../../documents/templates/html-factory");
6
- const merchant_1 = require("./merchant");
7
6
  /** Used when the `documents.numbering.timeZone` option is not given. */
8
7
  exports.DEFAULT_NUMBERING_TIME_ZONE = "Europe/Vienna";
9
8
  const resolveDocumentPluginOptions = (options) => {
@@ -24,14 +23,9 @@ const resolveDocumentPluginOptions = (options) => {
24
23
  typeof options.htmlFactory !== "function") {
25
24
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "The documents 'htmlFactory' option must be a function.");
26
25
  }
27
- if (options?.merchant !== undefined &&
28
- (typeof options.merchant !== "object" || options.merchant === null)) {
29
- throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "The documents 'merchant' option must be an object.");
30
- }
31
26
  const executablePath = options?.pdf?.executablePath?.trim() || "google-chrome";
32
27
  return {
33
28
  htmlFactory: options?.htmlFactory ?? (0, html_factory_1.createDefaultDocumentHtmlFactory)({ timeZone }),
34
- merchantFallback: (0, merchant_1.merchantOptionToFallback)(options?.merchant),
35
29
  numbering: { timeZone },
36
30
  pdf: { executablePath },
37
31
  };
@@ -11,6 +11,7 @@ const SETTING_TYPES = new Set([
11
11
  "boolean",
12
12
  "secret",
13
13
  "select",
14
+ "image",
14
15
  ]);
15
16
  const ACTION_MODES = new Set(["sync", "background"]);
16
17
  const ADMIN_PAGE_PREFIX = "/settings/hartl-services/";
@@ -33,6 +34,7 @@ const defaultMatchesType = (setting) => {
33
34
  case "string":
34
35
  return hasText(value);
35
36
  case "url":
37
+ case "image":
36
38
  return typeof value === "string" && isHttpUrl(value);
37
39
  case "number":
38
40
  return typeof value === "number" && Number.isFinite(value);
@@ -1,11 +1,10 @@
1
1
  import type { MedusaContainer } from "@medusajs/framework/types";
2
- import type { PluginSettingsFallback } from "./types";
3
2
  /**
4
3
  * The base's own descriptor: the shop's master data (Firmenangaben), edited
5
4
  * on the generic plugin page like any plugin's settings. It cannot be
6
5
  * switched off and declares no features.
7
6
  */
8
- export declare const shopMasterDataDescriptor: import("./types").PluginFeatureDescriptor<string, "legal_name" | "address_line1" | "address_line2" | "postal_code" | "city" | "country_code" | "vat_number" | "email" | "phone" | "website" | "iban" | "bic" | "company_register_number" | "company_register_court", never>;
7
+ export declare const shopMasterDataDescriptor: import("./types").PluginFeatureDescriptor<string, "country_code" | "legal_name" | "address_line1" | "address_line2" | "postal_code" | "city" | "vat_number" | "email" | "phone" | "website" | "iban" | "bic" | "company_register_number" | "company_register_court", never>;
9
8
  /** Keys of the shop master data settings. */
10
9
  export type ShopMasterDataKey = "legal_name" | "address_line1" | "address_line2" | "postal_code" | "city" | "country_code" | "vat_number" | "email" | "phone" | "website" | "iban" | "bic" | "company_register_number" | "company_register_court";
11
10
  /** Every key that has a value (stored > fallback). All values are strings. */
@@ -18,8 +17,8 @@ export type ResolvedShopMasterData = {
18
17
  missing: ShopMasterDataKey[];
19
18
  };
20
19
  /**
21
- * The shop's master data for server-side code. Precedence per key: stored in
22
- * the Admin > `fallback` (e.g. a plugin's own medusa-config option). A
23
- * missing base plugin fails with `UNEXPECTED_STATE`; read failures propagate.
20
+ * The shop's master data for server-side code, exactly as stored in the
21
+ * Admin: there is no config fallback. A missing base plugin fails with
22
+ * `UNEXPECTED_STATE`; read failures propagate.
24
23
  */
25
- export declare function getShopMasterData(container: MedusaContainer, fallback?: PluginSettingsFallback<ShopMasterDataKey>): Promise<ResolvedShopMasterData>;
24
+ export declare function getShopMasterData(container: MedusaContainer): Promise<ResolvedShopMasterData>;
@@ -12,7 +12,7 @@ const settings_1 = require("./settings");
12
12
  exports.shopMasterDataDescriptor = (0, descriptor_1.definePluginFeatures)({
13
13
  plugin_key: "shop",
14
14
  label: "Shop-Stammdaten",
15
- description: "Firmenangaben des Shops für Rechnungen, Dokumente und Steuerprüfungen.",
15
+ description: "Firmenangaben des Shops für E-Mails, Rechnungen, Dokumente und Steuerprüfungen.",
16
16
  switchable: false,
17
17
  features: [],
18
18
  settings: [
@@ -74,12 +74,12 @@ exports.shopMasterDataDescriptor = (0, descriptor_1.definePluginFeatures)({
74
74
  ],
75
75
  });
76
76
  /**
77
- * The shop's master data for server-side code. Precedence per key: stored in
78
- * the Admin > `fallback` (e.g. a plugin's own medusa-config option). A
79
- * missing base plugin fails with `UNEXPECTED_STATE`; read failures propagate.
77
+ * The shop's master data for server-side code, exactly as stored in the
78
+ * Admin: there is no config fallback. A missing base plugin fails with
79
+ * `UNEXPECTED_STATE`; read failures propagate.
80
80
  */
81
- async function getShopMasterData(container, fallback) {
82
- const resolved = await (0, settings_1.getPluginSettings)(container, exports.shopMasterDataDescriptor, fallback);
81
+ async function getShopMasterData(container) {
82
+ const resolved = await (0, settings_1.getPluginSettings)(container, exports.shopMasterDataDescriptor);
83
83
  const values = {};
84
84
  for (const [key, value] of Object.entries(resolved.values)) {
85
85
  if (typeof value === "string") {
@@ -30,7 +30,8 @@ export type PluginFeatureDefinition<K extends string = string> = {
30
30
  readonly default_enabled: boolean;
31
31
  readonly parent_key?: K;
32
32
  };
33
- export type PluginSettingType = "string" | "url" | "number" | "boolean" | "secret" | "select";
33
+ /** `image`: public http(s) URL of an image the Admin uploads (PNG/JPEG). */
34
+ export type PluginSettingType = "string" | "url" | "number" | "boolean" | "secret" | "select" | "image";
34
35
  /** One allowed value of a `"select"` setting. */
35
36
  export type PluginSettingOption = {
36
37
  readonly value: string;
@@ -18,7 +18,6 @@ const options_1 = require("./options");
18
18
  */
19
19
  function buildFoodShopConfig(options, environment = process.env) {
20
20
  (0, options_1.assertFoodShopConfigOptions)(options);
21
- const { merchant } = options;
22
21
  if (options.loadEnvFile !== false) {
23
22
  (0, utils_1.loadEnv)(process.env.NODE_ENV || "development", process.cwd());
24
23
  }
@@ -144,6 +143,8 @@ function buildFoodShopConfig(options, environment = process.env) {
144
143
  email: {
145
144
  jwtSecret: env.JWT_SECRET,
146
145
  shop: {
146
+ // The Admin dashboard mounts under admin.path ("/app").
147
+ adminOrderUrl: ({ orderId }) => new URL(`/app/orders/${encodeURIComponent(orderId)}`, backendOrigin).toString(),
147
148
  displayName: options.displayName,
148
149
  affiliatePortalUrl: (0, config_1.shopUrl)(storefrontOrigin, `${storefrontPath}/partnerportal`),
149
150
  // The Admin dashboard mounts "/invite" under admin.path ("/app").
@@ -155,12 +156,11 @@ function buildFoodShopConfig(options, environment = process.env) {
155
156
  orderNotificationEmail: options.orderNotificationEmail,
156
157
  // The e-mail module appends "#token=<token>".
157
158
  passwordResetUrl: () => (0, config_1.shopUrl)(storefrontOrigin, `${storefrontPath}/reset-password`),
159
+ timeZone: options.timeZone,
158
160
  },
159
161
  },
160
162
  documents: {
161
- // Fallback only: the shop master data in the Admin ("Hartl Services
162
- // Plugins" -> Shop-Stammdaten) wins per field.
163
- merchant,
163
+ htmlFactory: options.documentHtmlFactory,
164
164
  numbering: {
165
165
  // Chooses the annual document-number period; matches the date the
166
166
  // HTML factory prints.
@@ -175,7 +175,7 @@ function buildFoodShopConfig(options, environment = process.env) {
175
175
  },
176
176
  {
177
177
  resolve: "@hartl-services/medusa-tax-compliance",
178
- options: { merchant },
178
+ options: {},
179
179
  },
180
180
  {
181
181
  resolve: "@hartl-services/medusa-free-shipping",
@@ -1,23 +1,21 @@
1
- import type { DocumentMerchantOption } from "../documents/types";
1
+ import type { DocumentHtmlFactory } from "../documents/types";
2
2
  /**
3
3
  * The shop-specific facts of a food shop. Deployment values and secrets are
4
4
  * not options: they come from the environment (see the README).
5
5
  */
6
6
  export type FoodShopConfigOptions = Readonly<{
7
7
  /**
8
- * Fallback for the shop master data (Admin -> "Hartl Services Plugins" ->
9
- * Shop-Stammdaten) that documents and tax compliance read per field. Its
10
- * `legalName` is also the seeded store name.
8
+ * Shop name in the branding of the default e-mail templates (and the store
9
+ * name of a shop's initial seed). Company name, address and UID are not
10
+ * options: they come only from the Shop-Stammdaten in the Admin.
11
11
  */
12
- merchant: DocumentMerchantOption;
13
- /** Shop name in the branding of the default e-mail templates. */
14
12
  displayName: string;
15
13
  /** Receives the shop's order notification e-mails. */
16
14
  orderNotificationEmail: string;
17
15
  /**
18
16
  * IANA time zone (e.g. "Europe/Vienna"): chooses the annual document-number
19
- * period, matches the dates printed on documents, and schedules the
20
- * affiliate consignment reminders.
17
+ * period, matches the dates printed on documents and e-mails, and
18
+ * schedules the affiliate consignment reminders.
21
19
  */
22
20
  timeZone: string;
23
21
  /**
@@ -26,6 +24,13 @@ export type FoodShopConfigOptions = Readonly<{
26
24
  * affiliate referral links to `/<code>`.
27
25
  */
28
26
  storefrontCountryCode: string;
27
+ /**
28
+ * Replaces the default document markup (documents `htmlFactory`), e.g.
29
+ * `createDefaultDocumentHtmlFactory({ timeZone, theme })` from
30
+ * `@hartl-services/medusa-base/config` with the shop's own CSS and
31
+ * watermark. Omit it for the plugin's default look.
32
+ */
33
+ documentHtmlFactory?: DocumentHtmlFactory;
29
34
  /**
30
35
  * Load `.env.<NODE_ENV>` from the working directory into `process.env`
31
36
  * like `medusa-config.ts` does by default. Pass `false` when the
@@ -45,24 +45,14 @@ const requireObject = (value, name) => {
45
45
  */
46
46
  function assertFoodShopConfigOptions(options) {
47
47
  const candidate = requireObject(options, "options");
48
- const merchant = requireObject(candidate.merchant, "merchant");
49
- requireText(merchant.legalName, "merchant.legalName");
50
- requireCountryCode(merchant.countryCode, "merchant.countryCode");
51
- if (merchant.vatNumber !== undefined) {
52
- requireText(merchant.vatNumber, "merchant.vatNumber");
53
- }
54
- const address = requireObject(merchant.registeredAddress, "merchant.registeredAddress");
55
- requireText(address.address1, "merchant.registeredAddress.address1");
56
- if (address.address2 !== undefined) {
57
- requireText(address.address2, "merchant.registeredAddress.address2");
58
- }
59
- requireText(address.city, "merchant.registeredAddress.city");
60
- requireText(address.postalCode, "merchant.registeredAddress.postalCode");
61
- requireCountryCode(address.countryCode, "merchant.registeredAddress.countryCode");
62
48
  requireText(candidate.displayName, "displayName");
63
49
  requireEmail(candidate.orderNotificationEmail, "orderNotificationEmail");
64
50
  requireTimeZone(candidate.timeZone, "timeZone");
65
51
  requireCountryCode(candidate.storefrontCountryCode, "storefrontCountryCode");
52
+ if (candidate.documentHtmlFactory !== undefined &&
53
+ typeof candidate.documentHtmlFactory !== "function") {
54
+ fail("documentHtmlFactory", "a function");
55
+ }
66
56
  if (candidate.loadEnvFile !== undefined &&
67
57
  typeof candidate.loadEnvFile !== "boolean") {
68
58
  fail("loadEnvFile", "a boolean");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hartl-services/medusa-base",
3
- "version": "1.0.0",
3
+ "version": "2.0.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": [
@@ -88,7 +88,7 @@
88
88
  "mime-types": "3.0.1",
89
89
  "nodemailer": "6.10.1",
90
90
  "ulid": "^2.4.0",
91
- "@hartl-services/medusa-food-supplements-contracts": "^0.16.0"
91
+ "@hartl-services/medusa-food-supplements-contracts": "^0.17.0"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@medusajs/admin-sdk": "2.19.0",
@@ -1,22 +0,0 @@
1
- import type { DocumentMerchant, DocumentMerchantOption } from "../../../documents/types/documents";
2
- /** Shop master data keys (base plugin, `plugin_key: "shop"`) documents read. */
3
- export type DocumentMerchantKey = keyof DocumentMerchant;
4
- export type DocumentMerchantValues = Partial<Record<DocumentMerchantKey, string>>;
5
- /** Without these a financial document or packing list has no legal sender. */
6
- export declare const DOCUMENT_MERCHANT_REQUIRED_FIELDS: readonly ["legal_name", "address_line1", "postal_code", "city", "country_code"];
7
- /** Development placeholder UID: never printed on a document. */
8
- export declare const MERCHANT_VAT_PLACEHOLDER = "ATU00000000";
9
- /**
10
- * The plugin's `merchant` option as a per-field fallback for the shop master
11
- * data. Empty values are left out so they never shadow a declared default.
12
- */
13
- export declare const merchantOptionToFallback: (option: DocumentMerchantOption | undefined) => DocumentMerchantValues;
14
- export type DocumentMerchantResolution = {
15
- merchant: DocumentMerchant;
16
- missing: [];
17
- } | {
18
- merchant: null;
19
- missing: DocumentMerchantKey[];
20
- };
21
- /** Maps resolved shop master data to the render input's `merchant`. */
22
- export declare const toDocumentMerchant: (values: DocumentMerchantValues) => DocumentMerchantResolution;
@@ -1,85 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toDocumentMerchant = exports.merchantOptionToFallback = exports.MERCHANT_VAT_PLACEHOLDER = exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS = void 0;
4
- /** Without these a financial document or packing list has no legal sender. */
5
- exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS = [
6
- "legal_name",
7
- "address_line1",
8
- "postal_code",
9
- "city",
10
- "country_code",
11
- ];
12
- /** Development placeholder UID: never printed on a document. */
13
- exports.MERCHANT_VAT_PLACEHOLDER = "ATU00000000";
14
- const text = (value) => {
15
- if (typeof value !== "string") {
16
- return null;
17
- }
18
- const trimmed = value.trim();
19
- return trimmed ? trimmed : null;
20
- };
21
- const vatNumber = (value) => {
22
- const normalized = text(value);
23
- return normalized &&
24
- normalized.replace(/\s+/g, "").toUpperCase() === exports.MERCHANT_VAT_PLACEHOLDER
25
- ? null
26
- : normalized;
27
- };
28
- /**
29
- * The plugin's `merchant` option as a per-field fallback for the shop master
30
- * data. Empty values are left out so they never shadow a declared default.
31
- */
32
- const merchantOptionToFallback = (option) => {
33
- if (!option) {
34
- return {};
35
- }
36
- const address = option.registeredAddress;
37
- const entries = [
38
- ["legal_name", option.legalName],
39
- ["address_line1", address?.address1],
40
- ["address_line2", address?.address2],
41
- ["postal_code", address?.postalCode],
42
- ["city", address?.city],
43
- [
44
- "country_code",
45
- text(address?.countryCode ?? option.countryCode)?.toLowerCase(),
46
- ],
47
- ["vat_number", option.vatNumber],
48
- ];
49
- const fallback = {};
50
- for (const [key, value] of entries) {
51
- const normalized = text(value);
52
- if (normalized) {
53
- fallback[key] = normalized;
54
- }
55
- }
56
- return fallback;
57
- };
58
- exports.merchantOptionToFallback = merchantOptionToFallback;
59
- /** Maps resolved shop master data to the render input's `merchant`. */
60
- const toDocumentMerchant = (values) => {
61
- const missing = exports.DOCUMENT_MERCHANT_REQUIRED_FIELDS.filter((key) => !text(values[key]));
62
- if (missing.length > 0) {
63
- return { merchant: null, missing };
64
- }
65
- return {
66
- merchant: {
67
- legal_name: text(values.legal_name),
68
- address_line1: text(values.address_line1),
69
- address_line2: text(values.address_line2),
70
- postal_code: text(values.postal_code),
71
- city: text(values.city),
72
- country_code: text(values.country_code).toLowerCase(),
73
- vat_number: vatNumber(values.vat_number),
74
- email: text(values.email),
75
- phone: text(values.phone),
76
- website: text(values.website),
77
- iban: text(values.iban),
78
- bic: text(values.bic),
79
- company_register_number: text(values.company_register_number),
80
- company_register_court: text(values.company_register_court),
81
- },
82
- missing: [],
83
- };
84
- };
85
- exports.toDocumentMerchant = toDocumentMerchant;