@hartl-services/medusa-base 2.4.0 → 2.6.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 (291) hide show
  1. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/ImageSettingInput.js +48 -0
  2. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginActionsSection.js +3 -2
  3. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeaturesSettingsPage.js +17 -0
  4. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginPageShell.js +20 -0
  5. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginSettingsForm.js +16 -3
  6. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +5 -1
  7. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +20 -4
  8. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/hooks/usePluginFeatures.js +1 -1
  9. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +5 -1
  10. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/action-runs.d.ts +1 -1
  11. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/action-runs.js +3 -2
  12. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +20 -4
  13. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/plugin-features-admin-client.js +1 -1
  14. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/settings-draft.d.ts +17 -2
  15. package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/settings-draft.js +33 -1
  16. package/{dist → .medusa/server/src/admin}/admin-components/i18n/texts.d.ts +3 -0
  17. package/{dist → .medusa/server/src/admin}/admin-components/i18n/texts.js +3 -0
  18. package/.medusa/server/src/admin/admin-components/index.d.ts +36 -0
  19. package/.medusa/server/src/admin/admin-components/index.js +37 -0
  20. package/.medusa/server/src/admin/admin-components/shared/lib/date-input.d.ts +8 -0
  21. package/.medusa/server/src/admin/admin-components/shared/lib/date-input.js +43 -0
  22. package/.medusa/server/src/admin/admin-components/shared/lib/errors.d.ts +9 -0
  23. package/{dist/admin-components → .medusa/server/src/admin/admin-components/shared}/lib/errors.js +4 -6
  24. package/.medusa/server/src/admin/admin-components/shared/lib/file-validation.d.ts +24 -0
  25. package/.medusa/server/src/admin/admin-components/shared/lib/file-validation.js +18 -0
  26. package/.medusa/server/src/admin/admin-components/shared/lib/format.d.ts +32 -0
  27. package/.medusa/server/src/admin/admin-components/shared/lib/format.js +77 -0
  28. package/.medusa/server/src/admin/admin-components/shared/lib/pdf-preview.d.ts +16 -0
  29. package/.medusa/server/src/admin/admin-components/shared/lib/pdf-preview.js +31 -0
  30. package/.medusa/server/src/admin/admin-components/shared/lib/pdf.d.ts +6 -0
  31. package/.medusa/server/src/admin/admin-components/shared/lib/pdf.js +13 -0
  32. package/.medusa/server/src/admin/admin-components/shared/lib/use-debounced-value.d.ts +2 -0
  33. package/.medusa/server/src/admin/admin-components/shared/lib/use-debounced-value.js +10 -0
  34. package/.medusa/server/src/admin/admin-components/shared/lib/use-pdf-preview.d.ts +24 -0
  35. package/.medusa/server/src/admin/admin-components/shared/lib/use-pdf-preview.js +47 -0
  36. package/.medusa/server/src/admin/admin-components/shared/ui/AdminDatePicker.d.ts +8 -0
  37. package/.medusa/server/src/admin/admin-components/shared/ui/AdminDatePicker.js +9 -0
  38. package/.medusa/server/src/admin/admin-components/shared/ui/BackLink.d.ts +8 -0
  39. package/.medusa/server/src/admin/admin-components/shared/ui/BackLink.js +5 -0
  40. package/.medusa/server/src/admin/admin-components/shared/ui/DateRangeFilter.d.ts +20 -0
  41. package/.medusa/server/src/admin/admin-components/shared/ui/DateRangeFilter.js +17 -0
  42. package/.medusa/server/src/admin/admin-components/shared/ui/FilePickerButton.d.ts +14 -0
  43. package/.medusa/server/src/admin/admin-components/shared/ui/FilePickerButton.js +14 -0
  44. package/.medusa/server/src/admin/admin-components/shared/ui/MappedStatusBadge.d.ts +17 -0
  45. package/.medusa/server/src/admin/admin-components/shared/ui/MappedStatusBadge.js +12 -0
  46. package/.medusa/server/src/admin/admin-components/shared/ui/OffsetTablePagination.d.ts +15 -0
  47. package/.medusa/server/src/admin/admin-components/shared/ui/OffsetTablePagination.js +19 -0
  48. package/.medusa/server/src/admin/admin-components/shared/ui/PdfPreviewModal.d.ts +20 -0
  49. package/.medusa/server/src/admin/admin-components/shared/ui/PdfPreviewModal.js +7 -0
  50. package/.medusa/server/src/admin/admin-components/shared/ui/PluginFeatureRoute.d.ts +19 -0
  51. package/.medusa/server/src/admin/admin-components/shared/ui/PluginFeatureRoute.js +16 -0
  52. package/.medusa/server/src/admin/admin-components/shared/ui/QueryState.d.ts +24 -0
  53. package/.medusa/server/src/admin/admin-components/shared/ui/QueryState.js +23 -0
  54. package/.medusa/server/src/admin/admin-components/shared/ui/SearchableReferenceField.d.ts +63 -0
  55. package/.medusa/server/src/admin/admin-components/shared/ui/SearchableReferenceField.js +84 -0
  56. package/.medusa/server/src/admin/admin-components/shared/ui/SectionHeader.d.ts +15 -0
  57. package/.medusa/server/src/admin/admin-components/shared/ui/SectionHeader.js +13 -0
  58. package/.medusa/server/src/admin/admin-components/shared/ui/mapped-status.d.ts +6 -0
  59. package/.medusa/server/src/admin/admin-components/shared/ui/mapped-status.js +10 -0
  60. package/.medusa/server/src/admin/admin-components/shared/ui/offset-pagination.d.ts +17 -0
  61. package/.medusa/server/src/admin/admin-components/shared/ui/offset-pagination.js +15 -0
  62. package/.medusa/server/src/admin/admin-components/shared/ui/reference-results-state.d.ts +16 -0
  63. package/.medusa/server/src/admin/admin-components/shared/ui/reference-results-state.js +13 -0
  64. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/InsertMenu.d.ts +8 -0
  65. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/InsertMenu.js +10 -0
  66. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/LinkPrompt.d.ts +14 -0
  67. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/LinkPrompt.js +26 -0
  68. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/RichTextEditor.d.ts +15 -0
  69. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/RichTextEditor.js +8 -0
  70. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/RichTextToolbar.d.ts +10 -0
  71. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/RichTextToolbar.js +49 -0
  72. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/SlashMenu.d.ts +11 -0
  73. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/SlashMenu.js +44 -0
  74. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/build-extensions.d.ts +8 -0
  75. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/build-extensions.js +43 -0
  76. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/drag-handle.d.ts +15 -0
  77. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/drag-handle.js +161 -0
  78. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/drag-target.d.ts +30 -0
  79. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/drag-target.js +47 -0
  80. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/editor-styles.d.ts +6 -0
  81. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/editor-styles.js +46 -0
  82. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/index.d.ts +9 -0
  83. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/index.js +6 -0
  84. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/link-href.d.ts +8 -0
  85. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/link-href.js +21 -0
  86. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/link-policy.d.ts +13 -0
  87. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/link-policy.js +20 -0
  88. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/slash-command.d.ts +13 -0
  89. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/slash-command.js +60 -0
  90. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/slash-items.d.ts +25 -0
  91. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/slash-items.js +10 -0
  92. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/tiptap.d.ts +7 -0
  93. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/tiptap.js +7 -0
  94. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/use-rich-text-editor.d.ts +44 -0
  95. package/.medusa/server/src/admin/admin-components/shared/ui/rich-text-editor/use-rich-text-editor.js +37 -0
  96. package/.medusa/server/src/admin/index.js +4513 -1447
  97. package/.medusa/server/src/admin/index.mjs +4515 -1452
  98. package/.medusa/server/src/api/admin/email/attachments/[id]/mail-kinds/route.js +16 -0
  99. package/.medusa/server/src/api/admin/email/attachments/[id]/route.js +15 -0
  100. package/.medusa/server/src/api/admin/email/attachments/[id]/versions/[version_id]/download/route.js +16 -0
  101. package/.medusa/server/src/api/admin/email/attachments/[id]/versions/route.js +41 -0
  102. package/.medusa/server/src/api/admin/email/attachments/route.js +21 -0
  103. package/.medusa/server/src/api/admin/email/templates/[kind]/preview/route.js +31 -0
  104. package/.medusa/server/src/api/admin/email/templates/[kind]/route.js +28 -0
  105. package/.medusa/server/src/api/admin/email/templates/[kind]/send-test/route.js +179 -0
  106. package/.medusa/server/src/api/admin/email/templates/[kind]/test-sources/route.js +19 -0
  107. package/.medusa/server/src/api/admin/email/templates/route.js +15 -0
  108. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/route.js +2 -2
  109. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/runs/route.js +2 -2
  110. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/route.js +2 -2
  111. package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/settings/route.js +2 -2
  112. package/.medusa/server/src/api/admin/plugin-features/route.js +2 -2
  113. package/.medusa/server/src/api/attachment-download-response.js +12 -0
  114. package/.medusa/server/src/api/email-template-response.js +43 -0
  115. package/.medusa/server/src/api/middlewares.js +75 -17
  116. package/.medusa/server/src/api/plugin-actions.js +6 -5
  117. package/.medusa/server/src/api/plugin-features-http.js +4 -9
  118. package/.medusa/server/src/api/store/carts/[id]/completion-check/route.js +15 -0
  119. package/.medusa/server/src/api/store/plugin-features/[plugin_key]/route.js +2 -2
  120. package/.medusa/server/src/api/store/plugin-features/route.js +2 -2
  121. package/.medusa/server/src/documents/plugin-features.js +1 -7
  122. package/.medusa/server/src/documents/templates/financial-document.js +12 -11
  123. package/.medusa/server/src/documents/templates/html-factory.js +1 -1
  124. package/.medusa/server/src/documents/templates/html-utils.js +4 -10
  125. package/.medusa/server/src/documents/templates/layout.js +30 -11
  126. package/.medusa/server/src/documents/templates/packing-list.js +8 -7
  127. package/.medusa/server/src/documents/utils/document-render-settings.js +9 -6
  128. package/.medusa/server/src/email/application/handle-customer-verification-event.js +8 -3
  129. package/.medusa/server/src/email/application/handle-invite-event.js +8 -3
  130. package/.medusa/server/src/email/application/handle-password-reset-event.js +8 -3
  131. package/.medusa/server/src/email/application/notification-attachments.js +86 -0
  132. package/.medusa/server/src/email/application/template-data-from-order.js +339 -0
  133. package/.medusa/server/src/email/config/index.js +5 -29
  134. package/.medusa/server/src/email/domain/business-keys.js +5 -5
  135. package/.medusa/server/src/email/domain/documents-fact-fingerprint.js +3 -3
  136. package/.medusa/server/src/email/domain/order-summary.js +21 -27
  137. package/.medusa/server/src/email/domain/refund-summary.js +7 -12
  138. package/.medusa/server/src/email/domain/return-summary.js +32 -38
  139. package/.medusa/server/src/email/domain/shipment-summary.js +22 -28
  140. package/.medusa/server/src/email/plugin-features.js +2 -10
  141. package/.medusa/server/src/email/rendering/calendar-dates.js +19 -0
  142. package/.medusa/server/src/email/rendering/default-templates.js +99 -682
  143. package/.medusa/server/src/email/rendering/email-html.js +189 -0
  144. package/.medusa/server/src/email/rendering/load-email-shop-details.js +14 -12
  145. package/.medusa/server/src/email/rendering/render-email.js +46 -13
  146. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +3 -14
  147. package/.medusa/server/src/email/templates/blocks/dynamic-blocks.js +427 -0
  148. package/.medusa/server/src/email/templates/blocks/placeholders.js +71 -0
  149. package/.medusa/server/src/email/templates/blocks/render-document.js +137 -0
  150. package/.medusa/server/src/email/templates/code-override.js +18 -0
  151. package/.medusa/server/src/email/templates/defaults/documents.js +129 -0
  152. package/.medusa/server/src/email/templates/defaults/subjects.js +62 -0
  153. package/.medusa/server/src/email/templates/defaults/texts.js +42 -0
  154. package/.medusa/server/src/email/templates/index.js +22 -0
  155. package/.medusa/server/src/email/templates/load-template.js +39 -0
  156. package/.medusa/server/src/email/templates/preview.js +33 -0
  157. package/.medusa/server/src/email/templates/render.js +70 -0
  158. package/.medusa/server/src/email/templates/sample-data.js +165 -0
  159. package/.medusa/server/src/email/templates/types.js +3 -0
  160. package/.medusa/server/src/email/templates/validate.js +113 -0
  161. package/.medusa/server/src/files/config/index.js +6 -9
  162. package/.medusa/server/src/migration-scripts/move-logo-to-shop-appearance.js +53 -0
  163. package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +4 -3
  164. package/.medusa/server/src/modules/documents/domain/merchant.js +4 -3
  165. package/.medusa/server/src/modules/documents/domain/options.js +3 -5
  166. package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +4 -7
  167. package/.medusa/server/src/modules/documents/service.js +12 -33
  168. package/.medusa/server/src/modules/email/domain/attachment.js +22 -8
  169. package/.medusa/server/src/modules/email/migrations/Migration20261009083539.js +80 -0
  170. package/.medusa/server/src/modules/email/migrations/Migration20261009100654.js +17 -0
  171. package/.medusa/server/src/modules/email/models/email-attachment-document-version.js +39 -0
  172. package/.medusa/server/src/modules/email/models/email-attachment-document.js +27 -0
  173. package/.medusa/server/src/modules/email/models/email-template.js +25 -0
  174. package/.medusa/server/src/modules/email/service.js +370 -93
  175. package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +9 -11
  176. package/.medusa/server/src/modules/plugin-features/service.js +2 -1
  177. package/.medusa/server/src/providers/s3-cache/cache/cache-manager.js +3 -3
  178. package/.medusa/server/src/providers/s3-cache/s3-object-store.js +3 -9
  179. package/.medusa/server/src/providers/smtp/service.js +13 -13
  180. package/.medusa/server/src/public/descriptor.js +16 -18
  181. package/.medusa/server/src/public/enforcement.js +29 -1
  182. package/.medusa/server/src/public/index.js +8 -2
  183. package/.medusa/server/src/public/module-access.js +9 -2
  184. package/.medusa/server/src/public/shop-master-data.js +109 -18
  185. package/.medusa/server/src/shared/lib/coerce.js +26 -0
  186. package/.medusa/server/src/shared/lib/collections.js +19 -0
  187. package/.medusa/server/src/shared/lib/dates.js +43 -0
  188. package/.medusa/server/src/shared/lib/errors.js +11 -0
  189. package/.medusa/server/src/shared/lib/hash.js +8 -0
  190. package/.medusa/server/src/shared/lib/index.js +29 -0
  191. package/.medusa/server/src/shared/lib/logging.js +61 -0
  192. package/.medusa/server/src/shared/lib/money.js +47 -0
  193. package/.medusa/server/src/shared/lib/text.js +53 -0
  194. package/.medusa/server/src/shared/lib/transaction.js +58 -0
  195. package/.medusa/server/src/{api → shared/lib}/wire-response.js +1 -1
  196. package/.medusa/server/src/shop-config/options.js +3 -5
  197. package/.medusa/server/src/subscribers/packing-list-fulfillment-canceled.js +4 -6
  198. package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +10 -12
  199. package/.medusa/server/src/workflows/documents/issue-packing-list.js +5 -6
  200. package/.medusa/server/src/workflows/documents/reconcile-order-cancellation.js +6 -8
  201. package/.medusa/server/src/workflows/email/dispatch-email-message.js +11 -41
  202. package/.medusa/server/src/workflows/email/prepare-order-received-email.js +2 -2
  203. package/.medusa/server/src/workflows/hooks/check-cart-completion.js +32 -0
  204. package/.medusa/server/src/workflows/hooks/complete-cart-fields.js +30 -0
  205. package/.medusa/server/src/workflows/hooks/complete-cart-participants.js +4 -8
  206. package/.medusa/server/src/workflows/hooks/complete-cart.js +6 -2
  207. package/.medusa/server/src/workflows/hooks/refunded-cart-payment.js +23 -0
  208. package/README.md +446 -54
  209. package/dist/documents/templates/financial-document.js +11 -10
  210. package/dist/documents/templates/html-factory.d.ts +1 -1
  211. package/dist/documents/templates/html-factory.js +1 -1
  212. package/dist/documents/templates/html-utils.d.ts +0 -1
  213. package/dist/documents/templates/html-utils.js +3 -9
  214. package/dist/documents/templates/layout.js +29 -10
  215. package/dist/documents/templates/packing-list.js +7 -6
  216. package/dist/documents/types/documents.d.ts +7 -1
  217. package/dist/email/config/index.js +4 -28
  218. package/dist/email/types/shop-configuration.d.ts +6 -5
  219. package/dist/files/config/index.js +5 -8
  220. package/dist/modules/documents/domain/options.js +2 -4
  221. package/dist/public/descriptor.js +15 -17
  222. package/dist/public/enforcement.d.ts +21 -1
  223. package/dist/public/enforcement.js +28 -0
  224. package/dist/public/index.d.ts +3 -2
  225. package/dist/public/index.js +7 -1
  226. package/dist/public/module-access.d.ts +8 -0
  227. package/dist/public/module-access.js +8 -1
  228. package/dist/public/shop-master-data.d.ts +22 -5
  229. package/dist/public/shop-master-data.js +108 -17
  230. package/dist/public/types.d.ts +10 -2
  231. package/dist/shared/lib/coerce.d.ts +19 -0
  232. package/dist/shared/lib/coerce.js +25 -0
  233. package/dist/shared/lib/collections.d.ts +3 -0
  234. package/dist/shared/lib/collections.js +18 -0
  235. package/dist/shared/lib/dates.d.ts +12 -0
  236. package/dist/shared/lib/dates.js +42 -0
  237. package/dist/shared/lib/errors.d.ts +2 -0
  238. package/dist/shared/lib/errors.js +10 -0
  239. package/dist/shared/lib/hash.d.ts +2 -0
  240. package/dist/shared/lib/hash.js +7 -0
  241. package/dist/shared/lib/index.d.ts +10 -0
  242. package/dist/shared/lib/index.js +28 -0
  243. package/dist/shared/lib/logging.d.ts +26 -0
  244. package/dist/shared/lib/logging.js +60 -0
  245. package/dist/shared/lib/money.d.ts +13 -0
  246. package/dist/shared/lib/money.js +46 -0
  247. package/dist/shared/lib/text.d.ts +19 -0
  248. package/dist/shared/lib/text.js +52 -0
  249. package/dist/shared/lib/transaction.d.ts +26 -0
  250. package/dist/shared/lib/transaction.js +57 -0
  251. package/dist/shared/lib/wire-response.d.ts +6 -0
  252. package/dist/shared/lib/wire-response.js +9 -0
  253. package/dist/shop-config/options.js +2 -4
  254. package/package.json +19 -4
  255. package/.medusa/server/src/api/admin/email/terms-versions/[id]/download/route.js +0 -16
  256. package/.medusa/server/src/api/admin/email/terms-versions/route.js +0 -47
  257. package/.medusa/server/src/api/terms-download-response.js +0 -12
  258. package/.medusa/server/src/modules/email/models/email-terms-version.js +0 -37
  259. package/dist/admin-components/features/plugin-features/components/ImageSettingInput.js +0 -42
  260. package/dist/admin-components/features/plugin-features/components/PluginFeaturesSettingsPage.js +0 -16
  261. package/dist/admin-components/features/plugin-features/components/PluginPageShell.js +0 -20
  262. package/dist/admin-components/features/plugin-features/lib/image-upload.d.ts +0 -6
  263. package/dist/admin-components/features/plugin-features/lib/image-upload.js +0 -17
  264. package/dist/admin-components/index.d.ts +0 -14
  265. package/dist/admin-components/index.js +0 -14
  266. package/dist/admin-components/lib/errors.d.ts +0 -6
  267. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/ImageSettingInput.d.ts +0 -0
  268. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginActionsSection.d.ts +0 -0
  269. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginDetailPage.d.ts +0 -0
  270. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginDetailPage.js +0 -0
  271. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginFeatureGate.d.ts +0 -0
  272. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginFeatureGate.js +0 -0
  273. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginFeatureSections.d.ts +0 -0
  274. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginFeatureSections.js +0 -0
  275. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginFeaturesCard.d.ts +0 -0
  276. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginFeaturesCard.js +0 -0
  277. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginFeaturesSettingsPage.d.ts +0 -0
  278. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginHubCard.d.ts +0 -0
  279. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginHubCard.js +0 -0
  280. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginPageShell.d.ts +0 -0
  281. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginSettingsForm.d.ts +0 -0
  282. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginStatusChips.d.ts +0 -0
  283. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/components/PluginStatusChips.js +0 -0
  284. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.js +0 -0
  285. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/hooks/usePluginSettingsForm.js +0 -0
  286. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/plugin-paths.d.ts +0 -0
  287. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/lib/plugin-paths.js +0 -0
  288. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/queries/pluginFeatureKeys.d.ts +0 -0
  289. /package/{dist → .medusa/server/src/admin}/admin-components/features/plugin-features/queries/pluginFeatureKeys.js +0 -0
  290. /package/{dist/admin-components → .medusa/server/src/admin/admin-components/shared}/lib/sdk.d.ts +0 -0
  291. /package/{dist/admin-components → .medusa/server/src/admin/admin-components/shared}/lib/sdk.js +0 -0
