@hartl-services/medusa-base 0.4.0 → 1.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.
Files changed (258) hide show
  1. package/.medusa/server/src/admin/index.js +1657 -111
  2. package/.medusa/server/src/admin/index.mjs +1647 -101
  3. package/.medusa/server/src/api/admin/email/messages/[id]/resends/route.js +24 -0
  4. package/.medusa/server/src/api/admin/email/messages/[id]/route.js +12 -0
  5. package/.medusa/server/src/api/admin/email/messages/route.js +12 -0
  6. package/.medusa/server/src/api/admin/email/terms-versions/[id]/download/route.js +16 -0
  7. package/.medusa/server/src/api/admin/email/terms-versions/route.js +47 -0
  8. package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/download/route.js +21 -0
  9. package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/regenerate/route.js +38 -0
  10. package/.medusa/server/src/api/admin/orders/[id]/documents/regenerate/route.js +32 -0
  11. package/.medusa/server/src/api/admin/orders/[id]/documents/route.js +36 -0
  12. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/download/route.js +24 -0
  13. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/retry/route.js +44 -0
  14. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/route.js +82 -0
  15. package/.medusa/server/src/api/document-download-response.js +16 -0
  16. package/.medusa/server/src/api/document-response.js +23 -0
  17. package/.medusa/server/src/api/files/[asset]/route.js +24 -0
  18. package/.medusa/server/src/api/files/file-response.js +43 -0
  19. package/.medusa/server/src/api/files/gateway-options.js +31 -0
  20. package/.medusa/server/src/api/files/private/[token]/route.js +29 -0
  21. package/.medusa/server/src/api/files/upload/[token]/route.js +66 -0
  22. package/.medusa/server/src/api/middlewares.js +168 -1
  23. package/.medusa/server/src/api/packing-list-response.js +22 -0
  24. package/.medusa/server/src/api/route.js +27 -0
  25. package/.medusa/server/src/api/store/documents/[id]/download/route.js +21 -0
  26. package/.medusa/server/src/api/store/documents/[id]/route.js +15 -0
  27. package/.medusa/server/src/api/store/documents/route.js +11 -0
  28. package/.medusa/server/src/api/terms-download-response.js +12 -0
  29. package/.medusa/server/src/documents/config/index.js +18 -0
  30. package/.medusa/server/src/documents/paths/documents.js +6 -0
  31. package/.medusa/server/src/documents/paths/index.js +18 -0
  32. package/.medusa/server/src/documents/plugin-features.js +38 -0
  33. package/.medusa/server/src/documents/schemas/documents.js +18 -0
  34. package/.medusa/server/src/documents/schemas/index.js +18 -0
  35. package/.medusa/server/src/documents/scripts/verify-documents-file-storage.js +55 -0
  36. package/.medusa/server/src/documents/templates/financial-document.js +160 -0
  37. package/.medusa/server/src/documents/templates/format.js +73 -0
  38. package/.medusa/server/src/documents/templates/html-factory.js +42 -0
  39. package/.medusa/server/src/documents/templates/html-utils.js +23 -0
  40. package/.medusa/server/src/documents/templates/layout.js +142 -0
  41. package/.medusa/server/src/documents/templates/packing-list.js +52 -0
  42. package/.medusa/server/src/documents/types/admin-api.js +3 -0
  43. package/.medusa/server/src/documents/types/document-summary.js +3 -0
  44. package/.medusa/server/src/documents/types/documents.js +3 -0
  45. package/.medusa/server/src/documents/types/html-factory.js +6 -0
  46. package/.medusa/server/src/documents/types/index.js +22 -0
  47. package/.medusa/server/src/documents/types/store-api.js +3 -0
  48. package/.medusa/server/src/documents/utils/document-merchant.js +22 -0
  49. package/.medusa/server/src/documents/utils/document-render-settings.js +59 -0
  50. package/.medusa/server/src/documents/utils/html-to-pdf.js +61 -0
  51. package/.medusa/server/src/documents/utils/private-file-storage.js +64 -0
  52. package/.medusa/server/src/documents/utils/resolve-services.js +7 -0
  53. package/.medusa/server/src/email/application/handle-invite-event.js +318 -0
  54. package/.medusa/server/src/email/application/handle-password-reset-event.js +366 -0
  55. package/.medusa/server/src/email/config/index.js +84 -0
  56. package/.medusa/server/src/email/config/notification-module.js +20 -0
  57. package/.medusa/server/src/email/domain/business-keys.js +50 -0
  58. package/.medusa/server/src/email/domain/documents-fact-fingerprint.js +7 -0
  59. package/.medusa/server/src/email/domain/event-schemas.js +36 -0
  60. package/.medusa/server/src/email/domain/failure-classification.js +53 -0
  61. package/.medusa/server/src/email/domain/order-summary.js +103 -0
  62. package/.medusa/server/src/email/domain/refund-summary.js +43 -0
  63. package/.medusa/server/src/email/domain/return-summary.js +118 -0
  64. package/.medusa/server/src/email/domain/shipment-summary.js +74 -0
  65. package/.medusa/server/src/email/plugin-features.js +39 -0
  66. package/.medusa/server/src/email/rendering/default-templates.js +719 -0
  67. package/.medusa/server/src/email/rendering/load-email-shop-details.js +36 -0
  68. package/.medusa/server/src/email/rendering/render-email.js +50 -0
  69. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +67 -0
  70. package/.medusa/server/src/email/types/email-kinds.js +18 -0
  71. package/.medusa/server/src/email/types/index.js +21 -0
  72. package/.medusa/server/src/email/types/shop-configuration.js +3 -0
  73. package/.medusa/server/src/email/types/smtp-configuration.js +3 -0
  74. package/.medusa/server/src/email/types/template-data.js +232 -0
  75. package/.medusa/server/src/files/config/index.js +256 -0
  76. package/.medusa/server/src/files/gateway/index.js +115 -0
  77. package/.medusa/server/src/files/types/index.js +6 -0
  78. package/.medusa/server/src/files/types/public.js +6 -0
  79. package/.medusa/server/src/jobs/document-fact-publication-reconciliation.js +18 -0
  80. package/.medusa/server/src/jobs/document-resolution-reconciliation.js +20 -0
  81. package/.medusa/server/src/jobs/packing-list-reconciliation.js +49 -0
  82. package/.medusa/server/src/jobs/reconcile-email-attempts.js +63 -0
  83. package/.medusa/server/src/jobs/reconcile-pending-email-messages.js +41 -0
  84. package/.medusa/server/src/jobs/retry-email-messages.js +27 -0
  85. package/.medusa/server/src/migration-scripts/dismiss-orphaned-cart-promotion-links.js +26 -0
  86. package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +97 -0
  87. package/.medusa/server/src/modules/documents/domain/document-number.js +36 -0
  88. package/.medusa/server/src/modules/documents/domain/document-resolution.js +24 -0
  89. package/.medusa/server/src/modules/documents/domain/issuance-rules.js +37 -0
  90. package/.medusa/server/src/modules/documents/domain/merchant.js +86 -0
  91. package/.medusa/server/src/modules/documents/domain/options.js +40 -0
  92. package/.medusa/server/src/modules/documents/domain/packing-list.js +90 -0
  93. package/.medusa/server/src/modules/documents/domain/snapshot-normalization.js +17 -0
  94. package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +66 -0
  95. package/.medusa/server/src/modules/documents/index.js +15 -0
  96. package/.medusa/server/src/modules/documents/loaders/assert-private-file-storage.js +20 -0
  97. package/.medusa/server/src/modules/documents/migrations/Migration20260827061749.js +75 -0
  98. package/.medusa/server/src/modules/documents/migrations/Migration20260830111207.js +28 -0
  99. package/.medusa/server/src/modules/documents/models/document-artifact.js +25 -0
  100. package/.medusa/server/src/modules/documents/models/document-number-sequence.js +29 -0
  101. package/.medusa/server/src/modules/documents/models/document.js +82 -0
  102. package/.medusa/server/src/modules/documents/models/order-cancellation-resolution.js +51 -0
  103. package/.medusa/server/src/modules/documents/models/packing-list-revision.js +36 -0
  104. package/.medusa/server/src/modules/documents/models/packing-list.js +27 -0
  105. package/.medusa/server/src/modules/documents/models/refund-document-resolution.js +66 -0
  106. package/.medusa/server/src/modules/documents/models/shipment-invoice-assignment.js +51 -0
  107. package/.medusa/server/src/modules/documents/models/shipment-invoice-coordinator.js +44 -0
  108. package/.medusa/server/src/modules/documents/service.js +1701 -0
  109. package/.medusa/server/src/modules/email/domain/attachment.js +25 -0
  110. package/.medusa/server/src/modules/email/domain/message-state.js +69 -0
  111. package/.medusa/server/src/modules/email/index.js +13 -0
  112. package/.medusa/server/src/modules/email/migrations/Migration20260826173422.js +32 -0
  113. package/.medusa/server/src/modules/email/migrations/Migration20260827172945.js +16 -0
  114. package/.medusa/server/src/modules/email/migrations/Migration20260827194804.js +22 -0
  115. package/.medusa/server/src/modules/email/migrations/Migration20260906195205.js +16 -0
  116. package/.medusa/server/src/modules/email/models/email-attempt.js +56 -0
  117. package/.medusa/server/src/modules/email/models/email-message.js +71 -0
  118. package/.medusa/server/src/modules/email/models/email-terms-version.js +37 -0
  119. package/.medusa/server/src/modules/email/service.js +1285 -0
  120. package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +4 -3
  121. package/.medusa/server/src/providers/s3-cache/cache/atomic-write.js +25 -0
  122. package/.medusa/server/src/providers/s3-cache/cache/cache-manager.js +115 -0
  123. package/.medusa/server/src/providers/s3-cache/cache/keyed-lock.js +28 -0
  124. package/.medusa/server/src/providers/s3-cache/index.js +8 -0
  125. package/.medusa/server/src/providers/s3-cache/object-key.js +53 -0
  126. package/.medusa/server/src/providers/s3-cache/s3-cache-file.js +229 -0
  127. package/.medusa/server/src/providers/s3-cache/s3-object-store.js +86 -0
  128. package/.medusa/server/src/providers/smtp/index.js +8 -0
  129. package/.medusa/server/src/providers/smtp/service.js +236 -0
  130. package/.medusa/server/src/providers/smtp/types.js +11 -0
  131. package/.medusa/server/src/public/config.js +186 -0
  132. package/.medusa/server/src/public/descriptor.js +3 -1
  133. package/.medusa/server/src/public/gateway.js +19 -0
  134. package/.medusa/server/src/public/instrumentation.js +56 -0
  135. package/.medusa/server/src/public/shop-config.js +12 -0
  136. package/.medusa/server/src/public/shop-master-data.js +2 -2
  137. package/.medusa/server/src/public/shop-types.js +24 -0
  138. package/.medusa/server/src/shop-config/build-food-shop-config.js +252 -0
  139. package/.medusa/server/src/shop-config/options.js +75 -0
  140. package/.medusa/server/src/subscribers/affiliate-consignment-reminder-due.js +26 -0
  141. package/.medusa/server/src/subscribers/documents-order-canceled.js +24 -0
  142. package/.medusa/server/src/subscribers/documents-order-cancellation-ready.js +26 -0
  143. package/.medusa/server/src/subscribers/documents-refund-ready.js +24 -0
  144. package/.medusa/server/src/subscribers/documents-shipment-created.js +36 -0
  145. package/.medusa/server/src/subscribers/documents-shipment-invoice-assignment-decided.js +26 -0
  146. package/.medusa/server/src/subscribers/email-order-canceled.js +28 -0
  147. package/.medusa/server/src/subscribers/email-shipment-created.js +32 -0
  148. package/.medusa/server/src/subscribers/order-placed.js +27 -0
  149. package/.medusa/server/src/subscribers/packing-list-allocation-revised.js +32 -0
  150. package/.medusa/server/src/subscribers/packing-list-fulfillment-canceled.js +54 -0
  151. package/.medusa/server/src/subscribers/packing-list-fulfillment-created.js +31 -0
  152. package/.medusa/server/src/subscribers/password-reset.js +23 -0
  153. package/.medusa/server/src/subscribers/payment-refunded.js +38 -0
  154. package/.medusa/server/src/subscribers/return-received.js +27 -0
  155. package/.medusa/server/src/subscribers/return-requested.js +27 -0
  156. package/.medusa/server/src/subscribers/user-invite.js +23 -0
  157. package/.medusa/server/src/utils/cart-promotion-links.js +101 -0
  158. package/.medusa/server/src/workflows/documents/issue-cancellation-after-refund.js +246 -0
  159. package/.medusa/server/src/workflows/documents/issue-document.js +165 -0
  160. package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +521 -0
  161. package/.medusa/server/src/workflows/documents/issue-packing-list.js +390 -0
  162. package/.medusa/server/src/workflows/documents/publish-document-facts.js +53 -0
  163. package/.medusa/server/src/workflows/documents/reconcile-document-resolutions.js +48 -0
  164. package/.medusa/server/src/workflows/documents/reconcile-order-cancellation.js +92 -0
  165. package/.medusa/server/src/workflows/documents/regenerate-issued-document.js +61 -0
  166. package/.medusa/server/src/workflows/email/dispatch-email-message.js +301 -0
  167. package/.medusa/server/src/workflows/email/documents-required-facts.js +30 -0
  168. package/.medusa/server/src/workflows/email/prepare-affiliate-reminder-email.js +58 -0
  169. package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +102 -0
  170. package/.medusa/server/src/workflows/email/prepare-order-received-email.js +82 -0
  171. package/.medusa/server/src/workflows/email/prepare-refund-email.js +71 -0
  172. package/.medusa/server/src/workflows/email/prepare-return-email.js +136 -0
  173. package/.medusa/server/src/workflows/email/prepare-shipment-email.js +115 -0
  174. package/.medusa/server/src/workflows/email/retry-email-messages.js +31 -0
  175. package/.medusa/server/src/workflows/hooks/dismiss-deleted-promotion-cart-links.js +23 -0
  176. package/README.md +1777 -8
  177. package/dist/admin-components/features/plugin-features/components/ImageSettingInput.d.ts +15 -0
  178. package/dist/admin-components/features/plugin-features/components/ImageSettingInput.js +42 -0
  179. package/dist/admin-components/features/plugin-features/components/PluginSettingsForm.js +4 -2
  180. package/dist/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +1 -1
  181. package/dist/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +4 -4
  182. package/dist/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +1 -1
  183. package/dist/admin-components/features/plugin-features/lib/image-upload.d.ts +6 -0
  184. package/dist/admin-components/features/plugin-features/lib/image-upload.js +17 -0
  185. package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +4 -4
  186. package/dist/admin-components/i18n/texts.d.ts +6 -0
  187. package/dist/admin-components/i18n/texts.js +6 -0
  188. package/dist/documents/config/index.d.ts +11 -0
  189. package/dist/documents/config/index.js +17 -0
  190. package/dist/documents/templates/financial-document.d.ts +3 -0
  191. package/dist/documents/templates/financial-document.js +159 -0
  192. package/dist/documents/templates/format.d.ts +30 -0
  193. package/dist/documents/templates/format.js +72 -0
  194. package/dist/documents/templates/html-factory.d.ts +15 -0
  195. package/dist/documents/templates/html-factory.js +41 -0
  196. package/dist/documents/templates/html-utils.d.ts +8 -0
  197. package/dist/documents/templates/html-utils.js +22 -0
  198. package/dist/documents/templates/layout.d.ts +35 -0
  199. package/dist/documents/templates/layout.js +141 -0
  200. package/dist/documents/templates/packing-list.d.ts +3 -0
  201. package/dist/documents/templates/packing-list.js +51 -0
  202. package/dist/documents/types/admin-api.d.ts +1 -0
  203. package/dist/documents/types/admin-api.js +2 -0
  204. package/dist/documents/types/document-summary.d.ts +1 -0
  205. package/dist/documents/types/document-summary.js +2 -0
  206. package/dist/documents/types/documents.d.ts +191 -0
  207. package/dist/documents/types/documents.js +2 -0
  208. package/dist/documents/types/html-factory.d.ts +8 -0
  209. package/dist/documents/types/html-factory.js +5 -0
  210. package/dist/documents/types/index.d.ts +5 -0
  211. package/dist/documents/types/index.js +21 -0
  212. package/dist/documents/types/store-api.d.ts +1 -0
  213. package/dist/documents/types/store-api.js +2 -0
  214. package/dist/email/config/index.d.ts +17 -0
  215. package/dist/email/config/index.js +83 -0
  216. package/dist/email/config/notification-module.d.ts +22 -0
  217. package/dist/email/config/notification-module.js +19 -0
  218. package/dist/email/types/email-kinds.d.ts +15 -0
  219. package/dist/email/types/email-kinds.js +17 -0
  220. package/dist/email/types/index.d.ts +4 -0
  221. package/dist/email/types/index.js +20 -0
  222. package/dist/email/types/shop-configuration.d.ts +68 -0
  223. package/dist/email/types/shop-configuration.js +2 -0
  224. package/dist/email/types/smtp-configuration.d.ts +9 -0
  225. package/dist/email/types/smtp-configuration.js +2 -0
  226. package/dist/email/types/template-data.d.ts +609 -0
  227. package/dist/email/types/template-data.js +231 -0
  228. package/dist/files/config/index.d.ts +32 -0
  229. package/dist/files/config/index.js +255 -0
  230. package/dist/files/gateway/index.d.ts +30 -0
  231. package/dist/files/gateway/index.js +114 -0
  232. package/dist/files/types/index.d.ts +2 -0
  233. package/dist/files/types/index.js +5 -0
  234. package/dist/files/types/public.d.ts +90 -0
  235. package/dist/files/types/public.js +5 -0
  236. package/dist/modules/documents/domain/merchant.d.ts +22 -0
  237. package/dist/modules/documents/domain/merchant.js +85 -0
  238. package/dist/modules/documents/domain/options.d.ts +34 -0
  239. package/dist/modules/documents/domain/options.js +39 -0
  240. package/dist/public/config.d.ts +59 -0
  241. package/dist/public/config.js +185 -0
  242. package/dist/public/descriptor.js +2 -0
  243. package/dist/public/gateway.d.ts +2 -0
  244. package/dist/public/gateway.js +18 -0
  245. package/dist/public/instrumentation.d.ts +12 -0
  246. package/dist/public/instrumentation.js +55 -0
  247. package/dist/public/shop-config.d.ts +8 -0
  248. package/dist/public/shop-config.js +11 -0
  249. package/dist/public/shop-master-data.d.ts +1 -1
  250. package/dist/public/shop-master-data.js +1 -1
  251. package/dist/public/shop-types.d.ts +7 -0
  252. package/dist/public/shop-types.js +23 -0
  253. package/dist/public/types.d.ts +19 -2
  254. package/dist/shop-config/build-food-shop-config.d.ts +44 -0
  255. package/dist/shop-config/build-food-shop-config.js +251 -0
  256. package/dist/shop-config/options.d.ts +48 -0
  257. package/dist/shop-config/options.js +74 -0
  258. package/package.json +48 -5
