docs: added restock notification guide (#10413)
* docs: added restock notification guide * changes * lint fixes * fixes * more changes * remove get email step * add og image * update sendRestockNotificationsWorkflow * updates * fix links
This commit is contained in:
1 parent
4b384eaf05
commit
e41ab50f59
12 files changed
+1554
-406
No files matched your search
@@ -182,14 +182,14 @@ export const serviceHighlights1 = [
|
|||||||
|
|
||||||
```ts title="src/modules/resend/service.ts" highlights={serviceHighlights1}
|
```ts title="src/modules/resend/service.ts" highlights={serviceHighlights1}
|
||||||
import {
|
import {
|
||||||
AbstractNotificationProviderService
|
AbstractNotificationProviderService,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import {
|
import {
|
||||||
Logger
|
Logger,
|
||||||
} from "@medusajs/framework/types";
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
Resend
|
Resend,
|
||||||
} from "resend";
|
} from "resend"
|
||||||
|
|
||||||
type ResendOptions = {
|
type ResendOptions = {
|
||||||
api_key: string
|
api_key: string
|
||||||
@@ -263,7 +263,7 @@ So, add to the `ResendNotificationProviderService` the `validateOptions` method:
|
|||||||
// other imports...
|
// other imports...
|
||||||
import {
|
import {
|
||||||
// other imports...
|
// other imports...
|
||||||
MedusaError
|
MedusaError,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
@@ -397,7 +397,7 @@ class ResendNotificationProviderService extends AbstractNotificationProviderServ
|
|||||||
from: this.options.from,
|
from: this.options.from,
|
||||||
to: [notification.to],
|
to: [notification.to],
|
||||||
subject: this.getTemplateSubject(notification.template as Templates),
|
subject: this.getTemplateSubject(notification.template as Templates),
|
||||||
html: ""
|
html: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof template === "string") {
|
if (typeof template === "string") {
|
||||||
@@ -440,7 +440,7 @@ Create the file `src/modules/resend/index.ts` with the following content:
|
|||||||
```ts title="src/modules/resend/index.ts"
|
```ts title="src/modules/resend/index.ts"
|
||||||
import {
|
import {
|
||||||
ModuleProvider,
|
ModuleProvider,
|
||||||
Modules
|
Modules,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import ResendNotificationProviderService from "./service"
|
import ResendNotificationProviderService from "./service"
|
||||||
|
|
||||||
@@ -562,7 +562,7 @@ function OrderPlacedEmailComponent({ order }: OrderPlacedEmailProps) {
|
|||||||
<Heading>Thank you for your order</Heading>
|
<Heading>Thank you for your order</Heading>
|
||||||
{order.email}'s Items
|
{order.email}'s Items
|
||||||
<Container>
|
<Container>
|
||||||
{order.items.map(item => {
|
{order.items.map((item) => {
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -606,10 +606,10 @@ Next, update the `templates` variable in `src/modules/resend/service.ts` to assi
|
|||||||
|
|
||||||
```ts title="src/modules/resend/service.ts"
|
```ts title="src/modules/resend/service.ts"
|
||||||
// other imports...
|
// other imports...
|
||||||
import { orderPlacedEmail } from "./emails/order-placed";
|
import { orderPlacedEmail } from "./emails/order-placed"
|
||||||
|
|
||||||
const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = {
|
const templates: {[key in Templates]?: (props: unknown) => React.ReactNode} = {
|
||||||
[Templates.ORDER_PLACED]: orderPlacedEmail
|
[Templates.ORDER_PLACED]: orderPlacedEmail,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -696,10 +696,10 @@ export const workflowHighlights = [
|
|||||||
```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights}
|
```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights}
|
||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
WorkflowResponse
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk";
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows";
|
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||||
import { sendNotificationStep } from "./steps/send-notification";
|
import { sendNotificationStep } from "./steps/send-notification"
|
||||||
|
|
||||||
type WorkflowInput = {
|
type WorkflowInput = {
|
||||||
id: string
|
id: string
|
||||||
@@ -719,8 +719,8 @@ export const sendOrderConfirmationWorkflow = createWorkflow(
|
|||||||
"items.*",
|
"items.*",
|
||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
id
|
id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const notification = sendNotificationStep([{
|
const notification = sendNotificationStep([{
|
||||||
@@ -728,8 +728,8 @@ export const sendOrderConfirmationWorkflow = createWorkflow(
|
|||||||
channel: "email",
|
channel: "email",
|
||||||
template: "order-placed",
|
template: "order-placed",
|
||||||
data: {
|
data: {
|
||||||
order: orders[0]
|
order: orders[0],
|
||||||
}
|
},
|
||||||
}])
|
}])
|
||||||
|
|
||||||
return new WorkflowResponse(notification)
|
return new WorkflowResponse(notification)
|
||||||
@@ -787,8 +787,8 @@ export default async function orderPlacedHandler({
|
|||||||
await sendOrderConfirmationWorkflow(container)
|
await sendOrderConfirmationWorkflow(container)
|
||||||
.run({
|
.run({
|
||||||
input: {
|
input: {
|
||||||
id: data.id
|
id: data.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,371 +19,14 @@ Medusa provides the necessary architecture and tools to implement commerce autom
|
|||||||
|
|
||||||
## Re-Stock Notifications
|
## Re-Stock Notifications
|
||||||
|
|
||||||
Customers may be interested in a product that is currently out of stock.
|
Customers may be interested in a product that is currently out of stock. The following guide explains how to add restock notifications in your Medusa application:
|
||||||
|
|
||||||
To implement sending restock notifications, you can:
|
<Card
|
||||||
|
href="/recipes/commerce-automation/restock-notification"
|
||||||
- Create a module that manages the customers subscribed to a variant's restock notification.
|
title="Restock Notification Guide"
|
||||||
- Create relationships to the Product and Sales Channel modules. A variant's inventory is managed by the sales channel's associated stock locations.
|
text="Learn how to implement restock notifications in the Medusa application."
|
||||||
- Create an API route that allows customers to subscribe to a variant's restock notification.
|
icon={AcademicCapSolid}
|
||||||
- Create a subscriber that listens to the `inventory-item.updated` event and sends a notification to the subscribed customers if the variant's quantity is more than `0`.
|
/>
|
||||||
|
|
||||||
<Note type="soon">
|
|
||||||
|
|
||||||
The `inventory-item.updated` event is currently not emitted.
|
|
||||||
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
<CardList items={[
|
|
||||||
{
|
|
||||||
href: "!docs!/learn/fundamentals/modules",
|
|
||||||
title: "Create a Module",
|
|
||||||
text: "Learn how to create a module in Medusa.",
|
|
||||||
icon: AcademicCapSolid,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "!docs!/learn/fundamentals/modules#1-create-data-model",
|
|
||||||
title: "Create a Data Model",
|
|
||||||
text: "Learn how to create a data model.",
|
|
||||||
icon: AcademicCapSolid,
|
|
||||||
},
|
|
||||||
]} />
|
|
||||||
|
|
||||||
<CardList items={[
|
|
||||||
{
|
|
||||||
href: "!docs!/learn/fundamentals/api-routes",
|
|
||||||
title: "Create an API Route",
|
|
||||||
text: "Learn how to create an API route in Medusa.",
|
|
||||||
icon: AcademicCapSolid,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "!docs!/learn/fundamentals/events-and-subscribers",
|
|
||||||
title: "Create a Subscriber",
|
|
||||||
text: "Learn how to create a subscriber in Medusa.",
|
|
||||||
icon: AcademicCapSolid,
|
|
||||||
},
|
|
||||||
]} className="mt-1" />
|
|
||||||
|
|
||||||
<Details summaryContent="Example">
|
|
||||||
|
|
||||||
In this example, you'll create a Restock Notification Module with the features explained above.
|
|
||||||
|
|
||||||
### Create Restock Notification Module
|
|
||||||
|
|
||||||
Start by creating the `src/modules/restock-notification` directory.
|
|
||||||
|
|
||||||
Then, create the file `src/modules/restock-notification/models/restock-notification.ts` with the following content:
|
|
||||||
|
|
||||||
export const restockModelHighlights = [
|
|
||||||
["5", "email", "The email of the customer to send the notification to when the item is restocked."],
|
|
||||||
["6", "variant_id", "The ID of the variant the customer is subscribed to."],
|
|
||||||
["7", "sales_channel_id", "The ID of the sales channel the customer is viewing the product variant from."]
|
|
||||||
]
|
|
||||||
|
|
||||||
```ts title="src/modules/restock-notification/models/restock-notification.ts" highlights={restockModelHighlights}
|
|
||||||
import { model } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
const RestockNotification = model.define("restock_notification", {
|
|
||||||
id: model.id().primaryKey(),
|
|
||||||
email: model.text(),
|
|
||||||
variant_id: model.text(),
|
|
||||||
sales_channel_id: model.text(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export default RestockNotification
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a `RestockNotification` data model with the following properties:
|
|
||||||
|
|
||||||
- `id`: An automatically generated ID.
|
|
||||||
- `email`: The email of the customer to send the notification to when the item is restocked.
|
|
||||||
- `variant_id`: The ID of the variant the customer is subscribed to. This will later be used to form a relationship with the `ProductVariant` data model of the Product Module.
|
|
||||||
- `sales_channel_id`: The ID of the sales channel the customer is viewing the product variant from. This will later be used to form a relationship with the `SalesChannel` data model of the Sales Channel Module.
|
|
||||||
|
|
||||||
Since a variant's inventory is managed based on the locations of each sales channel, you have to specify which sales channel to check stock quantity in.
|
|
||||||
|
|
||||||
Next, create the file `src/modules/restock-notification/migrations/Migration20240516140616.ts` with the following content:
|
|
||||||
|
|
||||||
```ts title="src/modules/restock-notification/migrations/Migration20240516140616.ts"
|
|
||||||
import { Migration } from "@mikro-orm/migrations"
|
|
||||||
|
|
||||||
export class Migration20240516140616 extends Migration {
|
|
||||||
|
|
||||||
async up(): Promise<void> {
|
|
||||||
this.addSql("create table if not exists \"restock_notification\" (\"id\" text not null, \"email\" text not null, \"variant_id\" text not null, \"sales_channel_id\" text not null, constraint \"restock_notification_pkey\" primary key (\"id\"));")
|
|
||||||
}
|
|
||||||
|
|
||||||
async down(): Promise<void> {
|
|
||||||
this.addSql("drop table if exists \"restock_notification\" cascade;")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll run the migration to reflect the changes on the database after finishing the module's definition.
|
|
||||||
|
|
||||||
Then, create the module's main service at `src/modules/restock-notification/service.ts` with the following content:
|
|
||||||
|
|
||||||
```ts title="src/modules/restock-notification/service.ts"
|
|
||||||
import { MedusaService } from "@medusajs/framework/utils"
|
|
||||||
import RestockNotification from "./models/restock-notification"
|
|
||||||
|
|
||||||
class RestockNotificationModuleService extends MedusaService({
|
|
||||||
RestockNotification,
|
|
||||||
}){
|
|
||||||
// TODO add custom methods
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RestockNotificationModuleService
|
|
||||||
```
|
|
||||||
|
|
||||||
The module's main service extends the service factory which generates basic management features for the `RestockNotification` data model.
|
|
||||||
|
|
||||||
Next, create the module's definition file `src/modules/restock-notification/index.ts` with the following content:
|
|
||||||
|
|
||||||
```ts title="src/modules/restock-notification/index.ts"
|
|
||||||
import RestockNotificationModuleService from "./service"
|
|
||||||
import { Module } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export default Module("restock-notification", {
|
|
||||||
service: RestockNotificationModuleService,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, add the module to the `modules` object in `medusa-config.ts`:
|
|
||||||
|
|
||||||
```ts title="medusa-config.ts"
|
|
||||||
module.exports = defineConfig({
|
|
||||||
// ...
|
|
||||||
modules: [
|
|
||||||
{
|
|
||||||
resolve: "./src/modules/restock-notification",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now run the migrations with the following command:
|
|
||||||
|
|
||||||
```bash npm2yarn
|
|
||||||
npx medusa db:migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create Restock Notification API Route
|
|
||||||
|
|
||||||
Create the file `src/api/store/restock-notification/route.ts` with the following content:
|
|
||||||
|
|
||||||
```ts title="src/api/store/restock-notification/route.ts" collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
|
||||||
import type {
|
|
||||||
MedusaRequest,
|
|
||||||
MedusaResponse,
|
|
||||||
} from "@medusajs/framework/http"
|
|
||||||
import RestockNotificationModuleService
|
|
||||||
from "../../../modules/restock-notification/service"
|
|
||||||
|
|
||||||
type RestockNotificationReq = {
|
|
||||||
email: string
|
|
||||||
variant_id: string
|
|
||||||
sales_channel_id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(
|
|
||||||
req: MedusaRequest<RestockNotificationReq>,
|
|
||||||
res: MedusaResponse
|
|
||||||
) {
|
|
||||||
const restockNotificationModuleService:
|
|
||||||
RestockNotificationModuleService = req.scope.resolve(
|
|
||||||
"restockNotificationModuleService"
|
|
||||||
)
|
|
||||||
|
|
||||||
await restockNotificationModuleService.createRestockNotifications(
|
|
||||||
req.body
|
|
||||||
)
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a `POST` API route at `/store/restock-notification`. It accepts the `email`, `variant_id`, and `sales_channel_id` request body parameters and creates a restock notification.
|
|
||||||
|
|
||||||
### Create Inventory Item Updated Subscriber
|
|
||||||
|
|
||||||
To handle the sending of the restock notifications, create a subscriber that listens to the `inventory-item.updated` event, then sends a notification using the Notification Module to subscribed emails.
|
|
||||||
|
|
||||||
<Note type="soon">
|
|
||||||
|
|
||||||
The `inventory-item.updated` event is currently not emitted.
|
|
||||||
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
{/* To handle the sending of the restock notifications, create the file `src/subscribers/inventory-item-update.ts` with the following content: */}
|
|
||||||
|
|
||||||
export const subscriberHighlights = [
|
|
||||||
["48", "inventoryVariantLinkService", "Retrieve an instance of the link service for the product-variant-inventory-item link module."],
|
|
||||||
["55", "inventoryVariantItems", "Retrieve the variants linked to the updated inventory item."],
|
|
||||||
["68", "restockQuery", "Assemble the query to retrieve the restock notifications with their associated variants."],
|
|
||||||
["81", "restockNotifications", "Retrieve the restock notifications using the query."],
|
|
||||||
["84", "salesChannelLocationService", "Retrieve an instance of the link service for the sales-channel-stock-location link module."],
|
|
||||||
["93", "salesChannelLocations", "Retrieve the stock locations linked to the restock notification's sales channel."],
|
|
||||||
["107", "availableQuantity", "Retrieve the available quantity of the variant in the retrieved stock locations."],
|
|
||||||
["116", "continue", "Only send the notification if the available quantity is greater than `0`"],
|
|
||||||
["119", "createNotifications", "Send the notification to the customer using the Notification Module."],
|
|
||||||
["122", '"test_template"', "Replace with the actual template used for sending the email."],
|
|
||||||
["123", "data", "The data to send along to the third-party service sending the notification."],
|
|
||||||
["131", "deleteRestockNotifications", "Delete the restock notification to not send the notification again."]
|
|
||||||
]
|
|
||||||
|
|
||||||
{/* ```ts title="src/subscribers/inventory-item-update.ts" highlights={subscriberHighlights} collapsibleLines="1-20" expandButtonLabel="Show Imports"
|
|
||||||
import type {
|
|
||||||
SubscriberArgs,
|
|
||||||
SubscriberConfig,
|
|
||||||
} from "@medusajs/framework"
|
|
||||||
import {
|
|
||||||
IInventoryService,
|
|
||||||
INotificationModuleService,
|
|
||||||
RemoteQueryFunction,
|
|
||||||
} from "@medusajs/framework/types"
|
|
||||||
import {
|
|
||||||
ContainerRegistrationKeys,
|
|
||||||
Modules,
|
|
||||||
remoteQueryObjectFromString,
|
|
||||||
} from "@medusajs/framework/utils"
|
|
||||||
import {
|
|
||||||
RemoteLink,
|
|
||||||
} from "@medusajs/framework/modules-sdk"
|
|
||||||
import RestockNotificationModuleService
|
|
||||||
from "../modules/restock-notification/service"
|
|
||||||
|
|
||||||
// subscriber function
|
|
||||||
export default async function inventoryItemUpdateHandler({
|
|
||||||
data,
|
|
||||||
container,
|
|
||||||
}: SubscriberArgs<{ id: string }>) {
|
|
||||||
const remoteQuery: RemoteQueryFunction = container.resolve(
|
|
||||||
ContainerRegistrationKeys.REMOTE_QUERY
|
|
||||||
)
|
|
||||||
const remoteLink: RemoteLink = container.resolve(
|
|
||||||
ContainerRegistrationKeys.REMOTE_LINK
|
|
||||||
)
|
|
||||||
const restockNotificationModuleService:
|
|
||||||
RestockNotificationModuleService = container.resolve(
|
|
||||||
"restockNotificationModuleService"
|
|
||||||
)
|
|
||||||
const inventoryModuleService: IInventoryService =
|
|
||||||
container.resolve(Modules.INVENTORY)
|
|
||||||
const notificationModuleService: INotificationModuleService =
|
|
||||||
container.resolve(
|
|
||||||
Modules.NOTIFICATION
|
|
||||||
)
|
|
||||||
|
|
||||||
const inventoryItemId = data.data.id
|
|
||||||
|
|
||||||
const inventoryVariantLinkService = remoteLink.getLinkModule(
|
|
||||||
Modules.PRODUCT,
|
|
||||||
"variant_id",
|
|
||||||
Modules.INVENTORY,
|
|
||||||
"inventory_item_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
const inventoryVariantItems =
|
|
||||||
await inventoryVariantLinkService.list({
|
|
||||||
inventory_item_id: [inventoryItemId],
|
|
||||||
}) as {
|
|
||||||
variant_id: string,
|
|
||||||
inventory_item_id: string
|
|
||||||
}[]
|
|
||||||
|
|
||||||
if (!inventoryVariantItems.length) {
|
|
||||||
console.log("no inventory variant items")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const restockQuery = remoteQueryObjectFromString({
|
|
||||||
entryPoint: "restock_notification",
|
|
||||||
fields: [
|
|
||||||
"email",
|
|
||||||
"variant.name",
|
|
||||||
],
|
|
||||||
variables: {
|
|
||||||
filters: {
|
|
||||||
variant_id: inventoryVariantItems[0].variant_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const restockNotifications =
|
|
||||||
await remoteQuery(restockQuery)
|
|
||||||
|
|
||||||
const salesChannelLocationService = remoteLink.getLinkModule(
|
|
||||||
Modules.SALES_CHANNEL,
|
|
||||||
"sales_channel_id",
|
|
||||||
Modules.STOCK_LOCATION,
|
|
||||||
"stock_location_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const restockNotification of restockNotifications) {
|
|
||||||
const salesChannelLocations =
|
|
||||||
await salesChannelLocationService.list({
|
|
||||||
sales_channel_id: [
|
|
||||||
restockNotification.sales_channel_id,
|
|
||||||
],
|
|
||||||
}) as {
|
|
||||||
stock_location_id: string
|
|
||||||
sales_channel_id: string
|
|
||||||
}[]
|
|
||||||
|
|
||||||
if (!salesChannelLocations.length) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableQuantity = await inventoryModuleService
|
|
||||||
.retrieveAvailableQuantity(
|
|
||||||
inventoryItemId,
|
|
||||||
salesChannelLocations.map(
|
|
||||||
(salesChannelLocation) =>
|
|
||||||
salesChannelLocation.stock_location_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (availableQuantity === 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationModuleService.createNotifications({
|
|
||||||
to: restockNotification.email,
|
|
||||||
channel: "email",
|
|
||||||
template: "test_template",
|
|
||||||
data: {
|
|
||||||
variant_id: restockNotification.variant_id,
|
|
||||||
variant_name: restockNotification.variant.title,
|
|
||||||
// other data...
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// delete the restock notification
|
|
||||||
await restockNotificationModuleService
|
|
||||||
.deleteRestockNotifications(restockNotification.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscriber config
|
|
||||||
export const config: SubscriberConfig = {
|
|
||||||
event: "inventory-item.updated",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This adds a subscriber to the `inventory-item.updated` event. In the subscriber handler function, you:
|
|
||||||
|
|
||||||
- Retrieve an instance of the link service for the product-variant-inventory-item link module.
|
|
||||||
- Retrieve the variants linked to the updated inventory item.
|
|
||||||
- Retrieve the restock notifications of those variants.
|
|
||||||
- For each restock notification, you:
|
|
||||||
- Retrieve its quantity based on the stock location associated with the restock notification's sales channel.
|
|
||||||
- If the quantity is greater than `0`, you send a notification using the Notification Module and delete the restock notification. */}
|
|
||||||
|
|
||||||
</Details>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -458,13 +101,13 @@ export const syncProductsWorkflowHighlight = [
|
|||||||
|
|
||||||
```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight} collapsibleLines="1-16" expandButtonLabel="Show Imports"
|
```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight} collapsibleLines="1-16" expandButtonLabel="Show Imports"
|
||||||
import {
|
import {
|
||||||
Modules
|
Modules,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import {
|
import {
|
||||||
IProductModuleService,
|
IProductModuleService,
|
||||||
IStoreModuleService,
|
IStoreModuleService,
|
||||||
ProductDTO,
|
ProductDTO,
|
||||||
StoreDTO
|
StoreDTO,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import {
|
import {
|
||||||
StepResponse,
|
StepResponse,
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -894,7 +894,7 @@ import {
|
|||||||
import { Modules, promiseAll } from "@medusajs/framework/utils"
|
import { Modules, promiseAll } from "@medusajs/framework/utils"
|
||||||
import {
|
import {
|
||||||
cancelOrderWorkflow,
|
cancelOrderWorkflow,
|
||||||
createOrderWorkflow
|
createOrderWorkflow,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
import MarketplaceModuleService from "../../../../modules/marketplace/service"
|
import MarketplaceModuleService from "../../../../modules/marketplace/service"
|
||||||
import { MARKETPLACE_MODULE } from "../../../../modules/marketplace"
|
import { MARKETPLACE_MODULE } from "../../../../modules/marketplace"
|
||||||
@@ -1010,9 +1010,9 @@ try {
|
|||||||
await promiseAll(
|
await promiseAll(
|
||||||
vendorIds.map(async (vendorId) => {
|
vendorIds.map(async (vendorId) => {
|
||||||
const items = vendorsItems[vendorId]
|
const items = vendorsItems[vendorId]
|
||||||
const vendor = vendors.find(v => v.id === vendorId)!
|
const vendor = vendors.find((v) => v.id === vendorId)!
|
||||||
|
|
||||||
const {result: childOrder} = await createOrderWorkflow(
|
const { result: childOrder } = await createOrderWorkflow(
|
||||||
container
|
container
|
||||||
)
|
)
|
||||||
.run({
|
.run({
|
||||||
@@ -1025,11 +1025,11 @@ try {
|
|||||||
|
|
||||||
linkDefs.push({
|
linkDefs.push({
|
||||||
[MARKETPLACE_MODULE]: {
|
[MARKETPLACE_MODULE]: {
|
||||||
vendor_id: vendor.id
|
vendor_id: vendor.id,
|
||||||
},
|
},
|
||||||
[Modules.ORDER]: {
|
[Modules.ORDER]: {
|
||||||
order_id: childOrder.id
|
order_id: childOrder.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -1037,7 +1037,7 @@ try {
|
|||||||
return StepResponse.permanentFailure(
|
return StepResponse.permanentFailure(
|
||||||
`An error occured while creating vendor orders: ${e}`,
|
`An error occured while creating vendor orders: ${e}`,
|
||||||
{
|
{
|
||||||
created_orders: createdOrders
|
created_orders: createdOrders,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1174,7 +1174,7 @@ const createVendorOrdersWorkflow = createWorkflow(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { vendorsItems } = groupVendorItemsStep({
|
const { vendorsItems } = groupVendorItemsStep({
|
||||||
cart: carts[0]
|
cart: carts[0],
|
||||||
})
|
})
|
||||||
|
|
||||||
const order = getOrderDetailWorkflow.runAsStep({
|
const order = getOrderDetailWorkflow.runAsStep({
|
||||||
|
|||||||
+2
-2
@@ -29,7 +29,7 @@ export const fetchHighlights = [
|
|||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
fields: "category_children.id,category_children.name",
|
fields: "category_children.id,category_children.name",
|
||||||
include_descendants_tree: true,
|
include_descendants_tree: true,
|
||||||
parent_category_id: null
|
parent_category_id: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
fetch(`http://localhost:9000/store/product-categories/${id}?${
|
fetch(`http://localhost:9000/store/product-categories/${id}?${
|
||||||
@@ -84,7 +84,7 @@ export const highlights = [
|
|||||||
|
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
fields: "category_children.id,category_children.name",
|
fields: "category_children.id,category_children.name",
|
||||||
parent_category_id: null
|
parent_category_id: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
fetch(`http://localhost:9000/store/product-categories/${id}?${
|
fetch(`http://localhost:9000/store/product-categories/${id}?${
|
||||||
|
|||||||
@@ -5659,5 +5659,6 @@ export const generatedEditDates = {
|
|||||||
"references/workflows/classes/workflows.WorkflowResponse/page.mdx": "2024-12-09T13:22:04.820Z",
|
"references/workflows/classes/workflows.WorkflowResponse/page.mdx": "2024-12-09T13:22:04.820Z",
|
||||||
"references/workflows/interfaces/workflows.ApplyStepOptions/page.mdx": "2024-12-09T13:22:04.808Z",
|
"references/workflows/interfaces/workflows.ApplyStepOptions/page.mdx": "2024-12-09T13:22:04.808Z",
|
||||||
"references/workflows/types/workflows.WorkflowData/page.mdx": "2024-12-09T13:22:04.836Z",
|
"references/workflows/types/workflows.WorkflowData/page.mdx": "2024-12-09T13:22:04.836Z",
|
||||||
"app/integrations/guides/resend/page.mdx": "2024-12-09T16:19:17.798Z"
|
"app/integrations/guides/resend/page.mdx": "2024-12-09T16:19:17.798Z",
|
||||||
|
"app/recipes/commerce-automation/restock-notification/page.mdx": "2024-12-10T14:15:39.178Z"
|
||||||
}
|
}
|
||||||
@@ -667,6 +667,10 @@ export const filesMap = [
|
|||||||
"filePath": "/www/apps/resources/app/recipes/commerce-automation/page.mdx",
|
"filePath": "/www/apps/resources/app/recipes/commerce-automation/page.mdx",
|
||||||
"pathname": "/recipes/commerce-automation"
|
"pathname": "/recipes/commerce-automation"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"filePath": "/www/apps/resources/app/recipes/commerce-automation/restock-notification/page.mdx",
|
||||||
|
"pathname": "/recipes/commerce-automation/restock-notification"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filePath": "/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx",
|
"filePath": "/www/apps/resources/app/recipes/digital-products/examples/standard/page.mdx",
|
||||||
"pathname": "/recipes/digital-products/examples/standard"
|
"pathname": "/recipes/digital-products/examples/standard"
|
||||||
|
|||||||
@@ -96,7 +96,16 @@ export const generatedSidebar = [
|
|||||||
"type": "link",
|
"type": "link",
|
||||||
"path": "/recipes/commerce-automation",
|
"path": "/recipes/commerce-automation",
|
||||||
"title": "Commerce Automation",
|
"title": "Commerce Automation",
|
||||||
"children": []
|
"children": [
|
||||||
|
{
|
||||||
|
"loaded": true,
|
||||||
|
"isPathHref": true,
|
||||||
|
"type": "link",
|
||||||
|
"path": "/recipes/commerce-automation/restock-notification",
|
||||||
|
"title": "Example: Restock Notifications",
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"loaded": true,
|
"loaded": true,
|
||||||
|
|||||||
@@ -68,6 +68,13 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
|||||||
type: "link",
|
type: "link",
|
||||||
path: "/recipes/commerce-automation",
|
path: "/recipes/commerce-automation",
|
||||||
title: "Commerce Automation",
|
title: "Commerce Automation",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: "link",
|
||||||
|
path: "/recipes/commerce-automation/restock-notification",
|
||||||
|
title: "Example: Restock Notifications",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "link",
|
type: "link",
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const WorkflowDiagramCanvasDepth = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<div className="flex flex-col justify-center gap-y-docs_0.5">
|
<div className="flex flex-col justify-center gap-y-docs_0.5">
|
||||||
{cluster.map((step) => (
|
{cluster.map((step, index) => (
|
||||||
<WorkflowDiagramStepNode key={step.name} step={step} />
|
<WorkflowDiagramStepNode key={`${step.name}-${index}`} step={step} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<WorkflowDiagramLine step={next} />
|
<WorkflowDiagramLine step={next} />
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const WorkflowDiagramDepth = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<div className="flex flex-col justify-center gap-y-docs_0.5">
|
<div className="flex flex-col justify-center gap-y-docs_0.5">
|
||||||
{cluster.map((step) => (
|
{cluster.map((step, index) => (
|
||||||
<WorkflowDiagramStepNode key={step.name} step={step} />
|
<WorkflowDiagramStepNode key={`${step.name}-${index}`} step={step} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<WorkflowDiagramLine step={next} />
|
<WorkflowDiagramLine step={next} />
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const WorkflowDiagramListDepth = ({
|
|||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<WorkflowDiagramLine step={cluster} />
|
<WorkflowDiagramLine step={cluster} />
|
||||||
<div className="flex flex-col justify-center gap-y-docs_0.5">
|
<div className="flex flex-col justify-center gap-y-docs_0.5">
|
||||||
{cluster.map((step) => (
|
{cluster.map((step, index) => (
|
||||||
<WorkflowDiagramStepNode key={step.name} step={step} />
|
<WorkflowDiagramStepNode key={`${step.name}-${index}`} step={step} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user