@hartl-services/medusa-base 1.0.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 +117 -1
- package/.medusa/server/src/admin/index.mjs +118 -2
- package/.medusa/server/src/documents/plugin-features.js +15 -1
- package/.medusa/server/src/documents/templates/financial-document.js +69 -51
- package/.medusa/server/src/documents/templates/format.js +44 -2
- package/.medusa/server/src/documents/templates/html-factory.js +7 -5
- package/.medusa/server/src/documents/templates/html-utils.js +12 -2
- package/.medusa/server/src/documents/templates/layout.js +115 -70
- package/.medusa/server/src/documents/templates/packing-list.js +10 -8
- package/.medusa/server/src/documents/utils/document-render-settings.js +59 -0
- package/.medusa/server/src/email/application/handle-invite-event.js +8 -2
- package/.medusa/server/src/email/application/handle-password-reset-event.js +16 -4
- 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 +9 -1
- package/.medusa/server/src/email/rendering/default-templates.js +693 -65
- package/.medusa/server/src/email/rendering/load-email-shop-details.js +36 -0
- package/.medusa/server/src/email/rendering/render-email.js +16 -3
- package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +20 -1
- package/.medusa/server/src/email/types/template-data.js +155 -2
- package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +4 -3
- package/.medusa/server/src/public/descriptor.js +3 -1
- package/.medusa/server/src/public/shop-master-data.js +2 -2
- package/.medusa/server/src/shop-config/build-food-shop-config.js +5 -1
- package/.medusa/server/src/shop-config/options.js +5 -1
- package/.medusa/server/src/workflows/documents/issue-document.js +4 -2
- package/.medusa/server/src/workflows/documents/issue-packing-list.js +8 -2
- package/.medusa/server/src/workflows/email/dispatch-email-message.js +4 -2
- package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +42 -4
- package/.medusa/server/src/workflows/email/prepare-order-received-email.js +31 -5
- package/.medusa/server/src/workflows/email/prepare-refund-email.js +25 -1
- package/.medusa/server/src/workflows/email/prepare-return-email.js +64 -3
- package/.medusa/server/src/workflows/email/prepare-shipment-email.js +36 -3
- package/README.md +166 -9
- 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/templates/financial-document.d.ts +3 -3
- package/dist/documents/templates/financial-document.js +68 -50
- package/dist/documents/templates/format.d.ts +14 -0
- package/dist/documents/templates/format.js +43 -1
- package/dist/documents/templates/html-factory.d.ts +6 -2
- package/dist/documents/templates/html-factory.js +6 -4
- package/dist/documents/templates/html-utils.d.ts +6 -0
- package/dist/documents/templates/html-utils.js +11 -1
- package/dist/documents/templates/layout.d.ts +21 -3
- package/dist/documents/templates/layout.js +114 -69
- package/dist/documents/templates/packing-list.js +9 -7
- package/dist/documents/types/documents.d.ts +17 -0
- package/dist/documents/types/html-factory.d.ts +1 -0
- package/dist/email/types/shop-configuration.d.ts +25 -0
- package/dist/email/types/template-data.d.ts +509 -0
- package/dist/email/types/template-data.js +154 -1
- package/dist/public/descriptor.js +2 -0
- package/dist/public/shop-master-data.d.ts +1 -1
- package/dist/public/shop-master-data.js +1 -1
- package/dist/public/types.d.ts +2 -1
- package/dist/shop-config/build-food-shop-config.js +4 -0
- package/dist/shop-config/options.d.ts +10 -3
- package/dist/shop-config/options.js +4 -0
- package/package.json +2 -2
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",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type
|
|
3
|
-
export declare const renderFinancialDocument: (input:
|
|
1
|
+
import type { FinancialDocumentRenderInput } from "../types";
|
|
2
|
+
import { type DocumentLayout } from "./layout";
|
|
3
|
+
export declare const renderFinancialDocument: (input: FinancialDocumentRenderInput, layout: DocumentLayout, timeZone: string) => string;
|
|
@@ -3,20 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.renderFinancialDocument = void 0;
|
|
4
4
|
const format_1 = require("./format");
|
|
5
5
|
const html_utils_1 = require("./html-utils");
|
|
6
|
+
const layout_1 = require("./layout");
|
|
6
7
|
const labelsByKind = {
|
|
7
8
|
cancellation: {
|
|
8
|
-
|
|
9
|
-
recipient: "Stornorechnung an",
|
|
9
|
+
number: "Stornonummer",
|
|
10
10
|
title: "Stornorechnung",
|
|
11
11
|
},
|
|
12
12
|
credit_note: {
|
|
13
|
-
|
|
14
|
-
recipient: "Gutschrift an",
|
|
13
|
+
number: "Gutschriftnummer",
|
|
15
14
|
title: "Gutschrift",
|
|
16
15
|
},
|
|
17
16
|
invoice: {
|
|
18
|
-
|
|
19
|
-
recipient: "Rechnung an",
|
|
17
|
+
number: "Rechnungsnummer",
|
|
20
18
|
title: "Rechnung",
|
|
21
19
|
},
|
|
22
20
|
};
|
|
@@ -26,7 +24,8 @@ const renderAddress = (input) => {
|
|
|
26
24
|
return (0, html_utils_1.escapeHtml)(input.order.email);
|
|
27
25
|
}
|
|
28
26
|
return [
|
|
29
|
-
|
|
27
|
+
address.company,
|
|
28
|
+
[address.first_name, address.last_name]
|
|
30
29
|
.filter((part) => Boolean(part))
|
|
31
30
|
.join(" "),
|
|
32
31
|
address.address_1,
|
|
@@ -34,34 +33,43 @@ const renderAddress = (input) => {
|
|
|
34
33
|
[address.postal_code, address.city]
|
|
35
34
|
.filter((part) => Boolean(part))
|
|
36
35
|
.join(" "),
|
|
37
|
-
address.country_code
|
|
36
|
+
address.country_code ? (0, layout_1.formatCountryName)(address.country_code) : null,
|
|
38
37
|
]
|
|
39
38
|
.filter((part) => Boolean(part))
|
|
40
39
|
.map(html_utils_1.escapeHtml)
|
|
41
40
|
.join("<br />");
|
|
42
41
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
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
|
+
})
|
|
47
54
|
.join("");
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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>
|
|
51
60
|
${line.sku ? `<div class="sku">SKU: ${(0, html_utils_1.escapeHtml)(line.sku)}</div>` : ""}
|
|
52
|
-
${
|
|
61
|
+
${renderBatchDetails(line, timeZone)}
|
|
53
62
|
</td>
|
|
54
|
-
<td class="number">${(0, html_utils_1.escapeHtml)(line.quantity)}</td>
|
|
55
|
-
<td class="number">${(0, html_utils_1.
|
|
56
|
-
<td class="number">${(0, html_utils_1.
|
|
57
|
-
</tr
|
|
58
|
-
})
|
|
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>`)
|
|
59
67
|
.join("");
|
|
68
|
+
const isExemptIntraCommunitySupply = (input) => input.order.tax_compliance?.exemption_wording ===
|
|
69
|
+
"steuerfreie_innergemeinschaftliche_lieferung";
|
|
60
70
|
const renderTaxCompliance = (input) => {
|
|
61
71
|
const evidence = input.order.tax_compliance;
|
|
62
|
-
if (!evidence ||
|
|
63
|
-
evidence.exemption_wording !==
|
|
64
|
-
"steuerfreie_innergemeinschaftliche_lieferung") {
|
|
72
|
+
if (!evidence || !isExemptIntraCommunitySupply(input)) {
|
|
65
73
|
return "";
|
|
66
74
|
}
|
|
67
75
|
return `<section class="tax-compliance">
|
|
@@ -72,16 +80,18 @@ const renderTaxCompliance = (input) => {
|
|
|
72
80
|
</section>`;
|
|
73
81
|
};
|
|
74
82
|
const renderDiscountTotal = (input) => {
|
|
75
|
-
const
|
|
76
|
-
const displayAmount = input.document.kind === "invoice" && Number(amount) > 0
|
|
77
|
-
? `-${amount}`
|
|
78
|
-
: amount;
|
|
79
|
-
return (0, html_utils_1.
|
|
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);
|
|
80
88
|
};
|
|
81
89
|
const formatTaxRate = (rate) => new Intl.NumberFormat(format_1.DEFAULT_FORMAT_LOCALE, {
|
|
82
90
|
maximumFractionDigits: 2,
|
|
83
91
|
style: "percent",
|
|
84
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>`;
|
|
85
95
|
const renderTaxRows = (input) => {
|
|
86
96
|
const { tax_breakdown: breakdown, tax_total: taxTotal } = input.totals;
|
|
87
97
|
const rows = breakdown.length > 0
|
|
@@ -91,16 +101,17 @@ const renderTaxRows = (input) => {
|
|
|
91
101
|
}))
|
|
92
102
|
: [{ label: "Umsatzsteuer", money: taxTotal }];
|
|
93
103
|
return rows
|
|
94
|
-
.map(({ label, money }) =>
|
|
104
|
+
.map(({ label, money }) => renderTotalRow(label, (0, html_utils_1.renderMoney)(money)))
|
|
95
105
|
.join("");
|
|
96
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
|
+
: "";
|
|
97
111
|
const renderFinancialDocument = (input, layout, timeZone) => {
|
|
98
112
|
const labels = labelsByKind[input.document.kind];
|
|
99
113
|
const metadata = [
|
|
100
|
-
{
|
|
101
|
-
label: labels.date,
|
|
102
|
-
value: (0, format_1.formatDate)(input.document.issued_at, { timeZone }),
|
|
103
|
-
},
|
|
114
|
+
{ label: labels.number, value: input.document.number },
|
|
104
115
|
{
|
|
105
116
|
label: "Bestellung",
|
|
106
117
|
value: (0, html_utils_1.formatOrderReference)(input.order.display_id),
|
|
@@ -112,30 +123,37 @@ const renderFinancialDocument = (input, layout, timeZone) => {
|
|
|
112
123
|
value: input.document.source_document_number ?? "-",
|
|
113
124
|
});
|
|
114
125
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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))}
|
|
129
144
|
${renderTaxRows(input)}
|
|
130
|
-
|
|
145
|
+
${renderTotalRow("Gesamt", (0, html_utils_1.renderMoney)(totals.total), "grand-total")}
|
|
131
146
|
</tbody>
|
|
132
|
-
</table
|
|
147
|
+
</table>
|
|
148
|
+
${renderTaxCompliance(input)}
|
|
149
|
+
${renderPaymentTerms(input)}`;
|
|
133
150
|
return layout({
|
|
134
151
|
body,
|
|
135
152
|
documentTitle: labels.title,
|
|
136
153
|
metadata,
|
|
137
154
|
number: input.document.number,
|
|
138
155
|
pageTitle: labels.title,
|
|
156
|
+
recipient: renderAddress(input),
|
|
139
157
|
});
|
|
140
158
|
};
|
|
141
159
|
exports.renderFinancialDocument = renderFinancialDocument;
|
|
@@ -14,3 +14,17 @@ export type FormatDateOptions = {
|
|
|
14
14
|
readonly options?: Omit<Intl.DateTimeFormatOptions, "timeZone">;
|
|
15
15
|
};
|
|
16
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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatDate = exports.formatMoney = exports.DEFAULT_FORMAT_LOCALE = void 0;
|
|
3
|
+
exports.formatDocumentDate = exports.formatMoneyParts = exports.formatDate = exports.formatMoney = exports.DEFAULT_FORMAT_LOCALE = void 0;
|
|
4
4
|
exports.DEFAULT_FORMAT_LOCALE = "de-AT";
|
|
5
5
|
const formatMoney = ({ amount, currencyCode }, { locale = exports.DEFAULT_FORMAT_LOCALE } = {}) => {
|
|
6
6
|
const numericAmount = Number(amount);
|
|
@@ -28,3 +28,45 @@ const formatDate = (value, { locale = exports.DEFAULT_FORMAT_LOCALE, options, ti
|
|
|
28
28
|
return new Intl.DateTimeFormat(locale, { timeZone, ...options }).format(date);
|
|
29
29
|
};
|
|
30
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;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { DocumentHtmlFactory } from "../types";
|
|
2
|
+
import { type DocumentTheme } from "./layout";
|
|
2
3
|
/**
|
|
3
4
|
* The plugin's default document markup (German, locale de-AT). The legal
|
|
4
5
|
* sender comes with every render input (`input.merchant`, the base plugin's
|
|
5
6
|
* shop master data). `timeZone` is the IANA time zone in which printed dates
|
|
6
7
|
* are shown; the plugin passes its resolved `numbering.timeZone` so the date
|
|
7
|
-
* on a document matches the period its number was allocated in.
|
|
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`).
|
|
8
11
|
*/
|
|
9
|
-
export declare const createDefaultDocumentHtmlFactory: ({ timeZone, }: {
|
|
12
|
+
export declare const createDefaultDocumentHtmlFactory: ({ timeZone, theme, }: {
|
|
10
13
|
timeZone: string;
|
|
14
|
+
theme?: DocumentTheme;
|
|
11
15
|
}) => DocumentHtmlFactory;
|
|
@@ -10,9 +10,11 @@ const packing_list_1 = require("./packing-list");
|
|
|
10
10
|
* sender comes with every render input (`input.merchant`, the base plugin's
|
|
11
11
|
* shop master data). `timeZone` is the IANA time zone in which printed dates
|
|
12
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.
|
|
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`).
|
|
14
16
|
*/
|
|
15
|
-
const createDefaultDocumentHtmlFactory = ({ timeZone, }) => {
|
|
17
|
+
const createDefaultDocumentHtmlFactory = ({ timeZone, theme, }) => {
|
|
16
18
|
return (input) => {
|
|
17
19
|
const kind = input.document.kind;
|
|
18
20
|
switch (kind) {
|
|
@@ -20,12 +22,12 @@ const createDefaultDocumentHtmlFactory = ({ timeZone, }) => {
|
|
|
20
22
|
case "cancellation":
|
|
21
23
|
case "credit_note":
|
|
22
24
|
if ("totals" in input) {
|
|
23
|
-
return (0, financial_document_1.renderFinancialDocument)(input, (0, layout_1.createDocumentLayout)(input.merchant, true), timeZone);
|
|
25
|
+
return (0, financial_document_1.renderFinancialDocument)(input, (0, layout_1.createDocumentLayout)(input.merchant, input.settings, true, theme), timeZone);
|
|
24
26
|
}
|
|
25
27
|
break;
|
|
26
28
|
case "packing_list":
|
|
27
29
|
if ("fulfillment" in input) {
|
|
28
|
-
return (0, packing_list_1.renderPackingList)(input, (0, layout_1.createDocumentLayout)(input.merchant, false), timeZone);
|
|
30
|
+
return (0, packing_list_1.renderPackingList)(input, (0, layout_1.createDocumentLayout)(input.merchant, input.settings, false, theme), timeZone);
|
|
29
31
|
}
|
|
30
32
|
break;
|
|
31
33
|
default: {
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
import type { DocumentMoney } from "../types";
|
|
1
2
|
export declare const escapeHtml: (value: string | number | null | undefined) => string;
|
|
2
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;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatOrderReference = exports.escapeHtml = void 0;
|
|
3
|
+
exports.renderMoney = exports.formatOrderReference = exports.escapeHtml = void 0;
|
|
4
|
+
const format_1 = require("./format");
|
|
4
5
|
const escapeHtml = (value) => String(value ?? "")
|
|
5
6
|
.replaceAll("&", "&")
|
|
6
7
|
.replaceAll("<", "<")
|
|
@@ -10,3 +11,12 @@ const escapeHtml = (value) => String(value ?? "")
|
|
|
10
11
|
exports.escapeHtml = escapeHtml;
|
|
11
12
|
const formatOrderReference = (displayId) => displayId === null ? "-" : `#${displayId}`;
|
|
12
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;
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
import type { DocumentMerchant } from "../types";
|
|
1
|
+
import type { DocumentRenderSettings, DocumentMerchant } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Shop-specific look on top of the default markup. A project passes it to
|
|
4
|
+
* `createDefaultDocumentHtmlFactory` from its own `htmlFactory`, so it keeps
|
|
5
|
+
* the plugin's content and changes only the appearance.
|
|
6
|
+
*/
|
|
7
|
+
export type DocumentTheme = {
|
|
8
|
+
/**
|
|
9
|
+
* CSS appended after the default stylesheet; later rules win. A
|
|
10
|
+
* full-page image such as a watermark belongs in `@page { background }`:
|
|
11
|
+
* it covers the whole sheet on every page, while Chromium clips fixed
|
|
12
|
+
* elements at the page margin and sizes an inline `<svg>` in them wrongly
|
|
13
|
+
* from the second page on.
|
|
14
|
+
*/
|
|
15
|
+
readonly stylesheet?: string;
|
|
16
|
+
};
|
|
2
17
|
export type DocumentLayoutInput = {
|
|
3
18
|
readonly body: string;
|
|
4
19
|
readonly documentTitle: string;
|
|
@@ -8,10 +23,13 @@ export type DocumentLayoutInput = {
|
|
|
8
23
|
}[];
|
|
9
24
|
readonly number: string;
|
|
10
25
|
readonly pageTitle: string;
|
|
26
|
+
/** Escaped recipient address markup; `null` leaves the block empty. */
|
|
27
|
+
readonly recipient: string | null;
|
|
11
28
|
};
|
|
12
29
|
export type DocumentLayout = (input: DocumentLayoutInput) => string;
|
|
30
|
+
export declare const formatCountryName: (countryCode: string) => string;
|
|
13
31
|
/**
|
|
14
32
|
* `includeLegalDetails` adds UID, company register, contact and bank details
|
|
15
|
-
* to the footer (financial documents); packing lists show only the
|
|
33
|
+
* to the footer (financial documents); packing lists show only the sender.
|
|
16
34
|
*/
|
|
17
|
-
export declare const createDocumentLayout: (merchant: DocumentMerchant, includeLegalDetails: boolean) => DocumentLayout;
|
|
35
|
+
export declare const createDocumentLayout: (merchant: DocumentMerchant, settings: DocumentRenderSettings, includeLegalDetails: boolean, theme?: DocumentTheme) => DocumentLayout;
|