@hartl-services/medusa-base 2.8.0 → 2.10.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/admin-components/features/plugin-features/components/PluginSettingsForm.js +4 -5
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.d.ts +0 -5
- package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.js +0 -5
- package/.medusa/server/src/admin/admin-components/i18n/texts.d.ts +1 -2
- package/.medusa/server/src/admin/admin-components/i18n/texts.js +1 -2
- package/.medusa/server/src/admin/index.js +106 -69
- package/.medusa/server/src/admin/index.mjs +107 -70
- package/.medusa/server/src/api/admin/email/templates/[kind]/send-test/route.js +7 -1
- package/.medusa/server/src/documents/templates/financial-document.js +23 -4
- package/.medusa/server/src/email/application/warn-credential-expiry.js +77 -0
- package/.medusa/server/src/email/config/index.js +8 -2
- package/.medusa/server/src/email/config/notification-module.js +28 -2
- package/.medusa/server/src/email/domain/business-keys.js +5 -2
- package/.medusa/server/src/email/domain/failure-classification.js +9 -1
- package/.medusa/server/src/email/plugin-features.js +123 -1
- package/.medusa/server/src/email/rendering/default-templates.js +16 -1
- package/.medusa/server/src/email/rendering/render-email.js +2 -1
- package/.medusa/server/src/email/types/email-kinds.js +2 -1
- package/.medusa/server/src/email/types/template-data.js +8 -1
- package/.medusa/server/src/jobs/email-credential-expiry.js +27 -0
- package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +4 -1
- package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +103 -29
- package/.medusa/server/src/modules/email/domain/message-state.js +3 -1
- package/.medusa/server/src/modules/email/migrations/Migration20261011120000.js +20 -0
- package/.medusa/server/src/providers/smtp/delivery-settings.js +75 -0
- package/.medusa/server/src/providers/smtp/graph-transport.js +150 -0
- package/.medusa/server/src/providers/smtp/service.js +105 -37
- package/.medusa/server/src/providers/smtp/types.js +7 -1
- package/.medusa/server/src/shared/lib/index.js +2 -1
- package/.medusa/server/src/shared/lib/shipping-tax.js +12 -0
- package/.medusa/server/src/shop-config/build-food-shop-config.js +3 -5
- package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +44 -33
- package/README.md +118 -69
- package/dist/documents/templates/financial-document.js +22 -3
- package/dist/documents/types/documents.d.ts +13 -0
- package/dist/email/config/index.d.ts +6 -1
- package/dist/email/config/index.js +7 -1
- package/dist/email/config/notification-module.d.ts +25 -0
- package/dist/email/config/notification-module.js +27 -1
- package/dist/email/types/email-kinds.d.ts +2 -1
- package/dist/email/types/email-kinds.js +1 -0
- package/dist/email/types/template-data.d.ts +10 -0
- package/dist/email/types/template-data.js +7 -0
- package/dist/shared/lib/index.d.ts +1 -0
- package/dist/shared/lib/index.js +1 -0
- package/dist/shared/lib/shipping-tax.d.ts +8 -0
- package/dist/shared/lib/shipping-tax.js +11 -0
- package/dist/shop-config/build-food-shop-config.d.ts +1 -1
- package/dist/shop-config/build-food-shop-config.js +2 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ packing lists) and S3 file storage, and the turnkey `medusa-config.ts` preset
|
|
|
50
50
|
`@hartl-services/medusa-base/config` and Sentry's `register()` from
|
|
51
51
|
`@hartl-services/medusa-base/instrumentation` (see "Config helpers" and
|
|
52
52
|
"Sentry instrumentation").
|
|
53
|
-
- Sends the shop's transactional e-mails over SMTP (module `email`, see
|
|
53
|
+
- Sends the shop's transactional e-mails over SMTP or Microsoft 365, chosen in the Admin (module `email`, see
|
|
54
54
|
"E-Mail"), issues invoices, cancellation documents and packing lists as
|
|
55
55
|
private PDFs (module `documents`, see "Dokumente") and stores files in
|
|
56
56
|
S3-compatible storage behind the `/files` gateway (see "Dateispeicher (S3)").
|
|
@@ -234,13 +234,18 @@ All optional; the Admin response always carries them (`null`, `[]` or
|
|
|
234
234
|
unchanged, e.g. by the plugin switch of a switchable plugin). The Admin locks
|
|
235
235
|
the switch of the last enabled top-level feature with a hint. The Admin DTO
|
|
236
236
|
field is `require_enabled_feature: boolean` (`false` when not declared).
|
|
237
|
-
- `setting_groups`: `{ name, description?, note?, requires_feature? }` per
|
|
237
|
+
- `setting_groups`: `{ name, description?, note?, requires_feature?, visible_when? }` per
|
|
238
238
|
settings group. `name` equals the `group` of at least one setting and is
|
|
239
239
|
unique; `requires_feature` is a declared feature key, the Admin dims the group
|
|
240
240
|
(still editable) while that feature is not effectively enabled.
|
|
241
|
+
`visible_when: { setting, equals }` names a `select` or `boolean` setting
|
|
242
|
+
outside the group and one of its values: the group applies only while that
|
|
243
|
+
setting has the value. The Admin hides it otherwise (by the form's current
|
|
244
|
+
value), and its required settings do not count as `missing`; stored values
|
|
245
|
+
are kept. The Admin DTO carries `visible_when` or `null`.
|
|
241
246
|
- Per setting: `hint` (short text under the field, shown instead of
|
|
242
247
|
`description`), `placeholder` (not for `boolean`, `select`, `image`,
|
|
243
|
-
`color`), `width` (`"full"` default or `"half"`: consecutive half settings
|
|
248
|
+
`color`, `date`), `width` (`"full"` default or `"half"`: consecutive half settings
|
|
244
249
|
share a two-column row; not for `boolean`) and `section` (sub-heading inside
|
|
245
250
|
the group, shared by consecutive settings with the same value).
|
|
246
251
|
|
|
@@ -338,7 +343,7 @@ in-memory copy of the toggle (see "Kontobestätigung per E-Mail").
|
|
|
338
343
|
|
|
339
344
|
Configuration a plugin reads at RUN time (for example an external system's
|
|
340
345
|
URL and API key) is declared in the descriptor and edited on the plugin's
|
|
341
|
-
Admin page. Boot-time provider configuration (
|
|
346
|
+
Admin page. Boot-time provider configuration (S3, Sentry, Redis) and
|
|
342
347
|
function-valued options stay in `medusa-config.ts`.
|
|
343
348
|
|
|
344
349
|
```ts
|
|
@@ -383,7 +388,8 @@ class DemoModuleService extends MedusaService({ /* models */ }) {
|
|
|
383
388
|
through Medusa's `/admin/uploads` into the public File Module storage and
|
|
384
389
|
previews; replacing or removing it leaves the old file in storage), `color`
|
|
385
390
|
(a hex color `#rrggbb`, stored lowercase; the Admin shows a hex field with a
|
|
386
|
-
color picker and checks the format before saving)
|
|
391
|
+
color picker and checks the format before saving), `date` (a calendar day
|
|
392
|
+
`YYYY-MM-DD`; the Admin shows `AdminDatePicker`). An optional `group`
|
|
387
393
|
(non-empty text) is a section heading: the Admin starts a new section with
|
|
388
394
|
that heading whenever the group changes from one setting to the next. Keys
|
|
389
395
|
follow the feature key format and are unique. A `secret` has no default;
|
|
@@ -394,8 +400,8 @@ class DemoModuleService extends MedusaService({ /* models */ }) {
|
|
|
394
400
|
`api_url` so a stored key is never sent to a new host. `definePluginFeatures`
|
|
395
401
|
throws at import time otherwise (also for an `invalidates` entry that is not
|
|
396
402
|
a secret of the plugin). The Admin renders a `select` as a dropdown, gives
|
|
397
|
-
number inputs their bounds and shows "
|
|
398
|
-
|
|
403
|
+
number inputs their bounds and shows "Wird gelöscht, wenn … sich ändert"
|
|
404
|
+
under each secret named in an `invalidates`.
|
|
399
405
|
- Precedence per key: stored in the Admin > `pluginSettingsFallback`
|
|
400
406
|
(medusa-config) > declared default > not set. A value that does not fit the
|
|
401
407
|
declared type is skipped. A plugin is `configured` when every required
|
|
@@ -1326,7 +1332,7 @@ have no dependency on Medusa; the subpath does not load `@sentry/node`.
|
|
|
1326
1332
|
| `resolvePublicOrigin(value, name, allowHttp)`, `shopUrl(origin, path)` | Validate a bare HTTP(S) origin; build an absolute URL that cannot leave the origin |
|
|
1327
1333
|
| `resolveStripe(values)` | Checks the `sk_` key and the `whsec_` webhook secret |
|
|
1328
1334
|
| `resolveRedisEventBus(environment, phase)` | `{ url } \| undefined`: optional in development, required at runtime elsewhere, never used in a build, authenticated `rediss://` in production |
|
|
1329
|
-
| `createSmtpNotificationModuleConfig`, `resolveSmtp`, `SMTP_ENVIRONMENT_VARIABLES`, `SMTP_BUILD_PLACEHOLDERS
|
|
1335
|
+
| `createEmailNotificationModuleConfig`, `defineShopEmailConfiguration` (deprecated: `createSmtpNotificationModuleConfig`, `resolveSmtp`, `SMTP_ENVIRONMENT_VARIABLES`, `SMTP_BUILD_PLACEHOLDERS`) | Mail notification provider and E-Mail configuration (see "E-Mail") |
|
|
1330
1336
|
| `createS3CacheStorageConfig`, `createS3CacheFileModuleConfig`, `resolveS3Credentials`, `resolveExpirationStrategy`, `S3_BUILD_PLACEHOLDERS` | S3 file storage (see "Dateispeicher (S3)") |
|
|
1331
1337
|
| `createDefaultDocumentHtmlFactory`, `DOCUMENTS_BUILD_PLACEHOLDERS` | Documents markup and build placeholder (see "Dokumente") |
|
|
1332
1338
|
|
|
@@ -1373,36 +1379,32 @@ this export (or `sentry-logger`) lists `@sentry/node` in its own
|
|
|
1373
1379
|
## E-Mail
|
|
1374
1380
|
|
|
1375
1381
|
The base owns transactional e-mail for the shop: logical email messages,
|
|
1376
|
-
attempts, rendering, the Medusa Notification integration, SMTP
|
|
1377
|
-
retries and e-mail-specific Admin operations. It is
|
|
1378
|
-
|
|
1382
|
+
attempts, rendering, the Medusa Notification integration, the SMTP and
|
|
1383
|
+
Microsoft 365 transports, retries and e-mail-specific Admin operations. It is
|
|
1384
|
+
the `email` module, its notification provider (`providers/smtp`, id `smtp`),
|
|
1385
|
+
its subscribers, jobs and
|
|
1379
1386
|
workflows, and the Admin page `/settings/hartl-services/email`. There is no
|
|
1380
1387
|
separate e-mail package since 1.0.0.
|
|
1381
1388
|
|
|
1382
1389
|
### Configuration
|
|
1383
1390
|
|
|
1384
|
-
The
|
|
1385
|
-
`
|
|
1386
|
-
`@hartl-services/medusa-base/config`);
|
|
1391
|
+
The notification provider is a `modules` entry built by
|
|
1392
|
+
`createEmailNotificationModuleConfig` (from
|
|
1393
|
+
`@hartl-services/medusa-base/config`); which service it sends through and the
|
|
1394
|
+
credentials are set in the Admin (see "Versand"). The branding and rendering
|
|
1387
1395
|
configuration goes into the `email` namespace of the base options:
|
|
1388
1396
|
|
|
1389
1397
|
```ts
|
|
1390
|
-
import {
|
|
1391
|
-
createSmtpNotificationModuleConfig,
|
|
1392
|
-
resolveSmtp,
|
|
1393
|
-
SMTP_ENVIRONMENT_VARIABLES,
|
|
1394
|
-
} from "@hartl-services/medusa-base/config";
|
|
1395
|
-
|
|
1396
|
-
// Validates the seven SMTP_* variables (see "SMTP environment helpers").
|
|
1397
|
-
const smtp = resolveSmtp(
|
|
1398
|
-
Object.fromEntries(
|
|
1399
|
-
SMTP_ENVIRONMENT_VARIABLES.map((name) => [name, process.env[name] ?? ""]),
|
|
1400
|
-
) as Record<(typeof SMTP_ENVIRONMENT_VARIABLES)[number], string>,
|
|
1401
|
-
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test",
|
|
1402
|
-
);
|
|
1398
|
+
import { createEmailNotificationModuleConfig } from "@hartl-services/medusa-base/config";
|
|
1403
1399
|
|
|
1404
1400
|
export default defineConfig({
|
|
1405
|
-
modules: [
|
|
1401
|
+
modules: [
|
|
1402
|
+
createEmailNotificationModuleConfig({
|
|
1403
|
+
// Permits "Verschlüsselung: Keine" (a local SMTP server); never in production.
|
|
1404
|
+
allowInsecure:
|
|
1405
|
+
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test",
|
|
1406
|
+
}),
|
|
1407
|
+
],
|
|
1406
1408
|
plugins: [
|
|
1407
1409
|
{
|
|
1408
1410
|
resolve: "@hartl-services/medusa-base",
|
|
@@ -1456,24 +1458,73 @@ affiliate-reminder and partner-application e-mails come from
|
|
|
1456
1458
|
`@hartl-services/medusa-food-supplements-contracts/events/documents` and
|
|
1457
1459
|
`/events/affiliate`, so installing the base does not pull in Affiliate.
|
|
1458
1460
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
development/test):
|
|
1462
|
-
|
|
1463
|
-
```text
|
|
1464
|
-
SMTP_HOST=
|
|
1465
|
-
SMTP_PORT=
|
|
1466
|
-
SMTP_SECURE=
|
|
1467
|
-
SMTP_REQUIRE_TLS=
|
|
1468
|
-
SMTP_USER=
|
|
1469
|
-
SMTP_PASSWORD=
|
|
1470
|
-
SMTP_FROM=
|
|
1471
|
-
```
|
|
1461
|
+
The provider reads no environment variables (since 2.9.0; the former
|
|
1462
|
+
`SMTP_*` variables are ignored).
|
|
1472
1463
|
|
|
1473
1464
|
Set `REDIS_URL` to use `@medusajs/event-bus-redis` instead of Medusa's local
|
|
1474
1465
|
Event Bus; production requires an authenticated `rediss://` URL. See "Event
|
|
1475
1466
|
Bus" below for retry/backoff behavior.
|
|
1476
1467
|
|
|
1468
|
+
### Versand
|
|
1469
|
+
|
|
1470
|
+
The page E-Mail → "Versand" (`/settings/hartl-services/email/delivery`, linked
|
|
1471
|
+
under "Verwaltung") edits the plugin's settings. It stays reachable while the
|
|
1472
|
+
E-Mail plugin is off, because the login-code mail is still sent then.
|
|
1473
|
+
|
|
1474
|
+
| Key | Type | Required |
|
|
1475
|
+
|---|---|---|
|
|
1476
|
+
| `provider` (Versand über, group "Anbieter") | select `smtp` \| `microsoft365` | yes |
|
|
1477
|
+
| `smtp_host`, `smtp_port` (default 587), `smtp_security` (`starttls` default, `tls`, `none`), `smtp_from` (Absender, `Name <address>` allowed) | string / number / select / string | yes, while SMTP |
|
|
1478
|
+
| `smtp_user`, `smtp_password` (secret) | string / secret | no (no login without a user) |
|
|
1479
|
+
| `microsoft_tenant_id`, `microsoft_client_id`, `microsoft_client_secret` (secret), `microsoft_client_secret_expires_on` (date), `microsoft_mailbox` (Postfach) | string / secret / date | yes, while Microsoft 365 |
|
|
1480
|
+
|
|
1481
|
+
The groups "SMTP" and "Microsoft 365" are shown and required only for the
|
|
1482
|
+
selected provider (`visible_when`). Changing the host deletes the SMTP
|
|
1483
|
+
password; changing tenant or client id deletes the client secret.
|
|
1484
|
+
|
|
1485
|
+
The provider reads the settings on every send, never at boot; a shop without
|
|
1486
|
+
them boots and its mails fail as `not_configured` (retried like a connection
|
|
1487
|
+
failure, then `failed`; a log warning names the missing keys). `none`
|
|
1488
|
+
encryption is accepted only with `allowInsecure`. A rejected login (SMTP
|
|
1489
|
+
`EAUTH`, a Graph token or 401/403 error) is `auth_failed` and not retried.
|
|
1490
|
+
The test mail of the template editor shows both as their own message.
|
|
1491
|
+
|
|
1492
|
+
**Microsoft 365** sends with Microsoft Graph `POST /users/{mailbox}/sendMail`
|
|
1493
|
+
and an app-only token (client credentials, cached in memory per tenant,
|
|
1494
|
+
client and secret). Graph has one body, so the mail goes out as HTML; it is
|
|
1495
|
+
saved to the mailbox's Sent Items. A request above 4 MB (large PDF
|
|
1496
|
+
attachments) fails as `invalid_content`. 429 and 5xx are temporary, other 4xx
|
|
1497
|
+
permanent. Setup:
|
|
1498
|
+
|
|
1499
|
+
1. Microsoft Entra admin center → App registrations → New registration
|
|
1500
|
+
(single tenant, no redirect URI). Note the Application (client) ID and
|
|
1501
|
+
Directory (tenant) ID.
|
|
1502
|
+
2. Certificates & secrets → New client secret (at most 24 months); copy the
|
|
1503
|
+
value and its expiry date.
|
|
1504
|
+
3. Do **not** grant the Graph application permission `Mail.Send` in Entra: it
|
|
1505
|
+
would allow sending as every mailbox. Grant it in Exchange Online, limited
|
|
1506
|
+
to the shop's mailbox (RBAC for Applications), in Exchange Online
|
|
1507
|
+
PowerShell:
|
|
1508
|
+
|
|
1509
|
+
```powershell
|
|
1510
|
+
New-ServicePrincipal -AppId <client-id> -ObjectId <enterprise-app-object-id> -DisplayName "Shop Mail"
|
|
1511
|
+
New-ManagementScope -Name "Shop Mailbox" -RecipientRestrictionFilter "PrimarySmtpAddress -eq 'shop@example.com'"
|
|
1512
|
+
New-ManagementRoleAssignment -App <client-id> -Role "Application Mail.Send" -CustomResourceScope "Shop Mailbox"
|
|
1513
|
+
```
|
|
1514
|
+
|
|
1515
|
+
(`<enterprise-app-object-id>` is the object id under Enterprise
|
|
1516
|
+
applications, not the one of the app registration.)
|
|
1517
|
+
4. In the Admin: E-Mail → Versand → Anbieter "Microsoft 365", enter tenant
|
|
1518
|
+
id, client id, secret, its expiry date and the mailbox; send a test mail
|
|
1519
|
+
from a template.
|
|
1520
|
+
|
|
1521
|
+
Seven days before "Secret gültig bis" (shop time zone) the daily job
|
|
1522
|
+
`email-credential-expiry` (07:00 server time, while the E-Mail plugin is on
|
|
1523
|
+
and Microsoft 365 is selected) sends the mail `system.credential_expiry.admin`
|
|
1524
|
+
to the e-mail of the Shop-Stammdaten, once per client id and expiry date (a
|
|
1525
|
+
log warning when the Shop-Stammdaten have no e-mail). A new secret with a new
|
|
1526
|
+
date warns again.
|
|
1527
|
+
|
|
1477
1528
|
### Feature toggles
|
|
1478
1529
|
|
|
1479
1530
|
The E-Mail module declares its toggles to `@hartl-services/medusa-base` under the
|
|
@@ -1996,30 +2047,14 @@ configuration (blank `displayName`, a non-function `layout` or renderer, an
|
|
|
1996
2047
|
unknown renderer kind) fails at startup and not at the first send. Custom
|
|
1997
2048
|
renderers must escape dynamic values in `bodyHtml` themselves.
|
|
1998
2049
|
|
|
1999
|
-
### SMTP
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
them only while building.
|
|
2008
|
-
- `resolveSmtp(values, allowInsecure)` — validates the values and returns the
|
|
2009
|
-
`ResolvedSmtpConfiguration` for `createSmtpNotificationModuleConfig`. It
|
|
2010
|
-
rejects a port outside 1–65535, a non-`"true"`/`"false"` boolean and an
|
|
2011
|
-
invalid `SMTP_FROM`. `allowInsecure` permits plain SMTP (development, test,
|
|
2012
|
-
build); otherwise `SMTP_SECURE` or `SMTP_REQUIRE_TLS` must be `true`.
|
|
2013
|
-
|
|
2014
|
-
Runtime requires `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_REQUIRE_TLS`,
|
|
2015
|
-
`SMTP_USER`, `SMTP_PASSWORD`, and `SMTP_FROM`. `SMTP_FROM` is a bare address
|
|
2016
|
-
or carries the sender's display name: `Example GmbH <info@example.com>` (quote
|
|
2017
|
-
the value in a `.env` file). `SMTP_SECURE=true` selects
|
|
2018
|
-
implicit TLS; `SMTP_REQUIRE_TLS=true` requires STARTTLS. Outside development
|
|
2019
|
-
and test, at least one must be true. Certificate verification is never
|
|
2020
|
-
disabled. Development and test may set both to `false` for an isolated local
|
|
2021
|
-
SMTP server. SMTP endpoints and credentials never belong in the shop
|
|
2022
|
-
configuration.
|
|
2050
|
+
### Deprecated SMTP helpers
|
|
2051
|
+
|
|
2052
|
+
`createSmtpNotificationModuleConfig(smtp)`, `resolveSmtp`,
|
|
2053
|
+
`SMTP_ENVIRONMENT_VARIABLES` and `SMTP_BUILD_PLACEHOLDERS` remain exported
|
|
2054
|
+
for shops that still read `SMTP_*` in their `medusa-config.ts`. The values
|
|
2055
|
+
given to `createSmtpNotificationModuleConfig` only fill SMTP settings that are
|
|
2056
|
+
not stored in the Admin (the Admin status does not count them as configured).
|
|
2057
|
+
Use `createEmailNotificationModuleConfig` and the "Versand" page instead.
|
|
2023
2058
|
|
|
2024
2059
|
### Delivery Semantics
|
|
2025
2060
|
|
|
@@ -2339,10 +2374,24 @@ separate documents package since 1.0.0.
|
|
|
2339
2374
|
persists the customer company, customer UID and exemption wording in that
|
|
2340
2375
|
same snapshot. B2C and non-eligible evidence add no tax-proof statement.
|
|
2341
2376
|
- Freezes a tax breakdown (`totals.tax_breakdown`) grouped by the order's
|
|
2342
|
-
item
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2377
|
+
item tax-line name and rate, as configured on the Medusa tax region, with
|
|
2378
|
+
the tax and the net amount (`taxable_amount`, after discounts) per rate.
|
|
2379
|
+
Shipping follows the items: a shipping tax line with the code
|
|
2380
|
+
`APPORTIONED_SHIPPING_TAX_CODE` (`/lib`; Tax Compliance's "Anteilige
|
|
2381
|
+
Versandsteuer") is split back onto the item rates by the items' net
|
|
2382
|
+
amount after discounts, the key Tax Compliance weights it by, so the
|
|
2383
|
+
invoice never prints a weighted rate. Other shipping tax joins the items'
|
|
2384
|
+
row when the cart has one item rate equal to it, otherwise it keeps its
|
|
2385
|
+
own row.
|
|
2386
|
+
Amounts are rounded to the currency and add up exactly to the tax total;
|
|
2387
|
+
cancellations scale them proportionally. Snapshots issued before these
|
|
2388
|
+
fields existed read as an empty breakdown or without net amounts.
|
|
2389
|
+
- Prints net amounts before discounts: line unit price and "Summe",
|
|
2390
|
+
"Zwischensumme" (items), "Versand" (before a free-shipping promotion),
|
|
2391
|
+
then all promotions as one net "Rabatt" (only when there is a discount),
|
|
2392
|
+
the tax rows and the gross "Gesamt"; the rows add up to the total. With
|
|
2393
|
+
several tax rates each tax row also names its net amount ("AT Reduziert
|
|
2394
|
+
10 % auf € 55,00").
|
|
2346
2395
|
- Persists one immutable decision per shipped Fulfillment. A leased, Order-
|
|
2347
2396
|
unique coordinator chooses exactly one invoice carrier without assuming
|
|
2348
2397
|
event delivery order; all other shipments become `non_carrier`.
|
|
@@ -63,7 +63,7 @@ const renderLines = (input, timeZone) => input.lines
|
|
|
63
63
|
</td>
|
|
64
64
|
<td class="number quantity">${(0, lib_1.escapeHtml)(line.quantity)}</td>
|
|
65
65
|
<td class="number">${(0, html_utils_1.renderMoney)(line.unit_price)}</td>
|
|
66
|
-
<td class="number">${(0, html_utils_1.renderMoney)(line.
|
|
66
|
+
<td class="number">${(0, html_utils_1.renderMoney)(line.subtotal)}</td>
|
|
67
67
|
</tr>`)
|
|
68
68
|
.join("");
|
|
69
69
|
const isExemptIntraCommunitySupply = (input) => input.order.tax_compliance?.exemption_wording ===
|
|
@@ -93,11 +93,30 @@ const formatTaxRate = (rate) => new Intl.NumberFormat(format_1.DEFAULT_FORMAT_LO
|
|
|
93
93
|
}).format(rate / 100);
|
|
94
94
|
/** One totals row below the Menge/Einzelpreis columns; the amount sits under "Summe". */
|
|
95
95
|
const renderTotalRow = (label, value, className) => `<tr${className ? ` class="${className}"` : ""}><td class="spacer"></td><td class="label" colspan="2">${(0, lib_1.escapeHtml)(label)}</td><td class="number">${value}</td></tr>`;
|
|
96
|
+
/** No discount, no row. */
|
|
97
|
+
const renderDiscountRow = (input) => Number(input.totals.discount_total.amount) === 0
|
|
98
|
+
? ""
|
|
99
|
+
: renderTotalRow("Rabatt", renderDiscountTotal(input));
|
|
100
|
+
/**
|
|
101
|
+
* One row per tax rate. With several rates each row also names its net
|
|
102
|
+
* amount, since an invoice states the amount taxed at every rate.
|
|
103
|
+
*/
|
|
96
104
|
const renderTaxRows = (input) => {
|
|
97
105
|
const { tax_breakdown: breakdown, tax_total: taxTotal } = input.totals;
|
|
106
|
+
const withNetAmount = breakdown.length > 1;
|
|
98
107
|
const rows = breakdown.length > 0
|
|
99
108
|
? breakdown.map((entry) => ({
|
|
100
|
-
label:
|
|
109
|
+
label: [
|
|
110
|
+
`${entry.name} ${formatTaxRate(entry.rate)}`,
|
|
111
|
+
withNetAmount && entry.taxable_amount
|
|
112
|
+
? `auf ${(0, format_1.formatMoney)({
|
|
113
|
+
amount: entry.taxable_amount.amount,
|
|
114
|
+
currencyCode: entry.taxable_amount.currency_code,
|
|
115
|
+
})}`
|
|
116
|
+
: null,
|
|
117
|
+
]
|
|
118
|
+
.filter((part) => Boolean(part))
|
|
119
|
+
.join(" "),
|
|
101
120
|
money: entry.tax_total,
|
|
102
121
|
}))
|
|
103
122
|
: [{ label: "Umsatzsteuer", money: taxTotal }];
|
|
@@ -141,7 +160,7 @@ const renderFinancialDocument = (input, layout, timeZone) => {
|
|
|
141
160
|
<tbody class="totals">
|
|
142
161
|
${renderTotalRow("Zwischensumme", (0, html_utils_1.renderMoney)(totals.subtotal))}
|
|
143
162
|
${renderTotalRow("Versand", (0, html_utils_1.renderMoney)(totals.shipping_total))}
|
|
144
|
-
${
|
|
163
|
+
${renderDiscountRow(input)}
|
|
145
164
|
${renderTaxRows(input)}
|
|
146
165
|
${renderTotalRow("Gesamt", (0, html_utils_1.renderMoney)(totals.total), "grand-total")}
|
|
147
166
|
</tbody>
|
|
@@ -40,13 +40,20 @@ export type DocumentTaxBreakdownEntry = {
|
|
|
40
40
|
code: string | null;
|
|
41
41
|
rate: number;
|
|
42
42
|
tax_total: DocumentMoney;
|
|
43
|
+
/**
|
|
44
|
+
* Net amount taxed at this rate (items and their share of shipping, after
|
|
45
|
+
* discounts). Missing in snapshots issued before it was recorded.
|
|
46
|
+
*/
|
|
47
|
+
taxable_amount?: DocumentMoney;
|
|
43
48
|
};
|
|
44
49
|
export type DocumentTemplateLine = {
|
|
45
50
|
id: string;
|
|
46
51
|
title: string;
|
|
47
52
|
sku: string | null;
|
|
48
53
|
quantity: number;
|
|
54
|
+
/** Net, before discounts (older invoice snapshots: Medusa's unit price). */
|
|
49
55
|
unit_price: DocumentMoney;
|
|
56
|
+
/** Net, before discounts; printed under "Summe". */
|
|
50
57
|
subtotal: DocumentMoney;
|
|
51
58
|
tax_total: DocumentMoney;
|
|
52
59
|
total: DocumentMoney;
|
|
@@ -74,6 +81,12 @@ export type DocumentTemplateInput = {
|
|
|
74
81
|
tax_compliance: DocumentTaxComplianceDTO | null;
|
|
75
82
|
};
|
|
76
83
|
lines: DocumentTemplateLine[];
|
|
84
|
+
/**
|
|
85
|
+
* Subtotal (items) and shipping are net amounts before discounts, the
|
|
86
|
+
* discount is net, so they add up with the tax to the total. Older invoice
|
|
87
|
+
* snapshots carry Medusa's order subtotal (items plus shipping) and gross
|
|
88
|
+
* shipping and discount totals.
|
|
89
|
+
*/
|
|
77
90
|
totals: {
|
|
78
91
|
subtotal: DocumentMoney;
|
|
79
92
|
tax_total: DocumentMoney;
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import type { ResolvedSmtpConfiguration, ShopEmailConfiguration } from "../types";
|
|
2
|
-
export { createSmtpNotificationModuleConfig } from "./notification-module";
|
|
2
|
+
export { createEmailNotificationModuleConfig, createSmtpNotificationModuleConfig, } from "./notification-module";
|
|
3
3
|
export declare const defineShopEmailConfiguration: <Configuration extends ShopEmailConfiguration>(configuration: Configuration) => Configuration;
|
|
4
4
|
export type { ResolvedSmtpConfiguration, ShopEmailConfiguration, } from "../types";
|
|
5
|
+
/** @deprecated The provider is set in the Admin (E-Mail > Versand). */
|
|
5
6
|
export declare const SMTP_ENVIRONMENT_VARIABLES: readonly ["SMTP_HOST", "SMTP_PORT", "SMTP_SECURE", "SMTP_USER", "SMTP_PASSWORD", "SMTP_FROM", "SMTP_REQUIRE_TLS"];
|
|
6
7
|
/**
|
|
8
|
+
* @deprecated The provider is set in the Admin (E-Mail > Versand).
|
|
9
|
+
*
|
|
7
10
|
* `medusa build` loads the configuration without deployment secrets. These
|
|
8
11
|
* placeholders only satisfy the required-variable checks of a build; they
|
|
9
12
|
* never reach a running server.
|
|
10
13
|
*/
|
|
11
14
|
export declare const SMTP_BUILD_PLACEHOLDERS: Readonly<Record<string, string>>;
|
|
12
15
|
/**
|
|
16
|
+
* @deprecated The provider is set in the Admin (E-Mail > Versand).
|
|
17
|
+
*
|
|
13
18
|
* Validates the SMTP environment values and returns the transport
|
|
14
19
|
* configuration. `allowInsecure` permits plain SMTP for development, tests
|
|
15
20
|
* and builds; otherwise `SMTP_SECURE` or `SMTP_REQUIRE_TLS` must be `true`.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveSmtp = exports.SMTP_BUILD_PLACEHOLDERS = exports.SMTP_ENVIRONMENT_VARIABLES = exports.defineShopEmailConfiguration = exports.createSmtpNotificationModuleConfig = void 0;
|
|
3
|
+
exports.resolveSmtp = exports.SMTP_BUILD_PLACEHOLDERS = exports.SMTP_ENVIRONMENT_VARIABLES = exports.defineShopEmailConfiguration = exports.createSmtpNotificationModuleConfig = exports.createEmailNotificationModuleConfig = void 0;
|
|
4
4
|
const config_1 = require("../../public/config");
|
|
5
5
|
const sender_1 = require("../domain/sender");
|
|
6
6
|
var notification_module_1 = require("./notification-module");
|
|
7
|
+
Object.defineProperty(exports, "createEmailNotificationModuleConfig", { enumerable: true, get: function () { return notification_module_1.createEmailNotificationModuleConfig; } });
|
|
7
8
|
Object.defineProperty(exports, "createSmtpNotificationModuleConfig", { enumerable: true, get: function () { return notification_module_1.createSmtpNotificationModuleConfig; } });
|
|
8
9
|
const defineShopEmailConfiguration = (configuration) => configuration;
|
|
9
10
|
exports.defineShopEmailConfiguration = defineShopEmailConfiguration;
|
|
11
|
+
/** @deprecated The provider is set in the Admin (E-Mail > Versand). */
|
|
10
12
|
exports.SMTP_ENVIRONMENT_VARIABLES = [
|
|
11
13
|
"SMTP_HOST",
|
|
12
14
|
"SMTP_PORT",
|
|
@@ -17,6 +19,8 @@ exports.SMTP_ENVIRONMENT_VARIABLES = [
|
|
|
17
19
|
"SMTP_REQUIRE_TLS",
|
|
18
20
|
];
|
|
19
21
|
/**
|
|
22
|
+
* @deprecated The provider is set in the Admin (E-Mail > Versand).
|
|
23
|
+
*
|
|
20
24
|
* `medusa build` loads the configuration without deployment secrets. These
|
|
21
25
|
* placeholders only satisfy the required-variable checks of a build; they
|
|
22
26
|
* never reach a running server.
|
|
@@ -31,6 +35,8 @@ exports.SMTP_BUILD_PLACEHOLDERS = {
|
|
|
31
35
|
SMTP_USER: "build-user",
|
|
32
36
|
};
|
|
33
37
|
/**
|
|
38
|
+
* @deprecated The provider is set in the Admin (E-Mail > Versand).
|
|
39
|
+
*
|
|
34
40
|
* Validates the SMTP environment values and returns the transport
|
|
35
41
|
* configuration. `allowInsecure` permits plain SMTP for development, tests
|
|
36
42
|
* and builds; otherwise `SMTP_SECURE` or `SMTP_REQUIRE_TLS` must be `true`.
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
import type { ResolvedSmtpConfiguration } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* The Notification Module with the shop's mail provider. Provider and
|
|
4
|
+
* credentials are set in the Admin (E-Mail > Versand); `allowInsecure`
|
|
5
|
+
* (development and tests only) permits SMTP without encryption there.
|
|
6
|
+
*/
|
|
7
|
+
export declare const createEmailNotificationModuleConfig: (options?: Readonly<{
|
|
8
|
+
allowInsecure?: boolean;
|
|
9
|
+
}>) => {
|
|
10
|
+
resolve: string;
|
|
11
|
+
options: {
|
|
12
|
+
providers: {
|
|
13
|
+
id: string;
|
|
14
|
+
options: {
|
|
15
|
+
allowInsecure: boolean;
|
|
16
|
+
channels: string[];
|
|
17
|
+
};
|
|
18
|
+
resolve: string;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Use `createEmailNotificationModuleConfig` and set the provider
|
|
24
|
+
* in the Admin (E-Mail > Versand). The SMTP values given here only fill the
|
|
25
|
+
* SMTP settings that are not stored in the Admin.
|
|
26
|
+
*/
|
|
2
27
|
export declare const createSmtpNotificationModuleConfig: (smtp: ResolvedSmtpConfiguration) => {
|
|
3
28
|
resolve: string;
|
|
4
29
|
options: {
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createSmtpNotificationModuleConfig = void 0;
|
|
3
|
+
exports.createSmtpNotificationModuleConfig = exports.createEmailNotificationModuleConfig = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The Notification Module with the shop's mail provider. Provider and
|
|
6
|
+
* credentials are set in the Admin (E-Mail > Versand); `allowInsecure`
|
|
7
|
+
* (development and tests only) permits SMTP without encryption there.
|
|
8
|
+
*/
|
|
9
|
+
const createEmailNotificationModuleConfig = (options = {}) => ({
|
|
10
|
+
resolve: "@medusajs/medusa/notification",
|
|
11
|
+
options: {
|
|
12
|
+
providers: [
|
|
13
|
+
{
|
|
14
|
+
id: "smtp",
|
|
15
|
+
options: {
|
|
16
|
+
allowInsecure: options.allowInsecure === true,
|
|
17
|
+
channels: ["email"],
|
|
18
|
+
},
|
|
19
|
+
resolve: "@hartl-services/medusa-base/providers/smtp",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
exports.createEmailNotificationModuleConfig = createEmailNotificationModuleConfig;
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated Use `createEmailNotificationModuleConfig` and set the provider
|
|
27
|
+
* in the Admin (E-Mail > Versand). The SMTP values given here only fill the
|
|
28
|
+
* SMTP settings that are not stored in the Admin.
|
|
29
|
+
*/
|
|
4
30
|
const createSmtpNotificationModuleConfig = (smtp) => ({
|
|
5
31
|
resolve: "@medusajs/medusa/notification",
|
|
6
32
|
options: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "@medusajs/framework/zod";
|
|
2
|
-
export declare const EMAIL_KINDS: readonly ["order.received", "order.received.admin", "order.shipped", "order.canceled", "order.return_requested", "order.return_received", "order.refunded", "auth.password_reset", "affiliate.consignment_report_due", "affiliate.application_submitted.admin", "user.invite", "auth.verification", "auth.login_code"];
|
|
2
|
+
export declare const EMAIL_KINDS: readonly ["order.received", "order.received.admin", "order.shipped", "order.canceled", "order.return_requested", "order.return_received", "order.refunded", "auth.password_reset", "affiliate.consignment_report_due", "affiliate.application_submitted.admin", "user.invite", "auth.verification", "auth.login_code", "system.credential_expiry.admin"];
|
|
3
3
|
export declare const EmailKindSchema: z.ZodEnum<{
|
|
4
4
|
"order.received": "order.received";
|
|
5
5
|
"order.received.admin": "order.received.admin";
|
|
@@ -14,6 +14,7 @@ export declare const EmailKindSchema: z.ZodEnum<{
|
|
|
14
14
|
"user.invite": "user.invite";
|
|
15
15
|
"auth.verification": "auth.verification";
|
|
16
16
|
"auth.login_code": "auth.login_code";
|
|
17
|
+
"system.credential_expiry.admin": "system.credential_expiry.admin";
|
|
17
18
|
}>;
|
|
18
19
|
export type EmailKind = (typeof EMAIL_KINDS)[number];
|
|
19
20
|
/**
|
|
@@ -376,6 +376,11 @@ export declare const EmailPersistedDataSchemas: {
|
|
|
376
376
|
locale: z.ZodNullable<z.ZodString>;
|
|
377
377
|
}, z.core.$strict>;
|
|
378
378
|
readonly "auth.login_code": z.ZodObject<{}, z.core.$strict>;
|
|
379
|
+
readonly "system.credential_expiry.admin": z.ZodObject<{
|
|
380
|
+
provider: z.ZodLiteral<"microsoft365">;
|
|
381
|
+
client_id: z.ZodString;
|
|
382
|
+
expires_on: z.ZodString;
|
|
383
|
+
}, z.core.$strict>;
|
|
379
384
|
};
|
|
380
385
|
export declare const EmailRenderDataSchemas: {
|
|
381
386
|
readonly "auth.password_reset": z.ZodObject<{
|
|
@@ -619,6 +624,11 @@ export declare const EmailRenderDataSchemas: {
|
|
|
619
624
|
company_name: z.ZodNullable<z.ZodString>;
|
|
620
625
|
message: z.ZodNullable<z.ZodString>;
|
|
621
626
|
}, z.core.$strict>;
|
|
627
|
+
readonly "system.credential_expiry.admin": z.ZodObject<{
|
|
628
|
+
provider: z.ZodLiteral<"microsoft365">;
|
|
629
|
+
client_id: z.ZodString;
|
|
630
|
+
expires_on: z.ZodString;
|
|
631
|
+
}, z.core.$strict>;
|
|
622
632
|
};
|
|
623
633
|
export type EmailPersistedDataMap = {
|
|
624
634
|
readonly [Kind in EmailKind]: Readonly<z.infer<(typeof EmailPersistedDataSchemas)[Kind]>>;
|
|
@@ -233,6 +233,13 @@ exports.EmailPersistedDataSchemas = {
|
|
|
233
233
|
.strict(),
|
|
234
234
|
// The code itself is never stored: it only exists in the render context.
|
|
235
235
|
"auth.login_code": zod_1.z.object({}).strict(),
|
|
236
|
+
"system.credential_expiry.admin": zod_1.z
|
|
237
|
+
.object({
|
|
238
|
+
provider: zod_1.z.literal("microsoft365"),
|
|
239
|
+
client_id: NonEmptyStringSchema,
|
|
240
|
+
expires_on: affiliate_1.CalendarDateSchema,
|
|
241
|
+
})
|
|
242
|
+
.strict(),
|
|
236
243
|
};
|
|
237
244
|
exports.EmailRenderDataSchemas = {
|
|
238
245
|
...exports.EmailPersistedDataSchemas,
|
package/dist/shared/lib/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __exportStar(require("./hash"), exports);
|
|
|
24
24
|
__exportStar(require("./logging"), exports);
|
|
25
25
|
__exportStar(require("./money"), exports);
|
|
26
26
|
__exportStar(require("./refreshing-snapshot"), exports);
|
|
27
|
+
__exportStar(require("./shipping-tax"), exports);
|
|
27
28
|
__exportStar(require("./text"), exports);
|
|
28
29
|
__exportStar(require("./transaction"), exports);
|
|
29
30
|
__exportStar(require("./wire-response"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code of a shipping tax line whose rate is the item rates weighted by the
|
|
3
|
+
* items' net amount after discounts (shipping follows the main supplies,
|
|
4
|
+
* split by their Entgelt). Tax Compliance writes it; Documents splits such a
|
|
5
|
+
* line back onto the item tax rates by the same key, because an invoice
|
|
6
|
+
* states the tax per actual rate.
|
|
7
|
+
*/
|
|
8
|
+
export declare const APPORTIONED_SHIPPING_TAX_CODE = "shipping_blended";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APPORTIONED_SHIPPING_TAX_CODE = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Code of a shipping tax line whose rate is the item rates weighted by the
|
|
6
|
+
* items' net amount after discounts (shipping follows the main supplies,
|
|
7
|
+
* split by their Entgelt). Tax Compliance writes it; Documents splits such a
|
|
8
|
+
* line back onto the item tax rates by the same key, because an invoice
|
|
9
|
+
* states the tax per actual rate.
|
|
10
|
+
*/
|
|
11
|
+
exports.APPORTIONED_SHIPPING_TAX_CODE = "shipping_blended";
|
|
@@ -31,7 +31,7 @@ export type FoodShopConfigInput = {
|
|
|
31
31
|
};
|
|
32
32
|
/**
|
|
33
33
|
* Builds the plain `medusa-config.ts` input of a food shop: Medusa core
|
|
34
|
-
* settings, the Redis event bus,
|
|
34
|
+
* settings, the Redis event bus, the mail provider (set up in the Admin), S3 file storage, tax and
|
|
35
35
|
* Stripe payment providers, and the Hartl Services plugins (the base plugin
|
|
36
36
|
* with its E-Mail, Documents and file storage modules plus five feature
|
|
37
37
|
* plugins). Deployment
|
|
@@ -12,7 +12,7 @@ const sentry_logger_1 = require("../public/sentry-logger");
|
|
|
12
12
|
const options_1 = require("./options");
|
|
13
13
|
/**
|
|
14
14
|
* Builds the plain `medusa-config.ts` input of a food shop: Medusa core
|
|
15
|
-
* settings, the Redis event bus,
|
|
15
|
+
* settings, the Redis event bus, the mail provider (set up in the Admin), S3 file storage, tax and
|
|
16
16
|
* Stripe payment providers, and the Hartl Services plugins (the base plugin
|
|
17
17
|
* with its E-Mail, Documents and file storage modules plus five feature
|
|
18
18
|
* plugins). Deployment
|
|
@@ -35,7 +35,6 @@ function buildFoodShopConfig(options, environment = process.env) {
|
|
|
35
35
|
"COOKIE_SECRET",
|
|
36
36
|
"MEDUSA_BACKEND_URL",
|
|
37
37
|
"STOREFRONT_URL",
|
|
38
|
-
...config_1.SMTP_ENVIRONMENT_VARIABLES,
|
|
39
38
|
"STRIPE_API_KEY",
|
|
40
39
|
"STRIPE_WEBHOOK_SECRET",
|
|
41
40
|
"S3_CACHE_STORAGE_BUCKET",
|
|
@@ -43,7 +42,6 @@ function buildFoodShopConfig(options, environment = process.env) {
|
|
|
43
42
|
"DOCUMENT_PDF_EXECUTABLE",
|
|
44
43
|
], phase, {
|
|
45
44
|
...config_1.MEDUSA_BUILD_PLACEHOLDERS,
|
|
46
|
-
...config_1.SMTP_BUILD_PLACEHOLDERS,
|
|
47
45
|
...config_1.S3_BUILD_PLACEHOLDERS,
|
|
48
46
|
...config_1.DOCUMENTS_BUILD_PLACEHOLDERS,
|
|
49
47
|
});
|
|
@@ -101,7 +99,7 @@ function buildFoodShopConfig(options, environment = process.env) {
|
|
|
101
99
|
},
|
|
102
100
|
]
|
|
103
101
|
: []),
|
|
104
|
-
(0, config_1.
|
|
102
|
+
(0, config_1.createEmailNotificationModuleConfig)({ allowInsecure }),
|
|
105
103
|
s3CacheStorage.fileModule,
|
|
106
104
|
{
|
|
107
105
|
// Replaces Medusa's default auth entry (same module key): the same
|