package/README.md CHANGED
@@ -31,7 +31,8 @@ packing lists) and S3 file storage, and the turnkey `medusa-config.ts` preset
31
31
  - Holds the shop's master data (Firmenangaben) as its own settings and
32
32
  exports `getShopMasterData` (see "Shop-Stammdaten").
33
33
  - Exports Admin components from `@hartl-services/medusa-base/admin-components`
34
- so a plugin composes the base's sections into its own page and hides
34
+ (also `sdk`, `getErrorMessage` and the rich-text editor) and server helpers
35
+ from `@hartl-services/medusa-base/lib` (`toWireResponse`) so a plugin composes the base's sections into its own page and hides
35
36
  widgets of disabled features (see "Admin components for plugin pages").
36
37
  - Owns the `completeCartWorkflow` hooks `validate` and `orderCreated` and runs
37
38
  the workflows that plugins declare for them (see "Complete-cart hook
@@ -112,7 +113,7 @@ exports from the matching `@hartl-services/medusa-base` subpath. The
112
113
  e-mail module follows the Documents `invoices` toggle.
113
114
 
114
115
  Peer dependencies are the host's Medusa 2.19 packages (`@medusajs/framework`,
115
- `@medusajs/medusa`, `@medusajs/admin-sdk`, `@medusajs/js-sdk`, `@medusajs/ui`)
116
+ `@medusajs/medusa`, `@medusajs/admin-sdk`, `@medusajs/js-sdk`, `@medusajs/ui`, `@medusajs/icons`)
116
117
  plus React, React Router and TanStack Query in the versions the Medusa 2.19
