docs: add notes + missing links for user guide (#11621)
* docs: add notes + missing links for user guide * fix build errors * fixes
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
import { AcademicCapSolid, BoltSolid } from "@medusajs/icons"
|
||||
import { BetaBadge } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Commerce Automation Recipe`,
|
||||
}
|
||||
|
||||
# {metadata.title} <BetaBadge text="Soon" tooltipText="This recipe is a work in progress." />
|
||||
# {metadata.title}
|
||||
|
||||
This recipe provides the general steps to implement commerce automation with Medusa.
|
||||
|
||||
@@ -36,11 +35,11 @@ Customer support is essential to build a store's brand and customer loyalty. Thi
|
||||
|
||||
You can use the Notification Module to send notifications when an action is triggered, such as when a customer or their order is updated.
|
||||
|
||||
{/* <Note title="Tip">
|
||||
<Note title="Tip">
|
||||
|
||||
The [Events reference](../../events-reference/page.mdx) shows an extensive list of events triggered by the each commerce module.
|
||||
The [Events reference](../../events-reference/page.mdx) shows an extensive list of events triggered for each commerce module.
|
||||
|
||||
</Note> */}
|
||||
</Note>
|
||||
|
||||
Medusa also provides Notification Module Providers that integrate with third-party services, such as SendGrid.
|
||||
|
||||
@@ -77,211 +76,6 @@ To implement that:
|
||||
icon={AcademicCapSolid}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example: Synchronizing products with a third-party service">
|
||||
|
||||
For example, create the file `src/workflows/sync-products.ts` with the following content:
|
||||
|
||||
export const syncProductsWorkflowHighlight = [
|
||||
["20", "retrieveStoreStep", "A step that retrieves the store by its ID."],
|
||||
["36", "retrieveProductsToUpdateStep", "A step that retrieves the products to update based on a last update date."],
|
||||
["56", "syncProductsStep", "A step to sync the product with a third-party service."],
|
||||
["59", "productSyncModuleService", "Assuming this is a custom module's main service that provides connection to the third-party service."],
|
||||
["63", "productsBeforeSync", "Retrieve old product data from third-party service for compensation function."],
|
||||
["68", "sync", "Sync the product data in the third-party service."],
|
||||
["72", "", "Pass products data before sync to compensation function."],
|
||||
["75", "", "A compensation function to revert the sync when an error occurs."],
|
||||
["81", "sync", "Revert the product's data in the third-party service to its old data before the synchronization."],
|
||||
["90", "updateStoreLastSyncStep", "A step to update the `last_sync_data` of the store."],
|
||||
["96", "prevLastSyncDate", "Retrieve the previous value of `last_sync_date` to pass it to compensation function."],
|
||||
["98", "update", "Update the `last_sync_date` of the store."],
|
||||
["106", "", "Pass previous last sync date to compensation function."],
|
||||
["109", "", "A compensation function to revert the update of `last_sync_data` if an error occurs."],
|
||||
["125", "syncProductsWorkflow", "Define the workflow that uses the above steps."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight} collapsibleLines="1-16" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
IProductModuleService,
|
||||
IStoreModuleService,
|
||||
ProductDTO,
|
||||
StoreDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
StepResponse,
|
||||
createStep,
|
||||
createWorkflow,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
type RetrieveStoreStepInput = {
|
||||
id: string
|
||||
}
|
||||
|
||||
const retrieveStoreStep = createStep(
|
||||
"retrieve-store-step",
|
||||
async ({ id }: RetrieveStoreStepInput, { container }) => {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(Modules.STORE)
|
||||
|
||||
const store = await storeModuleService.retrieveStore(id)
|
||||
|
||||
return new StepResponse({ store })
|
||||
}
|
||||
)
|
||||
|
||||
type RetrieveProductsToUpdateStepInput = {
|
||||
last_sync_date?: string
|
||||
}
|
||||
|
||||
const retrieveProductsToUpdateStep = createStep(
|
||||
"retrieve-products-to-update-step",
|
||||
async ({ last_sync_date }: RetrieveProductsToUpdateStepInput, { container }) => {
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(Modules.PRODUCT)
|
||||
|
||||
const products = await productModuleService.listProducts({
|
||||
updated_at: {
|
||||
$gt: last_sync_date,
|
||||
},
|
||||
})
|
||||
|
||||
return new StepResponse({ products })
|
||||
}
|
||||
)
|
||||
|
||||
type SyncProductsStepInput = {
|
||||
products: ProductDTO[]
|
||||
}
|
||||
|
||||
const syncProductsStep = createStep(
|
||||
"sync-products-step",
|
||||
async ({ products }: SyncProductsStepInput, { container }) => {
|
||||
const productSyncModuleService = container.resolve(
|
||||
"productSyncModuleService"
|
||||
)
|
||||
|
||||
const productsBeforeSync = await productSyncModuleService.listProductSyncs({
|
||||
id: products.map((product) => product.id),
|
||||
})
|
||||
|
||||
for (const product of products) {
|
||||
await productSyncModuleService.sync(product)
|
||||
}
|
||||
|
||||
return new StepResponse({}, {
|
||||
products: productsBeforeSync,
|
||||
})
|
||||
},
|
||||
async ({ products }, { container }) => {
|
||||
const productSyncModuleService = container.resolve(
|
||||
"productSyncModuleService"
|
||||
)
|
||||
|
||||
for (const product of products) {
|
||||
await productSyncModuleService.sync(product)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
type UpdateStoreLastSyncStepInput = {
|
||||
store: StoreDTO
|
||||
}
|
||||
|
||||
const updateStoreLastSyncStep = createStep(
|
||||
"update-store-last-sync-step",
|
||||
async ({ store }: UpdateStoreLastSyncStepInput, { container }) => {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(Modules.STORE)
|
||||
|
||||
const prevLastSyncDate = store.metadata.last_sync_date
|
||||
|
||||
await storeModuleService.updateStores(store.id, {
|
||||
metadata: {
|
||||
last_sync_date: (new Date()).toString(),
|
||||
},
|
||||
})
|
||||
|
||||
return new StepResponse({}, {
|
||||
id: store.id,
|
||||
last_sync_date: prevLastSyncDate,
|
||||
})
|
||||
},
|
||||
async ({ id, last_sync_date }, { container }) => {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(Modules.STORE)
|
||||
|
||||
await storeModuleService.updateStores(id, {
|
||||
metadata: {
|
||||
last_sync_date,
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
type SyncProductsWorkflowInput = {
|
||||
store_id: string
|
||||
}
|
||||
|
||||
export const syncProductsWorkflow = createWorkflow(
|
||||
"sync-products-workflow",
|
||||
function ({ store_id }: SyncProductsWorkflowInput) {
|
||||
const { store } = retrieveStoreStep({
|
||||
id: store_id,
|
||||
})
|
||||
|
||||
const { products } = retrieveProductsToUpdateStep({
|
||||
last_sync_date: store.metadata.last_sync_date,
|
||||
})
|
||||
|
||||
syncProductsStep({
|
||||
products,
|
||||
})
|
||||
|
||||
updateStoreLastSyncStep({
|
||||
store,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This creates a workflow with the following steps:
|
||||
|
||||
1. Retrieve the store by its ID.
|
||||
2. Retrieve products to update based on the last date and time the products were synced. The last sync date is retrieved from the store's `metadata` property.
|
||||
3. Sync the retrieved products with a third-party service. It's assumed that the connection to the third-party service is implemented within a custom module's main service.
|
||||
4. Update the last sync date of the store to the current date.
|
||||
|
||||
Then, create a scheduled job at `src/jobs/sync-products.ts` that executes the workflow at the specified interval:
|
||||
|
||||
```ts
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import {
|
||||
syncProductsWorkflow,
|
||||
} from "../workflows/sync-products"
|
||||
|
||||
export default async function syncProductsJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
await syncProductsWorkflow(container)
|
||||
.run({
|
||||
input: {
|
||||
name: "John",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "sync-products",
|
||||
// execute every minute
|
||||
schedule: "0 0 * * *",
|
||||
numberOfExecutions: 3,
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
## Order Management Automation
|
||||
@@ -290,20 +84,7 @@ Using Medusa's architecture and commerce features, you can automate a large amou
|
||||
|
||||
To handle events within an order flow and automate actions, create a subscriber. For example, create a subscriber that listens to the `order.placed` event and automatically creates a fulfillment if predefined conditions are met.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
The `order.placed` event is currently not emitted.
|
||||
|
||||
</Note>
|
||||
|
||||
<Card
|
||||
href="!docs!/learn/fundamentals/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
icon={AcademicCapSolid}
|
||||
/>
|
||||
|
||||
{/* <CardList items={[
|
||||
<CardList items={[
|
||||
{
|
||||
href: "!docs!/learn/fundamentals/events-and-subscribers",
|
||||
title: "Create a Subscriber",
|
||||
@@ -316,7 +97,7 @@ The `order.placed` event is currently not emitted.
|
||||
text: "Check out triggered events by each commerce module.",
|
||||
icon: AcademicCapSolid,
|
||||
},
|
||||
]} /> */}
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
@@ -361,12 +142,6 @@ For example, to group customers with over twenty orders:
|
||||
1. Create a subscriber that listens to the `order.placed` event.
|
||||
2. If the customer has more than 20 orders, add them to the VIP customer group.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
The `order.placed` event is currently not emitted.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "/commerce-modules/customer",
|
||||
@@ -386,91 +161,14 @@ The `order.placed` event is currently not emitted.
|
||||
text: "Learn how to create a subscriber in Medusa.",
|
||||
icon: AcademicCapSolid,
|
||||
},
|
||||
{
|
||||
href: "/events-reference",
|
||||
title: "Events Reference",
|
||||
text: "Check out triggered events by each commerce module.",
|
||||
icon: AcademicCapSolid,
|
||||
},
|
||||
]} />
|
||||
|
||||
<Details summaryContent="Example: Add customer to VIP group">
|
||||
|
||||
Here’s an example of a subscriber that listens to the `order.placed` event and checks whether the customer should be added to the VIP customer group based on their number of orders:
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
The `order.placed` event is currently not emitted.
|
||||
|
||||
</Note>
|
||||
|
||||
```ts title="src/subscribers/add-custom-to-vip.ts" collapsibleLines="1-12" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/framework"
|
||||
import {
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
ICustomerModuleService,
|
||||
IOrderModuleService,
|
||||
} from "@medusajs/framework/types"
|
||||
|
||||
export default async function orderCreatedHandler({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const orderId = data.id
|
||||
|
||||
const orderModuleService: IOrderModuleService = container
|
||||
.resolve(Modules.ORDER)
|
||||
|
||||
const customerModuleService:
|
||||
ICustomerModuleService = container.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
// check if VIP group exists
|
||||
const vipGroup = await customerModuleService
|
||||
.listCustomerGroups({
|
||||
name: "VIP",
|
||||
}, {
|
||||
relations: ["customers"],
|
||||
})
|
||||
|
||||
if (!vipGroup.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve the order
|
||||
const order = await orderModuleService.retrieveOrder(orderId)
|
||||
|
||||
if (!order ||
|
||||
!order.customer_id ||
|
||||
vipGroup[0].customers.find(
|
||||
(customer) => customer.id === order.customer_id
|
||||
) !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const [, count] = await orderModuleService.listAndCountOrders({
|
||||
customer_id: order.customer_id,
|
||||
})
|
||||
|
||||
if (count < 20) {
|
||||
return
|
||||
}
|
||||
|
||||
// add customer to VIP group
|
||||
await customerModuleService.addCustomerToGroup({
|
||||
customer_group_id: vipGroup[0].id,
|
||||
customer_id: order.customer_id,
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "order.placed",
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Marketing Automation
|
||||
@@ -495,99 +193,3 @@ You can also create a scheduled job that checks whether the number of new produc
|
||||
icon: AcademicCapSolid,
|
||||
},
|
||||
]} />
|
||||
|
||||
<Details summaryContent="Example: Sending a newsletter email after adding ten products">
|
||||
|
||||
For example, create the file `src/subscribers/send-products-newsletter.ts` with the following content:
|
||||
|
||||
export const newsletterHighlights = [
|
||||
["32", "store", "Retrieve the first store in the application."],
|
||||
["34", "products", "Retrieve the products created since the last newsletter send date."],
|
||||
["40", "", "Check whether more than 10 products have been created before proceeding."],
|
||||
["44", "customers", "Retrieve all customers, assuming they're considered subscribed."],
|
||||
["46", "createNotifications", "Send a notification (newsletter) to each customer using the Notification Module."],
|
||||
["49", '"email"', "Send the notification through the email channel."],
|
||||
["50", '"newsletter_template"', "Specify the template name in the third-party service (for example, SendGrid)."],
|
||||
["52", "products", "Pass the created products to the template."],
|
||||
["57", "updateStores", "Update the store's `last_newsletter_send_date` property with the current date."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/send-products-newsletter.ts" highlights={newsletterHighlights} collapsibleLines="1-14" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/framework"
|
||||
import {
|
||||
Modules,
|
||||
} from "@medusajs/framework/utils"
|
||||
import {
|
||||
ICustomerModuleService,
|
||||
IProductModuleService,
|
||||
IStoreModuleService,
|
||||
INotificationModuleService,
|
||||
} from "@medusajs/framework/types"
|
||||
|
||||
export default async function productCreateHandler({
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(Modules.PRODUCT)
|
||||
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(Modules.STORE)
|
||||
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
container.resolve(Modules.CUSTOMER)
|
||||
|
||||
const notificationModuleService:
|
||||
INotificationModuleService = container.resolve(
|
||||
Modules.NOTIFICATION
|
||||
)
|
||||
|
||||
const store = (await storeModuleService.listStores())[0]
|
||||
|
||||
const products = await productModuleService.listProducts({
|
||||
created_at: {
|
||||
$gt: store.metadata.last_newsletter_send_date,
|
||||
},
|
||||
})
|
||||
|
||||
if (products.length < 10) {
|
||||
return
|
||||
}
|
||||
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
|
||||
await notificationModuleService.createNotifications(
|
||||
customers.map((customer) => ({
|
||||
to: customer.email,
|
||||
channel: "email",
|
||||
template: "newsletter_template",
|
||||
data: {
|
||||
products,
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
await storeModuleService.updateStores(store.id, {
|
||||
metadata: {
|
||||
last_newsletter_send_date: (new Date()).toString(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "product.created",
|
||||
}
|
||||
```
|
||||
|
||||
In the subscriber function, you:
|
||||
|
||||
1. Retrieve the first store in our application.
|
||||
2. Retrieve products created since the last time a newsletter is sent. The last send date is stored in the store's `metadata` property.
|
||||
3. If the count of last created products is less than 10, stop execution.
|
||||
4. Retrieve all customers. Here, it's assumed that all customers are considered subscribed for simplicity.
|
||||
5. Use the Notification Module to send a notification to the customer. This uses the Notification Module Provider configured for the `email` channel.
|
||||
6. Update the store's `last_newsletter_send_date` with the current date.
|
||||
|
||||
</Details>
|
||||
|
||||
Reference in New Issue
Block a user