@@ -0,0 +1,15 @@
1
+ type ImageSettingInputProps = {
2
+ id: string;
3
+ hintId: string;
4
+ label: string;
5
+ /** Public URL of the current image; `""` = none. */
6
+ value: string;
7
+ disabled: boolean;
8
+ onChange: (url: string) => void;
9
+ };
10
+ /**
11
+ * Uploads an image to the public File Module storage and hands its URL to
12
+ * the settings draft; the form stores it on save like any other value.
13
+ */
14
+ export declare const ImageSettingInput: ({ id, hintId, label, value, disabled, onChange, }: ImageSettingInputProps) => import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Hint, Text } from "@medusajs/ui";
3
+ import { useMutation } from "@tanstack/react-query";
4
+ import { useRef, useState } from "react";
5
+ import { texts } from "../../../i18n/texts";
6
+ import { sdk } from "../../../lib/sdk";
7
+ import { validateSettingImage } from "../lib/image-upload";
8
+ /**
9
+ * Uploads an image to the public File Module storage and hands its URL to
10
+ * the settings draft; the form stores it on save like any other value.
11
+ */
12
+ export const ImageSettingInput = ({ id, hintId, label, value, disabled, onChange, }) => {
13
+ const fileInput = useRef(null);
14
+ const [error, setError] = useState(null);
15
+ const upload = useMutation({
16
+ mutationFn: async (file) => {
17
+ const { files } = await sdk.admin.upload.create({ files: [file] });
18
+ const url = files[0]?.url;
19
+ if (!url) {
20
+ throw new Error("upload returned no file");
21
+ }
22
+ return url;
23
+ },
24
+ onSuccess: (url) => onChange(url),
25
+ onError: () => setError(texts.settings.imageUploadFailed),
26
+ });
27
+ const busy = disabled || upload.isPending;
28
+ const selectFile = (file) => {
29
+ if (!file) {
30
+ return;
31
+ }
32
+ const invalid = validateSettingImage(file);
33
+ setError(invalid);
34
+ if (!invalid) {
35
+ upload.mutate(file);
36
+ }
37
+ };
38
+ return (_jsxs("div", { className: "grid gap-2", children: [value && (_jsx("div", { className: "bg-ui-bg-subtle flex items-center justify-center rounded-md border p-4", children: _jsx("img", { src: value, alt: texts.settings.imagePreviewAlt(label), className: "max-h-24 max-w-full object-contain" }) })), _jsx("input", { ref: fileInput, id: id, "aria-describedby": hintId, type: "file", accept: "image/png,image/jpeg", className: "hidden", onChange: (event) => {
39
+ selectFile(event.target.files?.[0]);
40
+ event.target.value = "";
41
+ } }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", size: "small", variant: "secondary", isLoading: upload.isPending, disabled: busy, onClick: () => fileInput.current?.click(), children: value ? texts.settings.imageReplace : texts.settings.imageUpload }), value && (_jsx(Button, { type: "button", size: "small", variant: "transparent", disabled: busy, onClick: () => onChange(""), children: texts.settings.imageRemove })), !value && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.imageNone }))] }), error && _jsx(Hint, { variant: "error", children: error })] }));
42
+ };
@@ -2,6 +2,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Alert, Button, Container, Heading, Hint, Input, Label, Select, Switch, Text, } from "@medusajs/ui";
3
3
  import { texts } from "../../../i18n/texts";
4
4
  import { usePluginSettingsForm } from "../hooks/usePluginSettingsForm";
5
+ import { ImageSettingInput } from "./ImageSettingInput";
5
6
  const fieldId = (pluginKey, key) => `plugin-setting-${pluginKey}-${key}`;
6
7
  export const missingLabels = (plugin) => plugin.missing
7
8
  .map((key) => plugin.settings.find((setting) => setting.key === key)?.label ?? key)
@@ -27,13 +28,14 @@ const SettingField = ({ pluginKey, setting, invalidatesLabels, value, invalid, d
27
28
  ? texts.settings.secretClearUndoLabel(setting.label)
28
29
  : texts.settings.secretClearLabel(setting.label), onClick: () => onChange(clearPending ? "" : null), children: clearPending
29
30
  ? texts.settings.secretClearUndo
30
- : texts.settings.secretClear }))] })) : setting.type === "select" ? (_jsxs(Select, { value: typeof value === "string" ? value : "", disabled: disabled, onValueChange: (next) => onChange(next), children: [_jsx(Select.Trigger, { id: id, "aria-describedby": hintId, children: _jsx(Select.Value, { placeholder: texts.settings.selectPlaceholder }) }), _jsx(Select.Content, { children: setting.options.map((option) => (_jsx(Select.Item, { value: option.value, children: option.label }, option.value))) })] })) : setting.type === "number" ? (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: "number", step: "any", min: setting.min ?? undefined, max: setting.max ?? undefined, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value, event.target.validity.badInput) })) : (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: setting.type === "url" ? "url" : "text", value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value) })), _jsxs("div", { id: hintId, className: "grid gap-1", children: [setting.description && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: setting.description })), clearPending && (_jsx(Hint, { variant: "info", children: texts.settings.secretClearPending })), invalidatesLabels && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.invalidates(invalidatesLabels) })), bounds && !invalid && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: bounds })), invalid && (_jsx(Hint, { variant: "error", children: bounds ?? texts.settings.invalidNumber }))] })] }));
31
+ : texts.settings.secretClear }))] })) : setting.type === "select" ? (_jsxs(Select, { value: typeof value === "string" ? value : "", disabled: disabled, onValueChange: (next) => onChange(next), children: [_jsx(Select.Trigger, { id: id, "aria-describedby": hintId, children: _jsx(Select.Value, { placeholder: texts.settings.selectPlaceholder }) }), _jsx(Select.Content, { children: setting.options.map((option) => (_jsx(Select.Item, { value: option.value, children: option.label }, option.value))) })] })) : setting.type === "image" ? (_jsx(ImageSettingInput, { id: id, hintId: hintId, label: setting.label, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (url) => onChange(url) })) : setting.type === "number" ? (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: "number", step: "any", min: setting.min ?? undefined, max: setting.max ?? undefined, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value, event.target.validity.badInput) })) : (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: setting.type === "url" ? "url" : "text", value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value) })), _jsxs("div", { id: hintId, className: "grid gap-1", children: [setting.description && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: setting.description })), clearPending && (_jsx(Hint, { variant: "info", children: texts.settings.secretClearPending })), invalidatesLabels && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.invalidates(invalidatesLabels) })), bounds && !invalid && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: bounds })), invalid && (_jsx(Hint, { variant: "error", children: bounds ?? texts.settings.invalidNumber }))] })] }));
31
32
  };
