diff --git a/www/apps/resources/app/commerce-modules/customer/extend/page.mdx b/www/apps/resources/app/commerce-modules/customer/extend/page.mdx
index 2a84396158..6bbf7a358b 100644
--- a/www/apps/resources/app/commerce-modules/customer/extend/page.mdx
+++ b/www/apps/resources/app/commerce-modules/customer/extend/page.mdx
@@ -18,7 +18,7 @@ You'll then learn how to:
-Similar steps can bee applied to the `CustomerAddress` data model.
+Similar steps can be applied to the `CustomerAddress` data model.
@@ -270,7 +270,7 @@ In the workflow, you:
2. Create the `Custom` record using the `createCustomStep`.
3. Use the `when-then` utility to link the customer to the `Custom` record if it was created. Learn more about why you can't use if-then conditions in a workflow without using `when-then` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
-You'll next call the workflow in the hook handler.
+You'll next execute the workflow in the hook handler.
### Consume Workflow Hook
@@ -308,9 +308,9 @@ The hook handler executes the `createCustomFromCustomerWorkflow`, passing it its
To test it out, send a `POST` request to `/admin/customers` to create a customer, passing `custom_name` in `additional_data`:
```bash
-curl --location 'localhost:9000/admin/customers' \
---header 'Content-Type: application/json' \
---header 'Authorization: Bearer {token}' \
+curl -X POST 'localhost:9000/admin/customers' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
--data-raw '{
"email": "customer@gmail.com",
"additional_data": {
@@ -342,7 +342,7 @@ The `+` prefix in `+custom.*` indicates that the relation should be retrieved wi
For example:
```bash
-curl -X POST 'localhost:9000/admin/customers/{customer_id}?fields=+custom.*' \
+curl 'localhost:9000/admin/customers/{customer_id}?fields=+custom.*' \
-H 'Authorization: Bearer {token}'
```
@@ -670,8 +670,8 @@ To test it out, send a `POST` request to `/admin/customers/:id` to update a cust
```bash
curl -X POST 'localhost:9000/admin/customers/{customer_id}' \
---header 'Content-Type: application/json' \
---header 'Authorization: Bearer {token}' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
--data '{
"additional_data": {
"custom_name": "test3"
diff --git a/www/apps/resources/app/commerce-modules/pricing/concepts/page.mdx b/www/apps/resources/app/commerce-modules/pricing/concepts/page.mdx
index 5fe0b50c82..6b59a52f6e 100644
--- a/www/apps/resources/app/commerce-modules/pricing/concepts/page.mdx
+++ b/www/apps/resources/app/commerce-modules/pricing/concepts/page.mdx
@@ -4,11 +4,13 @@ export const metadata = {
# {metadata.title}
-In this document, you’ll learn about the main concepts in the Pricing Module, and how data is stored and related.
+In this document, you’ll learn about the main concepts in the Pricing Module.
## Price Set
-A [PriceSet](/references/pricing/models/PriceSet) represents a collection of prices that are linked to a resource (for example, a product or a shipping option). Each of these prices are represented by the [Price data module](/references/pricing/models/Price).
+A [PriceSet](/references/pricing/models/PriceSet) represents a collection of prices that are linked to a resource (for example, a product or a shipping option).
+
+Each of these prices are represented by the [Price data module](/references/pricing/models/Price).

@@ -16,6 +18,8 @@ A [PriceSet](/references/pricing/models/PriceSet) represents a collection of pri
## Price List
-A [PriceList](/references/pricing/models/PriceList) is a group of prices only enabled if their conditions and rules are satisfied. A price list has optional `start_date` and `end_date` properties, which indicate the date range in which a price list can be applied.
+A [PriceList](/references/pricing/models/PriceList) is a group of prices only enabled if their conditions and rules are satisfied.
+
+A price list has optional `start_date` and `end_date` properties that indicate the date range in which a price list can be applied.
Its associated prices are represented by the `Price` data model.
\ No newline at end of file
diff --git a/www/apps/resources/app/commerce-modules/pricing/examples/page.mdx b/www/apps/resources/app/commerce-modules/pricing/examples/page.mdx
index 0e0094122c..2d37576498 100644
--- a/www/apps/resources/app/commerce-modules/pricing/examples/page.mdx
+++ b/www/apps/resources/app/commerce-modules/pricing/examples/page.mdx
@@ -13,16 +13,15 @@ In this document, you’ll find common examples of how you can use the Pricing M
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const pricingModuleService: IPricingModuleService = request.scope.resolve(
+ const pricingModuleService = request.scope.resolve(
Modules.PRICING
)
@@ -84,16 +83,15 @@ export async function POST(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const pricingModuleService: IPricingModuleService = request.scope.resolve(
+ const pricingModuleService = request.scope.resolve(
Modules.PRICING
)
@@ -130,16 +128,15 @@ export async function GET(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const pricingModuleService: IPricingModuleService = request.scope.resolve(
+ const pricingModuleService = request.scope.resolve(
Modules.PRICING
)
@@ -176,16 +173,15 @@ export async function GET(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const pricingModuleService: IPricingModuleService = request.scope.resolve(
+ const pricingModuleService = request.scope.resolve(
Modules.PRICING
)
@@ -255,7 +251,7 @@ export async function POST(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const pricingModuleService: IPricingModuleService = request.scope.resolve(
+ const pricingModuleService = request.scope.resolve(
Modules.PRICING
)
@@ -330,16 +326,15 @@ export async function POST(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const pricingModuleService: IPricingModuleService = request.scope.resolve(
+ const pricingModuleService = request.scope.resolve(
Modules.PRICING
)
diff --git a/www/apps/resources/app/commerce-modules/pricing/links-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/pricing/links-to-other-modules/page.mdx
new file mode 100644
index 0000000000..debc6df062
--- /dev/null
+++ b/www/apps/resources/app/commerce-modules/pricing/links-to-other-modules/page.mdx
@@ -0,0 +1,29 @@
+export const metadata = {
+ title: `Links between Pricing Module and Other Modules`,
+}
+
+# {metadata.title}
+
+This document showcases the module links defined between the Pricing Module and other commerce modules.
+
+## Fulfillment Module
+
+The Fulfillment Module provides fulfillment-related functionalities, including shipping options that the customer chooses from when they place their order. However, it doesn't provide pricing-related functionalities for these options.
+
+Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
+
+
+
+---
+
+## Product Module
+
+The Product Module doesn't store or manage the prices of product variants.
+
+Medusa defines a link between the `ProductVariant` and the `PriceSet`. A product variant’s prices are stored as prices belonging to a price set.
+
+
+
+So, when you want to add prices for a product variant, you create a price set and add the prices to it.
+
+You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
diff --git a/www/apps/resources/app/commerce-modules/pricing/page.mdx b/www/apps/resources/app/commerce-modules/pricing/page.mdx
index 6819e56e4b..4f22de8385 100644
--- a/www/apps/resources/app/commerce-modules/pricing/page.mdx
+++ b/www/apps/resources/app/commerce-modules/pricing/page.mdx
@@ -6,7 +6,7 @@ export const metadata = {
# {metadata.title}
-The Pricing Module is the `@medusajs/medusa/pricing` NPM package that provides pricing-related features in your Medusa and Node.js applications.
+The Pricing Module provides pricing-related features in your Medusa and Node.js applications.
## How to Use Pricing Module's Service
@@ -15,18 +15,33 @@ You can use the Pricing Module's main service by resolving from the Medusa conta
For example:
+
+
+```ts title="src/workflows/hello-world/step1.ts"
+import { createStep } from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const step1 = createStep("step-1", async (_, { container }) => {
+ const pricingModuleService = container.resolve(
+ Modules.PRICING
+ )
+
+ const priceSets = await pricingModuleService.listPriceSets()
+})
+```
+
+
- ```ts title="src/api/store/custom/route.ts"
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts title="src/api/store/custom/route.ts"
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const pricingModuleService: IPricingModuleService = request.scope.resolve(
+ const pricingModuleService = request.scope.resolve(
Modules.PRICING
)
@@ -39,35 +54,17 @@ export async function GET(
- ```ts title="src/subscribers/custom-handler.ts"
- import { SubscriberArgs } from "@medusajs/framework"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts title="src/subscribers/custom-handler.ts"
+import { SubscriberArgs } from "@medusajs/framework"
+import { Modules } from "@medusajs/framework/utils"
export default async function subscriberHandler({ container }: SubscriberArgs) {
- const pricingModuleService: IPricingModuleService = container.resolve(
+ const pricingModuleService = container.resolve(
Modules.PRICING
)
const priceSets = await pricingModuleService.listPriceSets()
}
-```
-
-
-
-
- ```ts title="src/workflows/hello-world/step1.ts"
- import { createStep } from "@medusajs/framework/workflows-sdk"
- import { IPricingModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
-
-const step1 = createStep("step-1", async (_, { container }) => {
- const pricingModuleService: IPricingModuleService = container.resolve(
- Modules.PRICING
- )
-
- const priceSets = await pricingModuleService.listPriceSets()
-})
```
@@ -79,7 +76,7 @@ const step1 = createStep("step-1", async (_, { container }) => {
### Price Management
-With the Pricing Module, store the prices of a resource and manage them through the main service's methods.
+Store the prices of a resource and manage them through the main service's methods.
Prices are grouped in a price set, allowing you to add more than one price for a resource based on different conditions, such as currency code.
@@ -122,7 +119,9 @@ const priceSet = await pricingModuleService.addPrices({
### Price Lists
-Price lists allow you to group prices and apply them only in specific conditions. You can also use them to override existing prices for the specified conditions.
+Group prices and apply them only in specific conditions with price lists.
+
+You can also use them to override existing prices for specified conditions, or create a sale.
```ts
const priceList = await pricingModuleService.createPriceLists([
diff --git a/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx b/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx
index 406b80235d..a0c2fe96d8 100644
--- a/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx
+++ b/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx
@@ -6,7 +6,7 @@ export const metadata = {
# {metadata.title}
-In this document, you'll learn how prices are calculated when you use the `calculatePrices` method of the Pricing Module's main service.
+In this document, you'll learn how prices are calculated when you use the [calculatePrices method](/references/pricing/calculatePrices) of the Pricing Module's main service.
## calculatePrices Method
@@ -14,9 +14,7 @@ The [calculatePrices method](/references/pricing/calculatePrices) accepts as par
It returns a price object with the best matching price for each price set.
----
-
-## Calculation Context
+### Calculation Context
The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
@@ -34,16 +32,18 @@ const price = await pricingModuleService.calculatePrices(
)
```
----
+In this example, you retrieve the prices in a price set for the specified currency code and region ID.
-## Returned Price Object
+### Returned Price Object
-For each price set, the method selects two prices:
+For each price set, the `calculatePrices` method selects two prices:
-- The calculated price: Either the best context-matching price that belongs to a price list or the same as the original price.
-- The original price: Either the same as the calculated price if its price list is of type `override`, or the best context-matching price that doesn't belong to a price list.
+- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
+- An original price, which is either:
+ - The same price as the calculated price if the price list it belongs to is of type `override`;
+ - Or a price that doesn't belong to a price list and best matches the specified context.
-Both prices are returned in an object along with the following properties:
+Both prices are returned in an object that has the following properties:
+
+Learn more about the returned properties in [this guide](../price-calculation/page.mdx#returned-price-object).
+
+
+
- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive.
- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive.
@@ -65,10 +71,8 @@ A price is considered tax-inclusive if:
### Tax Context Precedence
-If:
+A region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive if:
- both the `region_id` and `currency_code` are provided in the calculation context;
- the selected price belongs to the region;
- and the region has a price preference
-
-Then, the region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive.
diff --git a/www/apps/resources/app/commerce-modules/product/examples/page.mdx b/www/apps/resources/app/commerce-modules/product/examples/page.mdx
index e9d63f0dce..e1e514ec6a 100644
--- a/www/apps/resources/app/commerce-modules/product/examples/page.mdx
+++ b/www/apps/resources/app/commerce-modules/product/examples/page.mdx
@@ -13,13 +13,12 @@ In this guide, you’ll find common examples of how you can use the Product Modu
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(request: MedusaRequest, res: MedusaResponse) {
- const productModuleService: IProductModuleService = request.scope.resolve(
+ const productModuleService = request.scope.resolve(
Modules.PRODUCT
)
@@ -94,13 +93,12 @@ export async function POST(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(request: MedusaRequest, res: MedusaResponse) {
- const productModuleService: IProductModuleService = request.scope.resolve(
+ const productModuleService = request.scope.resolve(
Modules.PRODUCT
)
@@ -137,13 +135,12 @@ export async function GET(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(request: MedusaRequest, res: MedusaResponse) {
- const productModuleService: IProductModuleService = request.scope.resolve(
+ const productModuleService = request.scope.resolve(
Modules.PRODUCT
)
@@ -184,13 +181,12 @@ export async function GET(
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(request: MedusaRequest, res: MedusaResponse) {
- const productModuleService: IProductModuleService = request.scope.resolve(
+ const productModuleService = request.scope.resolve(
Modules.PRODUCT
)
@@ -231,13 +227,12 @@ export async function GET(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(request: MedusaRequest, res: MedusaResponse) {
- const productModuleService: IProductModuleService = request.scope.resolve(
+ const productModuleService = request.scope.resolve(
Modules.PRODUCT
)
@@ -274,13 +269,12 @@ export async function GET(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(request: MedusaRequest, res: MedusaResponse) {
- const productModuleService: IProductModuleService = request.scope.resolve(
+ const productModuleService = request.scope.resolve(
Modules.PRODUCT
)
diff --git a/www/apps/resources/app/commerce-modules/product/extend/page.mdx b/www/apps/resources/app/commerce-modules/product/extend/page.mdx
new file mode 100644
index 0000000000..9046c43098
--- /dev/null
+++ b/www/apps/resources/app/commerce-modules/product/extend/page.mdx
@@ -0,0 +1,684 @@
+import { Prerequisites } from "docs-ui"
+
+export const metadata = {
+ title: `Extend Product Data Model`,
+}
+
+# {metadata.title}
+
+In this documentation, you'll learn how to extend a data model of the Product Module to add a custom property.
+
+You'll create a `Custom` data model in a module. This data model will have a `custom_name` property, which is the property you want to add to the [Product data model](/references/product/models/Product) defined in the Product Module.
+
+You'll then learn how to:
+
+- Link the `Custom` data model to the `Product` data model.
+- Set the `custom_name` property when a product is created or updated using Medusa's API routes.
+- Retrieve the `custom_name` property with the product's details, in custom or existing API routes.
+
+
+
+Similar steps can be applied to the `ProductVariant` or `ProductOption` data models.
+
+
+
+## Step 1: Define Custom Data Model
+
+Consider you have a Hello Module defined in the `/src/modules/hello` directory.
+
+
+
+If you don't have a module, follow [this guide](!docs!/basics/modules) to create one.
+
+
+
+To add the `custom_name` property to the `Product` data model, you'll create in the Hello Module a data model that has the `custom_name` property.
+
+Create the file `src/modules/hello/models/custom.ts` with the following content:
+
+```ts title="src/modules/hello/models/custom.ts"
+import { model } from "@medusajs/framework/utils"
+
+export const Custom = model.define("custom", {
+ id: model.id().primaryKey(),
+ custom_name: model.text(),
+})
+```
+
+This creates a `Custom` data model that has the `id` and `custom_name` properties.
+
+
+
+Learn more about data models in [this guide](!docs!/data-models).
+
+
+
+---
+
+## Step 2: Define Link to Product Data Model
+
+Next, you'll define a module link between the `Custom` and `Product` data model. A module link allows you to form a relation between two data models of separate modules while maintaining module isolation.
+
+
+
+Learn more about module links in [this guide](!docs!/module-links).
+
+
+
+Create the file `src/links/product-custom.ts` with the following content:
+
+```ts title="src/links/product-custom.ts"
+import { defineLink } from "@medusajs/framework/utils";
+import HelloModule from "../modules/hello"
+import ProductModule from "@medusajs/medusa/product"
+
+export default defineLink(
+ ProductModule.linkable.product,
+ HelloModule.linkable.custom,
+)
+```
+
+This defines a link between the `Product` and `Custom` data models. Using this link, you'll later query data across the modules, and link records of each data model.
+
+---
+
+## Step 3: Generate and Run Migrations
+
+
+
+To reflect the `Custom` data model in the database, generate a migration that defines the table to be created for it.
+
+Run the following command in your Medusa project's root:
+
+```bash
+npx medusa db:generate helloModuleService
+```
+
+Where `helloModuleService` is your module's name.
+
+Then, run the `db:migrate` command to run the migrations and create a table in the database for the link between the `Product` and `Custom` data models:
+
+```bash
+npx medusa db:migrate
+```
+
+A table for the link is now created in the database. You can now retrieve and manage the link between records of the data models.
+
+---
+
+## Step 4: Consume productsCreated Workflow Hook
+
+When a product is created, you also want to create a `Custom` record and set the `custom_name` property, then create a link between the `Product` and `Custom` records.
+
+To do that, you'll consume the [productsCreated](/references/medusa-workflows/createProductsWorkflow#productscreated) hook of the [createProductsWorkflow](/references/medusa-workflows/createProductsWorkflow). This workflow is executed in the [Create Product Admin API route](!api!/admin#products_postproducts)
+
+
+
+Learn more about workflow hooks in [this guide](!docs!/advanced-development/workflows/workflow-hooks).
+
+
+
+The API route accepts in its request body an `additional_data` parameter. You can pass in it custom data, which is passed to the workflow hook handler.
+
+### Add custom_name to Additional Data Validation
+
+To pass the `custom_name` in the `additional_data` parameter, you must add a validation rule that tells the Medusa application about this custom property.
+
+Create the file `src/api/middlewares.ts` with the following content:
+
+```ts title="src/api/middlewares.ts"
+import { defineMiddlewares } from "@medusajs/medusa"
+import { z } from "zod"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ method: "POST",
+ matcher: "/admin/products",
+ additionalDataValidator: {
+ custom_name: z.string().optional(),
+ },
+ },
+ ],
+})
+```
+
+The `additional_data` parameter validation is customized using the `defineMiddlewares` utility function. In the routes middleware configuration object, the `additionalDataValidator` property accepts [Zod](https://zod.dev/) validaiton rules.
+
+In the snippet above, you add a validation rule indicating that `custom_name` is a string that can be passed in the `additional_data` object.
+
+
+
+Learn more about additional data validation in [this guide](!docs!/advanced-development/api-routes/additional-data).
+
+
+
+### Create Workflow to Create Custom Record
+
+You'll now create a workflow that will be used in the hook handler.
+
+This workflow will create a `Custom` record, then link it to the product.
+
+Start by creating the step that creates the `Custom` record. Create the file `src/workflows/create-custom-from-product/steps/create-custom.ts` with the following content:
+
+```ts title="src/workflows/create-custom-from-product/steps/create-custom.ts"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import HelloModuleService from "../../../modules/hello/service"
+import { HELLO_MODULE } from "../../../modules/hello"
+
+type CreateCustomStepInput = {
+ custom_name?: string
+}
+
+export const createCustomStep = createStep(
+ "create-custom",
+ async (data: CreateCustomStepInput, { container }) => {
+ if (!data.custom_name) {
+ return
+ }
+
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ const custom = await helloModuleService.createCustoms(data)
+
+ return new StepResponse(custom, custom)
+ },
+ async (custom, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.deleteCustoms(custom.id)
+ }
+)
+```
+
+In the step, you resolve the Hello Module's main service and create a `Custom` record.
+
+In the compensation function that undoes the step's actions in case of an error, you delete the created record.
+
+
+
+Learn more about compensation functions in [this guide](!docs!/advanced-development/workflows/compensation-function).
+
+
+
+Then, create the workflow at `src/workflows/create-custom-from-product/index.ts` with the following content:
+
+```ts title="src/workflows/create-custom-from-product/index.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import { createWorkflow, transform, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+import { ProductDTO } from "@medusajs/framework/types"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+import { Modules } from "@medusajs/framework/utils"
+import { HELLO_MODULE } from "../../modules/hello"
+import { createCustomStep } from "./steps/create-custom"
+
+export type CreateCustomFromProductWorkflowInput = {
+ product: ProductDTO
+ additional_data?: {
+ custom_name?: string
+ }
+}
+
+export const createCustomFromProductWorkflow = createWorkflow(
+ "create-custom-from-product",
+ (input: CreateCustomFromProductWorkflowInput) => {
+ const customName = transform(
+ {
+ input
+ },
+ (data) => data.input.additional_data.custom_name || ""
+ )
+
+ const custom = createCustomStep({
+ custom_name: customName
+ })
+
+ when(({ custom }), ({ custom }) => custom !== undefined)
+ .then(() => {
+ createRemoteLinkStep([{
+ [Modules.PRODUCT]: {
+ product_id: input.product.id
+ },
+ [HELLO_MODULE]: {
+ custom_id: custom.id
+ }
+ }])
+ })
+
+ return new WorkflowResponse({
+ custom
+ })
+ }
+)
+```
+
+The workflow accepts as an input the created product and the `additional_data` parameter passed in the request. This is the same input that the `productsCreated` hook accepts.
+
+In the workflow, you:
+
+1. Use the `transform` utility to get the value of `custom_name` based on whether it's set in `additional_data`. Learn more about why you can't use conditional operators in a workflow without using `transform` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
+2. Create the `Custom` record using the `createCustomStep`.
+3. Use the `when-then` utility to link the product to the `Custom` record if it was created. Learn more about why you can't use if-then conditions in a workflow without using `when-then` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
+
+You'll next execute the workflow in the hook handler.
+
+### Consume Workflow Hook
+
+You can now consume the `productsCreated` hook, which is executed in the `createProductsWorkflow` after the product is created.
+
+To consume the hook, create the file `src/workflow/hooks/product-created.ts` with the following content:
+
+```ts title="src/workflow/hooks/product-created.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import { createProductsWorkflow } from "@medusajs/medusa/core-flows"
+import {
+ createCustomFromProductWorkflow,
+ CreateCustomFromProductWorkflowInput
+} from "../create-custom-from-product"
+
+createProductsWorkflow.hooks.productsCreated(
+ async ({ products, additional_data }, { container }) => {
+ const workflow = createCustomFromProductWorkflow(container)
+
+ for (let product of products) {
+ await workflow.run({
+ input: {
+ product,
+ additional_data
+ } as CreateCustomFromProductWorkflowInput
+ })
+ }
+ }
+)
+```
+
+The hook handler executes the `createCustomFromProductWorkflow`, passing it its input.
+
+### Test it Out
+
+To test it out, send a `POST` request to `/admin/products` to create a product, passing `custom_name` in `additional_data`:
+
+```bash
+curl -X POST 'localhost:9000/admin/products' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data '{
+ "title": "Shoes",
+ "additional_data": {
+ "custom_name": "test"
+ }
+}'
+```
+
+Make sure to replace `{token}` with an admin user's JWT token. Learn how to retrieve it in the [API reference](!api!/admin#1-bearer-authorization-with-jwt-tokens).
+
+The request will return the product's details. You'll learn how to retrive the `custom_name` property with the product's details in the next section.
+
+---
+
+## Step 5: Retrieve custom_name with Product Details
+
+When you extend an existing data model through links, you also want to retrieve the custom properties with the data model.
+
+### Retrieve in API Routes
+
+To retrieve the `custom_name` property when you're retrieving the product through API routes, such as the [Get Product API Route](!api!/admin#products_getproductsid), pass in the `fields` query parameter `+custom.*`, which retrieves the linked `Custom` record's details.
+
+
+
+The `+` prefix in `+custom.*` indicates that the relation should be retrieved with the default product fields. Learn more about selecting fields and relations in the [API reference](!api!/admin#select-fields-and-relations).
+
+
+
+For example:
+
+```bash
+curl 'localhost:9000/admin/products/{product_id}?fields=+custom.*' \
+-H 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{product_id}` with the product's ID, and `{token}` with an admin user's JWT token.
+
+Among the returned `product` object, you'll find a `custom` property which holds the details of the linked `Custom` record:
+
+```json
+{
+ "product": {
+ // ...
+ "custom": {
+ "id": "01J9NP7ANXDZ0EAYF0956ZE1ZA",
+ "custom_name": "test",
+ "created_at": "2024-10-08T09:09:06.877Z",
+ "updated_at": "2024-10-08T09:09:06.877Z",
+ "deleted_at": null
+ }
+ }
+}
+```
+
+### Retrieve using Query
+
+You can also retrieve the `Custom` record linked to a product in your code using [Query](!docs!/advanced-development/module-links/query).
+
+For example:
+
+```ts
+const { data: [product] } = await query.graph({
+ entity: "product",
+ fields: ["*", "custom.*"],
+ filters: {
+ id: product_id,
+ },
+})
+```
+
+Learn more about how to use Query in [this guide](!docs!/advanced-development/module-links/query).
+
+---
+
+## Step 6: Consume productsUpdated Workflow Hook
+
+Similar to the `productsCreated` hook, you'll consume the [productsUpdated](/references/medusa-workflows/updateProductsWorkflow#productsUpdated) hook of the [updateProductsWorkflow](/references/medusa-workflows/updateProductsWorkflow) to update `custom_name` when the product is updated.
+
+The `updateProductsWorkflow` is executed by the [Update Product API route](!api!/admin#products_postproductsid), which accepts the `additional_data` parameter to pass custom data to the hook.
+
+### Add custom_name to Additional Data Validation
+
+To allow passing `custom_name` in the `additional_data` parameter of the update product route, add in `src/api/middlewares.ts` a new route middleware configuration object:
+
+```ts title="src/api/middlewares.ts"
+import { defineMiddlewares } from "@medusajs/medusa"
+import { z } from "zod"
+
+export default defineMiddlewares({
+ routes: [
+ // ...
+ {
+ method: "POST",
+ matcher: "/admin/products/:id",
+ additionalDataValidator: {
+ custom_name: z.string().nullish(),
+ },
+ },
+ ],
+})
+```
+
+The validation schema is the similar to that of the Create Product API route, except you can pass a `null` value for `custom_name` to remove or unset the `custom_name`'s value.
+
+### Create Workflow to Update Custom Record
+
+Next, you'll create a workflow that creates, updates, or deletes `Custom` records based on the provided `additional_data` parameter:
+
+1. If `additional_data.custom_name` is set and it's `null`, the `Custom` record linked to the product is deleted.
+2. If `additional_data.custom_name` is set and the product doesn't have a linked `Custom` record, a new record is created and linked to the product.
+3. If `additional_data.custom_name` is set and the product has a linked `Custom` record, the `custom_name` property of the `Custom` record is updated.
+
+Start by creating the step that updates a `Custom` record. Create the file `src/workflows/update-custom-from-product/steps/update-custom.ts` with the following content:
+
+```ts title="src/workflows/update-custom-from-product/steps/update-custom.ts"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { HELLO_MODULE } from "../../../modules/hello"
+import HelloModuleService from "../../../modules/hello/service"
+
+type UpdateCustomStepInput = {
+ id: string
+ custom_name: string
+}
+
+export const updateCustomStep = createStep(
+ "update-custom",
+ async ({ id, custom_name }: UpdateCustomStepInput, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ const prevData = await helloModuleService.retrieveCustom(id)
+
+ const custom = await helloModuleService.updateCustoms({
+ id,
+ custom_name,
+ })
+
+ return new StepResponse(custom, prevData)
+ },
+ async (prevData, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.updateCustoms(prevData)
+ }
+)
+```
+
+In this step, you update a `Custom` record. In the compensation function, you revert the update.
+
+Next, you'll create the step that deletes a `Custom` record. Create the file `src/workflows/update-custom-from-product/steps/delete-custom.ts` with the following content:
+
+```ts title="src/workflows/update-custom-from-product/steps/delete-custom.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { Custom } from "../../../modules/hello/models/custom"
+import { InferTypeOf } from "@medusajs/framework/types"
+import HelloModuleService from "../../../modules/hello/service"
+import { HELLO_MODULE } from "../../../modules/hello"
+
+type DeleteCustomStepInput = {
+ custom: InferTypeOf
+}
+
+export const deleteCustomStep = createStep(
+ "delete-custom",
+ async ({ custom }: DeleteCustomStepInput, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.deleteCustoms(custom.id)
+
+ return new StepResponse(custom, custom)
+ },
+ async (custom, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.createCustoms(custom)
+ }
+)
+```
+
+In this step, you delete a `Custom` record. In the compensation function, you create it again.
+
+Finally, you'll create the workflow. Create the file `src/workflows/update-custom-from-product/index.ts` with the following content:
+
+```ts title="src/workflows/update-custom-from-product/index.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import { ProductDTO } from "@medusajs/framework/types"
+import { createWorkflow, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+import { createRemoteLinkStep, dismissRemoteLinkStep, useRemoteQueryStep } from "@medusajs/medusa/core-flows"
+import { createCustomStep } from "../create-custom-from-cart/steps/create-custom"
+import { Modules } from "@medusajs/framework/utils"
+import { HELLO_MODULE } from "../../modules/hello"
+import { deleteCustomStep } from "./steps/delete-custom"
+import { updateCustomStep } from "./steps/update-custom"
+
+export type UpdateCustomFromProductStepInput = {
+ product: ProductDTO
+ additional_data?: {
+ custom_name?: string | null
+ }
+}
+
+export const updateCustomFromProductWorkflow = createWorkflow(
+ "update-custom-from-product",
+ (input: UpdateCustomFromProductStepInput) => {
+ const productData = useRemoteQueryStep({
+ entry_point: "product",
+ fields: ["custom.*"],
+ variables: {
+ filters: {
+ id: input.product.id
+ }
+ },
+ list: false
+ })
+
+ // TODO create, update, or delete Custom record
+ }
+)
+```
+
+The workflow accepts the same input as the `productsUpdated` workflow hook handler would.
+
+In the workflow, you retrieve the product's linked `Custom` record using Query.
+
+Next, replace the `TODO` with the following:
+
+```ts title="src/workflows/update-custom-from-product/index.ts"
+const created = when({
+ input,
+ productData
+}, (data) =>
+ !data.productData.custom &&
+ data.input.additional_data?.custom_name?.length > 0
+)
+.then(() => {
+ const custom = createCustomStep({
+ custom_name: input.additional_data.custom_name
+ })
+
+ createRemoteLinkStep([{
+ [Modules.PRODUCT]: {
+ product_id: input.product.id
+ },
+ [HELLO_MODULE]: {
+ custom_id: custom.id
+ }
+ }])
+
+ return custom
+})
+
+// TODO update, or delete Custom record
+```
+
+Using the `when-then` utility, you check if the product doesn't have a linked `Custom` record and the `custom_name` property is set. If so, you create a `Custom` record and link it to the product.
+
+To create the `Custom` record, you use the `createCustomStep` you created in an earlier section.
+
+Next, replace the new `TODO` with the following:
+
+```ts title="src/workflows/update-custom-from-product/index.ts"
+const deleted = when({
+ input,
+ productData
+}, (data) =>
+ data.productData.custom && (
+ data.input.additional_data?.custom_name === null ||
+ data.input.additional_data?.custom_name.length === 0
+ )
+)
+.then(() => {
+ deleteCustomStep({
+ custom: productData.custom
+ })
+
+ dismissRemoteLinkStep({
+ [HELLO_MODULE]: {
+ custom_id: productData.custom.id
+ }
+ })
+
+ return productData.custom.id
+})
+
+// TODO delete Custom record
+```
+
+Using the `when-then` utility, you check if the product has a linked `Custom` record and `custom_name` is `null` or an empty string. If so, you delete the linked `Custom` record and dismiss its links.
+
+Finally, replace the new `TODO` with the following:
+
+```ts title="src/workflows/update-custom-from-product/index.ts"
+const updated = when({
+ input,
+ productData
+}, (data) => data.productData.custom && data.input.additional_data?.custom_name?.length > 0)
+.then(() => {
+ const custom = updateCustomStep({
+ id: productData.custom.id,
+ custom_name: input.additional_data.custom_name
+ })
+
+ return custom
+})
+
+return new WorkflowResponse({
+ created,
+ updated,
+ deleted
+})
+```
+
+Using the `when-then` utility, you check if the product has a linked `Custom` record and `custom_name` is passed in the `additional_data`. If so, you update the linked `Custom` recod.
+
+You return in the workflow response the created, updated, and deleted `Custom` record.
+
+### Consume productsUpdated Workflow Hook
+
+You can now consume the `productsUpdated` and execute the workflow you created.
+
+Create the file `src/workflows/hooks/product-updated.ts` with the following content:
+
+```ts title="src/workflows/hooks/product-updated.ts"
+import { updateProductsWorkflow } from "@medusajs/medusa/core-flows"
+import {
+ UpdateCustomFromProductStepInput,
+ updateCustomFromProductWorkflow
+} from "../update-custom-from-product"
+
+updateProductsWorkflow.hooks.productsUpdated(
+ async ({ products, additional_data }, { container }) => {
+ const workflow = updateCustomFromProductWorkflow(container)
+
+ for (let product of products) {
+ await workflow.run({
+ input: {
+ product,
+ additional_data
+ } as UpdateCustomFromProductStepInput
+ })
+ }
+ }
+)
+```
+
+In the workflow hook handler, you execute the workflow, passing it the hook's input.
+
+### Test it Out
+
+To test it out, send a `POST` request to `/admin/products/:id` to update a product, passing `custom_name` in `additional_data`:
+
+```bash
+curl -X POST 'localhost:9000/admin/products/{product_id}?fields=+custom.*' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data '{
+ "additional_data": {
+ "custom_name": "test 2"
+ }
+}'
+```
+
+Make sure to replace `{product_id}` with the product's ID, and `{token}` with the JWT token of an admin user.
+
+The request will return the product's details with the updated `custom` linked record.
diff --git a/www/apps/resources/app/commerce-modules/product/guides/price-with-taxes/page.mdx b/www/apps/resources/app/commerce-modules/product/guides/price-with-taxes/page.mdx
index a1e91676a5..d4903742cc 100644
--- a/www/apps/resources/app/commerce-modules/product/guides/price-with-taxes/page.mdx
+++ b/www/apps/resources/app/commerce-modules/product/guides/price-with-taxes/page.mdx
@@ -14,7 +14,7 @@ In this document, you'll learn how to calculate a product variant's price with t
You'll need the following resources for the taxes calculation:
-1. Query to retrieve the product's variants' prices for a context. Learn more about that in [this guide](../price/page.mdx).
+1. [Query](!docs!/advanced-development/module-links/query) to retrieve the product's variants' prices for a context. Learn more about that in [this guide](../price/page.mdx).
2. The Tax Module's main service to get the tax lines for each product.
```ts
@@ -37,6 +37,12 @@ const taxModuleService = container.resolve(
After resolving the resources, use Query to retrieve the products with the variants' prices for a context:
+
+
+Learn more about retrieving product variants' prices for a context in [this guide](../price/page.mdx).
+
+
+
```ts
import { QueryContext } from "@medusajs/framework/utils"
@@ -63,12 +69,6 @@ const { data: products } = await query.graph({
})
```
-
-
-Learn more about retrieving product variants' prices for a context in [this guide](../price/page.mdx).
-
-
-
---
## Step 2: Get Tax Lines for Products
diff --git a/www/apps/resources/app/commerce-modules/product/guides/price/page.mdx b/www/apps/resources/app/commerce-modules/product/guides/price/page.mdx
index 84aa982c36..e69b068af8 100644
--- a/www/apps/resources/app/commerce-modules/product/guides/price/page.mdx
+++ b/www/apps/resources/app/commerce-modules/product/guides/price/page.mdx
@@ -1,5 +1,5 @@
---
-sidebar_label: "Get Product Variant Prices"
+sidebar_label: "Get Variant Prices"
---
export const metadata = {
@@ -8,7 +8,7 @@ export const metadata = {
# {metadata.title}
-In this document, you'll learn how to retrieve product variant prices in the Medusa application using the [Query](!docs!/advanced-development/module-links/query).
+In this document, you'll learn how to retrieve product variant prices in the Medusa application using [Query](!docs!/advanced-development/module-links/query).
@@ -57,11 +57,11 @@ Learn more about prices calculation in [this Pricing Module documentation](../..
To retrieve calculated prices of variants based on a context, retrieve the products using Query and:
- Pass `variants.calculated_price.*` in the `fields` property.
-- Pass a `context` property in the object parameter. Its value is an object of objects to sets the context for the retrieved fields.
+- Pass a `context` property in the object parameter. Its value is an object of objects that sets the context for the retrieved fields.
For example:
-```ts highlights={[["6"], ["12"], ["13"], ["14"], ["15"], ["16"], ["17"]]}
+```ts highlights={[["10"], ["15"], ["16"], ["17"], ["18"], ["19"], ["20"], ["21"], ["22"]]}
import { QueryContext } from "@medusajs/framework/utils"
// ...
diff --git a/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx
new file mode 100644
index 0000000000..d4c39b7f43
--- /dev/null
+++ b/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx
@@ -0,0 +1,39 @@
+export const metadata = {
+ title: `Links between Product Module and Other Modules`,
+}
+
+# {metadata.title}
+
+This document showcases the module links defined between the Product Module and other commerce modules.
+
+## Pricing Module
+
+The Product Module doesn't provide pricing-related features.
+
+Instead, Medusa defines a link between the `ProductVariant` and the `PriceSet` data models. A product variant’s prices are stored belonging to a price set.
+
+
+
+So, to add prices for a product variant, create a price set and add the prices to it.
+
+---
+
+## Sales Channel Module
+
+The Sales Channel Module provides functionalities to manage multiple selling channels in your store.
+
+Medusa defines a link between the `Product` and `SalesChannel` data models. A product can have different availability in different sales channels.
+
+
+
+---
+
+## Inventory Module
+
+The Inventory Module provides inventory-management features for any stock-kept item.
+
+Medusa defines a link between the `ProductVariant` and `InventoryItem` data models. Each product variant has different inventory details.
+
+
+
+When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
diff --git a/www/apps/resources/app/commerce-modules/product/page.mdx b/www/apps/resources/app/commerce-modules/product/page.mdx
index 5b73a2fcc6..0336b23f5d 100644
--- a/www/apps/resources/app/commerce-modules/product/page.mdx
+++ b/www/apps/resources/app/commerce-modules/product/page.mdx
@@ -6,7 +6,7 @@ export const metadata = {
# {metadata.title}
-The Product Module is the `@medusajs/medusa/product` NPM package that provides product-related features in your Medusa and Node.js applications.
+The Product Module provides product-related features in your Medusa and Node.js applications.
## How to Use Product Module's Service
@@ -15,15 +15,30 @@ You can use the Product Module's main service by resolving from the Medusa conta
For example:
+
+
+```ts title="src/workflows/hello-world/step1.ts"
+import { createStep } from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const step1 = createStep("step-1", async (_, { container }) => {
+ const productModuleService = container.resolve(
+ Modules.PRODUCT
+ )
+
+ const products = await productModuleService.listProducts()
+})
+```
+
+
- ```ts title="src/api/store/custom/route.ts"
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts title="src/api/store/custom/route.ts"
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(request: MedusaRequest, res: MedusaResponse) {
- const productModuleService: IProductModuleService = request.scope.resolve(
+ const productModuleService = request.scope.resolve(
Modules.PRODUCT
)
@@ -36,35 +51,17 @@ export async function GET(request: MedusaRequest, res: MedusaResponse) {
- ```ts title="src/subscribers/custom-handler.ts"
- import { SubscriberArgs } from "@medusajs/framework"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts title="src/subscribers/custom-handler.ts"
+import { SubscriberArgs } from "@medusajs/framework"
+import { Modules } from "@medusajs/framework/utils"
export default async function subscriberHandler({ container }: SubscriberArgs) {
- const productModuleService: IProductModuleService = container.resolve(
+ const productModuleService = container.resolve(
Modules.PRODUCT
)
const products = await productModuleService.listProducts()
}
-```
-
-
-
-
- ```ts title="src/workflows/hello-world/step1.ts"
- import { createStep } from "@medusajs/framework/workflows-sdk"
- import { IProductModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
-
-const step1 = createStep("step-1", async (_, { container }) => {
- const productModuleService: IProductModuleService = container.resolve(
- Modules.PRODUCT
- )
-
- const products = await productModuleService.listProducts()
-})
```
diff --git a/www/apps/resources/app/commerce-modules/product/relations-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/product/relations-to-other-modules/page.mdx
deleted file mode 100644
index bab8fcd996..0000000000
--- a/www/apps/resources/app/commerce-modules/product/relations-to-other-modules/page.mdx
+++ /dev/null
@@ -1,53 +0,0 @@
-export const metadata = {
- title: `Relations between Product Module and Other Modules`,
-}
-
-# {metadata.title}
-
-This document showcases the link modules defined between the Product Module and other commerce modules.
-
-## Cart Module
-
-A cart's line item is associated with a product and its variant. Medusa defines a link module that builds a relationship between the `Cart`, `Product`, and `ProductVariant` data models.
-
-
-
----
-
-## Order Module
-
-An order's line item is associated with the purchased product and its variant. Medusa defines a link module that builds a relationship between the `LineItem`, `Product`, and `ProductVariant` data models.
-
-
-
----
-
-## Pricing Module
-
-A product variant’s prices are stored as money amounts belonging to a price set. Medusa defines a link module that builds a relationship between the `ProductVariant` and the `PriceSet` data models.
-
-
-
-So, to add prices for a product variant, create a price set and add the prices as money amounts to it.
-
-Learn more about the `PriceSet` data model in the [Pricing Concepts](../../pricing/concepts/page.mdx#price-list)
-
----
-
-## Sales Channel Module
-
-A product can have different availability in different sales channels. Medusa defines a link module that builds a relationship between the `Product` and `SalesChannel` data models.
-
-
-
----
-
-## Inventory Module
-
-Each product variant has different inventory details. Medusa defines a link module that builds a relationship between the `ProductVariant` and `InventoryItem` data models.
-
-
-
-When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
-
-Learn more about the `InventoryItem` data model in the [Inventory Concepts](../../inventory/concepts/page.mdx#inventoryitem)
\ No newline at end of file
diff --git a/www/apps/resources/app/commerce-modules/promotion/actions/page.mdx b/www/apps/resources/app/commerce-modules/promotion/actions/page.mdx
index ef8578bb95..81887ca586 100644
--- a/www/apps/resources/app/commerce-modules/promotion/actions/page.mdx
+++ b/www/apps/resources/app/commerce-modules/promotion/actions/page.mdx
@@ -4,7 +4,7 @@ export const metadata = {
# {metadata.title}
-In this document, you’ll learn about promotion actions and how they’re computed and used.
+In this document, you’ll learn about promotion actions and how they’re computed using the [computeActions method](/references/promotion/computeActions).
## computeActions Method
@@ -32,7 +32,7 @@ export interface AddItemAdjustmentAction {
}
```
-This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module.
+This action means that a new record should be created of the `LineItemAdjustment` data model in the Cart Module, or `OrderLineItemAdjustment` data model in the Order Module.
@@ -57,7 +57,7 @@ export interface RemoveItemAdjustmentAction {
}
```
-This action means that a new record should be removed of the `LineItemAdjustment` with the specified ID in the `adjustment_id` property.
+This action means that a new record should be removed of the `LineItemAdjustment` (or `OrderLineItemAdjustment`) with the specified ID in the `adjustment_id` property.
@@ -81,7 +81,7 @@ export interface AddShippingMethodAdjustment {
}
```
-This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module.
+This action means that a new record should be created of the `ShippingMethodAdjustment` data model in the Cart Module, or `OrderShippingMethodAdjustment` data model in the Order Module.
@@ -105,7 +105,7 @@ export interface RemoveShippingMethodAdjustment {
}
```
-When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` with the specified ID in the `adjustment_id` property.
+When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` (or `OrderShippingMethodAdjustment`) with the specified ID in the `adjustment_id` property.
diff --git a/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx b/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx
index b7706bccac..e13dfd151b 100644
--- a/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx
+++ b/www/apps/resources/app/commerce-modules/promotion/concepts/page.mdx
@@ -10,7 +10,7 @@ In this document, you’ll learn about the main promotion and rule concepts in t
## What is a Promotion?
-A promotion, represented by the [Promotion data model](/references/promotion/models/Promotion), represents a discount applied on cart items, shipping methods, or entire orders.
+A promotion, represented by the [Promotion data model](/references/promotion/models/Promotion), is a discount that can be applied on cart items, shipping methods, or entire orders.
A promotion has two types:
@@ -72,15 +72,23 @@ A promotion has two types:
+---
+
## PromotionRule
-A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](/references/promotion/models/PromotionRule). For example, you can create a promotion that only customers of the `VIP` customer group can use.
+A promotion can be restricted by a set of rules, each rule is represented by the [PromotionRule data model](/references/promotion/models/PromotionRule).
+
+For example, you can create a promotion that only customers of the `VIP` customer group can use.

-A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied. For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
+A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied.
-When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself. For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
+For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
+
+When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself.
+
+For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
---
@@ -90,8 +98,8 @@ The `PromotionRule`'s `operator` property adds more flexibility to the rule’s
For example, to restrict the promotion to only `VIP` and `B2B` customer groups:
-- Add a `PromotionRule` with its `attribute` property set to `customer_group_id` and `operator` property to `in`.
-- Add two `PromotionRuleValue` associated with the rule: one with the value `VIP` and the other `B2B`.
+- Add a `PromotionRule` record with its `attribute` property set to `customer_group_id` and `operator` property to `in`.
+- Add two `PromotionRuleValue` records associated with the rule: one with the value `VIP` and the other `B2B`.

diff --git a/www/apps/resources/app/commerce-modules/promotion/examples/page.mdx b/www/apps/resources/app/commerce-modules/promotion/examples/page.mdx
index 3c408044bc..f25901ff83 100644
--- a/www/apps/resources/app/commerce-modules/promotion/examples/page.mdx
+++ b/www/apps/resources/app/commerce-modules/promotion/examples/page.mdx
@@ -13,16 +13,15 @@ In this document, you’ll find common examples of how you can use the Promotion
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPromotionModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const promotionModuleService: IPromotionModuleService = request.scope.resolve(
+ const promotionModuleService = request.scope.resolve(
Modules.PROMOTION
)
@@ -78,16 +77,15 @@ export async function POST(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPromotionModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const promotionModuleService: IPromotionModuleService = request.scope.resolve(
+ const promotionModuleService = request.scope.resolve(
Modules.PROMOTION
)
@@ -135,16 +133,15 @@ export async function POST(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPromotionModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function POST(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const promotionModuleService: IPromotionModuleService = request.scope.resolve(
+ const promotionModuleService = request.scope.resolve(
Modules.PROMOTION
)
@@ -213,16 +210,15 @@ export async function POST(request: Request) {
- ```ts
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPromotionModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const promotionModuleService: IPromotionModuleService = request.scope.resolve(
+ const promotionModuleService = request.scope.resolve(
Modules.PROMOTION
)
diff --git a/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx b/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx
new file mode 100644
index 0000000000..1d701d1b5b
--- /dev/null
+++ b/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx
@@ -0,0 +1,696 @@
+import { Prerequisites } from "docs-ui"
+
+export const metadata = {
+ title: `Extend Promotion Data Model`,
+}
+
+# {metadata.title}
+
+In this documentation, you'll learn how to extend a data model of the Promotion Module to add a custom property.
+
+You'll create a `Custom` data model in a module. This data model will have a `custom_name` property, which is the property you want to add to the [Promotion data model](/references/promotion/models/Promotion) defined in the Promotion Module.
+
+You'll then learn how to:
+
+- Link the `Custom` data model to the `Promotion` data model.
+- Set the `custom_name` property when a promotion is created or updated using Medusa's API routes.
+- Retrieve the `custom_name` property with the promotion's details, in custom or existing API routes.
+
+
+
+Similar steps can be applied to the `Campaign` data model.
+
+
+
+## Step 1: Define Custom Data Model
+
+Consider you have a Hello Module defined in the `/src/modules/hello` directory.
+
+
+
+If you don't have a module, follow [this guide](!docs!/basics/modules) to create one.
+
+
+
+To add the `custom_name` property to the `Promotion` data model, you'll create in the Hello Module a data model that has the `custom_name` property.
+
+Create the file `src/modules/hello/models/custom.ts` with the following content:
+
+```ts title="src/modules/hello/models/custom.ts"
+import { model } from "@medusajs/framework/utils"
+
+export const Custom = model.define("custom", {
+ id: model.id().primaryKey(),
+ custom_name: model.text(),
+})
+```
+
+This creates a `Custom` data model that has the `id` and `custom_name` properties.
+
+
+
+Learn more about data models in [this guide](!docs!/data-models).
+
+
+
+---
+
+## Step 2: Define Link to Promotion Data Model
+
+Next, you'll define a module link between the `Custom` and `Promotion` data model. A module link allows you to form a relation between two data models of separate modules while maintaining module isolation.
+
+
+
+Learn more about module links in [this guide](!docs!/module-links).
+
+
+
+Create the file `src/links/promotion-custom.ts` with the following content:
+
+```ts title="src/links/promotion-custom.ts"
+import { defineLink } from "@medusajs/framework/utils";
+import HelloModule from "../modules/hello"
+import PromotionModule from "@medusajs/medusa/promotion"
+
+export default defineLink(
+ PromotionModule.linkable.promotion,
+ HelloModule.linkable.custom,
+)
+```
+
+This defines a link between the `Promotion` and `Custom` data models. Using this link, you'll later query data across the modules, and link records of each data model.
+
+---
+
+## Step 3: Generate and Run Migrations
+
+
+
+To reflect the `Custom` data model in the database, generate a migration that defines the table to be created for it.
+
+Run the following command in your Medusa project's root:
+
+```bash
+npx medusa db:generate helloModuleService
+```
+
+Where `helloModuleService` is your module's name.
+
+Then, run the `db:migrate` command to run the migrations and create a table in the database for the link between the `Promotion` and `Custom` data models:
+
+```bash
+npx medusa db:migrate
+```
+
+A table for the link is now created in the database. You can now retrieve and manage the link between records of the data models.
+
+---
+
+## Step 4: Consume promotionsCreated Workflow Hook
+
+When a promotion is created, you also want to create a `Custom` record and set the `custom_name` property, then create a link between the `Promotion` and `Custom` records.
+
+To do that, you'll consume the [promotionsCreated](/references/medusa-workflows/createPromotionsWorkflow#promotionsCreated) hook of the [createPromotionsWorkflow](/references/medusa-workflows/createPromotionsWorkflow). This workflow is executed in the [Create Promotion Admin API route](!api!/admin#promotions_postpromotions)
+
+
+
+Learn more about workflow hooks in [this guide](!docs!/advanced-development/workflows/workflow-hooks).
+
+
+
+The API route accepts in its request body an `additional_data` parameter. You can pass in it custom data, which is passed to the workflow hook handler.
+
+### Add custom_name to Additional Data Validation
+
+To pass the `custom_name` in the `additional_data` parameter, you must add a validation rule that tells the Medusa application about this custom property.
+
+Create the file `src/api/middlewares.ts` with the following content:
+
+```ts title="src/api/middlewares.ts"
+import { defineMiddlewares } from "@medusajs/medusa"
+import { z } from "zod"
+
+export default defineMiddlewares({
+ routes: [
+ {
+ method: "POST",
+ matcher: "/admin/promotions",
+ additionalDataValidator: {
+ custom_name: z.string().optional(),
+ },
+ },
+ ],
+})
+```
+
+The `additional_data` parameter validation is customized using the `defineMiddlewares` utility function. In the routes middleware configuration object, the `additionalDataValidator` property accepts [Zod](https://zod.dev/) validaiton rules.
+
+In the snippet above, you add a validation rule indicating that `custom_name` is a string that can be passed in the `additional_data` object.
+
+
+
+Learn more about additional data validation in [this guide](!docs!/advanced-development/api-routes/additional-data).
+
+
+
+### Create Workflow to Create Custom Record
+
+You'll now create a workflow that will be used in the hook handler.
+
+This workflow will create a `Custom` record, then link it to the promotion.
+
+Start by creating the step that creates the `Custom` record. Create the file `src/workflows/create-custom-from-promotion/steps/create-custom.ts` with the following content:
+
+```ts title="src/workflows/create-custom-from-promotion/steps/create-custom.ts"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import HelloModuleService from "../../../modules/hello/service"
+import { HELLO_MODULE } from "../../../modules/hello"
+
+type CreateCustomStepInput = {
+ custom_name?: string
+}
+
+export const createCustomStep = createStep(
+ "create-custom",
+ async (data: CreateCustomStepInput, { container }) => {
+ if (!data.custom_name) {
+ return
+ }
+
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ const custom = await helloModuleService.createCustoms(data)
+
+ return new StepResponse(custom, custom)
+ },
+ async (custom, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.deleteCustoms(custom.id)
+ }
+)
+```
+
+In the step, you resolve the Hello Module's main service and create a `Custom` record.
+
+In the compensation function that undoes the step's actions in case of an error, you delete the created record.
+
+
+
+Learn more about compensation functions in [this guide](!docs!/advanced-development/workflows/compensation-function).
+
+
+
+Then, create the workflow at `src/workflows/create-custom-from-promotion/index.ts` with the following content:
+
+```ts title="src/workflows/create-custom-from-promotion/index.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports"
+import { createWorkflow, transform, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+import { PromotionDTO } from "@medusajs/framework/types"
+import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
+import { Modules } from "@medusajs/framework/utils"
+import { HELLO_MODULE } from "../../modules/hello"
+import { createCustomStep } from "./steps/create-custom"
+
+export type CreateCustomFromPromotionWorkflowInput = {
+ promotion: PromotionDTO
+ additional_data?: {
+ custom_name?: string
+ }
+}
+
+export const createCustomFromPromotionWorkflow = createWorkflow(
+ "create-custom-from-promotion",
+ (input: CreateCustomFromPromotionWorkflowInput) => {
+ const customName = transform(
+ {
+ input
+ },
+ (data) => data.input.additional_data.custom_name || ""
+ )
+
+ const custom = createCustomStep({
+ custom_name: customName
+ })
+
+ when(({ custom }), ({ custom }) => custom !== undefined)
+ .then(() => {
+ createRemoteLinkStep([{
+ [Modules.PROMOTION]: {
+ promotion_id: input.promotion.id
+ },
+ [HELLO_MODULE]: {
+ custom_id: custom.id
+ }
+ }])
+ })
+
+ return new WorkflowResponse({
+ custom
+ })
+ }
+)
+```
+
+The workflow accepts as an input the created promotion and the `additional_data` parameter passed in the request. This is the same input that the `promotionsCreated` hook accepts.
+
+In the workflow, you:
+
+1. Use the `transform` utility to get the value of `custom_name` based on whether it's set in `additional_data`. Learn more about why you can't use conditional operators in a workflow without using `transform` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
+2. Create the `Custom` record using the `createCustomStep`.
+3. Use the `when-then` utility to link the promotion to the `Custom` record if it was created. Learn more about why you can't use if-then conditions in a workflow without using `when-then` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
+
+You'll next execute the workflow in the hook handler.
+
+### Consume Workflow Hook
+
+You can now consume the `promotionsCreated` hook, which is executed in the `createPromotionsWorkflow` after the promotion is created.
+
+To consume the hook, create the file `src/workflow/hooks/promotion-created.ts` with the following content:
+
+```ts title="src/workflow/hooks/promotion-created.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import { createPromotionsWorkflow } from "@medusajs/medusa/core-flows"
+import {
+ createCustomFromPromotionWorkflow,
+ CreateCustomFromPromotionWorkflowInput
+} from "../create-custom-from-promotion"
+
+createPromotionsWorkflow.hooks.promotionsCreated(
+ async ({ promotions, additional_data }, { container }) => {
+ const workflow = createCustomFromPromotionWorkflow(container)
+
+ for (let promotion of promotions) {
+ await workflow.run({
+ input: {
+ promotion,
+ additional_data
+ } as CreateCustomFromPromotionWorkflowInput
+ })
+ }
+ }
+)
+```
+
+The hook handler executes the `createPromotionsWorkflow`, passing it its input.
+
+### Test it Out
+
+To test it out, send a `POST` request to `/admin/promotions` to create a promotion, passing `custom_name` in `additional_data`:
+
+```bash
+curl --location 'localhost:9000/admin/promotions' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data '{
+ "additional_data": {
+ "custom_name": "test"
+ },
+ "code": "50OFF",
+ "type": "standard",
+ "application_method": {
+ "description": "My promotion",
+ "value": 50,
+ "currency_code": "usd",
+ "max_quantity": 1,
+ "type": "percentage",
+ "target_type": "items",
+ "apply_to_quantity": 0,
+ "buy_rules_min_quantity": 1,
+ "allocation": "each"
+ }
+}'
+```
+
+Make sure to replace `{token}` with an admin user's JWT token. Learn how to retrieve it in the [API reference](!api!/admin#1-bearer-authorization-with-jwt-tokens).
+
+The request will return the promotion's details. You'll learn how to retrive the `custom_name` property with the promotion's details in the next section.
+
+---
+
+## Step 5: Retrieve custom_name with Promotion Details
+
+When you extend an existing data model through links, you also want to retrieve the custom properties with the data model.
+
+### Retrieve in API Routes
+
+To retrieve the `custom_name` property when you're retrieving the promotion through API routes, such as the [Get Promotion API Route](!api!/admin#promotions_getpromotionsid), pass in the `fields` query parameter `+custom.*`, which retrieves the linked `Custom` record's details.
+
+
+
+The `+` prefix in `+custom.*` indicates that the relation should be retrieved with the default promotion fields. Learn more about selecting fields and relations in the [API reference](!api!/admin#select-fields-and-relations).
+
+
+
+For example:
+
+```bash
+curl 'localhost:9000/admin/promotions/{promotion_id}?fields=+custom.*' \
+-H 'Authorization: Bearer {token}'
+```
+
+Make sure to replace `{promotion_id}` with the promotion's ID, and `{token}` with an admin user's JWT token.
+
+Among the returned `promotion` object, you'll find a `custom` property which holds the details of the linked `Custom` record:
+
+```json
+{
+ "promotion": {
+ // ...
+ "custom": {
+ "id": "01J9NP7ANXDZ0EAYF0956ZE1ZA",
+ "custom_name": "test",
+ "created_at": "2024-10-08T09:09:06.877Z",
+ "updated_at": "2024-10-08T09:09:06.877Z",
+ "deleted_at": null
+ }
+ }
+}
+```
+
+### Retrieve using Query
+
+You can also retrieve the `Custom` record linked to a promotion in your code using [Query](!docs!/advanced-development/module-links/query).
+
+For example:
+
+```ts
+const { data: [promotion] } = await query.graph({
+ entity: "promotion",
+ fields: ["*", "custom.*"],
+ filters: {
+ id: promotion_id,
+ },
+})
+```
+
+Learn more about how to use Query in [this guide](!docs!/advanced-development/module-links/query).
+
+---
+
+## Step 6: Consume promotionsUpdated Workflow Hook
+
+Similar to the `promotionsCreated` hook, you'll consume the [promotionsUpdated](/references/medusa-workflows/updatePromotionsWorkflow#promotionsUpdated) hook of the [updatePromotionsWorkflow](/references/medusa-workflows/updatePromotionsWorkflow) to update `custom_name` when the promotion is updated.
+
+The `updatePromotionsWorkflow` is executed by the [Update Promotion API route](!api!/admin#promotions_postpromotionsid), which accepts the `additional_data` parameter to pass custom data to the hook.
+
+### Add custom_name to Additional Data Validation
+
+To allow passing `custom_name` in the `additional_data` parameter of the update promotion route, add in `src/api/middlewares.ts` a new route middleware configuration object:
+
+```ts title="src/api/middlewares.ts"
+import { defineMiddlewares } from "@medusajs/medusa"
+import { z } from "zod"
+
+export default defineMiddlewares({
+ routes: [
+ // ...
+ {
+ method: "POST",
+ matcher: "/admin/promotions/:id",
+ additionalDataValidator: {
+ custom_name: z.string().nullish(),
+ },
+ },
+ ],
+})
+```
+
+The validation schema is the similar to that of the Create Promotion API route, except you can pass a `null` value for `custom_name` to remove or unset the `custom_name`'s value.
+
+### Create Workflow to Update Custom Record
+
+Next, you'll create a workflow that creates, updates, or deletes `Custom` records based on the provided `additional_data` parameter:
+
+1. If `additional_data.custom_name` is set and it's `null`, the `Custom` record linked to the promotion is deleted.
+2. If `additional_data.custom_name` is set and the promotion doesn't have a linked `Custom` record, a new record is created and linked to the promotion.
+3. If `additional_data.custom_name` is set and the promotion has a linked `Custom` record, the `custom_name` property of the `Custom` record is updated.
+
+Start by creating the step that updates a `Custom` record. Create the file `src/workflows/update-custom-from-promotion/steps/update-custom.ts` with the following content:
+
+```ts title="src/workflows/update-custom-from-promotion/steps/update-custom.ts"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { HELLO_MODULE } from "../../../modules/hello"
+import HelloModuleService from "../../../modules/hello/service"
+
+type UpdateCustomStepInput = {
+ id: string
+ custom_name: string
+}
+
+export const updateCustomStep = createStep(
+ "update-custom",
+ async ({ id, custom_name }: UpdateCustomStepInput, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ const prevData = await helloModuleService.retrieveCustom(id)
+
+ const custom = await helloModuleService.updateCustoms({
+ id,
+ custom_name,
+ })
+
+ return new StepResponse(custom, prevData)
+ },
+ async (prevData, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.updateCustoms(prevData)
+ }
+)
+```
+
+In this step, you update a `Custom` record. In the compensation function, you revert the update.
+
+Next, you'll create the step that deletes a `Custom` record. Create the file `src/workflows/update-custom-from-promotion/steps/delete-custom.ts` with the following content:
+
+```ts title="src/workflows/update-custom-from-promotion/steps/delete-custom.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
+import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
+import { Custom } from "../../../modules/hello/models/custom"
+import { InferTypeOf } from "@medusajs/framework/types"
+import HelloModuleService from "../../../modules/hello/service"
+import { HELLO_MODULE } from "../../../modules/hello"
+
+type DeleteCustomStepInput = {
+ custom: InferTypeOf
+}
+
+export const deleteCustomStep = createStep(
+ "delete-custom",
+ async ({ custom }: DeleteCustomStepInput, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.deleteCustoms(custom.id)
+
+ return new StepResponse(custom, custom)
+ },
+ async (custom, { container }) => {
+ const helloModuleService: HelloModuleService = container.resolve(
+ HELLO_MODULE
+ )
+
+ await helloModuleService.createCustoms(custom)
+ }
+)
+```
+
+In this step, you delete a `Custom` record. In the compensation function, you create it again.
+
+Finally, you'll create the workflow. Create the file `src/workflows/update-custom-from-promotion/index.ts` with the following content:
+
+```ts title="src/workflows/update-custom-from-promotion/index.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
+import { PromotionDTO } from "@medusajs/framework/types"
+import { createWorkflow, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
+import { createRemoteLinkStep, dismissRemoteLinkStep, useRemoteQueryStep } from "@medusajs/medusa/core-flows"
+import { createCustomStep } from "../create-custom-from-cart/steps/create-custom"
+import { Modules } from "@medusajs/framework/utils"
+import { HELLO_MODULE } from "../../modules/hello"
+import { deleteCustomStep } from "./steps/delete-custom"
+import { updateCustomStep } from "./steps/update-custom"
+
+export type UpdateCustomFromPromotionStepInput = {
+ promotion: PromotionDTO
+ additional_data?: {
+ custom_name?: string | null
+ }
+}
+
+export const updateCustomFromPromotionWorkflow = createWorkflow(
+ "update-custom-from-promotion",
+ (input: UpdateCustomFromPromotionStepInput) => {
+ const promotionData = useRemoteQueryStep({
+ entry_point: "promotion",
+ fields: ["custom.*"],
+ variables: {
+ filters: {
+ id: input.promotion.id
+ }
+ },
+ list: false
+ })
+
+ // TODO create, update, or delete Custom record
+ }
+)
+```
+
+The workflow accepts the same input as the `promotionsUpdated` workflow hook handler would.
+
+In the workflow, you retrieve the promotion's linked `Custom` record using Query.
+
+Next, replace the `TODO` with the following:
+
+```ts title="src/workflows/update-custom-from-promotion/index.ts"
+const created = when({
+ input,
+ promotionData
+}, (data) =>
+ !data.promotionData.custom &&
+ data.input.additional_data?.custom_name?.length > 0
+)
+.then(() => {
+ const custom = createCustomStep({
+ custom_name: input.additional_data.custom_name
+ })
+
+ createRemoteLinkStep([{
+ [Modules.PROMOTION]: {
+ promotion_id: input.promotion.id
+ },
+ [HELLO_MODULE]: {
+ custom_id: custom.id
+ }
+ }])
+
+ return custom
+})
+
+// TODO update, or delete Custom record
+```
+
+Using the `when-then` utility, you check if the promotion doesn't have a linked `Custom` record and the `custom_name` property is set. If so, you create a `Custom` record and link it to the promotion.
+
+To create the `Custom` record, you use the `createCustomStep` you created in an earlier section.
+
+Next, replace the new `TODO` with the following:
+
+```ts title="src/workflows/update-custom-from-promotion/index.ts"
+const deleted = when({
+ input,
+ promotionData
+}, (data) =>
+ data.promotionData.custom && (
+ data.input.additional_data?.custom_name === null ||
+ data.input.additional_data?.custom_name.length === 0
+ )
+)
+.then(() => {
+ deleteCustomStep({
+ custom: promotionData.custom
+ })
+
+ dismissRemoteLinkStep({
+ [HELLO_MODULE]: {
+ custom_id: promotionData.custom.id
+ }
+ })
+
+ return promotionData.custom.id
+})
+
+// TODO delete Custom record
+```
+
+Using the `when-then` utility, you check if the promotion has a linked `Custom` record and `custom_name` is `null` or an empty string. If so, you delete the linked `Custom` record and dismiss its links.
+
+Finally, replace the new `TODO` with the following:
+
+```ts title="src/workflows/update-custom-from-promotion/index.ts"
+const updated = when({
+ input,
+ promotionData
+}, (data) => data.promotionData.custom && data.input.additional_data?.custom_name?.length > 0)
+.then(() => {
+ const custom = updateCustomStep({
+ id: promotionData.custom.id,
+ custom_name: input.additional_data.custom_name
+ })
+
+ return custom
+})
+
+return new WorkflowResponse({
+ created,
+ updated,
+ deleted
+})
+```
+
+Using the `when-then` utility, you check if the promotion has a linked `Custom` record and `custom_name` is passed in the `additional_data`. If so, you update the linked `Custom` recod.
+
+You return in the workflow response the created, updated, and deleted `Custom` record.
+
+### Consume promotionsUpdated Workflow Hook
+
+You can now consume the `promotionsUpdated` and execute the workflow you created.
+
+Create the file `src/workflows/hooks/promotion-updated.ts` with the following content:
+
+```ts title="src/workflows/hooks/promotion-updated.ts"
+import { updatePromotionsWorkflow } from "@medusajs/medusa/core-flows"
+import {
+ UpdateCustomFromPromotionStepInput,
+ updateCustomFromPromotionWorkflow
+} from "../update-custom-from-promotion"
+
+updatePromotionsWorkflow.hooks.promotionsUpdated(
+ async ({ promotions, additional_data }, { container }) => {
+ const workflow = updateCustomFromPromotionWorkflow(container)
+
+ for (let promotion of promotions) {
+ await workflow.run({
+ input: {
+ promotion,
+ additional_data
+ } as UpdateCustomFromPromotionStepInput
+ })
+ }
+ }
+)
+```
+
+In the workflow hook handler, you execute the workflow, passing it the hook's input.
+
+### Test it Out
+
+To test it out, send a `POST` request to `/admin/promotions/:id` to update a promotion, passing `custom_name` in `additional_data`:
+
+```bash
+curl -X POST 'localhost:9000/admin/promotions/{promotion_id}?fields=+custom.*' \
+-H 'Content-Type: application/json' \
+-H 'Authorization: Bearer {token}' \
+--data '{
+ "additional_data": {
+ "custom_name": "test 2"
+ }
+}'
+```
+
+Make sure to replace `{promotion_id}` with the promotion's ID, and `{token}` with the JWT token of an admin user.
+
+The request will return the promotion's details with the updated `custom` linked record.
diff --git a/www/apps/resources/app/commerce-modules/promotion/relations-to-other-modules/page.mdx b/www/apps/resources/app/commerce-modules/promotion/links-to-other-modules/page.mdx
similarity index 65%
rename from www/apps/resources/app/commerce-modules/promotion/relations-to-other-modules/page.mdx
rename to www/apps/resources/app/commerce-modules/promotion/links-to-other-modules/page.mdx
index 836f1c4986..c81834fbee 100644
--- a/www/apps/resources/app/commerce-modules/promotion/relations-to-other-modules/page.mdx
+++ b/www/apps/resources/app/commerce-modules/promotion/links-to-other-modules/page.mdx
@@ -1,14 +1,14 @@
export const metadata = {
- title: `Relations between Promotion Module and Other Modules`,
+ title: `Links between Promotion Module and Other Modules`,
}
# {metadata.title}
-This document showcases the link modules defined between the Promotion Module and other commerce modules.
+This document showcases the module links defined between the Promotion Module and other commerce modules.
## Cart Module
-A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link module that builds a relationship between the `Cart`, `LineItemAdjustment`, and `Promotion` data models.
+A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link between the `Cart`, `LineItemAdjustment`, and `Promotion` data models.

@@ -16,6 +16,6 @@ A promotion can be applied on line items and shipping methods of a cart. Medusa
## Order Module
-An order is associated with the promotion applied on it. Medusa defines a link module that builds a relationship between the `Order` and `Promotion` data models.
+An order is associated with the promotion applied on it. Medusa defines a link between the `Order` and `Promotion` data models.

\ No newline at end of file
diff --git a/www/apps/resources/app/commerce-modules/promotion/page.mdx b/www/apps/resources/app/commerce-modules/promotion/page.mdx
index 607c447897..fdee4fcbfa 100644
--- a/www/apps/resources/app/commerce-modules/promotion/page.mdx
+++ b/www/apps/resources/app/commerce-modules/promotion/page.mdx
@@ -6,7 +6,7 @@ export const metadata = {
# {metadata.title}
-The Promotion Module is the `@medusajs/medusa/promotion` NPM package that provides promotion-related features in your Medusa and Node.js applications.
+The Promotion Module provides promotion-related features in your Medusa and Node.js applications.
## How to Use the Promotion Module's Service
@@ -15,18 +15,33 @@ You can use the Promotion Module's main service by resolving from the Medusa con
For example:
+
+
+```ts title="src/workflows/hello-world/step1.ts"
+import { createStep } from "@medusajs/framework/workflows-sdk"
+import { Modules } from "@medusajs/framework/utils"
+
+const step1 = createStep("step-1", async (_, { container }) => {
+ const promotionModuleService = container.resolve(
+ Modules.PROMOTION
+ )
+
+ const promotions = await promotionModuleService.listPromotions()
+})
+```
+
+
- ```ts title="src/api/store/custom/route.ts"
- import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
- import { IPromotionModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts title="src/api/store/custom/route.ts"
+import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
+import { Modules } from "@medusajs/framework/utils"
export async function GET(
request: MedusaRequest,
res: MedusaResponse
): Promise {
- const promotionModuleService: IPromotionModuleService = request.scope.resolve(
+ const promotionModuleService = request.scope.resolve(
Modules.PROMOTION
)
@@ -39,35 +54,17 @@ export async function GET(
- ```ts title="src/subscribers/custom-handler.ts"
- import { SubscriberArgs } from "@medusajs/framework"
- import { IPromotionModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
+```ts title="src/subscribers/custom-handler.ts"
+import { SubscriberArgs } from "@medusajs/framework"
+import { Modules } from "@medusajs/framework/utils"
export default async function subscriberHandler({ container }: SubscriberArgs) {
- const promotionModuleService: IPromotionModuleService = container.resolve(
+ const promotionModuleService = container.resolve(
Modules.PROMOTION
)
const promotions = await promotionModuleService.listPromotions()
}
-```
-
-
-
-
- ```ts title="src/workflows/hello-world/step1.ts"
- import { createStep } from "@medusajs/framework/workflows-sdk"
- import { IPromotionModuleService } from "@medusajs/framework/types"
- import { Modules } from "@medusajs/framework/utils"
-
-const step1 = createStep("step-1", async (_, { container }) => {
- const promotionModuleService: IPromotionModuleService = container.resolve(
- Modules.PROMOTION
- )
-
- const promotions = await promotionModuleService.listPromotions()
-})
```
@@ -98,7 +95,9 @@ const promotion = await promotionModuleService.createPromotions({
### Flexible Promotion Rules
-A promotion has rules that restricts when it's applied. For example, you can create a promotion that's only applied to VIP customers.
+A promotion has rules that restricts when it's applied.
+
+For example, you can create a promotion that's only applied to VIP customers.
```ts
const promotion = await promotionModuleService.createPromotions({
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index 4eb5ed9653..2b92c1526d 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -60,29 +60,26 @@ export const generatedEditDates = {
"app/commerce-modules/payment/page.mdx": "2024-10-09T10:39:37.362Z",
"app/commerce-modules/pricing/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/pricing/_events/page.mdx": "2024-07-03T19:27:13+03:00",
- "app/commerce-modules/pricing/concepts/page.mdx": "2024-07-01T16:34:13+00:00",
- "app/commerce-modules/pricing/examples/page.mdx": "2024-09-30T08:43:53.164Z",
- "app/commerce-modules/pricing/price-calculation/page.mdx": "2024-07-26T10:09:41+03:00",
- "app/commerce-modules/pricing/price-rules/page.mdx": "2024-07-01T16:34:13+00:00",
- "app/commerce-modules/pricing/relations-to-other-modules/page.mdx": "2024-05-29T11:08:06+00:00",
- "app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx": "2024-07-18T19:03:37+02:00",
- "app/commerce-modules/pricing/page.mdx": "2024-09-30T08:43:53.164Z",
+ "app/commerce-modules/pricing/concepts/page.mdx": "2024-10-09T13:37:25.678Z",
+ "app/commerce-modules/pricing/examples/page.mdx": "2024-10-09T13:32:48.501Z",
+ "app/commerce-modules/pricing/price-calculation/page.mdx": "2024-10-09T13:43:14.038Z",
+ "app/commerce-modules/pricing/price-rules/page.mdx": "2024-10-09T13:38:47.112Z",
+ "app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx": "2024-10-09T13:48:23.261Z",
+ "app/commerce-modules/pricing/page.mdx": "2024-10-09T13:26:26.401Z",
"app/commerce-modules/product/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/product/_events/page.mdx": "2024-07-03T19:27:13+03:00",
- "app/commerce-modules/product/examples/page.mdx": "2024-09-30T08:43:53.164Z",
- "app/commerce-modules/product/guides/price/page.mdx": "2024-09-30T08:43:53.165Z",
- "app/commerce-modules/product/guides/price-with-taxes/page.mdx": "2024-09-30T08:43:53.165Z",
- "app/commerce-modules/product/relations-to-other-modules/page.mdx": "2024-06-26T07:55:59+00:00",
- "app/commerce-modules/product/page.mdx": "2024-09-30T08:43:53.165Z",
+ "app/commerce-modules/product/examples/page.mdx": "2024-10-09T13:59:32.887Z",
+ "app/commerce-modules/product/guides/price/page.mdx": "2024-10-09T14:02:24.737Z",
+ "app/commerce-modules/product/guides/price-with-taxes/page.mdx": "2024-10-09T14:04:20.900Z",
+ "app/commerce-modules/product/page.mdx": "2024-10-09T13:59:11.554Z",
"app/commerce-modules/promotion/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/promotion/_events/page.mdx": "2024-07-03T19:27:13+03:00",
- "app/commerce-modules/promotion/actions/page.mdx": "2024-06-26T07:55:59+00:00",
+ "app/commerce-modules/promotion/actions/page.mdx": "2024-10-09T14:49:01.645Z",
"app/commerce-modules/promotion/application-method/page.mdx": "2024-06-26T07:55:59+00:00",
"app/commerce-modules/promotion/campaign/page.mdx": "2024-05-29T11:08:06+00:00",
- "app/commerce-modules/promotion/concepts/page.mdx": "2024-06-26T07:55:59+00:00",
- "app/commerce-modules/promotion/examples/page.mdx": "2024-09-30T08:43:53.166Z",
- "app/commerce-modules/promotion/relations-to-other-modules/page.mdx": "2024-05-29T11:08:06+00:00",
- "app/commerce-modules/promotion/page.mdx": "2024-09-30T08:43:53.166Z",
+ "app/commerce-modules/promotion/concepts/page.mdx": "2024-10-09T14:50:50.255Z",
+ "app/commerce-modules/promotion/examples/page.mdx": "2024-10-09T14:46:47.191Z",
+ "app/commerce-modules/promotion/page.mdx": "2024-10-09T14:46:26.982Z",
"app/commerce-modules/region/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/region/_events/page.mdx": "2024-07-03T19:27:13+03:00",
"app/commerce-modules/region/examples/page.mdx": "2024-09-30T08:43:53.166Z",
@@ -2229,6 +2226,16 @@ export const generatedEditDates = {
"app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2024-10-08T08:05:36.596Z",
"app/commerce-modules/cart/extend/page.mdx": "2024-10-08T11:22:22.523Z",
"app/commerce-modules/cart/links-to-other-modules/page.mdx": "2024-10-08T08:22:35.190Z",
+ "app/commerce-modules/auth/reset-password/page.mdx": "2024-09-25T09:36:26.592Z",
+ "app/storefront-development/customers/reset-password/page.mdx": "2024-09-25T10:21:46.647Z",
+ "app/commerce-modules/customer/extend/page.mdx": "2024-10-09T14:43:37.836Z",
+ "app/commerce-modules/fulfillment/links-to-other-modules/page.mdx": "2024-10-08T14:58:24.935Z",
+ "app/commerce-modules/inventory/links-to-other-modules/page.mdx": "2024-10-08T15:18:30.109Z",
+ "app/commerce-modules/pricing/links-to-other-modules/page.mdx": "2024-10-09T13:51:49.986Z",
+ "app/commerce-modules/product/extend/page.mdx": "2024-10-09T14:43:54.303Z",
+ "app/commerce-modules/product/links-to-other-modules/page.mdx": "2024-10-09T14:14:09.401Z",
+ "app/commerce-modules/promotion/extend/page.mdx": "2024-10-09T15:17:01.513Z",
+ "app/commerce-modules/promotion/links-to-other-modules/page.mdx": "2024-10-09T14:51:37.194Z",
"app/commerce-modules/order/edit/page.mdx": "2024-10-09T08:50:05.334Z",
"app/commerce-modules/order/links-to-other-modules/page.mdx": "2024-10-09T11:23:05.488Z",
"app/commerce-modules/order/order-change/page.mdx": "2024-10-09T09:59:40.745Z",
diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs
index 526821c7ab..528b834ace 100644
--- a/www/apps/resources/generated/files-map.mjs
+++ b/www/apps/resources/generated/files-map.mjs
@@ -387,6 +387,10 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/commerce-modules/pricing/examples/page.mdx",
"pathname": "/commerce-modules/pricing/examples"
},
+ {
+ "filePath": "/www/apps/resources/app/commerce-modules/pricing/links-to-other-modules/page.mdx",
+ "pathname": "/commerce-modules/pricing/links-to-other-modules"
+ },
{
"filePath": "/www/apps/resources/app/commerce-modules/pricing/page.mdx",
"pathname": "/commerce-modules/pricing"
@@ -399,10 +403,6 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/commerce-modules/pricing/price-rules/page.mdx",
"pathname": "/commerce-modules/pricing/price-rules"
},
- {
- "filePath": "/www/apps/resources/app/commerce-modules/pricing/relations-to-other-modules/page.mdx",
- "pathname": "/commerce-modules/pricing/relations-to-other-modules"
- },
{
"filePath": "/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx",
"pathname": "/commerce-modules/pricing/tax-inclusive-pricing"
@@ -411,6 +411,10 @@ export const filesMap = [
"filePath": "/www/apps/resources/app/commerce-modules/product/examples/page.mdx",
"pathname": "/commerce-modules/product/examples"
},
+ {
+ "filePath": "/www/apps/resources/app/commerce-modules/product/extend/page.mdx",
+ "pathname": "/commerce-modules/product/extend"
+ },
{
"filePath": "/www/apps/resources/app/commerce-modules/product/guides/price/page.mdx",
"pathname": "/commerce-modules/product/guides/price"
@@ -420,12 +424,12 @@ export const filesMap = [
"pathname": "/commerce-modules/product/guides/price-with-taxes"
},
{
- "filePath": "/www/apps/resources/app/commerce-modules/product/page.mdx",
- "pathname": "/commerce-modules/product"
+ "filePath": "/www/apps/resources/app/commerce-modules/product/links-to-other-modules/page.mdx",
+ "pathname": "/commerce-modules/product/links-to-other-modules"
},
{
- "filePath": "/www/apps/resources/app/commerce-modules/product/relations-to-other-modules/page.mdx",
- "pathname": "/commerce-modules/product/relations-to-other-modules"
+ "filePath": "/www/apps/resources/app/commerce-modules/product/page.mdx",
+ "pathname": "/commerce-modules/product"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/promotion/actions/page.mdx",
@@ -448,12 +452,16 @@ export const filesMap = [
"pathname": "/commerce-modules/promotion/examples"
},
{
- "filePath": "/www/apps/resources/app/commerce-modules/promotion/page.mdx",
- "pathname": "/commerce-modules/promotion"
+ "filePath": "/www/apps/resources/app/commerce-modules/promotion/extend/page.mdx",
+ "pathname": "/commerce-modules/promotion/extend"
},
{
- "filePath": "/www/apps/resources/app/commerce-modules/promotion/relations-to-other-modules/page.mdx",
- "pathname": "/commerce-modules/promotion/relations-to-other-modules"
+ "filePath": "/www/apps/resources/app/commerce-modules/promotion/links-to-other-modules/page.mdx",
+ "pathname": "/commerce-modules/promotion/links-to-other-modules"
+ },
+ {
+ "filePath": "/www/apps/resources/app/commerce-modules/promotion/page.mdx",
+ "pathname": "/commerce-modules/promotion"
},
{
"filePath": "/www/apps/resources/app/commerce-modules/region/examples/page.mdx",
diff --git a/www/apps/resources/generated/sidebar.mjs b/www/apps/resources/generated/sidebar.mjs
index 8593be8bce..94464b7f76 100644
--- a/www/apps/resources/generated/sidebar.mjs
+++ b/www/apps/resources/generated/sidebar.mjs
@@ -4637,8 +4637,8 @@ export const generatedSidebar = [
"loaded": true,
"isPathHref": true,
"type": "link",
- "path": "/commerce-modules/pricing/relations-to-other-modules",
- "title": "Relation to Modules",
+ "path": "/commerce-modules/pricing/links-to-other-modules",
+ "title": "Links to Other Modules",
"children": []
}
]
@@ -5117,6 +5117,14 @@ export const generatedSidebar = [
"title": "Examples",
"children": []
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/commerce-modules/product/extend",
+ "title": "Extend Module",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
@@ -5127,8 +5135,8 @@ export const generatedSidebar = [
"loaded": true,
"isPathHref": true,
"type": "link",
- "path": "/commerce-modules/product/relations-to-other-modules",
- "title": "Relation to Modules",
+ "path": "/commerce-modules/product/links-to-other-modules",
+ "title": "Links to Other Modules",
"children": []
}
]
@@ -5145,7 +5153,7 @@ export const generatedSidebar = [
"isPathHref": true,
"type": "link",
"path": "/commerce-modules/product/guides/price",
- "title": "Get Product Variant Prices",
+ "title": "Get Variant Prices",
"children": []
},
{
@@ -5864,6 +5872,14 @@ export const generatedSidebar = [
"title": "Examples",
"children": []
},
+ {
+ "loaded": true,
+ "isPathHref": true,
+ "type": "link",
+ "path": "/commerce-modules/promotion/extend",
+ "title": "Extend Module",
+ "children": []
+ },
{
"loaded": true,
"isPathHref": true,
@@ -5906,8 +5922,8 @@ export const generatedSidebar = [
"loaded": true,
"isPathHref": true,
"type": "link",
- "path": "/commerce-modules/promotion/relations-to-other-modules",
- "title": "Relation to Modules",
+ "path": "/commerce-modules/promotion/links-to-other-modules",
+ "title": "Links to Modules",
"children": []
}
]
diff --git a/www/apps/resources/next.config.mjs b/www/apps/resources/next.config.mjs
index a01c1b11db..0949f0067a 100644
--- a/www/apps/resources/next.config.mjs
+++ b/www/apps/resources/next.config.mjs
@@ -74,6 +74,21 @@ const nextConfig = {
destination: "/commerce-modules/inventory/links-to-other-modules",
permanent: true,
},
+ {
+ source: "/commerce-modules/pricing/relations-to-other-modules",
+ destination: "/commerce-modules/pricing/links-to-other-modules",
+ permanent: true,
+ },
+ {
+ source: "/commerce-modules/product/relations-to-other-modules",
+ destination: "/commerce-modules/product/links-to-other-modules",
+ permanent: true,
+ },
+ {
+ source: "/commerce-modules/promotion/relations-to-other-modules",
+ destination: "/commerce-modules/promotion/links-to-other-modules",
+ permanent: true,
+ },
]
},
// Redirects shouldn't be necessary anymore since we have remark / rehype
diff --git a/www/apps/resources/sidebar.mjs b/www/apps/resources/sidebar.mjs
index 95b36e3bd4..699051adbc 100644
--- a/www/apps/resources/sidebar.mjs
+++ b/www/apps/resources/sidebar.mjs
@@ -851,8 +851,8 @@ export const sidebar = sidebarAttachHrefCommonOptions([
},
{
type: "link",
- path: "/commerce-modules/pricing/relations-to-other-modules",
- title: "Relation to Modules",
+ path: "/commerce-modules/pricing/links-to-other-modules",
+ title: "Links to Other Modules",
},
],
},
@@ -909,14 +909,19 @@ export const sidebar = sidebarAttachHrefCommonOptions([
path: "/commerce-modules/product/examples",
title: "Examples",
},
+ {
+ type: "link",
+ path: "/commerce-modules/product/extend",
+ title: "Extend Module",
+ },
{
type: "sub-category",
title: "Concepts",
children: [
{
type: "link",
- path: "/commerce-modules/product/relations-to-other-modules",
- title: "Relation to Modules",
+ path: "/commerce-modules/product/links-to-other-modules",
+ title: "Links to Other Modules",
},
],
},
@@ -978,6 +983,11 @@ export const sidebar = sidebarAttachHrefCommonOptions([
path: "/commerce-modules/promotion/examples",
title: "Examples",
},
+ {
+ type: "link",
+ path: "/commerce-modules/promotion/extend",
+ title: "Extend Module",
+ },
{
type: "sub-category",
title: "Concepts",
@@ -1004,8 +1014,8 @@ export const sidebar = sidebarAttachHrefCommonOptions([
},
{
type: "link",
- path: "/commerce-modules/promotion/relations-to-other-modules",
- title: "Relation to Modules",
+ path: "/commerce-modules/promotion/links-to-other-modules",
+ title: "Links to Modules",
},
],
},