117
118
  dashboard uses.
118
119
 
@@ -216,6 +217,9 @@ export default defineMiddlewares({
216
217
  })
217
218
  ```
218
219
 
220
+ `pluginFeatureGate(demoPluginFeatures)` returns the same entries as a short
221
+ `gate("/store/demo/coupons", "coupons")`.
222
+
219
223
  A disabled plugin or feature answers 404. With several keys, all must be
220
224
  enabled. The toggles are read once per request.
221
225
 
@@ -329,7 +333,11 @@ class DemoModuleService extends MedusaService({ /* models */ }) {
329
333
  non-empty, unique values; the stored value must be one of them), `image`
330
334
  (the http(s) URL of a PNG/JPEG of at most 2 MiB that the Admin uploads
331
335
  through Medusa's `/admin/uploads` into the public File Module storage and
332
- previews; replacing or removing it leaves the old file in storage). Keys
336
+ previews; replacing or removing it leaves the old file in storage), `color`
337
+ (a hex color `#rrggbb`, stored lowercase; the Admin shows a hex field with a
338
+ color picker and checks the format before saving). An optional `group`
339
+ (non-empty text) is a section heading: the Admin starts a new section with
340
+ that heading whenever the group changes from one setting to the next. Keys
333
341
  follow the feature key format and are unique. A `secret` has no default;
334
342
  any other default must match its type, options and bounds. `options` exist
335
343
  only on `select`, `min`/`max` only on `number`. `required` defaults to
@@ -488,6 +496,22 @@ settings like any plugin's:
488
496
  | `iban`, `bic` | string | no |
489
497
  | `company_register_number` (Firmenbuchnummer), `company_register_court` (Firmenbuchgericht) | string | no |
490
498
 
499
+ These company fields form the section "Firmendaten". The section
500
+ "Erscheinungsbild" below them holds the look of the default e-mail and
501
+ document layouts; all of it is optional:
502
+
503
+ | Key | Type | Used by the default e-mail layout | Used by the default document layout |
504
+ |---|---|---|---|
505
+ | `logo` | image | on top of every mail (else the `displayName`) | top left (else the legal name) |
506
+ | `color_accent` (Akzentfarbe) | color | buttons, heading, logo fallback text, tracking links | `--accent` |
507
+ | `color_text` (Textfarbe) | color | body text, values, item titles, totals | `--text` |
508
+ | `color_muted` (Nebentextfarbe) | color | labels, links, variants, footer | `--muted` |
509
+ | `color_line` (Linienfarbe) | color | borders and dividers | `--line` |
510
+ | `color_surface` (Flächenfarbe) | color | page background, info box | `--table-head` |
511
+
512
+ An empty color keeps the default of that output, so without any value mails
513
+ and documents look as before. Button text and the mail card stay white.
514
+
491
515
  Server-side code reads them exactly as stored in the Admin; there is no
492
516
  medusa-config fallback:
493
517
 
@@ -503,9 +527,24 @@ if (!configured) {
503
527
  }