32
33
  export const PluginSettingsForm = ({ plugin }) => {
33
34
  const form = usePluginSettingsForm(plugin);
34
35
  const pending = form.mutation.isPending;
35
36
  const hasTextFields = plugin.settings.some((setting) => setting.type !== "boolean" &&
36
37
  setting.type !== "secret" &&
37
- setting.type !== "select");
38
+ setting.type !== "select" &&
39
+ setting.type !== "image");
38
40
  return (_jsxs(Container, { className: "divide-y p-0", "aria-busy": pending, children: [_jsxs("div", { className: "px-6 py-4", children: [_jsx(Heading, { level: "h2", children: texts.sections.settings }), hasTextFields && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.clearHint }))] }), _jsxs("form", { onSubmit: form.submit, className: "grid gap-5 px-6 py-5", children: [!plugin.configured && (_jsx(Alert, { variant: "warning", children: texts.settings.missing(missingLabels(plugin)) })), plugin.settings.map((setting) => (_jsx(SettingField, { pluginKey: plugin.plugin_key, setting: setting, invalidatesLabels: invalidatesLabels(plugin, setting), value: form.draft[setting.key], invalid: form.invalid.includes(setting.key), disabled: pending, onChange: (value, badInput) => form.setValue(setting.key, value, badInput) }, setting.key))), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { type: "submit", size: "small", isLoading: pending, disabled: !form.dirty || form.invalid.length > 0 || pending, children: texts.settings.save }) })] })] }));
