@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.
- package/.medusa/server/src/admin/index.js +1657 -111
- package/.medusa/server/src/admin/index.mjs +1647 -101
- package/.medusa/server/src/api/admin/email/messages/[id]/resends/route.js +24 -0
- package/.medusa/server/src/api/admin/email/messages/[id]/route.js +12 -0
- package/.medusa/server/src/api/admin/email/messages/route.js +12 -0
- package/.medusa/server/src/api/admin/email/terms-versions/[id]/download/route.js +16 -0
- package/.medusa/server/src/api/admin/email/terms-versions/route.js +47 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/download/route.js +21 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/regenerate/route.js +38 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/regenerate/route.js +32 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/route.js +36 -0
- package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/download/route.js +24 -0
- package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/retry/route.js +44 -0
- package/.medusa/server/src/api/admin/orders/[id]/packing-lists/route.js +82 -0
- package/.medusa/server/src/api/document-download-response.js +16 -0
- package/.medusa/server/src/api/document-response.js +23 -0
- package/.medusa/server/src/api/files/[asset]/route.js +24 -0
- package/.medusa/server/src/api/files/file-response.js +43 -0
- package/.medusa/server/src/api/files/gateway-options.js +31 -0
- package/.medusa/server/src/api/files/private/[token]/route.js +29 -0
- package/.medusa/server/src/api/files/upload/[token]/route.js +66 -0
- package/.medusa/server/src/api/middlewares.js +168 -1
- package/.medusa/server/src/api/packing-list-response.js +22 -0
- package/.medusa/server/src/api/route.js +27 -0
- package/.medusa/server/src/api/store/documents/[id]/download/route.js +21 -0
- package/.medusa/server/src/api/store/documents/[id]/route.js +15 -0
- package/.medusa/server/src/api/store/documents/route.js +11 -0
- package/.medusa/server/src/api/terms-download-response.js +12 -0
- package/.medusa/server/src/documents/config/index.js +18 -0
- package/.medusa/server/src/documents/paths/documents.js +6 -0
- package/.medusa/server/src/documents/paths/index.js +18 -0
- package/.medusa/server/src/documents/plugin-features.js +38 -0
- package/.medusa/server/src/documents/schemas/documents.js +18 -0
- package/.medusa/server/src/documents/schemas/index.js +18 -0
- package/.medusa/server/src/documents/scripts/verify-documents-file-storage.js +55 -0
- package/.medusa/server/src/documents/templates/financial-document.js +160 -0
- package/.medusa/server/src/documents/templates/format.js +73 -0
- package/.medusa/server/src/documents/templates/html-factory.js +42 -0
- package/.medusa/server/src/documents/templates/html-utils.js +23 -0
- package/.medusa/server/src/documents/templates/layout.js +142 -0
- package/.medusa/server/src/documents/templates/packing-list.js +52 -0
- package/.medusa/server/src/documents/types/admin-api.js +3 -0
- package/.medusa/server/src/documents/types/document-summary.js +3 -0
- package/.medusa/server/src/documents/types/documents.js +3 -0
- package/.medusa/server/src/documents/types/html-factory.js +6 -0
- package/.medusa/server/src/documents/types/index.js +22 -0
- package/.medusa/server/src/documents/types/store-api.js +3 -0
- package/.medusa/server/src/documents/utils/document-merchant.js +22 -0
- package/.medusa/server/src/documents/utils/document-render-settings.js +59 -0
- package/.medusa/server/src/documents/utils/html-to-pdf.js +61 -0
- package/.medusa/server/src/documents/utils/private-file-storage.js +64 -0
- package/.medusa/server/src/documents/utils/resolve-services.js +7 -0
- package/.medusa/server/src/email/application/handle-invite-event.js +318 -0
- package/.medusa/server/src/email/application/handle-password-reset-event.js +366 -0
- package/.medusa/server/src/email/config/index.js +84 -0
- package/.medusa/server/src/email/config/notification-module.js +20 -0
- package/.medusa/server/src/email/domain/business-keys.js +50 -0
- package/.medusa/server/src/email/domain/documents-fact-fingerprint.js +7 -0
- package/.medusa/server/src/email/domain/event-schemas.js +36 -0
- package/.medusa/server/src/email/domain/failure-classification.js +53 -0
- package/.medusa/server/src/email/domain/order-summary.js +103 -0
- package/.medusa/server/src/email/domain/refund-summary.js +43 -0
- package/.medusa/server/src/email/domain/return-summary.js +118 -0
- package/.medusa/server/src/email/domain/shipment-summary.js +74 -0
- package/.medusa/server/src/email/plugin-features.js +39 -0
- package/.medusa/server/src/email/rendering/default-templates.js +719 -0
- package/.medusa/server/src/email/rendering/load-email-shop-details.js +36 -0
- package/.medusa/server/src/email/rendering/render-email.js +50 -0
- package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +67 -0
- package/.medusa/server/src/email/types/email-kinds.js +18 -0
- package/.medusa/server/src/email/types/index.js +21 -0
- package/.medusa/server/src/email/types/shop-configuration.js +3 -0
- package/.medusa/server/src/email/types/smtp-configuration.js +3 -0
- package/.medusa/server/src/email/types/template-data.js +232 -0
- package/.medusa/server/src/files/config/index.js +256 -0
- package/.medusa/server/src/files/gateway/index.js +115 -0
- package/.medusa/server/src/files/types/index.js +6 -0
- package/.medusa/server/src/files/types/public.js +6 -0
- package/.medusa/server/src/jobs/document-fact-publication-reconciliation.js +18 -0
- package/.medusa/server/src/jobs/document-resolution-reconciliation.js +20 -0
- package/.medusa/server/src/jobs/packing-list-reconciliation.js +49 -0
- package/.medusa/server/src/jobs/reconcile-email-attempts.js +63 -0
- package/.medusa/server/src/jobs/reconcile-pending-email-messages.js +41 -0
- package/.medusa/server/src/jobs/retry-email-messages.js +27 -0
- package/.medusa/server/src/migration-scripts/dismiss-orphaned-cart-promotion-links.js +26 -0
- package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +97 -0
- package/.medusa/server/src/modules/documents/domain/document-number.js +36 -0
- package/.medusa/server/src/modules/documents/domain/document-resolution.js +24 -0
- package/.medusa/server/src/modules/documents/domain/issuance-rules.js +37 -0
- package/.medusa/server/src/modules/documents/domain/merchant.js +86 -0
- package/.medusa/server/src/modules/documents/domain/options.js +40 -0
- package/.medusa/server/src/modules/documents/domain/packing-list.js +90 -0
- package/.medusa/server/src/modules/documents/domain/snapshot-normalization.js +17 -0
- package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +66 -0
- package/.medusa/server/src/modules/documents/index.js +15 -0
- package/.medusa/server/src/modules/documents/loaders/assert-private-file-storage.js +20 -0
- package/.medusa/server/src/modules/documents/migrations/Migration20260827061749.js +75 -0
- package/.medusa/server/src/modules/documents/migrations/Migration20260830111207.js +28 -0
- package/.medusa/server/src/modules/documents/models/document-artifact.js +25 -0
- package/.medusa/server/src/modules/documents/models/document-number-sequence.js +29 -0
- package/.medusa/server/src/modules/documents/models/document.js +82 -0
- package/.medusa/server/src/modules/documents/models/order-cancellation-resolution.js +51 -0
- package/.medusa/server/src/modules/documents/models/packing-list-revision.js +36 -0
- package/.medusa/server/src/modules/documents/models/packing-list.js +27 -0
- package/.medusa/server/src/modules/documents/models/refund-document-resolution.js +66 -0
- package/.medusa/server/src/modules/documents/models/shipment-invoice-assignment.js +51 -0
- package/.medusa/server/src/modules/documents/models/shipment-invoice-coordinator.js +44 -0
- package/.medusa/server/src/modules/documents/service.js +1701 -0
- package/.medusa/server/src/modules/email/domain/attachment.js +25 -0
- package/.medusa/server/src/modules/email/domain/message-state.js +69 -0
- package/.medusa/server/src/modules/email/index.js +13 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260826173422.js +32 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260827172945.js +16 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260827194804.js +22 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260906195205.js +16 -0
- package/.medusa/server/src/modules/email/models/email-attempt.js +56 -0
- package/.medusa/server/src/modules/email/models/email-message.js +71 -0
- package/.medusa/server/src/modules/email/models/email-terms-version.js +37 -0
- package/.medusa/server/src/modules/email/service.js +1285 -0
- package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +4 -3
- package/.medusa/server/src/providers/s3-cache/cache/atomic-write.js +25 -0
- package/.medusa/server/src/providers/s3-cache/cache/cache-manager.js +115 -0
- package/.medusa/server/src/providers/s3-cache/cache/keyed-lock.js +28 -0
- package/.medusa/server/src/providers/s3-cache/index.js +8 -0
- package/.medusa/server/src/providers/s3-cache/object-key.js +53 -0
- package/.medusa/server/src/providers/s3-cache/s3-cache-file.js +229 -0
- package/.medusa/server/src/providers/s3-cache/s3-object-store.js +86 -0
- package/.medusa/server/src/providers/smtp/index.js +8 -0
- package/.medusa/server/src/providers/smtp/service.js +236 -0
- package/.medusa/server/src/providers/smtp/types.js +11 -0
- package/.medusa/server/src/public/config.js +186 -0
- package/.medusa/server/src/public/descriptor.js +3 -1
- package/.medusa/server/src/public/gateway.js +19 -0
- package/.medusa/server/src/public/instrumentation.js +56 -0
- package/.medusa/server/src/public/shop-config.js +12 -0
- package/.medusa/server/src/public/shop-master-data.js +2 -2
- package/.medusa/server/src/public/shop-types.js +24 -0
- package/.medusa/server/src/shop-config/build-food-shop-config.js +252 -0
- package/.medusa/server/src/shop-config/options.js +75 -0
- package/.medusa/server/src/subscribers/affiliate-consignment-reminder-due.js +26 -0
- package/.medusa/server/src/subscribers/documents-order-canceled.js +24 -0
- package/.medusa/server/src/subscribers/documents-order-cancellation-ready.js +26 -0
- package/.medusa/server/src/subscribers/documents-refund-ready.js +24 -0
- package/.medusa/server/src/subscribers/documents-shipment-created.js +36 -0
- package/.medusa/server/src/subscribers/documents-shipment-invoice-assignment-decided.js +26 -0
- package/.medusa/server/src/subscribers/email-order-canceled.js +28 -0
- package/.medusa/server/src/subscribers/email-shipment-created.js +32 -0
- package/.medusa/server/src/subscribers/order-placed.js +27 -0
- package/.medusa/server/src/subscribers/packing-list-allocation-revised.js +32 -0
- package/.medusa/server/src/subscribers/packing-list-fulfillment-canceled.js +54 -0
- package/.medusa/server/src/subscribers/packing-list-fulfillment-created.js +31 -0
- package/.medusa/server/src/subscribers/password-reset.js +23 -0
- package/.medusa/server/src/subscribers/payment-refunded.js +38 -0
- package/.medusa/server/src/subscribers/return-received.js +27 -0
- package/.medusa/server/src/subscribers/return-requested.js +27 -0
- package/.medusa/server/src/subscribers/user-invite.js +23 -0
- package/.medusa/server/src/utils/cart-promotion-links.js +101 -0
- package/.medusa/server/src/workflows/documents/issue-cancellation-after-refund.js +246 -0
- package/.medusa/server/src/workflows/documents/issue-document.js +165 -0
- package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +521 -0
- package/.medusa/server/src/workflows/documents/issue-packing-list.js +390 -0
- package/.medusa/server/src/workflows/documents/publish-document-facts.js +53 -0
- package/.medusa/server/src/workflows/documents/reconcile-document-resolutions.js +48 -0
- package/.medusa/server/src/workflows/documents/reconcile-order-cancellation.js +92 -0
- package/.medusa/server/src/workflows/documents/regenerate-issued-document.js +61 -0
- package/.medusa/server/src/workflows/email/dispatch-email-message.js +301 -0
- package/.medusa/server/src/workflows/email/documents-required-facts.js +30 -0
- package/.medusa/server/src/workflows/email/prepare-affiliate-reminder-email.js +58 -0
- package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +102 -0
- package/.medusa/server/src/workflows/email/prepare-order-received-email.js +82 -0
- package/.medusa/server/src/workflows/email/prepare-refund-email.js +71 -0
- package/.medusa/server/src/workflows/email/prepare-return-email.js +136 -0
- package/.medusa/server/src/workflows/email/prepare-shipment-email.js +115 -0
- package/.medusa/server/src/workflows/email/retry-email-messages.js +31 -0
- package/.medusa/server/src/workflows/hooks/dismiss-deleted-promotion-cart-links.js +23 -0
- package/README.md +1777 -8
- package/dist/admin-components/features/plugin-features/components/ImageSettingInput.d.ts +15 -0
- package/dist/admin-components/features/plugin-features/components/ImageSettingInput.js +42 -0
- package/dist/admin-components/features/plugin-features/components/PluginSettingsForm.js +4 -2
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +1 -1
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +4 -4
- package/dist/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +1 -1
- package/dist/admin-components/features/plugin-features/lib/image-upload.d.ts +6 -0
- package/dist/admin-components/features/plugin-features/lib/image-upload.js +17 -0
- package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +4 -4
- package/dist/admin-components/i18n/texts.d.ts +6 -0
- package/dist/admin-components/i18n/texts.js +6 -0
- package/dist/documents/config/index.d.ts +11 -0
- package/dist/documents/config/index.js +17 -0
- package/dist/documents/templates/financial-document.d.ts +3 -0
- package/dist/documents/templates/financial-document.js +159 -0
- package/dist/documents/templates/format.d.ts +30 -0
- package/dist/documents/templates/format.js +72 -0
- package/dist/documents/templates/html-factory.d.ts +15 -0
- package/dist/documents/templates/html-factory.js +41 -0
- package/dist/documents/templates/html-utils.d.ts +8 -0
- package/dist/documents/templates/html-utils.js +22 -0
- package/dist/documents/templates/layout.d.ts +35 -0
- package/dist/documents/templates/layout.js +141 -0
- package/dist/documents/templates/packing-list.d.ts +3 -0
- package/dist/documents/templates/packing-list.js +51 -0
- package/dist/documents/types/admin-api.d.ts +1 -0
- package/dist/documents/types/admin-api.js +2 -0
- package/dist/documents/types/document-summary.d.ts +1 -0
- package/dist/documents/types/document-summary.js +2 -0
- package/dist/documents/types/documents.d.ts +191 -0
- package/dist/documents/types/documents.js +2 -0
- package/dist/documents/types/html-factory.d.ts +8 -0
- package/dist/documents/types/html-factory.js +5 -0
- package/dist/documents/types/index.d.ts +5 -0
- package/dist/documents/types/index.js +21 -0
- package/dist/documents/types/store-api.d.ts +1 -0
- package/dist/documents/types/store-api.js +2 -0
- package/dist/email/config/index.d.ts +17 -0
- package/dist/email/config/index.js +83 -0
- package/dist/email/config/notification-module.d.ts +22 -0
- package/dist/email/config/notification-module.js +19 -0
- package/dist/email/types/email-kinds.d.ts +15 -0
- package/dist/email/types/email-kinds.js +17 -0
- package/dist/email/types/index.d.ts +4 -0
- package/dist/email/types/index.js +20 -0
- package/dist/email/types/shop-configuration.d.ts +68 -0
- package/dist/email/types/shop-configuration.js +2 -0
- package/dist/email/types/smtp-configuration.d.ts +9 -0
- package/dist/email/types/smtp-configuration.js +2 -0
- package/dist/email/types/template-data.d.ts +609 -0
- package/dist/email/types/template-data.js +231 -0
- package/dist/files/config/index.d.ts +32 -0
- package/dist/files/config/index.js +255 -0
- package/dist/files/gateway/index.d.ts +30 -0
- package/dist/files/gateway/index.js +114 -0
- package/dist/files/types/index.d.ts +2 -0
- package/dist/files/types/index.js +5 -0
- package/dist/files/types/public.d.ts +90 -0
- package/dist/files/types/public.js +5 -0
- package/dist/modules/documents/domain/merchant.d.ts +22 -0
- package/dist/modules/documents/domain/merchant.js +85 -0
- package/dist/modules/documents/domain/options.d.ts +34 -0
- package/dist/modules/documents/domain/options.js +39 -0
- package/dist/public/config.d.ts +59 -0
- package/dist/public/config.js +185 -0
- package/dist/public/descriptor.js +2 -0
- package/dist/public/gateway.d.ts +2 -0
- package/dist/public/gateway.js +18 -0
- package/dist/public/instrumentation.d.ts +12 -0
- package/dist/public/instrumentation.js +55 -0
- package/dist/public/shop-config.d.ts +8 -0
- package/dist/public/shop-config.js +11 -0
- package/dist/public/shop-master-data.d.ts +1 -1
- package/dist/public/shop-master-data.js +1 -1
- package/dist/public/shop-types.d.ts +7 -0
- package/dist/public/shop-types.js +23 -0
- package/dist/public/types.d.ts +19 -2
- package/dist/shop-config/build-food-shop-config.d.ts +44 -0
- package/dist/shop-config/build-food-shop-config.js +251 -0
- package/dist/shop-config/options.d.ts +48 -0
- package/dist/shop-config/options.js +74 -0
- 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
|
+
};
|
package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts
CHANGED
|
@@ -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,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("&", "&")
|
|
7
|
+
.replaceAll("<", "<")
|
|
8
|
+
.replaceAll(">", ">")
|
|
9
|
+
.replaceAll('"', """)
|
|
10
|
+
.replaceAll("'", "'");
|
|
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;
|