@hartl-services/medusa-base 2.2.1 → 2.4.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 +131 -48
- package/.medusa/server/src/admin/index.mjs +131 -48
- package/.medusa/server/src/api/admin/draft-orders/[id]/order-confirmation/route.js +23 -0
- package/.medusa/server/src/api/middlewares.js +16 -2
- package/.medusa/server/src/email/domain/business-keys.js +4 -2
- package/.medusa/server/src/email/plugin-features.js +3 -3
- package/.medusa/server/src/email/rendering/default-templates.js +37 -2
- package/.medusa/server/src/email/rendering/render-email.js +2 -1
- package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +5 -1
- package/.medusa/server/src/email/types/email-kinds.js +2 -1
- package/.medusa/server/src/email/types/template-data.js +12 -1
- package/.medusa/server/src/modules/email/migrations/Migration20261001120000.js +16 -0
- package/.medusa/server/src/shop-config/build-food-shop-config.js +3 -1
- package/.medusa/server/src/subscribers/affiliate-application-submitted.js +26 -0
- package/.medusa/server/src/subscribers/order-zero-total-paid.js +19 -0
- package/.medusa/server/src/workflows/email/prepare-affiliate-application-email.js +46 -0
- package/.medusa/server/src/workflows/email/update-draft-order-confirmation.js +49 -0
- package/.medusa/server/src/workflows/hooks/draft-order-confirmation-default.js +39 -0
- package/.medusa/server/src/workflows/order/mark-zero-total-order-paid.js +45 -0
- package/README.md +93 -8
- package/dist/email/types/email-kinds.d.ts +2 -1
- package/dist/email/types/email-kinds.js +1 -0
- package/dist/email/types/shop-configuration.d.ts +8 -0
- package/dist/email/types/template-data.d.ts +18 -0
- package/dist/email/types/template-data.js +11 -0
- package/dist/shop-config/build-food-shop-config.js +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const core_flows_1 = require("@medusajs/medusa/core-flows");
|
|
6
|
+
/**
|
|
7
|
+
* A draft order starts without a customer order confirmation: drafts are
|
|
8
|
+
* Admin tasks (sample shipments, manual orders), and the Admin opts in per
|
|
9
|
+
* draft through the "Bestellbestätigung" widget. `createOrderWorkflow` is the
|
|
10
|
+
* workflow behind `POST /admin/draft-orders`; its `no_notification_order`
|
|
11
|
+
* flag is overridden here because the Admin dashboard never sends it.
|
|
12
|
+
*/
|
|
13
|
+
core_flows_1.createOrderWorkflow.hooks.orderCreated(async ({ order }, { container }) => {
|
|
14
|
+
const { data } = await container
|
|
15
|
+
.resolve(utils_1.ContainerRegistrationKeys.QUERY)
|
|
16
|
+
.graph({
|
|
17
|
+
entity: "order",
|
|
18
|
+
fields: ["id", "is_draft_order", "no_notification"],
|
|
19
|
+
filters: { id: order.id },
|
|
20
|
+
});
|
|
21
|
+
const created = data[0];
|
|
22
|
+
if (!created?.is_draft_order || created.no_notification === true) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await container
|
|
26
|
+
.resolve(utils_1.Modules.ORDER)
|
|
27
|
+
.updateOrders(created.id, { no_notification: true });
|
|
28
|
+
return new workflows_sdk_1.StepResponse(undefined, {
|
|
29
|
+
id: created.id,
|
|
30
|
+
no_notification: created.no_notification ?? false,
|
|
31
|
+
});
|
|
32
|
+
}, async (previous, { container }) => {
|
|
33
|
+
if (!previous)
|
|
34
|
+
return;
|
|
35
|
+
await container
|
|
36
|
+
.resolve(utils_1.Modules.ORDER)
|
|
37
|
+
.updateOrders(previous.id, { no_notification: previous.no_notification });
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHJhZnQtb3JkZXItY29uZmlybWF0aW9uLWRlZmF1bHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL2hvb2tzL2RyYWZ0LW9yZGVyLWNvbmZpcm1hdGlvbi1kZWZhdWx0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQ0EscURBR2tDO0FBQ2xDLHFFQUFnRTtBQUNoRSw0REFBaUU7QUFhakU7Ozs7OztHQU1HO0FBQ0gsZ0NBQW1CLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FDcEMsS0FBSyxFQUFFLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxTQUFTLEVBQUUsRUFBRSxFQUFFO0lBQ2pDLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxNQUFNLFNBQVM7U0FDN0IsT0FBTyxDQUFDLGlDQUF5QixDQUFDLEtBQUssQ0FBQztTQUN4QyxLQUFLLENBQUM7UUFDTCxNQUFNLEVBQUUsT0FBTztRQUNmLE1BQU0sRUFBRSxDQUFDLElBQUksRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsQ0FBQztRQUNuRCxPQUFPLEVBQUUsRUFBRSxFQUFFLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRTtLQUMxQixDQUFDLENBQUE7SUFDSixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUE2QixDQUFBO0lBQ25ELElBQUksQ0FBQyxPQUFPLEVBQUUsY0FBYyxJQUFJLE9BQU8sQ0FBQyxlQUFlLEtBQUssSUFBSSxFQUFFLENBQUM7UUFDakUsT0FBTTtJQUNSLENBQUM7SUFFRCxNQUFNLFNBQVM7U0FDWixPQUFPLENBQXNCLGVBQU8sQ0FBQyxLQUFLLENBQUM7U0FDM0MsWUFBWSxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsRUFBRSxlQUFlLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQTtJQUN0RCxPQUFPLElBQUksNEJBQVksQ0FBa0MsU0FBUyxFQUFFO1FBQ2xFLEVBQUUsRUFBRSxPQUFPLENBQUMsRUFBRTtRQUNkLGVBQWUsRUFBRSxPQUFPLENBQUMsZUFBZSxJQUFJLEtBQUs7S0FDbEQsQ0FBQyxDQUFBO0FBQ0osQ0FBQyxFQUNELEtBQUssRUFBRSxRQUEwQyxFQUFFLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUNsRSxJQUFJLENBQUMsUUFBUTtRQUFFLE9BQU07SUFDckIsTUFBTSxTQUFTO1NBQ1osT0FBTyxDQUFzQixlQUFPLENBQUMsS0FBSyxDQUFDO1NBQzNDLFlBQVksQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLEVBQUUsZUFBZSxFQUFFLFFBQVEsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxDQUFBO0FBQzdFLENBQUMsQ0FDRixDQUFBIn0=
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.markZeroTotalOrderPaidWorkflow = void 0;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
6
|
+
const core_flows_1 = require("@medusajs/medusa/core-flows");
|
|
7
|
+
/**
|
|
8
|
+
* An order that costs nothing and has no payment collection (an Admin draft,
|
|
9
|
+
* e.g. a free sample shipment) would stay "Nicht bezahlt" forever: Medusa
|
|
10
|
+
* derives the payment status from payment collections only. A completed
|
|
11
|
+
* 0 € collection makes it "Bezahlt" in the Admin and in database queries,
|
|
12
|
+
* without a payment provider. Orders that already carry a collection (every
|
|
13
|
+
* checkout order) are left alone, so the workflow is safe to run twice.
|
|
14
|
+
*/
|
|
15
|
+
exports.markZeroTotalOrderPaidWorkflow = (0, workflows_sdk_1.createWorkflow)("order-mark-zero-total-paid", (input) => {
|
|
16
|
+
const lockKey = (0, workflows_sdk_1.transform)(input, ({ order_id }) => `order-zero-total-paid:${order_id}`);
|
|
17
|
+
(0, core_flows_1.acquireLockStep)({ key: lockKey, timeout: 10, ttl: 60 });
|
|
18
|
+
const { data: orders } = (0, core_flows_1.useQueryGraphStep)({
|
|
19
|
+
entity: "order",
|
|
20
|
+
fields: ["id", "total", "payment_collections.id"],
|
|
21
|
+
filters: { id: input.order_id },
|
|
22
|
+
});
|
|
23
|
+
const needsPayment = (0, workflows_sdk_1.transform)(orders, (rows) => {
|
|
24
|
+
const order = rows[0];
|
|
25
|
+
return (!!order &&
|
|
26
|
+
utils_1.MathBN.eq(utils_1.MathBN.convert(order.total), 0) &&
|
|
27
|
+
!(order.payment_collections ?? []).some(Boolean));
|
|
28
|
+
});
|
|
29
|
+
const created = (0, workflows_sdk_1.when)("order-zero-total-needs-payment", needsPayment, (needed) => needed).then(() => {
|
|
30
|
+
const collections = core_flows_1.createOrderPaymentCollectionWorkflow.runAsStep({
|
|
31
|
+
input: { order_id: input.order_id, amount: 0 },
|
|
32
|
+
});
|
|
33
|
+
const completion = (0, workflows_sdk_1.transform)(collections, (rows) => ({
|
|
34
|
+
selector: { id: rows[0].id },
|
|
35
|
+
update: { status: utils_1.PaymentCollectionStatus.COMPLETED },
|
|
36
|
+
}));
|
|
37
|
+
(0, core_flows_1.updatePaymentCollectionStep)(completion);
|
|
38
|
+
return collections;
|
|
39
|
+
});
|
|
40
|
+
(0, core_flows_1.releaseLockStep)({ key: lockKey });
|
|
41
|
+
return new workflows_sdk_1.WorkflowResponse((0, workflows_sdk_1.transform)({ created }, ({ created }) => ({
|
|
42
|
+
marked_paid: !!created?.length,
|
|
43
|
+
})));
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFyay16ZXJvLXRvdGFsLW9yZGVyLXBhaWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL29yZGVyL21hcmstemVyby10b3RhbC1vcmRlci1wYWlkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFEQUdrQztBQUNsQyxxRUFLMEM7QUFDMUMsNERBTW9DO0FBV3BDOzs7Ozs7O0dBT0c7QUFDVSxRQUFBLDhCQUE4QixHQUFHLElBQUEsOEJBQWMsRUFDMUQsNEJBQTRCLEVBQzVCLENBQUMsS0FBa0MsRUFBRSxFQUFFO0lBQ3JDLE1BQU0sT0FBTyxHQUFHLElBQUEseUJBQVMsRUFDdkIsS0FBSyxFQUNMLENBQUMsRUFBRSxRQUFRLEVBQUUsRUFBRSxFQUFFLENBQUMseUJBQXlCLFFBQVEsRUFBRSxDQUN0RCxDQUFBO0lBQ0QsSUFBQSw0QkFBZSxFQUFDLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFLEdBQUcsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFBO0lBRXZELE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLEdBQUcsSUFBQSw4QkFBaUIsRUFBQztRQUN6QyxNQUFNLEVBQUUsT0FBTztRQUNmLE1BQU0sRUFBRSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsd0JBQXdCLENBQUM7UUFDakQsT0FBTyxFQUFFLEVBQUUsRUFBRSxFQUFFLEtBQUssQ0FBQyxRQUFRLEVBQUU7S0FDaEMsQ0FBQyxDQUFBO0lBQ0YsTUFBTSxZQUFZLEdBQUcsSUFBQSx5QkFBUyxFQUFDLE1BQU0sRUFBRSxDQUFDLElBQUksRUFBVyxFQUFFO1FBQ3ZELE1BQU0sS0FBSyxHQUFJLElBQTRCLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDOUMsT0FBTyxDQUNMLENBQUMsQ0FBQyxLQUFLO1lBQ1AsY0FBTSxDQUFDLEVBQUUsQ0FBQyxjQUFNLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUF3QixDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQzVELENBQUMsQ0FBQyxLQUFLLENBQUMsbUJBQW1CLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUNqRCxDQUFBO0lBQ0gsQ0FBQyxDQUFDLENBQUE7SUFFRixNQUFNLE9BQU8sR0FBRyxJQUFBLG9CQUFJLEVBQ2xCLGdDQUFnQyxFQUNoQyxZQUFZLEVBQ1osQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FDbkIsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO1FBQ1YsTUFBTSxXQUFXLEdBQUcsaURBQW9DLENBQUMsU0FBUyxDQUFDO1lBQ2pFLEtBQUssRUFBRSxFQUFFLFFBQVEsRUFBRSxLQUFLLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUU7U0FDL0MsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxVQUFVLEdBQUcsSUFBQSx5QkFBUyxFQUFDLFdBQVcsRUFBRSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQztZQUNuRCxRQUFRLEVBQUUsRUFBRSxFQUFFLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtZQUM1QixNQUFNLEVBQUUsRUFBRSxNQUFNLEVBQUUsK0JBQXVCLENBQUMsU0FBUyxFQUFFO1NBQ3RELENBQUMsQ0FBQyxDQUFBO1FBQ0gsSUFBQSx3Q0FBMkIsRUFBQyxVQUFVLENBQUMsQ0FBQTtRQUN2QyxPQUFPLFdBQVcsQ0FBQTtJQUNwQixDQUFDLENBQUMsQ0FBQTtJQUVGLElBQUEsNEJBQWUsRUFBQyxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFBO0lBQ2pDLE9BQU8sSUFBSSxnQ0FBZ0IsQ0FDekIsSUFBQSx5QkFBUyxFQUNQLEVBQUUsT0FBTyxFQUFFLEVBQ1gsQ0FBQyxFQUFFLE9BQU8sRUFBRSxFQUFnQyxFQUFFLENBQUMsQ0FBQztRQUM5QyxXQUFXLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNO0tBQy9CLENBQUMsQ0FDSCxDQUNGLENBQUE7QUFDSCxDQUFDLENBQ0YsQ0FBQSJ9
|
package/README.md
CHANGED
|
@@ -41,6 +41,9 @@ packing lists) and S3 file storage, and the turnkey `medusa-config.ts` preset
|
|
|
41
41
|
repair").
|
|
42
42
|
- Requires passwords of at least 15 characters for customers and Admin users
|
|
43
43
|
(see "Password policy").
|
|
44
|
+
- Starts Admin draft orders without a customer order confirmation and marks
|
|
45
|
+
placed 0 € orders without payment as paid (see "Draft orders and 0 €
|
|
46
|
+
orders").
|
|
44
47
|
- Exports the shop's `medusa-config.ts` helpers from
|
|
45
48
|
`@hartl-services/medusa-base/config` and Sentry's `register()` from
|
|
46
49
|
`@hartl-services/medusa-base/instrumentation` (see "Config helpers" and
|
|
@@ -739,6 +742,36 @@ links itself:
|
|
|
739
742
|
Nothing to configure; `medusa db:migrate` after installing or upgrading to
|
|
740
743
|
0.4.1 is enough.
|
|
741
744
|
|
|
745
|
+
## Draft orders and 0 € orders
|
|
746
|
+
|
|
747
|
+
Draft orders (Medusa's `@medusajs/draft-order` plugin, loaded by default) are
|
|
748
|
+
the shop's way to enter an order in the Admin: sample shipments, orders taken
|
|
749
|
+
by phone, replacements. Item prices and the shipping amount are set directly
|
|
750
|
+
in the draft; converting it never involves a payment provider.
|
|
751
|
+
|
|
752
|
+
- **Customer order confirmation off by default.** The
|
|
753
|
+
`createOrderWorkflow.hooks.orderCreated` hook
|
|
754
|
+
(`workflows/hooks/draft-order-confirmation-default`) sets Medusa's
|
|
755
|
+
`no_notification` on every new draft, also when an API caller sent
|
|
756
|
+
`no_notification_order: false`. The Admin switches it per draft in the
|
|
757
|
+
**Bestellbestätigung** widget on the draft's detail page
|
|
758
|
+
(`GET`/`POST /admin/draft-orders/:id/order-confirmation`, see the E-Mail
|
|
759
|
+
"Admin API"). On conversion the `order-placed` subscriber records the
|
|
760
|
+
customer confirmation as `suppressed` (`notifications_disabled`); the copy
|
|
761
|
+
to the shop's order address is still sent. The shipment mail follows the
|
|
762
|
+
Admin's "notify customer" choice per shipment, not this switch.
|
|
763
|
+
- **0 € orders count as paid.** Medusa derives an order's payment status from
|
|
764
|
+
its payment collections only, so a converted 0 € draft would stay
|
|
765
|
+
"Nicht bezahlt". The subscriber `order-zero-total-paid` runs
|
|
766
|
+
`order-mark-zero-total-paid` on `order.placed`: an order with a total of 0
|
|
767
|
+
and no payment collection gets a completed 0 € payment collection and reads
|
|
768
|
+
"Bezahlt" (`payment_status: captured`). Orders that already carry a
|
|
769
|
+
collection, i.e. every checkout order, are left alone. In SQL, a paid
|
|
770
|
+
order is one whose linked `payment_collection` (via
|
|
771
|
+
`order_payment_collection`) has `status = 'completed'`.
|
|
772
|
+
|
|
773
|
+
Orders placed before installing this version keep their state.
|
|
774
|
+
|
|
742
775
|
## Config helpers
|
|
743
776
|
|
|
744
777
|
`@hartl-services/medusa-base/config` holds the generic environment helpers a
|
|
@@ -864,6 +897,10 @@ Options of the `email` namespace:
|
|
|
864
897
|
`orderNotificationEmail` mailbox. `verifyAccountUrl` (optional builder,
|
|
865
898
|
like `passwordResetUrl`; the mail appends `#token=`) is the storefront page
|
|
866
899
|
that confirms an account, needed while `customer_verification` is on.
|
|
900
|
+
`adminAffiliateApplicationsUrl` (optional builder, receives
|
|
901
|
+
`{ applicationId }`) is the Admin page of the partner applications; the
|
|
902
|
+
notification about a new application shows its "Im Admin prüfen" button
|
|
903
|
+
only with it.
|
|
867
904
|
`layout` and `renderers` are optional overrides, see "Shop configuration and
|
|
868
905
|
default templates".
|
|
869
906
|
|
|
@@ -878,7 +915,7 @@ is off. The former `documentsEnabled` option no longer exists: Documents is
|
|
|
878
915
|
always part of the base.
|
|
879
916
|
|
|
880
917
|
The event contracts the module consumes for shipment, cancellation, refund and
|
|
881
|
-
affiliate-reminder e-mails come from
|
|
918
|
+
affiliate-reminder and partner-application e-mails come from
|
|
882
919
|
`@hartl-services/medusa-food-supplements-contracts/events/documents` and
|
|
883
920
|
`/events/affiliate`, so installing the base does not pull in Affiliate.
|
|
884
921
|
|
|
@@ -913,10 +950,10 @@ feature is switched on again.
|
|
|
913
950
|
| Key | Label | Default | Gates | Keeps running when off |
|
|
914
951
|
| --------------------- | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
|
|
915
952
|
| (plugin switch) | E-Mail | on | every route under `/admin/email/*` (mail log, resends, terms versions; 404); jobs `email-reconcile-attempts`, `email-reconcile-pending-messages`, `email-retry-messages`; every feature below | checkout and orders; already queued messages stay stored |
|
|
916
|
-
| `order_mails` | Bestell-Mails | on | subscribers `order-placed`, `email-shipment-created`, `email-order-canceled`, `return-requested`, `return-received`, `documents-shipment-invoice-assignment-decided`, `documents-order-cancellation-ready`, `documents-refund-ready` | jobs, Admin routes, already created messages |
|
|
953
|
+
| `order_mails` | Bestell-Mails | on | subscribers `order-placed`, `email-shipment-created`, `email-order-canceled`, `return-requested`, `return-received`, `documents-shipment-invoice-assignment-decided`, `documents-order-cancellation-ready`, `documents-refund-ready`; route `/admin/draft-orders/:id/order-confirmation` (404) and its draft-order widget | jobs, Admin routes, already created messages |
|
|
917
954
|
| `account_mails` | Konto-Mails | on | subscribers `password-reset`, `user-invite`, `customer-verification` (the last one only with `customer_verification` on as well) | jobs, Admin routes, already created messages |
|
|
918
955
|
| `customer_verification` | Kontobestätigung per E-Mail | off | Medusa's e-mail verification of customer logins (see "Kontobestätigung per E-Mail"), route `POST /store/customer-verification/resend` (404 while off), subscriber `customer-verification`; child of `account_mails` | checkout and orders; confirmed accounts stay confirmed, already created messages |
|
|
919
|
-
| `affiliate_reminders` | Affiliate-
|
|
956
|
+
| `affiliate_reminders` | Affiliate-Mails | on | subscribers `affiliate-consignment-reminder-due` (reminders to affiliates) and `affiliate-application-submitted` (notification to the shop about a new partner application) | jobs, Admin routes, already created messages |
|
|
920
957
|
|
|
921
958
|
With the plugin switched off the three jobs return immediately, so nothing
|
|
922
959
|
is sent, retried or reconciled until it is switched on again; then the jobs
|
|
@@ -1043,7 +1080,8 @@ Storefront contract (DTOs, schema and path from the Contracts package,
|
|
|
1043
1080
|
### Ownership
|
|
1044
1081
|
|
|
1045
1082
|
- Documents owns document issuance and publishes immutable document facts.
|
|
1046
|
-
- Affiliate owns reminder eligibility and publishes reminder
|
|
1083
|
+
- Affiliate owns reminder eligibility and publishes reminder and application
|
|
1084
|
+
facts.
|
|
1047
1085
|
- Email resolves recipients, renders content, attaches files, and owns delivery
|
|
1048
1086
|
status plus the append-only global Terms PDF history.
|
|
1049
1087
|
- Public HTTP DTOs, schemas, and paths remain in
|
|
@@ -1167,6 +1205,28 @@ type Payload = {
|
|
|
1167
1205
|
};
|
|
1168
1206
|
```
|
|
1169
1207
|
|
|
1208
|
+
`affiliate.application_submitted.v1`
|
|
1209
|
+
|
|
1210
|
+
```ts
|
|
1211
|
+
type Payload = {
|
|
1212
|
+
schema_version: 1;
|
|
1213
|
+
application_id: string;
|
|
1214
|
+
customer_id: string;
|
|
1215
|
+
customer_email: string | null;
|
|
1216
|
+
customer_first_name: string | null;
|
|
1217
|
+
customer_last_name: string | null;
|
|
1218
|
+
company_name: string | null;
|
|
1219
|
+
message: string | null;
|
|
1220
|
+
};
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
Affiliate publishes it when `POST /store/affiliates/register` has stored an
|
|
1224
|
+
application. Email creates one `affiliate.application_submitted.admin` message
|
|
1225
|
+
per `application_id` (business key
|
|
1226
|
+
`affiliate-application-submitted-admin:<application_id>`, so a replay or a
|
|
1227
|
+
concurrent request never sends a second mail) addressed to
|
|
1228
|
+
`orderNotificationEmail`. The persisted template data are exactly these facts.
|
|
1229
|
+
|
|
1170
1230
|
`period_start` is a valid `YYYY-MM-01`, `due_date` is a valid `YYYY-MM-DD`, and
|
|
1171
1231
|
`reminder_key` is exactly
|
|
1172
1232
|
`affiliate-consignment:<affiliate_id>:<period_start>`. `locale`, when present,
|
|
@@ -1177,7 +1237,8 @@ has at most 35 characters. Email runtime-validates every contract before use.
|
|
|
1177
1237
|
- The accepted kinds are exactly `order.received`, `order.received.admin`, `order.shipped`,
|
|
1178
1238
|
`order.canceled`, `order.return_requested`, `order.return_received`,
|
|
1179
1239
|
`order.refunded`, `auth.password_reset`, `auth.verification`,
|
|
1180
|
-
`affiliate.consignment_report_due
|
|
1240
|
+
`affiliate.consignment_report_due`, `affiliate.application_submitted.admin`
|
|
1241
|
+
and `user.invite`.
|
|
1181
1242
|
- Every automatic message has a deterministic business key.
|
|
1182
1243
|
- Every placed Order creates independent customer and Shop intents. The customer
|
|
1183
1244
|
intent honors `no_notification`; the Shop intent always targets the static
|
|
@@ -1248,7 +1309,7 @@ an info box lists number, customer, e-mail and order time, followed by an
|
|
|
1248
1309
|
button is left out without it) and `timeZone` (IANA; the order time is left
|
|
1249
1310
|
out without it). `buildFoodShopConfig` sets both (`<MEDUSA_BACKEND_URL>/app/
|
|
1250
1311
|
orders/<id>` and its `timeZone` option). The layout receives `audience`
|
|
1251
|
-
(`"shop"` for this kind and `user.invite`, `"customer"` for all others) and
|
|
1312
|
+
(`"shop"` for this kind, `affiliate.application_submitted.admin` and `user.invite`, `"customer"` for all others) and
|
|
1252
1313
|
shows the contact line only to customers.
|
|
1253
1314
|
|
|
1254
1315
|
The shipping confirmation (`order.shipped`, one per fulfillment) receives a
|
|
@@ -1329,6 +1390,17 @@ Partnerbereich" button and says the mail can be ignored once the report is
|
|
|
1329
1390
|
submitted. The button leads to a 404 while the affiliate plugin's
|
|
1330
1391
|
`partner_portal` toggle is off.
|
|
1331
1392
|
|
|
1393
|
+
The partner application notification (`affiliate.application_submitted.admin`)
|
|
1394
|
+
goes to `orderNotificationEmail`, like the order notification, and is gated by
|
|
1395
|
+
the `affiliate_reminders` feature ("Affiliate-Mails"). Its subject is "Neue
|
|
1396
|
+
Partner-Bewerbung von <Firma>" (without a company the applicant's name, then
|
|
1397
|
+
the e-mail address; just "Neue Partner-Bewerbung" if none is known). An info
|
|
1398
|
+
box lists company, name and e-mail (only the present ones), the applicant's
|
|
1399
|
+
message follows as an escaped paragraph, and an "Im Admin prüfen" button links
|
|
1400
|
+
`adminAffiliateApplicationsUrl` (left out without it; `buildFoodShopConfig`
|
|
1401
|
+
sets `<MEDUSA_BACKEND_URL>/app/affiliates/applications`). Its layout audience
|
|
1402
|
+
is `"shop"`. All applicant text is HTML-escaped.
|
|
1403
|
+
|
|
1332
1404
|
The Admin invite (`user.invite`) has the subject "Einladung ins Admin-Team von
|
|
1333
1405
|
<displayName>", an "Einladung annehmen" button with the link as copyable text,
|
|
1334
1406
|
the invite's current expiry ("Die Einladung ist gültig bis 02.10.2026, 01:40
|
|
@@ -1430,6 +1502,11 @@ Email exposes authenticated Admin operations at:
|
|
|
1430
1502
|
most 5 MiB with its `effective_at` timestamp.
|
|
1431
1503
|
- `GET /admin/email/terms-versions/:id/download` streams one authenticated,
|
|
1432
1504
|
non-cacheable PDF download.
|
|
1505
|
+
- `GET /admin/draft-orders/:id/order-confirmation` returns
|
|
1506
|
+
`AdminDraftOrderConfirmationResponse` (`{ draft_order_id,
|
|
1507
|
+
send_order_confirmation }`); `POST` with
|
|
1508
|
+
`{ send_order_confirmation: boolean }` switches it. Both answer 404 for an
|
|
1509
|
+
order that is not (or no longer) a draft and while `order_mails` is off.
|
|
1433
1510
|
|
|
1434
1511
|
List and detail responses expose only safe identifiers and linkage metadata,
|
|
1435
1512
|
kind/status and safe status codes, resource references, trigger, attempt
|
|
@@ -1456,10 +1533,18 @@ composes the base's `PluginPageShell` and `PluginFeatureSections` from
|
|
|
1456
1533
|
two operations sections below them, in this order: **AGB/PDF-Versionen** and,
|
|
1457
1534
|
as the last section, the **E-Mail-Protokoll**. They show Terms PDF and mail
|
|
1458
1535
|
log state to act on, not configuration the plugin reads at run time. The mail
|
|
1459
|
-
log provides bounded server pagination, allowlisted status/kind filters,
|
|
1460
|
-
|
|
1536
|
+
log provides bounded server pagination, allowlisted status/kind filters, a
|
|
1537
|
+
search by the exact resource ID (e.g. `order_…`, `cus_…`, as pasted from
|
|
1538
|
+
elsewhere), safe message and attempt status, and an explicitly confirmed
|
|
1539
|
+
resend action.
|
|
1461
1540
|
Recipient, template, and payload data are intentionally absent from the page.
|
|
1462
1541
|
|
|
1542
|
+
The draft-order detail page carries the **Bestellbestätigung** widget (zone
|
|
1543
|
+
`draft_order.details.side.after`, hidden while `order_mails` is off). Its
|
|
1544
|
+
switch "An Kunden senden" starts off and decides whether the customer receives
|
|
1545
|
+
the order confirmation when the draft is converted (see "Draft orders and
|
|
1546
|
+
0 € orders").
|
|
1547
|
+
|
|
1463
1548
|
The `AGB/PDF-Versionen` section manages the global Terms PDFs. Admins can
|
|
1464
1549
|
upload a PDF of at most 5 MiB with its effective date, review versions newest
|
|
1465
1550
|
first, and download a selected version. Uploads receive a fresh request UUID;
|
|
@@ -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", "user.invite", "auth.verification"];
|
|
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"];
|
|
3
3
|
export declare const EmailKindSchema: z.ZodEnum<{
|
|
4
4
|
"order.received": "order.received";
|
|
5
5
|
"order.received.admin": "order.received.admin";
|
|
@@ -10,6 +10,7 @@ export declare const EmailKindSchema: z.ZodEnum<{
|
|
|
10
10
|
"order.refunded": "order.refunded";
|
|
11
11
|
"auth.password_reset": "auth.password_reset";
|
|
12
12
|
"affiliate.consignment_report_due": "affiliate.consignment_report_due";
|
|
13
|
+
"affiliate.application_submitted.admin": "affiliate.application_submitted.admin";
|
|
13
14
|
"user.invite": "user.invite";
|
|
14
15
|
"auth.verification": "auth.verification";
|
|
15
16
|
}>;
|
|
@@ -39,6 +39,14 @@ export type ShopEmailConfiguration = Readonly<{
|
|
|
39
39
|
adminOrderUrl?: (input: Readonly<{
|
|
40
40
|
orderId: string;
|
|
41
41
|
}>) => string;
|
|
42
|
+
/**
|
|
43
|
+
* Link to the partner applications in the Medusa Admin; the shop's
|
|
44
|
+
* notification about a new partner application shows an "Im Admin prüfen"
|
|
45
|
+
* button only when it is set.
|
|
46
|
+
*/
|
|
47
|
+
adminAffiliateApplicationsUrl?: (input: Readonly<{
|
|
48
|
+
applicationId: string;
|
|
49
|
+
}>) => string;
|
|
42
50
|
affiliatePortalUrl: string;
|
|
43
51
|
displayName: string;
|
|
44
52
|
inviteUrl: (input: Readonly<{
|
|
@@ -362,6 +362,15 @@ export declare const EmailPersistedDataSchemas: {
|
|
|
362
362
|
due_date: z.ZodString;
|
|
363
363
|
locale: z.ZodNullable<z.ZodString>;
|
|
364
364
|
}, z.core.$strict>;
|
|
365
|
+
readonly "affiliate.application_submitted.admin": z.ZodObject<{
|
|
366
|
+
application_id: z.ZodString;
|
|
367
|
+
customer_id: z.ZodString;
|
|
368
|
+
customer_email: z.ZodNullable<z.ZodString>;
|
|
369
|
+
customer_first_name: z.ZodNullable<z.ZodString>;
|
|
370
|
+
customer_last_name: z.ZodNullable<z.ZodString>;
|
|
371
|
+
company_name: z.ZodNullable<z.ZodString>;
|
|
372
|
+
message: z.ZodNullable<z.ZodString>;
|
|
373
|
+
}, z.core.$strict>;
|
|
365
374
|
readonly "user.invite": z.ZodObject<{}, z.core.$strict>;
|
|
366
375
|
readonly "auth.verification": z.ZodObject<{
|
|
367
376
|
locale: z.ZodNullable<z.ZodString>;
|
|
@@ -596,6 +605,15 @@ export declare const EmailRenderDataSchemas: {
|
|
|
596
605
|
due_date: z.ZodString;
|
|
597
606
|
locale: z.ZodNullable<z.ZodString>;
|
|
598
607
|
}, z.core.$strict>;
|
|
608
|
+
readonly "affiliate.application_submitted.admin": z.ZodObject<{
|
|
609
|
+
application_id: z.ZodString;
|
|
610
|
+
customer_id: z.ZodString;
|
|
611
|
+
customer_email: z.ZodNullable<z.ZodString>;
|
|
612
|
+
customer_first_name: z.ZodNullable<z.ZodString>;
|
|
613
|
+
customer_last_name: z.ZodNullable<z.ZodString>;
|
|
614
|
+
company_name: z.ZodNullable<z.ZodString>;
|
|
615
|
+
message: z.ZodNullable<z.ZodString>;
|
|
616
|
+
}, z.core.$strict>;
|
|
599
617
|
};
|
|
600
618
|
export type EmailPersistedDataMap = {
|
|
601
619
|
readonly [Kind in EmailKind]: Readonly<z.infer<(typeof EmailPersistedDataSchemas)[Kind]>>;
|
|
@@ -214,6 +214,17 @@ exports.EmailPersistedDataSchemas = {
|
|
|
214
214
|
locale: NullableLocaleSchema,
|
|
215
215
|
})
|
|
216
216
|
.strict(),
|
|
217
|
+
"affiliate.application_submitted.admin": zod_1.z
|
|
218
|
+
.object({
|
|
219
|
+
application_id: NonEmptyStringSchema,
|
|
220
|
+
customer_id: NonEmptyStringSchema,
|
|
221
|
+
customer_email: NonEmptyStringSchema.nullable(),
|
|
222
|
+
customer_first_name: NonEmptyStringSchema.nullable(),
|
|
223
|
+
customer_last_name: NonEmptyStringSchema.nullable(),
|
|
224
|
+
company_name: NonEmptyStringSchema.nullable(),
|
|
225
|
+
message: NonEmptyStringSchema.nullable(),
|
|
226
|
+
})
|
|
227
|
+
.strict(),
|
|
217
228
|
"user.invite": zod_1.z.object({}).strict(),
|
|
218
229
|
"auth.verification": zod_1.z
|
|
219
230
|
.object({
|
|
@@ -144,6 +144,8 @@ function buildFoodShopConfig(options, environment = process.env) {
|
|
|
144
144
|
email: {
|
|
145
145
|
jwtSecret: env.JWT_SECRET,
|
|
146
146
|
shop: {
|
|
147
|
+
// The Admin page lists the applications; the id is not part of the URL.
|
|
148
|
+
adminAffiliateApplicationsUrl: () => new URL("/app/affiliates/applications", backendOrigin).toString(),
|
|
147
149
|
// The Admin dashboard mounts under admin.path ("/app").
|
|
148
150
|
adminOrderUrl: ({ orderId }) => new URL(`/app/orders/${encodeURIComponent(orderId)}`, backendOrigin).toString(),
|
|
149
151
|
displayName: options.displayName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hartl-services/medusa-base",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Base plugin for Hartl Services Medusa shops: per-plugin feature toggles with a generic Admin settings page, a Store endpoint for storefronts, enforcement helpers, the complete-cart hook composition, the root redirect, cart-promotion link repair, the built-in E-Mail, Documents and S3 file storage modules, config helpers with the turnkey food-shop preset, and the Sentry logger wrapper and instrumentation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"mime-types": "3.0.1",
|
|
89
89
|
"nodemailer": "6.10.1",
|
|
90
90
|
"ulid": "^2.4.0",
|
|
91
|
-
"@hartl-services/medusa-food-supplements-contracts": "^0.
|
|
91
|
+
"@hartl-services/medusa-food-supplements-contracts": "^0.24.0"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"@medusajs/admin-sdk": "2.19.0",
|