39
41
  };
@@ -74,7 +74,7 @@ export declare const pluginFeatureEnabledQueryOptions: (pluginKey: string, featu
74
74
  source: "default" | "stored" | "config" | "none";
75
75
  invalidates: string[];
76
76
  } | {
77
- type: "string" | "boolean" | "url";
77
+ type: "string" | "boolean" | "url" | "image";
78
78
  default: string | number | boolean | null;
79
79
  value: string | number | boolean | null;
80
80
  key: string;
@@ -54,7 +54,7 @@ export declare function usePluginFeatures(): import("@tanstack/react-query").Use
54
54
  source: "default" | "stored" | "config" | "none";
55
55
  invalidates: string[];
56
56
  } | {
57
- type: "string" | "boolean" | "url";
57
+ type: "string" | "boolean" | "url" | "image";
58
58
  default: string | number | boolean | null;
59
59
  value: string | number | boolean | null;
60
60
  key: string;
@@ -141,7 +141,7 @@ export declare function usePluginFeature(pluginKey: string | undefined): import(
141
141
  source: "default" | "stored" | "config" | "none";
142
142
  invalidates: string[];
143
143
  } | {
144
- type: "string" | "boolean" | "url";
144
+ type: "string" | "boolean" | "url" | "image";
145
145
  default: string | number | boolean | null;
146
146
  value: string | number | boolean | null;
147
147
  key: string;
@@ -222,7 +222,7 @@ export declare function useUpdatePluginFeatures(): import("@tanstack/react-query
222
222
  source: "default" | "stored" | "config" | "none";
223
223
  invalidates: string[];
224
224
  } | {
225
- type: "string" | "boolean" | "url";
225
+ type: "string" | "boolean" | "url" | "image";
226
226
  default: string | number | boolean | null;
227
227
  value: string | number | boolean | null;
228
228
  key: string;
@@ -307,7 +307,7 @@ export declare function useUpdatePluginSettings(pluginKey: string): import("@tan
307
307
  source: "default" | "stored" | "config" | "none";
308
308
  invalidates: string[];
309
309
  } | {
310
- type: "string" | "boolean" | "url";
310
+ type: "string" | "boolean" | "url" | "image";
311
311
  default: string | number | boolean | null;
312
312
  value: string | number | boolean | null;
313
313
  key: string;
@@ -63,7 +63,7 @@ export declare function usePluginSettingsForm(plugin: AdminPluginFeatures): {
63
63
  source: "default" | "stored" | "config" | "none";
64
64
  invalidates: string[];
65
65
  } | {
66
- type: "string" | "boolean" | "url";
66
+ type: "string" | "boolean" | "url" | "image";
67
67
  default: string | number | boolean | null;
68
68
  value: string | number | boolean | null;
69
69
  key: string;
@@ -0,0 +1,6 @@
1
+ /** Largest image an `image` setting accepts in the Admin. */
2
+ export declare const SETTING_IMAGE_MAX_BYTES: number;
3
+ type ImageSelection = Pick<File, "size" | "type">;
4
+ /** Why `file` cannot be uploaded for an `image` setting; `null` = valid. */
5
+ export declare const validateSettingImage: (file: ImageSelection) => string | null;
6
+ export {};
@@ -0,0 +1,17 @@
1
+ /** Largest image an `image` setting accepts in the Admin. */
2
+ export const SETTING_IMAGE_MAX_BYTES = 2 * 1024 * 1024;
3
+ // Gmail and Outlook do not display SVG or WebP in e-mails.
4
+ const IMAGE_TYPES = new Set(["image/png", "image/jpeg"]);
5
+ /** Why `file` cannot be uploaded for an `image` setting; `null` = valid. */
6
+ export const validateSettingImage = (file) => {
7
+ if (!IMAGE_TYPES.has(file.type)) {
8
+ return "Bitte eine PNG- oder JPG-Datei auswählen.";
9
+ }
10
+ if (file.size === 0) {
11
+ return "Die Bilddatei ist leer.";
12
+ }
13
+ if (file.size > SETTING_IMAGE_MAX_BYTES) {
14
+ return "Die Bilddatei darf maximal 2 MiB groß sein.";
15
+ }
16
+ return null;
17
+ };
@@ -55,7 +55,7 @@ export declare const listPluginFeatures: () => Promise<{
55
55
  source: "default" | "stored" | "config" | "none";
56
56
  invalidates: string[];
57
57
  } | {
58
- type: "string" | "boolean" | "url";
58
+ type: "string" | "boolean" | "url" | "image";
59
59
  default: string | number | boolean | null;
60
60
  value: string | number | boolean | null;
61
61
  key: string;
@@ -135,7 +135,7 @@ export declare const retrievePluginFeatures: (pluginKey: string) => Promise<{
135
135
  source: "default" | "stored" | "config" | "none";
136
136
  invalidates: string[];
137
137
  } | {
138
- type: "string" | "boolean" | "url";
138
+ type: "string" | "boolean" | "url" | "image";
139
139
  default: string | number | boolean | null;
140
140
  value: string | number | boolean | null;
141
141
  key: string;
@@ -215,7 +215,7 @@ export declare const updatePluginFeatures: (payload: UpdatePluginFeaturesPayload
215
215
  source: "default" | "stored" | "config" | "none";
216
216
  invalidates: string[];
217
217
  } | {
218
- type: "string" | "boolean" | "url";
218
+ type: "string" | "boolean" | "url" | "image";
219
219
  default: string | number | boolean | null;
220
220
  value: string | number | boolean | null;
221
221
  key: string;
@@ -295,7 +295,7 @@ export declare const updatePluginSettings: (pluginKey: string, payload: UpdatePl
295
295
  source: "default" | "stored" | "config" | "none";
296
296
  invalidates: string[];
297
297
  } | {
298
- type: "string" | "boolean" | "url";
298
+ type: "string" | "boolean" | "url" | "image";
299
299
  default: string | number | boolean | null;
300
300
  value: string | number | boolean | null;
301
301
  key: string;
@@ -59,6 +59,12 @@ export declare const texts: {
59
59
  numberMax: (max: number) => string;
60
60
  numberRange: (min: number, max: number) => string;
61
61
  selectPlaceholder: string;
62
+ imageUpload: string;
63
+ imageReplace: string;
64
+ imageRemove: string;
65
+ imageNone: string;
66
+ imageUploadFailed: string;
67
+ imagePreviewAlt: (label: string) => string;
62
68
  clearHint: string;
63
69
  source: {
64
70
  stored: string;
@@ -59,6 +59,12 @@ export const texts = {
59
59
  numberMax: (max) => `Höchstens ${max}.`,
60
60
  numberRange: (min, max) => `Zwischen ${min} und ${max}.`,
61
61
  selectPlaceholder: "Bitte auswählen",
62
+ imageUpload: "Bild hochladen",
63
+ imageReplace: "Anderes Bild hochladen",
64
+ imageRemove: "Entfernen",
65
+ imageNone: "Kein Bild hinterlegt",
66
+ imageUploadFailed: "Das Bild konnte nicht hochgeladen werden.",
67
+ imagePreviewAlt: (label) => `Vorschau ${label}`,
62
68
  clearHint: "Ein leeres Feld speichern entfernt den gespeicherten Wert (dann gilt Konfiguration oder Standard).",
63
69
  source: {
64
70
  stored: "gespeichert",
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Documents part of the `@hartl-services/medusa-base/config` entry point: values a shop
3
+ * needs in `medusa-config.ts` or in a test, without loading the plugin itself.
4
+ */
5
+ export { createDefaultDocumentHtmlFactory } from "../templates/html-factory";
6
+ /**
7
+ * Placeholder environment values for `medusa build`. `medusa-config.ts` is
8
+ * evaluated at build time, where the runtime-only Documents PDF executable
9
+ * is not set; spread these under the real environment during the build.
10
+ */
11
+ export declare const DOCUMENTS_BUILD_PLACEHOLDERS: Readonly<Record<string, string>>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DOCUMENTS_BUILD_PLACEHOLDERS = exports.createDefaultDocumentHtmlFactory = void 0;
4
+ /**
5
+ * Documents part of the `@hartl-services/medusa-base/config` entry point: values a shop
6
+ * needs in `medusa-config.ts` or in a test, without loading the plugin itself.
7
+ */
8
+ var html_factory_1 = require("../templates/html-factory");
9
+ Object.defineProperty(exports, "createDefaultDocumentHtmlFactory", { enumerable: true, get: function () { return html_factory_1.createDefaultDocumentHtmlFactory; } });
10
+ /**
11
+ * Placeholder environment values for `medusa build`. `medusa-config.ts` is
12
+ * evaluated at build time, where the runtime-only Documents PDF executable
13
+ * is not set; spread these under the real environment during the build.
14
+ */
15
+ exports.DOCUMENTS_BUILD_PLACEHOLDERS = {
16
+ DOCUMENT_PDF_EXECUTABLE: "google-chrome",
17
+ };
@@ -0,0 +1,3 @@
1
+ import type { FinancialDocumentRenderInput } from "../types";
2
+ import { type DocumentLayout } from "./layout";
3
+ export declare const renderFinancialDocument: (input: FinancialDocumentRenderInput, layout: DocumentLayout, timeZone: string) => string;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderFinancialDocument = void 0;
4
+ const format_1 = require("./format");
5
+ const html_utils_1 = require("./html-utils");
6
+ const layout_1 = require("./layout");
7
+ const labelsByKind = {
8
+ cancellation: {
9
+ number: "Stornonummer",
10
+ title: "Stornorechnung",
11
+ },
12
+ credit_note: {
13
+ number: "Gutschriftnummer",
14
+ title: "Gutschrift",
15
+ },
16
+ invoice: {
17
+ number: "Rechnungsnummer",
18
+ title: "Rechnung",
19
+ },
20
+ };
21
+ const renderAddress = (input) => {
22
+ const address = input.order.billing_address;
23
+ if (!address) {
24
+ return (0, html_utils_1.escapeHtml)(input.order.email);
25
+ }
26
+ return [
27
+ address.company,
28
+ [address.first_name, address.last_name]
29
+ .filter((part) => Boolean(part))
30
+ .join(" "),
31
+ address.address_1,
32
+ address.address_2,
33
+ [address.postal_code, address.city]
34
+ .filter((part) => Boolean(part))
35
+ .join(" "),
36
+ address.country_code ? (0, layout_1.formatCountryName)(address.country_code) : null,
37
+ ]
38
+ .filter((part) => Boolean(part))
39
+ .map(html_utils_1.escapeHtml)
40
+ .join("<br />");
41
+ };
42
+ /** "Charge: L-1, MHD: 30.06.2027"; the quantity only when a line has several batches. */
43
+ const renderBatchDetails = (line, timeZone) => {
44
+ const showQuantity = line.batch_allocations.length > 1;
45
+ return line.batch_allocations
46
+ .map((allocation) => {
47
+ const parts = [
48
+ `Charge: ${allocation.lot_number}`,
49
+ `MHD: ${(0, format_1.formatDocumentDate)(allocation.expiry_date, timeZone)}`,
50
+ showQuantity ? `Menge: ${allocation.quantity}` : null,
51
+ ].filter((part) => Boolean(part));
52
+ return `<div class="batch">${(0, html_utils_1.escapeHtml)(parts.join(", "))}</div>`;
53
+ })
54
+ .join("");
55
+ };
56
+ const renderLines = (input, timeZone) => input.lines
57
+ .map((line) => `<tr>
58
+ <td class="item">
59
+ <div class="item-title">${(0, html_utils_1.escapeHtml)(line.title)}</div>
60
+ ${line.sku ? `<div class="sku">SKU: ${(0, html_utils_1.escapeHtml)(line.sku)}</div>` : ""}
61
+ ${renderBatchDetails(line, timeZone)}
62
+ </td>
63
+ <td class="number quantity">${(0, html_utils_1.escapeHtml)(line.quantity)}</td>
64
+ <td class="number">${(0, html_utils_1.renderMoney)(line.unit_price)}</td>
65
+ <td class="number">${(0, html_utils_1.renderMoney)(line.total)}</td>
66
+ </tr>`)
67
+ .join("");
68
+ const isExemptIntraCommunitySupply = (input) => input.order.tax_compliance?.exemption_wording ===
69
+ "steuerfreie_innergemeinschaftliche_lieferung";
70
+ const renderTaxCompliance = (input) => {
71
+ const evidence = input.order.tax_compliance;
72
+ if (!evidence || !isExemptIntraCommunitySupply(input)) {
73
+ return "";
74
+ }
75
+ return `<section class="tax-compliance">
76
+ <h2>Steuerliche Angaben</h2>
77
+ <strong>${(0, html_utils_1.escapeHtml)(evidence.customer_company_name)}</strong><br />
78
+ UID des Kunden: ${(0, html_utils_1.escapeHtml)(evidence.customer_vat_number)}<br />
79
+ Steuerfreie innergemeinschaftliche Lieferung
80
+ </section>`;
81
+ };
82
+ const renderDiscountTotal = (input) => {
83
+ const discount = input.totals.discount_total;
84
+ const displayAmount = input.document.kind === "invoice" && Number(discount.amount) > 0
85
+ ? `-${discount.amount}`
86
+ : discount.amount;
87
+ return (0, html_utils_1.renderMoney)(discount, displayAmount);
88
+ };
89
+ const formatTaxRate = (rate) => new Intl.NumberFormat(format_1.DEFAULT_FORMAT_LOCALE, {
90
+ maximumFractionDigits: 2,
91
+ style: "percent",
92
+ }).format(rate / 100);
93
+ /** One totals row below the Menge/Einzelpreis columns; the amount sits under "Summe". */
94
+ const renderTotalRow = (label, value, className) => `<tr${className ? ` class="${className}"` : ""}><td class="spacer"></td><td class="label" colspan="2">${(0, html_utils_1.escapeHtml)(label)}</td><td class="number">${value}</td></tr>`;
95
+ const renderTaxRows = (input) => {
96
+ const { tax_breakdown: breakdown, tax_total: taxTotal } = input.totals;
97
+ const rows = breakdown.length > 0
98
+ ? breakdown.map((entry) => ({
99
+ label: `${entry.name} ${formatTaxRate(entry.rate)}`,
100
+ money: entry.tax_total,
101
+ }))
102
+ : [{ label: "Umsatzsteuer", money: taxTotal }];
103
+ return rows
104
+ .map(({ label, money }) => renderTotalRow(label, (0, html_utils_1.renderMoney)(money)))
105
+ .join("");
106
+ };
107
+ /** Only invoices ask for payment; cancellations and credit notes do not. */
108
+ const renderPaymentTerms = (input) => input.document.kind === "invoice" && input.settings.payment_terms
109
+ ? `<section class="payment-terms">${(0, html_utils_1.escapeHtml)(input.settings.payment_terms)}</section>`
110
+ : "";
111
+ const renderFinancialDocument = (input, layout, timeZone) => {
112
+ const labels = labelsByKind[input.document.kind];
113
+ const metadata = [
114
+ { label: labels.number, value: input.document.number },
115
+ {
116
+ label: "Bestellung",
117
+ value: (0, html_utils_1.formatOrderReference)(input.order.display_id),
118
+ },
119
+ ];
120
+ if (input.document.kind !== "invoice") {
121
+ metadata.push({
122
+ label: "Ursprungsrechnung",
123
+ value: input.document.source_document_number ?? "-",
124
+ });
125
+ }
126
+ if (input.order.tax_compliance && isExemptIntraCommunitySupply(input)) {
127
+ metadata.push({
128
+ label: "Kunden-UID",
129
+ value: input.order.tax_compliance.customer_vat_number,
130
+ });
131
+ }
132
+ metadata.push({
133
+ label: "Datum",
134
+ value: (0, format_1.formatDocumentDate)(input.document.issued_at, timeZone),
135
+ });
136
+ const { totals } = input;
137
+ const body = `<table class="items financial-items">
138
+ <thead><tr><th class="item">Artikel</th><th class="number quantity">Menge</th><th class="number">Einzelpreis</th><th class="number">Summe</th></tr></thead>
139
+ <tbody class="lines">${renderLines(input, timeZone)}</tbody>
140
+ <tbody class="totals">
141
+ ${renderTotalRow("Zwischensumme", (0, html_utils_1.renderMoney)(totals.subtotal))}
142
+ ${renderTotalRow("Versand", (0, html_utils_1.renderMoney)(totals.shipping_total))}
143
+ ${renderTotalRow("Rabatt", renderDiscountTotal(input))}
144
+ ${renderTaxRows(input)}
145
+ ${renderTotalRow("Gesamt", (0, html_utils_1.renderMoney)(totals.total), "grand-total")}
146
+ </tbody>
147
+ </table>
148
+ ${renderTaxCompliance(input)}
149
+ ${renderPaymentTerms(input)}`;
150
+ return layout({
151
+ body,
152
+ documentTitle: labels.title,
153
+ metadata,
154
+ number: input.document.number,
155
+ pageTitle: labels.title,
156
+ recipient: renderAddress(input),
157
+ });
158
+ };
159
+ exports.renderFinancialDocument = renderFinancialDocument;
@@ -0,0 +1,30 @@
1
+ export declare const DEFAULT_FORMAT_LOCALE = "de-AT";
2
+ export type FormatMoneyInput = {
3
+ readonly amount: number | string;
4
+ readonly currencyCode: string;
5
+ };
6
+ export type FormatMoneyOptions = {
7
+ readonly locale?: Intl.LocalesArgument;
8
+ };
9
+ export declare const formatMoney: ({ amount, currencyCode }: FormatMoneyInput, { locale }?: FormatMoneyOptions) => string;
10
+ export type FormatDateOptions = {
11
+ readonly locale?: Intl.LocalesArgument;
12
+ /** IANA time zone the date is shown in; there is deliberately no default. */
13
+ readonly timeZone: string;
14
+ readonly options?: Omit<Intl.DateTimeFormatOptions, "timeZone">;
15
+ };
16
+ export declare const formatDate: (value: string | Date, { locale, options, timeZone }: FormatDateOptions) => string;
17
+ export type MoneyParts = {
18
+ /** Currency symbol or code; empty when the amount could not be formatted. */
19
+ readonly currency: string;
20
+ /** Signed, localized number without the currency. */
21
+ readonly amount: string;
22
+ };
23
+ /**
24
+ * `formatMoney` split into currency and number, so a template can align the
25
+ * currency and the amount separately. Falls back to the whole
26
+ * `formatMoney` text as `amount`.
27
+ */
28
+ export declare const formatMoneyParts: ({ amount, currencyCode }: FormatMoneyInput, { locale }?: FormatMoneyOptions) => MoneyParts;
29
+ /** A document date as "01.01.2027" in the given time zone. */
30
+ export declare const formatDocumentDate: (value: string | Date, timeZone: string) => string;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatDocumentDate = exports.formatMoneyParts = exports.formatDate = exports.formatMoney = exports.DEFAULT_FORMAT_LOCALE = void 0;
4
+ exports.DEFAULT_FORMAT_LOCALE = "de-AT";
5
+ const formatMoney = ({ amount, currencyCode }, { locale = exports.DEFAULT_FORMAT_LOCALE } = {}) => {
6
+ const numericAmount = Number(amount);
7
+ const normalizedCurrencyCode = currencyCode.toUpperCase();
8
+ const fallback = `${String(amount)} ${normalizedCurrencyCode}`;
9
+ if (!Number.isFinite(numericAmount)) {
10
+ return fallback;
11
+ }
12
+ try {
13
+ return new Intl.NumberFormat(locale, {
14
+ currency: normalizedCurrencyCode,
15
+ style: "currency",
16
+ }).format(numericAmount);
17
+ }
18
+ catch {
19
+ return fallback;
20
+ }
21
+ };
22
+ exports.formatMoney = formatMoney;
23
+ const formatDate = (value, { locale = exports.DEFAULT_FORMAT_LOCALE, options, timeZone }) => {
24
+ const date = value instanceof Date ? value : new Date(value);
25
+ if (Number.isNaN(date.getTime())) {
26
+ return String(value);
27
+ }
28
+ return new Intl.DateTimeFormat(locale, { timeZone, ...options }).format(date);
29
+ };
30
+ exports.formatDate = formatDate;
31
+ /**
32
+ * `formatMoney` split into currency and number, so a template can align the
33
+ * currency and the amount separately. Falls back to the whole
34
+ * `formatMoney` text as `amount`.
35
+ */
36
+ const formatMoneyParts = ({ amount, currencyCode }, { locale = exports.DEFAULT_FORMAT_LOCALE } = {}) => {
37
+ const numericAmount = Number(amount);
38
+ const fallback = {
39
+ currency: "",
40
+ amount: (0, exports.formatMoney)({ amount, currencyCode }, { locale }),
41
+ };
42
+ if (!Number.isFinite(numericAmount)) {
43
+ return fallback;
44
+ }
45
+ try {
46
+ const parts = new Intl.NumberFormat(locale, {
47
+ currency: currencyCode.toUpperCase(),
48
+ style: "currency",
49
+ }).formatToParts(numericAmount);
50
+ return {
51
+ currency: parts
52
+ .filter((part) => part.type === "currency")
53
+ .map((part) => part.value)
54
+ .join(""),
55
+ amount: parts
56
+ .filter((part) => part.type !== "currency" &&
57
+ !(part.type === "literal" && !part.value.trim()))
58
+ .map((part) => part.value)
59
+ .join(""),
60
+ };
61
+ }
62
+ catch {
63
+ return fallback;
64
+ }
65
+ };
66
+ exports.formatMoneyParts = formatMoneyParts;
67
+ /** A document date as "01.01.2027" in the given time zone. */
68
+ const formatDocumentDate = (value, timeZone) => (0, exports.formatDate)(value, {
69
+ timeZone,
70
+ options: { day: "2-digit", month: "2-digit", year: "numeric" },
71
+ });
72
+ exports.formatDocumentDate = formatDocumentDate;
@@ -0,0 +1,15 @@
1
+ import type { DocumentHtmlFactory } from "../types";
2
+ import { type DocumentTheme } from "./layout";
3
+ /**
4
+ * The plugin's default document markup (German, locale de-AT). The legal
5
+ * sender comes with every render input (`input.merchant`, the base plugin's
6
+ * shop master data). `timeZone` is the IANA time zone in which printed dates
7
+ * are shown; the plugin passes its resolved `numbering.timeZone` so the date
8
+ * on a document matches the period its number was allocated in. `theme`
9
+ * adds a project's CSS and page background (watermark) on top of the default
10
+ * look; the logo comes from the Documents settings (`input.settings`).
11
+ */
12
+ export declare const createDefaultDocumentHtmlFactory: ({ timeZone, theme, }: {
13
+ timeZone: string;
14
+ theme?: DocumentTheme;
15
+ }) => DocumentHtmlFactory;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /* eslint-disable @medusajs/use-medusa-error-not-generic-error -- Invalid render inputs are programmer errors, not HTTP responses. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createDefaultDocumentHtmlFactory = void 0;
5
+ const layout_1 = require("./layout");
6
+ const financial_document_1 = require("./financial-document");
7
+ const packing_list_1 = require("./packing-list");
8
+ /**
9
+ * The plugin's default document markup (German, locale de-AT). The legal
10
+ * sender comes with every render input (`input.merchant`, the base plugin's
11
+ * shop master data). `timeZone` is the IANA time zone in which printed dates
12
+ * are shown; the plugin passes its resolved `numbering.timeZone` so the date
13
+ * on a document matches the period its number was allocated in. `theme`
14
+ * adds a project's CSS and page background (watermark) on top of the default
15
+ * look; the logo comes from the Documents settings (`input.settings`).
16
+ */
17
+ const createDefaultDocumentHtmlFactory = ({ timeZone, theme, }) => {
18
+ return (input) => {
19
+ const kind = input.document.kind;
20
+ switch (kind) {
21
+ case "invoice":
22
+ case "cancellation":
23
+ case "credit_note":
24
+ if ("totals" in input) {
25
+ return (0, financial_document_1.renderFinancialDocument)(input, (0, layout_1.createDocumentLayout)(input.merchant, input.settings, true, theme), timeZone);
26
+ }
27
+ break;
28
+ case "packing_list":
29
+ if ("fulfillment" in input) {
30
+ return (0, packing_list_1.renderPackingList)(input, (0, layout_1.createDocumentLayout)(input.merchant, input.settings, false, theme), timeZone);
31
+ }
32
+ break;
33
+ default: {
34
+ const unsupportedKind = kind;
35
+ throw new TypeError(`Unsupported document kind: ${unsupportedKind}`);
36
+ }
37
+ }
38
+ throw new TypeError(`Invalid input for document kind: ${kind}`);
39
+ };
40
+ };
41
+ exports.createDefaultDocumentHtmlFactory = createDefaultDocumentHtmlFactory;
@@ -0,0 +1,8 @@
1
+ import type { DocumentMoney } from "../types";
2
+ export declare const escapeHtml: (value: string | number | null | undefined) => string;
3
+ export declare const formatOrderReference: (displayId: number | null) => string;
4
+ /**
5
+ * An amount as `<span class="money">` with separate currency and amount
6
+ * spans; `amount` overrides the money's own amount (e.g. a negated value).
7
+ */
8
+ export declare const renderMoney: (money: DocumentMoney, amount?: string) => string;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderMoney = exports.formatOrderReference = exports.escapeHtml = void 0;
4
+ const format_1 = require("./format");
5
+ const escapeHtml = (value) => String(value ?? "")
6
+ .replaceAll("&", "&amp;")
7
+ .replaceAll("<", "&lt;")
8
+ .replaceAll(">", "&gt;")
9
+ .replaceAll('"', "&quot;")
10
+ .replaceAll("'", "&#039;");
11
+ exports.escapeHtml = escapeHtml;
12
+ const formatOrderReference = (displayId) => displayId === null ? "-" : `#${displayId}`;
13
+ exports.formatOrderReference = formatOrderReference;
14
+ /**
15
+ * An amount as `<span class="money">` with separate currency and amount
16
+ * spans; `amount` overrides the money's own amount (e.g. a negated value).
17
+ */
18
+ const renderMoney = (money, amount = money.amount) => {
19
+ const parts = (0, format_1.formatMoneyParts)({ amount, currencyCode: money.currency_code });
20
+ return `<span class="money"><span class="currency">${(0, exports.escapeHtml)(parts.currency)}</span> <span class="amount">${(0, exports.escapeHtml)(parts.amount)}</span></span>`;
21
+ };
22
+ exports.renderMoney = renderMoney;