504
528
  ```
505
529
 
530
+ The appearance is read separately and holds only what is set:
531
+
532
+ ```ts
533
+ import { getShopAppearance } from "@hartl-services/medusa-base"
534
+
535
+ const { logo, colors } = await getShopAppearance(container)
536
+ // colors: { accent?, text?, muted?, line?, surface? } as lowercase #rrggbb
537
+ ```
538
+
506
539
  The base only checks presence and type; what counts as a usable value
507
540
  (for example a placeholder UID) is the consuming plugin's rule.
508
541
 
542
+ The migration script `move-logo-to-shop-appearance` (run once by
543
+ `medusa db:migrate`) moved the former `logo` settings of the E-Mail and the
544
+ Documents plugin into the appearance: the Documents logo, else the E-Mail
545
+ logo; a `logo` stored in the Shop-Stammdaten by an earlier release is kept
546
+ only when neither plugin had one.
547
+
509
548
  ## Admin pages
510
549
 
511
550
  - `/settings/hartl-services` (sidebar entry "Hartl Services Plugins") is the
@@ -542,6 +581,28 @@ build, with types) exports:
542
581
  | `usePluginFeatureEnabled(pluginKey, ...featureKeys)` | `{ enabled, isPending, isError }` from the shared list query (30 s stale time); plugin switch and every key must be effective; `enabled` is `false` while loading and on error |
543
582
  | `PluginFeatureGate({ pluginKey, featureKeys?, fallback? })` | Renders `children` only while enabled, otherwise `fallback` (default nothing) |
544
583
  | `PluginFeatureDisabledNotice({ pluginKey, featureLabel? })` | "Diese Funktion ist unter Einstellungen → Hartl Services Plugins ausgeschaltet." with a link to the hub |
584
+ | `sdk` | The Admin's configured Medusa JS SDK client (`sdk.client.fetch(path, { query })`); use it instead of a per-plugin copy |
585
+ | `getErrorMessage(error, fallback?)` | Readable message from an unknown error (SDK `response.data.message`, `body.message`, `Error.message`), else `fallback` |
586
+ | `ADMIN_LOCALE` (`de-AT`), `ADMIN_DASH`, `formatDate(value, { timeZone? })`, `formatDateTime(value, { timeZone? })`, `formatAmount(amount, currencyCode)`, `getTodayInputDate({ utc? })` | The one Admin locale and formatters; `"-"` for empty or invalid values; pass `{ timeZone: "UTC" }` for date-only values stored at UTC midnight |
587
+ | `useDebouncedValue(value, delayMs)` | Debounced copy of a value |
588
+ | `fetchPdfObjectUrl(path)` | Loads a PDF through `sdk` and returns an object URL; the caller revokes it |
589
+ | `usePdfPreview()` | Returns `{ isOpen, url, isLoading, error, open(load, errorMessage), close() }` for a `PdfPreviewModal`; `load` returns an object URL (e.g. `() => fetchPdfObjectUrl(path)`). Drops stale responses and revokes the URL on close, replacement and unmount |
590
+ | `dateFromInputValue(value)`, `inputValueFromDate(date)` | `"yyyy-mm-dd"` calendar-date string state <-> local `Date` for `DatePicker` (which reads and emits local dates); `null` / `""` for empty or invalid |
591
+ | `validateFile(file, { maxBytes, types, extensions?, messages })` | Pure check of a picked file: `messages.wrongType` / `empty` / `tooLarge` (German text of the caller) or `null`. With `extensions` the name must end with one and an empty MIME type is tolerated |
592
+ | `dateTimeFromInputValue(value)`, `inputValueFromDateTime(date)` | Same for `"yyyy-mm-ddThh:mm"` with `DatePicker granularity="minute"` |
593
+ | `SectionHeader({ title, description?, actions?, level? })` | Heading, subtle description and actions at the top of a `Container className="divide-y p-0"` section |
594
+ | `QueryState({ isLoading, error?, isError?, isEmpty?, loadingText?, errorText?, emptyText?, children? })` | Loading, error and empty body (`px-6 py-8`), otherwise `children`; `errorText` may be a function of the error message |
595
+ | `BackLink({ to, label })` | Arrow icon (`ArrowLongLeft`) + label link above a page |
596
+ | `PluginFeatureRoute({ pluginKey, featureKey?, featureLabel?, hideOnError?, children })` | Route body: nothing while pending, `children` when enabled, otherwise `PluginFeatureDisabledNotice` |
597
+ | `SearchableReferenceField<T>(props)`, type `SearchableReferenceFieldProps` | Search input with a debounced overlay result list (opens on focus/typing, closes on select, Escape or outside click), optional selected card with optional clear button; the caller runs its query on `onDebouncedQueryChange` |
598
+ | `PdfPreviewModal({ open, onOpenChange, title, url, isLoading?, error? })` | `FocusModal` with an iframe for a PDF object URL |
599
+ | `AdminDatePicker(props)` | Medusa `DatePicker` pinned to `ADMIN_LOCALE` (de-AT) via `I18nProvider`; the dashboard language would otherwise show "MM/DD/YYYY". Use it instead of `DatePicker` (importing that from `@medusajs/ui` is a lint error) |
600
+ | `DateRangeFilter({ from, to, onChange, fromLabel?, toLabel? })` | "Von" / "Bis" `AdminDatePicker` pair on `yyyy-mm-dd` strings; `onChange({ from, to })`. Renders the two fields as a fragment, the caller sets the layout. No from > to check (caller's job); an external reset needs a `key` |
601
+ | `FilePickerButton({ accept, label, disabled?, isLoading?, id?, onFileSelected })` | Medusa `Button` that opens the file dialog of a hidden `<input type="file">`; calls `onFileSelected(file)` and resets the input |
602
+ | `OffsetTablePagination({ count, offset, limit, onOffsetChange, maxOffset? })` | German `Table.Pagination` footer for an offset/limit list ("1 - 20 von 45 Ergebnisse", "Zurück", "Weiter") |
603
+ | `MappedStatusBadge({ value, map, fallback? })` | `StatusBadge` from a `value → { label, color }` map; empty value renders `ADMIN_DASH` |
604
+ | `useRichTextEditor(config, initialContent, deps)`, `RichTextEditor`, `InsertMenu`, `filterSlashItems`, `normalizeLinkHref`, `isDefaultAllowedHref`, types `RichTextEditorConfig`, `SlashItem`, `SlashContext`, `NestedContainer` | Generic TipTap editor, see "Rich-text editor" |
605
+ | `Node`, `Mark`, `Extension`, `mergeAttributes`, `ReactNodeViewRenderer`, `NodeViewWrapper`, `NodeViewContent`, `useEditorState`, `Plugin`, `PluginKey`, `NodeSelection`, `TextSelection`, types `AnyExtension`, `ChainedCommands`, `Editor`, `JSONContent`, `NodeViewProps`, `Range` | Re-exported TipTap and ProseMirror building blocks for editor extensions; a plugin needs no own `@tiptap/*` dependency |
545
606
 
546
607
  ```tsx
547
608
  // src/admin/routes/settings/hartl-services/demo/page.tsx
@@ -587,6 +648,151 @@ The consuming plugin lists `@hartl-services/medusa-base` in its
587
648
  external and the shop's Admin resolves one shared copy. The components use
588
649
  the host Admin's React, React Query, React Router and `@medusajs/ui`.
589
650
 
651
+ #### Rich-text editor
652
+
653
+ `useRichTextEditor(config, initialContent, deps)` returns the TipTap
654
+ `Editor | null` (`null` until it exists), `<RichTextEditor editor
655
+ linkInvalidMessage? className? styles? toolbarExtra? />` renders toolbar and
656
+ content. `config` (`RichTextEditorConfig`, all optional):
657
+
658
+ | Key | Meaning |
659
+ | --- | --- |
660
+ | `starterKit` | `Partial<StarterKitOptions>`; disable what the document may not contain (the starter kit's own link is always off) |
661
+ | `link` | `false` leaves the link mark and its toolbar button out; `{ isAllowedHref }` sets the link policy; omitted: http(s) and mailto (`isDefaultAllowedHref`). The policy is stored on the editor, so the Link mark, the toolbar and the link prompt always use the same function |
662
+ | `extensions` | The owner's nodes and marks, added after the starter kit and link |
663
+ | `slashItems({ query, editor })` | Returns the entries of the "/" menu (`SlashItem[]`, already filtered by `query`); the menu exists only with it |
664
+ | `dragHandle` | `true` for a grip left of each block, or `{ nestedContainers: [{ nodeName, contentSelector }] }` to give children of a container their own grip |
665
+ | `ariaLabel` | Accessible name of the writing area |
666
+ | `editorProps` | ProseMirror props; `attributes` must be an object, it is merged over the base's `role` and `aria-label` |
667
+
668
+ `SlashItem<T>` is `{ id, label, hint?, data: T, run(editor, range) }`;
669
+ `filterSlashItems(items, query)` keeps the entries whose label contains the
670
+ query. `normalizeLinkHref(input, isAllowed)` turns a typed target into an
671
+ allowed href (`mailto:` for a bare address, `https://` for a bare host) or
672
+ `null`. `InsertMenu({ label, options, labels, onSelect })` is a toolbar
673
+ dropdown for `toolbarExtra`. The e-mail template editor
674
+ (`features/email/template-editor/use-template-editor.ts`) is the reference
675
+ extension.
676
+
677
+ ```tsx
678
+ import {
679
+ Node,
680
+ RichTextEditor,
681
+ filterSlashItems,
682
+ mergeAttributes,
683
+ useRichTextEditor,
684
+ type JSONContent,
685
+ type RichTextEditorConfig,
686
+ type SlashItem,
687
+ } from "@hartl-services/medusa-base/admin-components"
688
+ import { Button } from "@medusajs/ui"
689
+
690
+ const Callout = Node.create({
691
+ name: "callout",
692
+ group: "block",
693
+ content: "inline*",
694
+ parseHTML: () => [{ tag: "div[data-callout]" }],
695
+ renderHTML: ({ HTMLAttributes }) => [
696
+ "div",
697
+ mergeAttributes(HTMLAttributes, { "data-callout": "" }),
698
+ 0,
699
+ ],
700
+ })
701
+
702
+ const calloutItems: SlashItem[] = [
703
+ {
704
+ id: "callout",
705
+ label: "Hinweisbox",
706
+ data: null,
707
+ run: (editor, range) =>
708
+ editor.chain().focus().deleteRange(range).setNode("callout").run(),
709
+ },
710
+ ]
711
+
712
+ const config = {
713
+ extensions: [Callout],
714
+ slashItems: ({ query }) => filterSlashItems(calloutItems, query),
715
+ ariaLabel: "Text",
716
+ } satisfies RichTextEditorConfig
717
+
718
+ export const NoteEditor = ({ initial }: { initial: JSONContent }) => {
719
+ const editor = useRichTextEditor(config, initial, [])
720
+ if (!editor) return null
721
+ return (
722
+ <RichTextEditor
723
+ editor={editor}
724
+ toolbarExtra={
725
+ <Button size="small" variant="secondary" onClick={() => editor.commands.setNode("callout")}>
726
+ Hinweisbox
727
+ </Button>
728
+ }
729
+ />
730
+ )
731
+ }
732
+ ```
733
+
734
+ Stability: the editor is created once and re-created only when `deps`
735
+ change (it then starts from the `config` and `initialContent` of that
736
+ render, so the cursor is lost). `starterKit`, `extensions`, which features are
737
+ on (`link`, `slashItems`, `dragHandle`), `ariaLabel` and `editorProps` are
738
+ read at creation; changing them has an effect only together with a change of
739
+ `deps` (for example the id of the loaded document). `slashItems` and the link
740
+ policy (`link.isAllowedHref`) are read on every use and always see the latest
741
+ render, so they need no stable reference. Pass `[]` for an editor that lives as
742
+ long as its component.
743
+
744
+ ### Server helpers
745
+
746
+ `@hartl-services/medusa-base/lib` (server and isomorphic code; a separate
747
+ subpath because unit tests mock `@hartl-services/medusa-base` wholesale)
748
+ exports:
749
+
750
+ | Export | Purpose |
751
+ | --- | --- |
752
+ | `toWireResponse(schema, value)` | Serializes `value` as HTTP JSON does (`JSON.parse(JSON.stringify(value))`), then `schema.parse`s it with a Contracts Zod schema, which strips fields outside the public response contract; returns `z.infer<typeof schema>`. Use it for `res.json(...)` in custom routes |
753
+ | `toNumericAmount(value)` | Finite number from a number, numeric string, Medusa `BigNumber` (by shape, not `instanceof`), bignumber.js value or raw `{ value }`; `null` for anything else (blank, `NaN`, `Infinity`) |
754
+ | `getCurrencyDecimalPlaces(currencyCode)` | Minor-unit digits from Medusa's currency table (EUR 2, JPY 0, KWD 3); 2 for an unknown currency |
755
+ | `SqlManager` (type) | Raw SQL surface of a module's transaction manager: `execute(query, params?, method?)`, optional `flush()` |
756
+ | `requireTransactionManager(sharedContext, message?)` | `sharedContext.transactionManager`; throws `UNEXPECTED_STATE` without one (no fallback to `manager`) |
757
+ | `acquireAdvisoryXactLock(manager, key)` | `select pg_advisory_xact_lock(hashtext(?))` with a string key; serializes work per business key until commit |
758
+ | `isUniqueConstraintViolation(error)` | `true` for Postgres 23505 (also in `cause` / `driverError`) or the `INVALID_DATA` MedusaError "... with <field>: <value>, already exists." |
759
+ | `describeError(error)` | Message of an `Error` or error-like object, else `String(error)` |
760
+ | `resolveOptionalLogger(source)` | Logger with `warn` from a container (`resolve("logger")`) or a module cradle (`cradle.logger`), else `undefined`; never throws |
761
+ | `createWarnOnce()` | Returns `warnOnce(key, signature, warn)`: runs `warn` unless the last warning for `key` had the same `signature` (returns whether it ran); `warnOnce.reset(key?)` re-arms |
762
+ | `sha256Hex(value)` | Lower-case hex SHA-256 of a string (UTF-8) or bytes |
763
+ | `toValidDate(value, message?)` | `Date` from `Date` / string / number; throws `INVALID_DATA` for an invalid date |
764
+ | `startOfUtcDate(value)` / `toIsoDate(value)` | Midnight UTC of the UTC day / its `yyyy-mm-dd`; throw for an invalid date |
765
+ | `toWireDate(value)` / `toOptionalWireDate(value)` | ISO-8601 string for a response (throws for an invalid date) / `null` for `null` and `undefined` |
766
+ | `isValidTimeZone(value)` | `true` for a non-blank IANA time zone the runtime knows |
767
+ | `normalizeVatNumber(value)`, `VAT_NUMBER_PLACEHOLDER` | Whitespace removed and upper-cased; the development placeholder UID `ATU00000000` |
768
+ | `normalizeEmail(value)` | Trimmed and lower-cased; `null` for blank or malformed (no "@", leading or trailing "@") so the caller decides whether to throw |
769
+ | `escapeLikePattern(value)` | Escapes `\`, `%` and `_` for `LIKE` / `ILIKE` |
770
+ | `escapeHtml(value)` | Escapes `& < > " '`; accepts numbers, `null` and `undefined` (empty string) |
771
+ | `isHttpUrl(value)` / `isNonEmptyString(value)` | Absolute `http:` / `https:` URL; string with a non-whitespace character (both type guards) |
772
+ | `chunk(items, size)` / `sleep(ms)` | Slices of at most `size` (positive integer, else `INVALID_DATA`) / promise resolved after `ms` |
773
+ | `asRecord(value)` / `asRecordOrNull(value)` | Plain object (arrays excluded), else `{}` / `null` |
774
+ | `asText(value)` | Trimmed string, `null` for blank or non-string |
775
+ | `asArray(value)` | The array, else `[]` |
776
+ | `asOptionalString(value, { allowBlank? })` | The string as is (not trimmed), `null` for non-strings and, unless `allowBlank`, blank strings |
777
+ | `asFiniteNumber(value)` | The number if finite, else `null` (no parsing) |
778
+
779
+ Admin code never imports from `/lib`; it uses `/admin-components`.
780
+
781
+ The main entry (`@hartl-services/medusa-base`) additionally exports, next to
782
+ `requirePluginFeature`:
783
+
784
+ | Export | Purpose |
785
+ | --- | --- |
786
+ | `pluginFeatureGate(descriptor)` | Returns `gate(matcher, ...featureKeys)`, a `defineMiddlewares` route entry (no `methods`, so a prefix match before authentication) whose middleware is `requirePluginFeature(descriptor, ...featureKeys)` |
787
+ | `isOwnPluginEvent(data, pluginKey)` | `true` when the event payload's `plugin_key` is `pluginKey`; never throws. For subscribers of `plugin-features.updated.v1` and `plugin-settings.updated.v1` |
788
+ | `isBasePluginRegistered(container)` / `resolveBaseModule(container)` | Whether the base module is registered (boot-time check) / its service, or `UNEXPECTED_STATE` |
789
+
790
+ `/admin-components` is built (after `medusa plugin:build`) into
791
+ `.medusa/server/src/admin/admin-components`, not into `dist`: the host Admin's
792
+ Tailwind scans only `.medusa/server/src/admin/**` of a plugin, so the classes of
793
+ the shared components are generated there. Do not move the outDir of
794
+ `tsconfig.admin-components.json`.
795
+
590
796
  ## Semantics
591
797
 
592
798
  - No stored row: every value is its declared default, `version` is 0.
@@ -665,6 +871,25 @@ Affiliate 100 (`affiliate-validate-complete-cart`,
665
871
  instead of silently leaving partial state.
666
872
  - The base never swallows a participant error and never reads toggles here.
667
873
 
874
+ **Refunded payment.** Before any `validate` participant, the base refuses a
875
+ cart whose payment collection already has a refund (`NOT_ALLOWED`, "Die
876
+ Zahlung für diesen Warenkorb wurde bereits erstattet. Bitte bestelle
877
+ erneut."). With Stripe's `capture: true` a completion failing after the
878
+ confirmation makes Medusa 2.19.0 refund the payment and keep the cart open
879
+ with the captured session (the succeeded PaymentIntent cannot be cancelled);
880
+ completing that cart later would place an order on the refunded payment. A
881
+ storefront replaces such a cart with a fresh one.
882
+
883
+ **Completion check.** `POST /store/carts/:id/completion-check` (contracts
884
+ `paths/checkout`, `types/checkout-store`) runs the same checks without
885
+ completing: it loads the cart with Medusa's own complete-cart field list,
886
+ refuses a completed or refunded cart and runs every `validate` participant.
887
+ It answers `{ cart_id, ready: true }` or the error the completion would
888
+ answer with, and writes nothing. Stripe captures on confirmation, so a
889
+ Storefront calls it right before confirming a payment. Plugins that guard
890
+ `/store/carts/:id/complete` with a middleware register it for this route too
891
+ (the affiliate plugin's registered-email gate does).
892
+
668
893
 
669
894
  ## Sentry logger wrapper
670
895
 
@@ -949,7 +1174,7 @@ feature is switched on again.
949
1174
 
950
1175
  | Key | Label | Default | Gates | Keeps running when off |
951
1176
  | --------------------- | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
952
- | (plugin switch) | E-Mail | on | every route under `/admin/email/*` (mail log, resends, terms versions; 404); jobs `email-reconcile-attempts`, `email-reconcile-pending-messages`, `email-retry-messages`; every feature below | checkout and orders; already queued messages stay stored |
1177
+ | (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 |
953
1178
  | `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 |
954
1179
  | `account_mails` | Konto-Mails | on | subscribers `password-reset`, `user-invite`, `customer-verification` (the last one only with `customer_verification` on as well) | jobs, Admin routes, already created messages |
955
1180
  | `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 |
@@ -962,15 +1187,14 @@ meantime, such as a password-reset link, is not sent). A toggle read
962
1187
  failure fails the subscriber with its fixed sentinel (the Event Bus retries
963
1188
  it) or fails the job run.
964
1189
 
965
- The plugin declares one setting, edited in the Einstellungen section of its
966
- Admin page: `logo` (type `image`, optional), the PNG/JPG shown on top of every
967
- mail. It is not a Shop-Stammdaten field; Documents has its own `logo`.
1190
+ The plugin declares no settings. Its logo and colors come from the
1191
+ Shop-Stammdaten (section "Erscheinungsbild").
968
1192
 
969
1193
  Admin surfaces of a disabled feature are hidden. The E-Mail settings page
970
1194
  keeps `PluginFeatureSections` visible (so the plugin can be switched back on)
971
- and gates the Terms PDF versions and mail log sections below it on the
972
- plugin switch, showing `PluginFeatureDisabledNotice` in their place while it
973
- is off.
1195
+ and gates the Anhänge card above it, the mail log section below it and the
1196
+ Anhänge sub-page on the plugin switch, showing `PluginFeatureDisabledNotice`
1197
+ in place of the mail log and the sub-page while it is off.
974
1198
 
975
1199
  ### Kontobestätigung per E-Mail
976
1200
 
@@ -1083,7 +1307,8 @@ Storefront contract (DTOs, schema and path from the Contracts package,
1083
1307
  - Affiliate owns reminder eligibility and publishes reminder and application
1084
1308
  facts.
1085
1309
  - Email resolves recipients, renders content, attaches files, and owns delivery
1086
- status plus the append-only global Terms PDF history.
1310
+ status plus the attachment library (documents with append-only PDF
1311
+ versions).
1087
1312
  - Public HTTP DTOs, schemas, and paths remain in
1088
1313
  `@hartl-services/medusa-food-supplements-contracts`.
1089
1314
 
@@ -1243,13 +1468,25 @@ has at most 35 characters. Email runtime-validates every contract before use.
1243
1468
  - Every placed Order creates independent customer and Shop intents. The customer
1244
1469
  intent honors `no_notification`; the Shop intent always targets the static
1245
1470
  role mailbox selected by the Shop configuration.
1246
- - A new customer `order.received` intent snapshots the newest global Terms PDF
1247
- whose `effective_at` is not later than the authoritative `order.created_at`.
1248
- If no version is effective, the customer message is intentionally created
1249
- without Terms. The Shop `order.received.admin` intent never receives it.
1250
- - Terms versions and their private files are immutable. Event replays, transport
1251
- retries, and explicit Admin resends reuse the original message snapshot, so a
1252
- later or backdated upload never changes an existing message.
1471
+ - Library attachments (see "Mail attachments") can be assigned to the customer
1472
+ kinds `order.received`, `order.shipped`, `order.canceled`,
1473
+ `order.return_requested`, `order.return_received` and `order.refunded`
1474
+ (`EMAIL_ATTACHABLE_KINDS`). When a message of such a kind is first created,
1475
+ it snapshots, for every document assigned to its kind, the newest version
1476
+ whose `effective_at` is not later than the reference time: the
1477
+ authoritative `order.created_at` for `order.received`, the creation time of
1478
+ the message otherwise. `order.return_received` is created together with the
1479
+ return request and so freezes the state of that moment. A document without
1480
+ an effective version is skipped. Shop, account and affiliate mails never
1481
+ receive library attachments.
1482
+ - A message lists Documents artifacts (invoice, cancellation) first, then its
1483
+ library attachments by document name, whichever correlated fact created it.
1484
+ - Attachment versions and their private files are immutable. Event replays,
1485
+ transport retries, and explicit Admin resends reuse the original message
1486
+ snapshot, so a later or backdated upload or a changed assignment never
1487
+ changes an existing message. A missing or altered library file fails the
1488
+ message terminally (`attachment_unavailable`/`attachment_invalid`); it is
1489
+ never sent without it.
1253
1490
  - Event payloads are runtime-validated before use and may be replayed, delayed,
1254
1491
  or delivered out of order.
1255
1492
  - With the Documents `invoices` feature on, shipment and
@@ -1266,29 +1503,32 @@ has at most 35 characters. Email runtime-validates every contract before use.
1266
1503
  - Password-reset and account-confirmation messages cannot be retried by the
1267
1504
  generic retry job or resent by an Admin.
1268
1505
  - Unknown transport outcomes are never automatically resent.
1269
- - Shop branding is the configured `displayName` plus the `logo` setting of
1270
- the E-Mail plugin and the company details of the Shop-Stammdaten; a shop overrides the default layout or
1271
- single renderers in its own configuration, not in the plugin.
1506
+ - Shop branding is the configured `displayName` plus the company details and
1507
+ the Erscheinungsbild (logo, colors) of the Shop-Stammdaten; a shop overrides
1508
+ the default layout or single renderers in its own configuration, not in the
1509
+ plugin.
1272
1510
 
1273
1511
  Read this section and the central design before changing Email domain behavior.
1274
1512
 
1275
1513
  ### Shop configuration and default templates
1276
1514
 
1277
1515
  The E-Mail module ships a neutral German layout in grey tones (table-based,
1278
- inline styles) and one renderer per accepted kind. The layout shows, from top
1279
- to bottom: the `logo` centered (an `image` setting of the E-Mail plugin,
1280
- edited on its Admin page; without one, the configured `displayName`), a white card with the subject as heading and the
1516
+ inline styles) and one renderer per accepted kind; the colors of the
1517
+ Erscheinungsbild replace the grey tones where set (see "Shop-Stammdaten").
1518
+ The layout shows, from top to bottom: the `logo` of the Erscheinungsbild
1519
+ centered (without one, the configured `displayName`), a white card with the subject as heading and the
1281
1520
  renderer's body, a contact line ("Fragen? Dann schreib uns an … oder ruf uns
1282
1521
  an unter …", only with `email`/`phone`), and a footer with the company details
1283
1522
  (`legal_name` or `displayName`, address, UID, Firmenbuchnummer and
1284
1523
  Firmenbuchgericht, website) plus "Diese E-Mail wurde automatisch versendet.".
1285
- Missing master data fields are left out. Email reads the logo and the
1286
- Shop-Stammdaten when it sends a mail, so a change applies to the next mail; if
1287
- a read fails the mail is sent without that part (logo or company details)
1288
- instead of failing. Besides
1524
+ Missing master data fields are left out. Email reads the Shop-Stammdaten and
1525
+ the Erscheinungsbild when it sends a mail, so a change applies to the next
1526
+ mail; if a read fails the mail is sent without that part (company details, or
1527
+ logo and colors) instead of failing. Besides
1289
1528
  the layout, `displayName` appears in the password-reset and invite subjects.
1290
- A custom `layout` receives the same data as `shop` (`EmailShopDetails`, every
1291
- field optional) next to `bodyHtml`, `preheader` and `subject`. A shop
1529
+ A custom `layout` receives the same data as `shop` (`EmailShopDetails`: the
1530
+ master data, `logo` and `colors`, every field optional) next to `bodyHtml`,
1531
+ `preheader` and `subject`; a renderer receives it as its second argument. A shop
1292
1532
  therefore needs only `displayName`, `affiliatePortalUrl`, `inviteUrl`,
1293
1533
  `passwordResetUrl` and `orderNotificationEmail`.
1294
1534
 
@@ -1496,12 +1736,44 @@ Email exposes authenticated Admin operations at:
1496
1736
  attempts.
1497
1737
  - `POST /admin/email/messages/:id/resends` makes one explicit resend decision
1498
1738
  using a request UUID.
1499
- - `GET /admin/email/terms-versions` lists safe Terms version metadata in newest
1500
- effective order.
1501
- - `POST /admin/email/terms-versions` idempotently stores one private PDF of at
1502
- most 5 MiB with its `effective_at` timestamp.
1503
- - `GET /admin/email/terms-versions/:id/download` streams one authenticated,
1504
- non-cacheable PDF download.
1739
+ - `GET /admin/email/attachments` lists every attachment document by name with
1740
+ its safe version metadata, newest effective version first.
1741
+ - `POST /admin/email/attachments` idempotently (per `request_id`) creates a
1742
+ document with a name and its assigned `mail_kinds`; `POST
1743
+ /admin/email/attachments/:id` renames it and, when `mail_kinds` is sent,
1744
+ replaces its assignments (left out = unchanged). An empty `mail_kinds` lets
1745
+ the document rest. Kinds outside `EMAIL_ATTACHABLE_KINDS` answer 400.
1746
+ - `POST /admin/email/attachments/:id/mail-kinds` (`{ kind, attached }`)
1747
+ attaches the document to one mail kind or detaches it. The kinds are read
1748
+ and changed under the document's row lock, so two toggles of different
1749
+ kinds both persist; repeating a request is harmless. A kind outside
1750
+ `EMAIL_ATTACHABLE_KINDS` answers 400, an unknown document 404. Answers like
1751
+ the update route (`{ created: false, attachment }`).
1752
+ - `POST /admin/email/attachments/:id/versions` idempotently stores one private
1753
+ PDF of at most 5 MiB with its `effective_at` timestamp; replaying a
1754
+ `request_id` with other data or for another document answers 409.
1755
+ - `GET /admin/email/attachments/:id/versions/:version_id/download` streams one
1756
+ authenticated, non-cacheable PDF download (404 when the version does not
1757
+ belong to the document).
1758
+ - `GET /admin/email/templates` lists the editable mails
1759
+ (`EMAIL_EDITABLE_KINDS`) with `customized` (a template is stored) and
1760
+ `code_override` (the shop's code configuration overrides the kind).
1761
+ `GET /admin/email/templates/:kind` returns the stored template or the
1762
+ default one, `POST` stores one (`{ subject, document }`, validated against
1763
+ `EmailTemplateDocumentSchema` and the kind's `EMAIL_TEMPLATE_CATALOG`, at
1764
+ most 256 KiB; 400 otherwise) and `DELETE` goes back to the default. `POST
1765
+ /admin/email/templates/:kind/preview` renders `{ subject, document }` with
1766
+ sample data in the current layout and appearance (`{ subject, html, text }`;
1767
+ not allowed while e-mail is not configured). Other kinds answer 404. `POST
1768
+ /admin/email/templates/:kind/send-test` (`{ subject, document, to,
1769
+ order_display_id?, source_id?, include_attachments? }`) renders the same
1770
+ preview, with sample data or the data of a real order, and sends it to
1771
+ `to` (`{ sent: true, to, subject, attachments }`, `attachments` = number of
1772
+ attached files). `GET
1773
+ /admin/email/templates/:kind/test-sources?order_display_id=N` lists the
1774
+ shipments, returns or refunds of that order a test of the mail can use
1775
+ (`{ sources: [{ id, occurred_at, amount, currency_code, latest }] }`);
1776
+ see "Testmail" under "Mail templates".
1505
1777
  - `GET /admin/draft-orders/:id/order-confirmation` returns
1506
1778
  `AdminDraftOrderConfirmationResponse` (`{ draft_order_id,
1507
1779
  send_order_confirmation }`); `POST` with
@@ -1512,8 +1784,9 @@ List and detail responses expose only safe identifiers and linkage metadata,
1512
1784
  kind/status and safe status codes, resource references, trigger, attempt
1513
1785
  counts, and timestamps. They never expose recipients, rendered content,
1514
1786
  template data, payloads, reset tokens, transport responses, or credentials.
1515
- Terms responses expose filename, effective timestamp, byte size, SHA-256, and
1516
- creation time, but never request IDs, file IDs, storage keys, URLs, or content.
1787
+ Attachment responses expose name, assigned kinds and, per version, filename,
1788
+ effective timestamp, byte size, SHA-256 and creation time, but never request
1789
+ IDs, file IDs, storage keys, URLs, or content.
1517
1790
 
1518
1791
  An Admin resend is accepted only for a `sent`, `unknown`, or terminal `failed`
1519
1792
  source. Password-reset messages can never use the generic resend operation.
@@ -1529,10 +1802,12 @@ The E-Mail module has one Admin page, `/settings/hartl-services/email`
1529
1802
  (**Settings → Hartl Services Plugins → E-Mail → Öffnen**; the descriptor's
1530
1803
  `admin_page` points the hub there) instead of a top-level sidebar item. It
1531
1804
  composes the base's `PluginPageShell` and `PluginFeatureSections` from
1532
- `@hartl-services/medusa-base/admin-components` (the toggles above) and adds
1533
- two operations sections below them, in this order: **AGB/PDF-Versionen** and,
1534
- as the last section, the **E-Mail-Protokoll**. They show Terms PDF and mail
1535
- log state to act on, not configuration the plugin reads at run time. The mail
1805
+ `@hartl-services/medusa-base/admin-components` (the toggles above). Above
1806
+ them sits the **Anhänge** card (documents and the mails they are assigned to,
1807
+ "Bearbeiten" opens the sub-page) and the **Vorlagen** card (the editable
1808
+ mails with "Standard"/"angepasst", "Bearbeiten" opens the template editor,
1809
+ see "Mail templates"); below them, as the last section, the
1810
+ **E-Mail-Protokoll**. The mail
1536
1811
  log provides bounded server pagination, allowlisted status/kind filters, a
1537
1812
  search by the exact resource ID (e.g. `order_…`, `cus_…`, as pasted from
1538
1813
  elsewhere), safe message and attempt status, and an explicitly confirmed
@@ -1545,11 +1820,123 @@ switch "An Kunden senden" starts off and decides whether the customer receives
1545
1820
  the order confirmation when the draft is converted (see "Draft orders and
1546
1821
  0 € orders").
1547
1822
 
1548
- The `AGB/PDF-Versionen` section manages the global Terms PDFs. Admins can
1549
- upload a PDF of at most 5 MiB with its effective date, review versions newest
1550
- first, and download a selected version. Uploads receive a fresh request UUID;
1551
- downloads use the configured authenticated Admin SDK and never expose storage
1552
- file IDs or URLs in the UI.
1823
+ The sub-page `/settings/hartl-services/email/attachments` manages the
1824
+ attachment library: "Neuer Anhang" creates a document (name, assigned mails,
1825
+ first PDF with its effective date), each document can be renamed and
1826
+ reassigned ("Bearbeiten") and gets further versions ("Neue Version"; PDF of
1827
+ at most 5 MiB). Versions are listed newest effective first, marked
1828
+ "aktuell" or "geplant", and can be downloaded. Every create and upload sends
1829
+ a fresh request UUID; downloads use the configured authenticated Admin SDK
1830
+ and never expose storage file IDs or URLs in the UI. There is no delete:
1831
+ removing every assigned mail lets a document rest.
1832
+
1833
+ ### Mail templates
1834
+
1835
+ All twelve mails (`EMAIL_EDITABLE_KINDS`) are block templates an Admin edits
1836
+ on the E-Mail page (card **Vorlagen**, grouped into Bestell-, Konto- and
1837
+ Affiliate-Mails → editor page `/settings/hartl-services/email/templates/:kind`).
1838
+ The editor offers a toolbar, slash commands ("/" lists what the mail
1839
+ allows), a drag handle to move top-level blocks, a live preview, a test mail
1840
+ and, for the six customer order mails, the attachment checkboxes of the
1841
+ mail (`POST /admin/email/attachments/:id/mail-kinds`, one kind at a time;
1842
+ the Anhänge page's edit dialog likewise saves the name only when it changed
1843
+ and then each changed mail kind on its own).
1844
+
1845
+ - **Document:** a subset of the editor's ProseMirror JSON
1846
+ (`EmailTemplateDocumentSchema` in Contracts): paragraphs with bold, italic,
1847
+ http(s)/mailto links and line breaks, bullet lists, placeholders, a
1848
+ **Hinweis** box (`notice`), a **Nur wenn …** section (`conditional`) and
1849
+ the dynamic blocks of the kind (`EMAIL_TEMPLATE_CATALOG`): `details`
1850
+ (Eckdaten), `order_summary` (items and totals, optional heading),
1851
+ `item_list` (items without prices, optional heading), `address`,
1852
+ `tracking`, `button` (the mail's action link: target fixed per kind from
1853
+ the data or the configuration, label editable; nothing without a URL),
1854
+ `link_fallback` (the plain link, HTML only), `validity` and `message` (the
1855
+ partner application's text). Dynamic blocks render the mail's real data at
1856
+ send time and render nothing without it. Conditions: `invoice_attached`,
1857
+ `cancellation_attached`, `more_to_follow`, `return_number`. Password reset,
1858
+ account confirmation and admin invite must contain the `button` block
1859
+ (`required_blocks`); a template without it is rejected.
1860
+ - **Placeholders** (`shop_name`, `order_number`, `customer_name`,
1861
+ `return_number`, `refund_amount`, `affiliate_name`, `report_month`,
1862
+ `applicant_name`, per kind as in the catalog) are escaped in
1863
+ the HTML and raw in subject and text; a missing value is empty. A custom
1864
+ subject writes them as `{{key}}`; blank subject = the default subject.
1865
+ - **Defaults:** without a stored template a mail renders its default
1866
+ document, which produces exactly the former code output. Messages stored
1867
+ before order snapshots existed keep their code fallback. Password reset,
1868
+ account confirmation and invite read the stored template in their own
1869
+ handlers, the other mails at dispatch.
1870
+ - **When it applies:** templates are read at send time, so a saved template
1871
+ also applies to queued, retried and resent mails; attachments are fixed
1872
+ when the mail is created. An invalid stored template or a failed read sends
1873
+ the default (logged as a warning); a template never fails a mail.
1874
+ - **Precedence:** a shop's code `renderers[kind]` > Admin template > default.
1875
+ The editor shows when a code override is active.
1876
+ - Stored in `email_template` (one row per kind, upsert on save).
1877
+ - **Testmail:** the editor sends the current draft to one address
1878
+ (`POST /admin/email/templates/:kind/send-test`). The subject starts with
1879
+ `[Test] `. The mail goes straight through the Notification module: it is
1880
+ not recorded as an email message, is neither retried nor deduplicated, never
1881
+ goes to the customer, and can be sent again freely. A rejected address, an
1882
+ unreachable mail server or an unclear outcome answers a German message
1883
+ without the provider's own text; while e-mail is not configured the route
1884
+ answers 400 (`not_allowed`).
1885
+ - **Data:** sample data by default. For the seven order mails,
1886
+ `order_display_id` (the order number, e.g. `14`) renders the data of that
1887
+ real, non-draft order instead, so the mail contains real customer data:
1888
+ the order itself (confirmation, shop notification; cancellation only for
1889
+ a canceled order), or one of its shipments (shipped, not canceled),
1890
+ returns (not canceled), received returns (status `received`) or refunds
1891
+ (over all payments). In the editor's dialog the order field searches by
1892
+ number, e-mail or name and offers up to eight orders.
1893
+ - **Source:** `GET …/test-sources?order_display_id=N` lists the candidates
1894
+ of shipping, return and refund mails with the same filters, newest first
1895
+ (by `shipped_at`, `created_at`, `received_at` or the refund's
1896
+ `created_at`; refunds with `amount` and `currency_code`, otherwise
1897
+ `null`); `latest` marks the default. Other mails list none (`[]`), an
1898
+ unknown order answers 404. `send-test` takes the chosen `source_id`;
1899
+ without it the newest is used. `source_id` without `order_display_id`,
1900
+ for a mail without candidates („Diese Mail hat keine Vorgänge zur
1901
+ Auswahl.“) or not among the order's candidates („Dieser Vorgang gehört
1902
+ nicht zu Bestellung #14 oder passt nicht zu dieser Mail.“) answers 400.
1903
+ An unknown order answers 404, a missing event 400 (e.g. „Bestellung #14
1904
+ hat keinen Versand.“), an order number for an account or affiliate mail
1905
+ 400. The Notification of such a test carries `resource_type: "order"`,
1906
+ the order id and `data: { admin_id }`; otherwise
1907
+ `resource_type: "email_template"`.
1908
+ - **Attachments:** `include_attachments` (default `true`) attaches the
1909
+ library documents currently assigned to the mail, each in the version
1910
+ effective at the cut-off date: the order's placing for the order
1911
+ confirmation with a real order (like the real mail), otherwise now. It is
1912
+ ignored for mails that cannot carry attachments.
1913
+ - **Receipts from the real mail:** with a real order and
1914
+ `include_attachments`, a shipping, cancellation or refund test carries
1915
+ exactly the receipts (invoice, cancellation document) the real mail of
1916
+ that shipment, cancellation or refund carried — found by its business
1917
+ key (`order-shipped:<fulfillment>`, `order-canceled:<order>`,
1918
+ `refund:<refund>`), its non-library attachments in their originally sent
1919
+ version — and sets `invoice_attached` / `cancellation_attached` as that
1920
+ mail had it, so its receipt hint shows. Without a real mail (e.g. the
1921
+ refund of a canceled order, whose receipt went with the cancellation, or
1922
+ receipts were switched off then) there is no receipt and no hint, as for
1923
+ the customer; the current Documents switches play no role. With
1924
+ `include_attachments: false` neither receipts, library documents nor the
1925
+ hint are sent. Receipts come first, then library documents; `attachments`
1926
+ counts both. A missing or changed file answers 400 („Anhang „X“ fehlt
1927
+ bzw. ist beschädigt.“), any other read failure „Anhang „X“ konnte
1928
+ gerade nicht geladen werden.“
1929
+
1930
+ ### Mail attachments
1931
+
1932
+ The global Terms PDFs (`AGB`), the EU legal-guarantee notice or a flyer are
1933
+ documents of the attachment library. A document has a name, the customer
1934
+ mail kinds it is attached to and immutable PDF versions, each valid from its
1935
+ `effective_at`. Which version a mail carries is decided once, when the mail
1936
+ is created (see the rules above). Migration `Migration20261009083539` turned
1937
+ the former Terms PDF versions into the document `AGB` (id `eatd_terms`,
1938
+ assigned to `order.received`, version ids kept) and rewrote every stored
1939
+ message snapshot accordingly; it cannot be reverted.
1553
1940
 
1554
1941
  Admin consumers import wire DTOs and paths only from the Contracts package and
1555
1942
  reuse their configured Medusa SDK:
@@ -1750,16 +2137,21 @@ every page. Packing lists show only the sender in the footer. Every page
1750
2137
  carries "Seite x von y" bottom right in an 8 mm bottom page margin
1751
2138
  (`@page { @bottom-right }`; the page count exists only in page margin boxes).
1752
2139
 
1753
- Settings under Settings → "Hartl Services Plugins" → **Dokumente**, read on
1754
- every render and passed as `input.settings` (`DocumentRenderSettings`). Like
2140
+ Settings under Settings → "Hartl Services Plugins" → **Dokumente** and the
2141
+ Erscheinungsbild of the Shop-Stammdaten, read on every render and passed as
2142
+ `input.settings` (`DocumentRenderSettings`). Like
1755
2143
  the merchant data they are not part of the snapshot: a regenerated document
1756
2144
  shows the current values.
1757
2145
 
1758
- - **Logo** (`logo`, PNG or JPG): each render downloads it (10 s timeout, at
1759
- most 2 MiB) and passes it inline as `settings.logo_src` (`data:` URL), so
1760
- Chromium never waits on the network. Without a logo, or when the download
1761
- fails (logged as a warning), the document shows the merchant's legal name;
1762
- a logo never blocks issuance.
2146
+ - **Logo** (the `logo` of the Shop-Stammdaten, section "Erscheinungsbild"):
2147
+ each render downloads it (10 s timeout, at most 2 MiB) and passes it inline
2148
+ as `settings.logo_src` (`data:` URL), so Chromium never waits on the
2149
+ network. Without a logo, or when the download fails (logged as a warning),
2150
+ the document shows the merchant's legal name; a logo never blocks issuance.
2151
+ - **Colors** (the colors of the Erscheinungsbild, `settings.colors`): the set
2152
+ ones override `--accent`, `--text`, `--muted`, `--line` and `--table-head`
2153
+ in a `:root` block after the default stylesheet and before the shop
2154
+ theme's stylesheet, so a shop theme still wins.
1763
2155
  - **Zahlungskonditionen** (`payment_terms`, text): printed below the totals
1764
2156
  of invoices only (not on cancellations or credit notes); blank means none.
1765
2157
  Line breaks in the text are kept.