docs: updates to use DML and other changes (#7834)
- Change existing data model guides and add new ones for DML - Change module's docs around service factory + remove guides that are now necessary - Hide/remove all mentions of module relationships, or label them as coming soon. - Change all data model creation snippets to use DML - use `property` instead of `field` when referring to a data model's properties. - Fix all snippets in commerce module guides to use new method suffix (no more main model methods) - Rework recipes, removing/hiding a lot of sections as a lot of recipes are incomplete with the current state of DML. ### Other changes - Highlight fixes in some guides - Remove feature flags guide - Fix code block styles when there are no line numbers. ### Upcoming changes in other PRs - Re-generate commerce module references (for the updates in the method names) - Ensure that the data model references are generated correctly for models using DML. - (probably at a very later point) revisit recipes
This commit is contained in:
@@ -8,6 +8,12 @@ export const metadata = {
|
||||
|
||||
This recipe provides the general steps to implement a B2B store with Medusa.
|
||||
|
||||
<Note type="soon" title="In Development">
|
||||
|
||||
This recipe is a work in progress, as some features are not ready yet in Medusa V2.
|
||||
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
In a B2B store, you provide different types of customers with relevant pricing, products, shopping experience, and more.
|
||||
@@ -180,6 +186,12 @@ This is useful in B2B sales, as you often negotiate special prices with each cus
|
||||
|
||||
You can create a B2B module that adds necessary data models to represent a B2B company. Then, you link that company to a customer group. Any customer belonging to that group also belongs to the company, meaning they're a B2B customer.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "!docs!/basics/modules-and-services",
|
||||
@@ -197,50 +209,37 @@ You can create a B2B module that adds necessary data models to represent a B2B c
|
||||
},
|
||||
]} />
|
||||
|
||||
<Card
|
||||
{/* <Card
|
||||
href="!docs!/advanced-development/modules/module-relationships"
|
||||
title="Create Module Relationships"
|
||||
text="Learn how to create a relationship between modules."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
className="mt-1"
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
In this section, you'll create a B2B module that has a `Company` data model. The `Company` data model has a relationship to the `CustomerGroup` data model of the Customer Module.
|
||||
|
||||
Start by creating the `src/modules/b2b` directory.
|
||||
|
||||
Then, create the file `src/modules/b2b/models/company.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/b2b/models/company.ts" highlights={[["23", "", "Field will be used to create a relationship to customer groups."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import {
|
||||
Entity,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
```ts title="src/modules/b2b/models/company.ts" highlights={[["8", "", "The property will be used to create a relationship to customer groups."]]}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
@Entity()
|
||||
export class Company extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
const Company = model.define("company", {
|
||||
id: model.id(),
|
||||
name: model.text(),
|
||||
city: model.text(),
|
||||
country_code: model.text(),
|
||||
customer_group_id: model.text().nullable(),
|
||||
})
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
city: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
country_code: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
customer_group_id?: string
|
||||
}
|
||||
export default Company
|
||||
```
|
||||
|
||||
This creates a `Company` data model with some relevant fields. Most importantly, it has a `customer_group_id` field. It'll later be used when creating the relationship to the `CustomerGroup` data model in the Customer Module.
|
||||
This creates a `Company` data model with some relevant properties. Most importantly, it has a `customer_group_id` property. It'll later be used when creating the relationship to the `CustomerGroup` data model in the Customer Module.
|
||||
|
||||
Next, create the migration in the file `src/modules/b2b/migrations/Migration20240516081502.ts` with the following content:
|
||||
|
||||
@@ -261,106 +260,22 @@ You can create a B2B module that adds necessary data models to represent a B2B c
|
||||
|
||||
You'll run the migration to reflect the data model in the database after finishing the module definition.
|
||||
|
||||
Before creating the module's main service, create the file `src/types/b2b/index.ts` with some helper types:
|
||||
|
||||
```ts title="src/types/b2b/index.ts"
|
||||
import { CustomerGroupDTO } from "@medusajs/types"
|
||||
|
||||
export type CompanyDTO = {
|
||||
id: string
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
customer_group?: CustomerGroupDTO
|
||||
}
|
||||
|
||||
export type CreateCompanyDTO = {
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can now create the module's main service at `src/modules/b2b/service.ts` with the following content:
|
||||
|
||||
export const mainServiceHighlights = [
|
||||
["42", "relationships", "Implement the relationship to the `CustomerGroup` data model in the Customer Module."],
|
||||
["56", "create", "Implement a create method to create a company."]
|
||||
]
|
||||
Then, create the module's main service at `src/modules/b2b/service.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/b2b/service.ts" highlights={mainServiceHighlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { ModuleJoinerConfig, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { Company } from "./models/company"
|
||||
import { CompanyDTO, CreateCompanyDTO } from "../../types/b2b"
|
||||
|
||||
type InjectedDependencies = {
|
||||
companyService: ModulesSdkTypes.InternalModuleService<any>
|
||||
```ts title="src/modules/b2b/service.ts"
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import Company from "./models/company"
|
||||
|
||||
class B2bModuleService extends MedusaService({
|
||||
Company,
|
||||
}){
|
||||
// TODO add custom methods
|
||||
}
|
||||
|
||||
type AllModelsDTO = {
|
||||
Company: {
|
||||
dto: CompanyDTO
|
||||
}
|
||||
}
|
||||
|
||||
class B2bModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
CompanyDTO,
|
||||
AllModelsDTO
|
||||
>(Company, []) {
|
||||
companyService_: ModulesSdkTypes.InternalModuleService<Company>
|
||||
|
||||
constructor({ companyService }: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.companyService_ = companyService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
serviceName: "b2bModuleService",
|
||||
alias: [
|
||||
{
|
||||
name: ["company"],
|
||||
args: {
|
||||
entity: Company.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.CUSTOMER,
|
||||
alias: "customer_group",
|
||||
primaryKey: "id",
|
||||
foreignKey: "customer_group_id",
|
||||
args: {
|
||||
methodSuffix: "CustomerGroups",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async create(data: CreateCompanyDTO): Promise<CompanyDTO> {
|
||||
const company = this.companyService_.create(data)
|
||||
|
||||
return company
|
||||
}
|
||||
}
|
||||
|
||||
export default B2bModuleService
|
||||
```
|
||||
|
||||
This creates a `B2bModuleService` that extends the service factory and implements:
|
||||
|
||||
- The module's relationship to the `CustomerGroup` data model in the Customer Module within the `__joinerConfig` method.
|
||||
- A `create` method to create a company.
|
||||
This creates a `B2bModuleService` that extends the service factory, which generates data-management functionalities for the `Company` data model.
|
||||
|
||||
Next, create the module definition at `src/modules/b2b/index.ts` with the following content:
|
||||
|
||||
@@ -398,7 +313,30 @@ export const mainServiceHighlights = [
|
||||
|
||||
To test out using the B2B Module, you'll add an API route to create a company.
|
||||
|
||||
Start by creating the file `src/workflows/create-company.ts` with the following content:
|
||||
Start by creating the file `src/types/b2b/index.ts` with some helper types:
|
||||
|
||||
```ts title="src/types/b2b/index.ts"
|
||||
import { CustomerGroupDTO } from "@medusajs/types"
|
||||
|
||||
export type CompanyDTO = {
|
||||
id: string
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
customer_group?: CustomerGroupDTO
|
||||
}
|
||||
|
||||
export type CreateCompanyDTO = {
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Then, create the file `src/workflows/create-company.ts` with the following content:
|
||||
|
||||
export const workflowHighlights = [
|
||||
["23", "tryToCreateCustomerGroupStep", "This step creates the customer group if its data is passed in the `customer_group` property."],
|
||||
@@ -419,7 +357,7 @@ export const workflowHighlights = [
|
||||
import { CreateCustomerGroupDTO } from "@medusajs/types"
|
||||
import { CompanyDTO, CreateCompanyDTO } from "../types/b2b"
|
||||
import B2bModuleService from "../modules/b2b/service"
|
||||
|
||||
|
||||
export type CreateCompanyWorkflowInput = CreateCompanyDTO & {
|
||||
customer_group?: CreateCustomerGroupDTO
|
||||
}
|
||||
@@ -471,7 +409,7 @@ export const workflowHighlights = [
|
||||
"b2bModuleService"
|
||||
)
|
||||
|
||||
const company = await b2bModuleService.create(
|
||||
const company = await b2bModuleService.createCompany(
|
||||
companyData
|
||||
)
|
||||
|
||||
@@ -498,10 +436,10 @@ export const workflowHighlights = [
|
||||
|
||||
You create a workflow with two steps:
|
||||
|
||||
1. The first one tries to create a customer group if its data is provided in the `customer_group` property and sets its value in the `customer_group_id` field.
|
||||
1. The first one tries to create a customer group if its data is provided in the `customer_group` property and sets its value in the `customer_group_id` property.
|
||||
2. The second one creates the company.
|
||||
|
||||
Then, create the file `src/api/admin/b2b/company/route.ts` with the following content:
|
||||
Finally, create the file `src/api/admin/b2b/company/route.ts` with the following content:
|
||||
|
||||
```ts title="src/api/admin/b2b/company/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
@@ -566,7 +504,7 @@ export const workflowHighlights = [
|
||||
|
||||
</Note>
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
## Add B2B Customers
|
||||
|
||||
@@ -687,18 +625,21 @@ The API route can check if the customer has any group with an associated company
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
For example, create the API route `src/api/store/b2b/check-customer/route.ts` with the following content:
|
||||
|
||||
export const checkCustomerHighlights = [
|
||||
["16", "retrieve", "Retrieve the customer along with its groups."],
|
||||
["20", "list", "List the companies that have a customer group ID matching any of the customer's group IDs."],
|
||||
["25", "", "Return whether there are any companies associated with the customer's groups."]
|
||||
["19", "retrieveCustomer", "Retrieve the customer along with its groups."],
|
||||
["26", "listCompanies", "List the companies that have a customer group ID matching any of the customer's group IDs."],
|
||||
["31", "", "Return whether there are any companies associated with the customer's groups."]
|
||||
]
|
||||
|
||||
```ts title="src/api/store/b2b/check-customer/route.ts" highlights={checkCustomerHighlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
|
||||
import type { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import B2bModuleService from "../../../../modules/b2b/service"
|
||||
@@ -713,11 +654,14 @@ export const checkCustomerHighlights = [
|
||||
"b2bModuleService"
|
||||
)
|
||||
|
||||
const customer = await customerModuleService.retrieve(req.auth.actor_id, {
|
||||
relations: ["groups"],
|
||||
})
|
||||
const customer = await customerModuleService.retrieveCustomer(
|
||||
req.auth_context.actor_id,
|
||||
{
|
||||
relations: ["groups"],
|
||||
}
|
||||
)
|
||||
|
||||
const companies = await b2bModuleService.list({
|
||||
const companies = await b2bModuleService.listCompanies({
|
||||
customer_group_id: customer.groups.map((group) => group.id),
|
||||
})
|
||||
|
||||
@@ -736,7 +680,10 @@ export const checkCustomerHighlights = [
|
||||
Before using the API route, create the file `src/api/middlewares.ts` with the following content:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { MiddlewaresConfig, authenticate } from "@medusajs/medusa"
|
||||
import {
|
||||
MiddlewaresConfig,
|
||||
authenticate,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
@@ -796,16 +743,12 @@ export const checkCustomerHighlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
---
|
||||
|
||||
## Customize Admin
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon!
|
||||
|
||||
</Note>
|
||||
|
||||
Based on your use case, you may need to customize the Medusa Admin to add new widgets or pages.
|
||||
|
||||
The Medusa Admin plugin can be extended to add widgets, new pages, and setting pages.
|
||||
@@ -851,9 +794,9 @@ Use the publishable API key you associated with your B2B sales channel in the st
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/storefront-development/tips",
|
||||
title: "Storefront Tips",
|
||||
text: "Find tips on developing a custom storefront.",
|
||||
href: "/storefront-development",
|
||||
title: "Storefront Development",
|
||||
text: "Find guides for your storefront development.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
|
||||
@@ -9,6 +9,12 @@ export const metadata = {
|
||||
|
||||
This recipe provides the general steps to implement a B2B store with Medusa.
|
||||
|
||||
<Note type="soon" title="In Development">
|
||||
|
||||
This recipe is a work in progress, as some features are not ready yet in Medusa V2.
|
||||
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
Commerce automation is essential for businesses to save costs, provide a better user experience, and avoid manual, repetitive tasks that lead to human errors. Businesses utilize automation in different domains, including marketing, customer support, and order management.
|
||||
@@ -49,13 +55,6 @@ The `inventory-item.updated` event is currently not emitted.
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/advanced-development/modules/module-relationships",
|
||||
title: "Module Relationships",
|
||||
text: "Learn how to create module relationships.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
]} />
|
||||
|
||||
<CardList items={[
|
||||
@@ -86,37 +85,27 @@ The `inventory-item.updated` event is currently not emitted.
|
||||
Then, create the file `src/modules/restock-notification/models/restock-notification.ts` with the following content:
|
||||
|
||||
export const restockModelHighlights = [
|
||||
["14", "email", "The email of the customer to send the notification to when the item is restocked."],
|
||||
["17", "variant_id", "The ID of the variant the customer is subscribed to."],
|
||||
["20", "sales_channel_id", "The ID of the sales channel the customer is viewing the product variant from."]
|
||||
["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} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import {
|
||||
Entity,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
export class RestockNotification extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
```ts title="src/modules/restock-notification/models/restock-notification.ts" highlights={restockModelHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
email: string
|
||||
const RestockNotification = model.define("restock_notification", {
|
||||
id: model.id(),
|
||||
email: model.text(),
|
||||
variant_id: model.text(),
|
||||
sales_channel_id: model.text(),
|
||||
})
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
variant_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
sales_channel_id: string
|
||||
}
|
||||
export default RestockNotification
|
||||
```
|
||||
|
||||
This creates a `RestockNotification` data model with the following fields:
|
||||
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.
|
||||
@@ -142,117 +131,23 @@ export const restockModelHighlights = [
|
||||
```
|
||||
|
||||
You'll run the migration to reflect the changes on the database after finishing the module's definition.
|
||||
|
||||
Now, create the file `src/types/restock-notification/index.ts` that holds common types to be used in the module's main service:
|
||||
|
||||
```ts title="src/types/restock-notification/index.ts"
|
||||
import { ProductVariantDTO, SalesChannelDTO } from "@medusajs/types"
|
||||
|
||||
export type RestockNotificationDTO = {
|
||||
id: string
|
||||
email: string
|
||||
variant_id: string
|
||||
sales_channel_id: string
|
||||
variant?: ProductVariantDTO
|
||||
sales_channel?: SalesChannelDTO
|
||||
}
|
||||
|
||||
export type CreateRestockNotificationDTO = {
|
||||
email: string
|
||||
variant_id: string
|
||||
sales_channel_id: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Then, create the module's main service at `src/modules/restock-notification/service.ts` with the following content:
|
||||
|
||||
export const restockModuleService = [
|
||||
["17", "abstractModuleServiceFactory", "Extend the service factory to have basic data management features."],
|
||||
["43", "", "Define a relationship to the `ProductVariant` data model of the Product Module."],
|
||||
["52", "", "Define a relationship to the `SalesChannel` data model of the Sales Channel Module."],
|
||||
["61", "create", "Implement the method to create a restock notification."]
|
||||
]
|
||||
```ts title="src/modules/restock-notification/service.ts"
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import RestockNotification from "./models/restock-notification"
|
||||
|
||||
```ts title="src/modules/restock-notification/service.ts" highlights={restockModuleService} collapsibleLines="1-5" expandButtonLabel="Show Imports"
|
||||
import { Modules, ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { RestockNotification } from "./models/restock-notification"
|
||||
import { ModuleJoinerConfig, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { CreateRestockNotificationDTO, RestockNotificationDTO } from "../../types/restock-notification"
|
||||
|
||||
type InjectedDependencies = {
|
||||
restockNotificationService: ModulesSdkTypes.InternalModuleService<any>
|
||||
class RestockNotificationModuleService extends MedusaService({
|
||||
RestockNotification,
|
||||
}){
|
||||
// TODO add custom methods
|
||||
}
|
||||
|
||||
type AllModelDTOs = {
|
||||
RestockNotification: {
|
||||
dto: RestockNotificationDTO
|
||||
}
|
||||
}
|
||||
|
||||
class RestockNotificationModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
RestockNotificationDTO,
|
||||
AllModelDTOs
|
||||
>(RestockNotification, []) {
|
||||
restockNotificationService_: ModulesSdkTypes.InternalModuleService<RestockNotification>
|
||||
|
||||
constructor({ restockNotificationService }: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.restockNotificationService_ = restockNotificationService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
serviceName: "restockNotificationModuleService",
|
||||
alias: [
|
||||
{
|
||||
name: "restock_notification",
|
||||
args: {
|
||||
entity: RestockNotification.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
alias: "variant",
|
||||
primaryKey: "id",
|
||||
foreignKey: "variant_id",
|
||||
args: {
|
||||
methodSuffix: "Variants",
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
alias: "sales_channel",
|
||||
primaryKey: "id",
|
||||
foreignKey: "sales_channel_id",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async create(data: CreateRestockNotificationDTO): Promise<RestockNotificationDTO> {
|
||||
const restockNotification = await this.restockNotificationService_.create(
|
||||
data
|
||||
)
|
||||
|
||||
return restockNotification
|
||||
}
|
||||
}
|
||||
|
||||
export default RestockNotificationModuleService
|
||||
```
|
||||
|
||||
In the module's main service, you:
|
||||
|
||||
- Extend the service factory to have basic data management features.
|
||||
- Define a relationship to the `ProductVariant` data model of the Product Module.
|
||||
- Define a relationship to the `SalesChannel` data model of the Sales Channel Module.
|
||||
- Implement the `create` method to create a restock notification.
|
||||
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:
|
||||
|
||||
@@ -290,18 +185,19 @@ export const restockModuleService = [
|
||||
|
||||
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-10" expandButtonLabel="Show Imports"
|
||||
```ts title="src/api/store/restock-notification/route.ts" collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import RestockNotificationModuleService
|
||||
from "../../../modules/restock-notification/service"
|
||||
import {
|
||||
CreateRestockNotificationDTO,
|
||||
} from "../../../types/restock-notification"
|
||||
|
||||
type RestockNotificationReq = CreateRestockNotificationDTO
|
||||
|
||||
type RestockNotificationReq = {
|
||||
email: string
|
||||
variant_id: string
|
||||
sales_channel_id: string
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest<RestockNotificationReq>,
|
||||
@@ -312,7 +208,7 @@ export const restockModuleService = [
|
||||
"restockNotificationModuleService"
|
||||
)
|
||||
|
||||
await restockNotificationModuleService.create(
|
||||
await restockNotificationModuleService.createRestockNotifications(
|
||||
req.body
|
||||
)
|
||||
|
||||
@@ -326,13 +222,15 @@ export const restockModuleService = [
|
||||
|
||||
### 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. The subscriber will only work once the event is emitted.
|
||||
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:
|
||||
{/* 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."],
|
||||
@@ -343,19 +241,19 @@ export const subscriberHighlights = [
|
||||
["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", "create", "Send the notification to the customer using the Notification Module."],
|
||||
["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", "delete", "Delete the restock notification to not send the notification again."]
|
||||
["131", "deleteRestockNotifications", "Delete the restock notification to not send the notification again."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/inventory-item-update.ts" highlights={subscriberHighlights} collapsibleLines="1-23" expandButtonLabel="Show Imports"
|
||||
{/* ```ts title="src/subscribers/inventory-item-update.ts" highlights={subscriberHighlights} collapsibleLines="1-20" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
IInventoryServiceNext,
|
||||
IInventoryService,
|
||||
INotificationModuleService,
|
||||
RemoteQueryFunction,
|
||||
} from "@medusajs/types"
|
||||
@@ -364,9 +262,6 @@ export const subscriberHighlights = [
|
||||
Modules,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
RestockNotificationDTO,
|
||||
} from "../types/restock-notification"
|
||||
import {
|
||||
RemoteLink,
|
||||
} from "@medusajs/modules-sdk"
|
||||
@@ -388,7 +283,7 @@ export const subscriberHighlights = [
|
||||
RestockNotificationModuleService = container.resolve(
|
||||
"restockNotificationModuleService"
|
||||
)
|
||||
const inventoryModuleService: IInventoryServiceNext =
|
||||
const inventoryModuleService: IInventoryService =
|
||||
container.resolve(Modules.INVENTORY)
|
||||
const notificationModuleService: INotificationModuleService =
|
||||
container.resolve(
|
||||
@@ -430,7 +325,7 @@ export const subscriberHighlights = [
|
||||
},
|
||||
})
|
||||
|
||||
const restockNotifications: RestockNotificationDTO[] =
|
||||
const restockNotifications =
|
||||
await remoteQuery(restockQuery)
|
||||
|
||||
const salesChannelLocationService = remoteLink.getLinkModule(
|
||||
@@ -468,7 +363,7 @@ export const subscriberHighlights = [
|
||||
continue
|
||||
}
|
||||
|
||||
notificationModuleService.create({
|
||||
notificationModuleService.createNotifications({
|
||||
to: restockNotification.email,
|
||||
channel: "email",
|
||||
template: "test_template",
|
||||
@@ -480,7 +375,8 @@ export const subscriberHighlights = [
|
||||
})
|
||||
|
||||
// delete the restock notification
|
||||
await restockNotificationModuleService.delete(restockNotification.id)
|
||||
await restockNotificationModuleService
|
||||
.deleteRestockNotifications(restockNotification.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +393,7 @@ export const subscriberHighlights = [
|
||||
- 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.
|
||||
- If the quantity is greater than `0`, you send a notification using the Notification Module and delete the restock notification. */}
|
||||
|
||||
</Details>
|
||||
|
||||
@@ -509,11 +405,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.
|
||||
|
||||
</Note>
|
||||
</Note> */}
|
||||
|
||||
Medusa also provides Notification Provider Modules that integrate with third-party services, such as SendGrid.
|
||||
|
||||
@@ -564,28 +460,39 @@ Scheduled jobs are coming soon.
|
||||
For example, create the file `src/workflows/sync-products.ts` with the following content:
|
||||
|
||||
export const syncProductsWorkflowHighlight = [
|
||||
["9", "retrieveStoreStep", "A step that retrieves the store by its ID."],
|
||||
["25", "retrieveProductsToUpdateStep", "A step that retrieves the products to update based on a last update date."],
|
||||
["45", "syncProductsStep", "A step to sync the product with a third-party service."],
|
||||
["48", "productSyncModuleService", "Assuming this is a custom module's main service that provides connection to the third-party service."],
|
||||
["52", "productsBeforeSync", "Retrieve old product data from third-party service for compensation function."],
|
||||
["57", "sync", "Sync the product data in the third-party service."],
|
||||
["61", "", "Pass products data before sync to compensation function."],
|
||||
["64", "", "A compensation function to revert the sync when an error occurs."],
|
||||
["70", "sync", "Revert the product's data in the third-party service to its old data before the synchronization."],
|
||||
["79", "updateStoreLastSyncStep", "A step to update the `last_sync_data` of the store."],
|
||||
["85", "prevLastSyncDate", "Retrieve the previous value of `last_sync_date` to pass it to compensation function."],
|
||||
["87", "update", "Update the `last_sync_date` of the store."],
|
||||
["95", "", "Pass previous last sync date to compensation function."],
|
||||
["98", "", "A compensation function to revert the update of `last_sync_data` if an error occurs."],
|
||||
["114", "syncProductsWorkflow", "Define the workflow that uses the above steps."]
|
||||
["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}
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, IStoreModuleService, ProductDTO, StoreDTO } from "@medusajs/types"
|
||||
import { StepResponse, createStep, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
|
||||
```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight} collapsibleLines="1-16" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
ModuleRegistrationName
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IProductModuleService,
|
||||
IStoreModuleService,
|
||||
ProductDTO,
|
||||
StoreDTO
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
StepResponse,
|
||||
createStep,
|
||||
createWorkflow
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
type RetrieveStoreStepInput = {
|
||||
id: string
|
||||
}
|
||||
@@ -596,7 +503,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.retrieve(id)
|
||||
const store = await storeModuleService.retrieveStore(id)
|
||||
|
||||
return new StepResponse({ store })
|
||||
}
|
||||
@@ -612,7 +519,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.list({
|
||||
const products = await productModuleService.listProducts({
|
||||
updated_at: {
|
||||
$gt: last_sync_date,
|
||||
},
|
||||
@@ -633,7 +540,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
"productSyncModuleService"
|
||||
)
|
||||
|
||||
const productsBeforeSync = await productSyncModuleService.list({
|
||||
const productsBeforeSync = await productSyncModuleService.listProductSyncs({
|
||||
id: products.map((product) => product.id),
|
||||
})
|
||||
|
||||
@@ -668,7 +575,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
|
||||
const prevLastSyncDate = store.metadata.last_sync_date
|
||||
|
||||
await storeModuleService.update(store.id, {
|
||||
await storeModuleService.updateStores(store.id, {
|
||||
metadata: {
|
||||
last_sync_date: (new Date()).toString(),
|
||||
},
|
||||
@@ -683,7 +590,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
await storeModuleService.update(id, {
|
||||
await storeModuleService.updateStores(id, {
|
||||
metadata: {
|
||||
last_sync_date,
|
||||
},
|
||||
@@ -722,7 +629,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
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` field.
|
||||
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.
|
||||
|
||||
@@ -742,7 +649,15 @@ The `order.placed` event is currently not emitted.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
<Card
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
{/* <CardList items={[
|
||||
{
|
||||
href: "!docs!/basics/events-and-subscribers",
|
||||
title: "Create a Subscriber",
|
||||
@@ -757,7 +672,7 @@ The `order.placed` event is currently not emitted.
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
]} />
|
||||
]} /> */}
|
||||
|
||||
---
|
||||
|
||||
@@ -786,13 +701,6 @@ Medusa's commerce features are geared towards automating RMA flows and ensuring
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/events-reference",
|
||||
title: "Events Reference",
|
||||
text: "Check out triggered events by each commerce module.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
@@ -891,7 +799,7 @@ The `order.placed` event is currently not emitted.
|
||||
}
|
||||
|
||||
// retrieve the order
|
||||
const order = await orderModuleService.retrieve(orderId)
|
||||
const order = await orderModuleService.retrieveOrder(orderId)
|
||||
|
||||
if (!order ||
|
||||
!order.customer_id ||
|
||||
@@ -901,7 +809,7 @@ The `order.placed` event is currently not emitted.
|
||||
return
|
||||
}
|
||||
|
||||
const [, count] = await orderModuleService.listAndCount({
|
||||
const [, count] = await orderModuleService.listAndCountOrders({
|
||||
customer_id: order.customer_id,
|
||||
})
|
||||
|
||||
@@ -948,13 +856,6 @@ Scheduled jobs are coming soon.
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/events-reference",
|
||||
title: "Events Reference",
|
||||
text: "Check out triggered events in each commerce module.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/basics/scheduled-jobs",
|
||||
title: "Scheduled Jobs",
|
||||
@@ -969,18 +870,18 @@ Scheduled jobs are coming soon.
|
||||
For example, create the file `src/subscribers/send-products-newsletter.ts` with the following content:
|
||||
|
||||
export const newsletterHighlights = [
|
||||
["35", "store", "Retrieve the first store in the application."],
|
||||
["37", "products", "Retrieve the products created since the last newsletter send date."],
|
||||
["43", "", "Check whether more than 10 products have been created before proceeding."],
|
||||
["47", "customers", "Retrieve all customers, assuming they're considered subscribed."],
|
||||
["49", "create", "Send a notification (newsletter) to each customer using the Notification Module."],
|
||||
["52", '"email"', "Send the notification through the email channel."],
|
||||
["53", '"newsletter_template"', "Specify the template name in the third-party service (for example, SendGrid)."],
|
||||
["55", "products", "Pass the created products to the template."],
|
||||
["60", "update", "Update the store's `last_newsletter_send_date` field with the current date."]
|
||||
["33", "store", "Retrieve the first store in the application."],
|
||||
["35", "products", "Retrieve the products created since the last newsletter send date."],
|
||||
["41", "", "Check whether more than 10 products have been created before proceeding."],
|
||||
["45", "customers", "Retrieve all customers, assuming they're considered subscribed."],
|
||||
["47", "createNotifications", "Send a notification (newsletter) to each customer using the Notification Module."],
|
||||
["50", '"email"', "Send the notification through the email channel."],
|
||||
["51", '"newsletter_template"', "Specify the template name in the third-party service (for example, SendGrid)."],
|
||||
["53", "products", "Pass the created products to the template."],
|
||||
["58", "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-16" expandButtonLabel="Show Imports"
|
||||
```ts title="src/subscribers/send-products-newsletter.ts" highlights={newsletterHighlights} collapsibleLines="1-14" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
@@ -988,13 +889,11 @@ export const newsletterHighlights = [
|
||||
import {
|
||||
ModuleRegistrationName,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
NotificationModuleService,
|
||||
} from "@medusajs/notification"
|
||||
import {
|
||||
ICustomerModuleService,
|
||||
IProductModuleService,
|
||||
IStoreModuleService,
|
||||
INotificationModuleService,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export default async function productCreateHandler({
|
||||
@@ -1011,13 +910,13 @@ export const newsletterHighlights = [
|
||||
container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const notificationModuleService:
|
||||
NotificationModuleService = container.resolve(
|
||||
INotificationModuleService = container.resolve(
|
||||
ModuleRegistrationName.NOTIFICATION
|
||||
)
|
||||
|
||||
const store = (await storeModuleService.list())[0]
|
||||
const store = (await storeModuleService.listStores())[0]
|
||||
|
||||
const products = await productModuleService.list({
|
||||
const products = await productModuleService.listProducts({
|
||||
created_at: {
|
||||
$gt: store.metadata.last_newsletter_send_date,
|
||||
},
|
||||
@@ -1027,9 +926,9 @@ export const newsletterHighlights = [
|
||||
return
|
||||
}
|
||||
|
||||
const customers = await customerModuleService.list()
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
|
||||
await notificationModuleService.create(
|
||||
await notificationModuleService.createNotifications(
|
||||
customers.map((customer) => ({
|
||||
to: customer.email,
|
||||
channel: "email",
|
||||
@@ -1040,7 +939,7 @@ export const newsletterHighlights = [
|
||||
}))
|
||||
)
|
||||
|
||||
await storeModuleService.update(store.id, {
|
||||
await storeModuleService.updateStores(store.id, {
|
||||
metadata: {
|
||||
last_newsletter_send_date: (new Date()).toString(),
|
||||
},
|
||||
@@ -1055,7 +954,7 @@ export const newsletterHighlights = [
|
||||
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` field.
|
||||
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 Provider Module configured for the `email` channel.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -102,7 +102,7 @@ export const serviceHighlights = [
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
You can store the product's ID in the external system using the `metadata` field of the `Product` data model in the Product Module. Alternatively, you can create a [data model](!docs!/basics/data-models) in your module to store data related to the external system.
|
||||
You can store the product's ID in the external system using the `metadata` property of the `Product` data model in the Product Module. Alternatively, you can create a [data model](!docs!/basics/data-models) in your module to store data related to the external system.
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -203,13 +203,13 @@ export const workflowHighlights = [
|
||||
.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const createdProductData = await productModuleService
|
||||
.retrieve(productId)
|
||||
.retrieveProduct(productId)
|
||||
|
||||
const erpProduct = await erpModuleService.createProduct(
|
||||
createdProductData
|
||||
)
|
||||
|
||||
await productModuleService.update(productId, {
|
||||
await productModuleService.updateProducts(productId, {
|
||||
metadata: {
|
||||
erp_id: erpProduct.id
|
||||
}
|
||||
@@ -229,7 +229,7 @@ export const workflowHighlights = [
|
||||
.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
await erpModuleService.deleteProduct(erpId)
|
||||
await productModuleService.update(productId, {
|
||||
await productModuleService.updateProducts(productId, {
|
||||
metadata: {}
|
||||
})
|
||||
})
|
||||
@@ -249,7 +249,7 @@ export const workflowHighlights = [
|
||||
|
||||
- Retrieves the product's data using the Product Module's main service.
|
||||
- Create the product in the ERP system using the ERP Module's main service.
|
||||
- Updates the product in Medusa by setting the ID of the ERP product in the product's `metadata` field.
|
||||
- Updates the product in Medusa by setting the ID of the ERP product in the product's `metadata` property.
|
||||
|
||||
The step also has a compensation function that rolls back changes when an error occurs. It deletes the product in the ERP system and removes the ID of the ERP product in the Medusa product.
|
||||
|
||||
@@ -334,7 +334,7 @@ For example, suppose an administrator changes the product data in the ERP system
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await productService.update(id, updatedData)
|
||||
await productService.updateProducts(id, updatedData)
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
|
||||
@@ -34,16 +34,22 @@ In a marketplace, an admin user has a store where they manage their products and
|
||||
|
||||
Create a Marketplace Module that holds and manages these relationships.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "/v2/basics/modules-and-services",
|
||||
href: "!docs!/basics/modules-and-services",
|
||||
title: "Create a Module",
|
||||
text: "Learn how to create a module",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/v2/advanced-development/modules/module-relationships",
|
||||
href: "!docs!/advanced-development/modules/module-relationships",
|
||||
title: "Module Relationships",
|
||||
text: "Create relationships between modules.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -60,80 +66,57 @@ Create a Marketplace Module that holds and manages these relationships.
|
||||
Then, create the file `src/modules/marketplace/models/store-user.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/marketplace/models/store-user.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import { Entity, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
class StoreUser extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
store_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
user_id: string
|
||||
}
|
||||
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const StoreUser = model.define("store_user", {
|
||||
id: model.id(),
|
||||
store_id: model.text(),
|
||||
user_id: model.text(),
|
||||
})
|
||||
|
||||
export default StoreUser
|
||||
```
|
||||
|
||||
This creates a `StoreUser` data model with the `store_id` and `user_id` fields. These fields will be used later to establish relationships to the Store and User modules.
|
||||
This creates a `StoreUser` data model with the `store_id` and `user_id` properties.
|
||||
|
||||
{/* These properties will be used later to establish relationships to the Store and User modules. */}
|
||||
|
||||
Next, create the file `src/modules/marketplace/models/store-product.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/marketplace/models/store-product.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import { Entity, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
class StoreProduct extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
store_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
product_id: string
|
||||
}
|
||||
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const StoreProduct = model.define("store_user", {
|
||||
id: model.id(),
|
||||
store_id: model.text(),
|
||||
product_id: model.text(),
|
||||
})
|
||||
|
||||
export default StoreProduct
|
||||
```
|
||||
|
||||
This creates a `StoreProduct` data model with the `store_id` and `product_id` fields. These fields will be used later to establish relationships to the Store and Product modules.
|
||||
This creates a `StoreProduct` data model with the `store_id` and `product_id` properties.
|
||||
|
||||
{/* These properties will be used later to establish relationships to the Store and Product modules. */}
|
||||
|
||||
Finally, create the file `src/modules/marketplace/models/store-order.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/marketplace/models/store-order.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import { Entity, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
class StoreOrder extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
store_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
order_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
parent_order_id?: string
|
||||
}
|
||||
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const StoreOrder = model.define("store_user", {
|
||||
id: model.id(),
|
||||
store_id: model.text(),
|
||||
order_id: model.text(),
|
||||
parent_order_id: model.text(),
|
||||
})
|
||||
|
||||
export default StoreOrder
|
||||
```
|
||||
|
||||
This creates a `StoreOrder` data model with the `store_id`, `order_id`, and `parent_order_id` fields. The `store_id` and `order_id` fields will be used to establish relationships to the Store and Order modules. You’ll learn about the use of `parent_order_id` in a later section.
|
||||
This creates a `StoreOrder` data model with the `store_id`, `order_id`, and `parent_order_id` properties.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
You can add relationships to data models of other modules in a similar manner.
|
||||
|
||||
</Note>
|
||||
{/* The `store_id` and `order_id` properties will be used to establish relationships to the Store and Order modules. You’ll learn about the use of `parent_order_id` in a later section. */}
|
||||
|
||||
To reflect these changes on the database, create the migration `src/modules/marketplace/migrations/Migration20240514143248.ts` with the following content:
|
||||
|
||||
@@ -159,244 +142,34 @@ Create a Marketplace Module that holds and manages these relationships.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You’ll run the migration after registering the module in the Medusa configurations.
|
||||
|
||||
Next, you’ll create types that you’ll use throughout your customizations.
|
||||
|
||||
Create the file `src/types/marketplace/index.ts` with the following content:
|
||||
|
||||
```ts title="src/types/marketplace/index.ts"
|
||||
import {
|
||||
StoreDTO,
|
||||
UserDTO,
|
||||
ProductDTO,
|
||||
OrderDTO,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export type StoreUserDTO = {
|
||||
id: string
|
||||
store_id: string
|
||||
user_id: string
|
||||
store?: StoreDTO
|
||||
user?: UserDTO
|
||||
}
|
||||
|
||||
export type StoreProductDTO = {
|
||||
id: string
|
||||
store_id: string
|
||||
product_id: string
|
||||
store?: StoreDTO
|
||||
product?: ProductDTO
|
||||
}
|
||||
|
||||
export type StoreOrderDTO = {
|
||||
id: string
|
||||
store_id: string
|
||||
order_id: string
|
||||
parent_order_id?: string
|
||||
store?: StoreDTO
|
||||
order?: OrderDTO
|
||||
}
|
||||
|
||||
export type CreateStoreUserDTO = {
|
||||
store_id: string
|
||||
user_id: string
|
||||
}
|
||||
|
||||
export type CreateStoreProductDTO = {
|
||||
store_id: string
|
||||
product_id: string
|
||||
}
|
||||
|
||||
export type CreateStoreOrderDTO = {
|
||||
store_id: string
|
||||
order_id: string
|
||||
parent_order_id?: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
Then, create the module’s main service at `src/modules/marketplace/service.ts` with the following content:
|
||||
|
||||
export const mainServiceHighlights = [
|
||||
["48", "abstractModuleServiceFactory", "Extends the service factory to generate basic data management features."],
|
||||
["102", "relationships", "Defines relationships to the Store, User, Product, and Order modules."],
|
||||
["131", "create", "Method to create a `StoreUser`."],
|
||||
["141", "createStoreProduct", "Method to create a `StoreProduct`."],
|
||||
["150", "createStoreOrder", "Method to create a `StoreOrder`."]
|
||||
["6", "MedusaService", "Extends the service factory to generate data management features."]
|
||||
]
|
||||
|
||||
```ts title="src/modules/marketplace/service.ts" highlights={mainServiceHighlights} collapsibleLines="1-17" expandButtonLabel="Show Imports"
|
||||
import { ModulesSdkUtils, Modules } from "@medusajs/utils"
|
||||
```ts title="src/modules/marketplace/service.ts" highlights={mainServiceHighlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import StoreUser from "./models/store-user"
|
||||
import StoreProduct from "./models/store-product"
|
||||
import StoreOrder from "./models/store-order"
|
||||
import {
|
||||
CreateStoreOrderDTO,
|
||||
CreateStoreProductDTO,
|
||||
CreateStoreUserDTO,
|
||||
StoreOrderDTO,
|
||||
StoreProductDTO,
|
||||
StoreUserDTO,
|
||||
} from "../../types/marketplace"
|
||||
import {
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
} from "@medusajs/types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
storeUserService: ModulesSdkTypes.InternalModuleService<
|
||||
any
|
||||
>
|
||||
storeProductService: ModulesSdkTypes.InternalModuleService<
|
||||
any
|
||||
>
|
||||
storeOrderService: ModulesSdkTypes.InternalModuleService<
|
||||
any
|
||||
>
|
||||
}
|
||||
|
||||
type AllModelsDTO = {
|
||||
StoreUser: {
|
||||
dto: StoreUserDTO
|
||||
},
|
||||
StoreProduct: {
|
||||
dto: StoreProductDTO
|
||||
},
|
||||
StoreOrder: {
|
||||
dto: StoreOrderDTO
|
||||
}
|
||||
}
|
||||
|
||||
const generateMethodsFor = [
|
||||
|
||||
class MarketplaceModuleService extends MedusaService({
|
||||
StoreUser,
|
||||
StoreProduct,
|
||||
StoreOrder,
|
||||
]
|
||||
|
||||
class MarketplaceModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
StoreUserDTO,
|
||||
AllModelsDTO
|
||||
>(
|
||||
StoreUser, generateMethodsFor
|
||||
) {
|
||||
storeUserService_: ModulesSdkTypes.InternalModuleService<
|
||||
StoreUser
|
||||
>
|
||||
storeProductService_: ModulesSdkTypes.InternalModuleService<
|
||||
StoreProduct
|
||||
>
|
||||
storeOrderService_: ModulesSdkTypes.InternalModuleService<
|
||||
StoreOrder
|
||||
>
|
||||
|
||||
constructor({
|
||||
storeUserService,
|
||||
storeProductService,
|
||||
storeOrderService,
|
||||
}: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.storeUserService_ = storeUserService
|
||||
this.storeProductService_ = storeProductService
|
||||
this.storeOrderService_ = storeOrderService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
serviceName: "marketplaceModuleService",
|
||||
alias: [
|
||||
{
|
||||
name: ["store_user"],
|
||||
args: {
|
||||
entity: StoreUser.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["store_product"],
|
||||
args: {
|
||||
entity: StoreProduct.name,
|
||||
methodSuffix: "StoreProducts",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["store_order"],
|
||||
args: {
|
||||
entity: StoreOrder.name,
|
||||
methodSuffix: "StoreOrders",
|
||||
},
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.STORE,
|
||||
alias: "store",
|
||||
primaryKey: "id",
|
||||
foreignKey: "store_id",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.USER,
|
||||
alias: "user",
|
||||
primaryKey: "id",
|
||||
foreignKey: "user_id",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
alias: "product",
|
||||
primaryKey: "id",
|
||||
foreignKey: "product_id",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.ORDER,
|
||||
alias: "order",
|
||||
primaryKey: "id",
|
||||
foreignKey: "order_id",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async create(
|
||||
data: CreateStoreUserDTO
|
||||
): Promise<StoreUserDTO> {
|
||||
const storeUser = await this.storeUserService_.create(
|
||||
data
|
||||
)
|
||||
|
||||
return storeUser
|
||||
}
|
||||
|
||||
async createStoreProduct(
|
||||
data: CreateStoreProductDTO
|
||||
): Promise<StoreProductDTO> {
|
||||
const storeProduct = await this.storeProductService_
|
||||
.create(data)
|
||||
|
||||
return storeProduct
|
||||
}
|
||||
|
||||
async createStoreOrder(
|
||||
data: CreateStoreOrderDTO
|
||||
): Promise<StoreOrderDTO> {
|
||||
const storeOrder = await this.storeOrderService_
|
||||
.create(data)
|
||||
|
||||
return storeOrder
|
||||
}
|
||||
}
|
||||
|
||||
}){
|
||||
// TODO add custom methods
|
||||
}
|
||||
|
||||
export default MarketplaceModuleService
|
||||
```
|
||||
|
||||
The module’s main service:
|
||||
|
||||
- Extends the service factory to generate basic data management features.
|
||||
- Defines relationships to the Store, User, Product, and Order modules.
|
||||
- Defines `create` methods for the `StoreUser`, `StoreProduct`, and `StoreOrder` data models.
|
||||
The module’s main service extends the service factory to generate data management features for the `StoreUser`, `StoreProduct`, and `StoreOrder` data models.
|
||||
|
||||
Finally, create the module definition at `src/modules/marketplace/index.ts` with the following content:
|
||||
|
||||
@@ -439,39 +212,49 @@ export const mainServiceHighlights = [
|
||||
|
||||
To attach admin users to their own stores, create a subscriber that listens to the `user.created` event and attaches the user to the store.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
- The `user.created` event is currently not emitted.
|
||||
- Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<Card
|
||||
href="/v2/basics/events-and-subscribers"
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/subscribers/user-created.ts` with the following content:
|
||||
|
||||
export const userSubscriberHighlights = [
|
||||
["10", "", "The event data payload with the created user's ID."],
|
||||
["24", "", "Retrieve the created user."],
|
||||
["26", "", "Create a store for that user using the Store Module."],
|
||||
["30", "", "Create a relationship between the user and the store by creating a `StoreUser` record."]
|
||||
["13", "data", "The event data payload with the created user's ID."],
|
||||
["27", "retrieveUser", "Retrieve the created user."],
|
||||
["29", "createStores", "Create a store for that user using the Store Module."],
|
||||
["33", "createStoreUsers", "Create a relationship between the user and the store by creating a `StoreUser` record."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={userSubscriberHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||
import { SubscriberArgs, type SubscriberConfig } from "@medusajs/medusa"
|
||||
```ts title="src/subscribers/user-created.ts" highlights={userSubscriberHighlights} collapsibleLines="1-11" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IUserModuleService,
|
||||
IStoreModuleService,
|
||||
} from "@medusajs/types"
|
||||
import MarketplaceModuleService from "../modules/marketplace/service"
|
||||
|
||||
|
||||
export default async function userCreatedHandler({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { id } = data.data || { data }
|
||||
const { id } = "data" in data ? data.data : data
|
||||
const userModuleService: IUserModuleService = container.resolve(
|
||||
ModuleRegistrationName.USER
|
||||
)
|
||||
@@ -481,21 +264,21 @@ export const userSubscriberHighlights = [
|
||||
const marketplaceModuleService: MarketplaceModuleService = container.resolve(
|
||||
"marketplaceModuleService"
|
||||
)
|
||||
|
||||
const user = await userModuleService.retrieve(id)
|
||||
|
||||
const store = await storeModuleService.create({
|
||||
|
||||
const user = await userModuleService.retrieveUser(id)
|
||||
|
||||
const store = await storeModuleService.createStores({
|
||||
name: `${user.first_name}'s Store`,
|
||||
})
|
||||
|
||||
const storeUser = await marketplaceModuleService.create({
|
||||
const storeUser = await marketplaceModuleService.createStoreUsers({
|
||||
store_id: store.id,
|
||||
user_id: user.id,
|
||||
})
|
||||
|
||||
|
||||
console.log(`Created StoreUser ${storeUser.id}`)
|
||||
}
|
||||
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
@@ -515,7 +298,7 @@ export const userSubscriberHighlights = [
|
||||
|
||||
At the end of the output, you should see the message `Created StoreUser {store_user_id}` where the `{store_user_id}` is the ID of the created `StoreUser`.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
---
|
||||
|
||||
@@ -523,37 +306,47 @@ export const userSubscriberHighlights = [
|
||||
|
||||
Similar to the previous section, to attach products to stores, create a subscriber that listens to the `product.created` event. In the subscriber, you attach the product to the store it’s created in.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
- The `product.created` event is currently not emitted.
|
||||
- Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<Card
|
||||
href="/v2/basics/events-and-subscribers"
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/subscribers/product-created.ts` with the following content:
|
||||
|
||||
export const productSubscriberHighlights = [
|
||||
["21", "", "Retrieve the created product."],
|
||||
["23", "", "This subscriber requires the store ID to be set in `product.metadata.store_id`. If not, the subscriber ends execution."],
|
||||
["27", "", "Create a relationship between the product and the store by creating a `StoreProduct` record."]
|
||||
["24", "retrieveProduct", "Retrieve the created product."],
|
||||
["26", "", "This subscriber requires the store ID to be set in `product.metadata.store_id`. If not, the subscriber ends execution."],
|
||||
["30", "createStoreProducts", "Create a relationship between the product and the store by creating a `StoreProduct` record."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/product-created.ts" highlights={productSubscriberHighlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { SubscriberArgs, type SubscriberConfig } from "@medusajs/medusa"
|
||||
```ts title="src/subscribers/product-created.ts" highlights={productSubscriberHighlights} collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IProductModuleService,
|
||||
} from "@medusajs/types"
|
||||
import MarketplaceModuleService from "../modules/marketplace/service"
|
||||
|
||||
|
||||
export default async function productCreateHandler({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { id } = data.data || data
|
||||
const { id } = "data" in data ? data.data : data
|
||||
const productModuleService: IProductModuleService = container.resolve(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
@@ -561,19 +354,19 @@ export const productSubscriberHighlights = [
|
||||
const marketplaceModuleService: MarketplaceModuleService = container.resolve(
|
||||
"marketplaceModuleService"
|
||||
)
|
||||
|
||||
const product = await productModuleService.retrieve(id)
|
||||
|
||||
|
||||
const product = await productModuleService.retrieveProduct(id)
|
||||
|
||||
if (!product.metadata?.store_id) {
|
||||
return
|
||||
}
|
||||
|
||||
await marketplaceModuleService.createStoreProduct({
|
||||
|
||||
await marketplaceModuleService.createStoreProducts({
|
||||
store_id: product.metadata.store_id as string,
|
||||
product_id: id,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "product.created",
|
||||
}
|
||||
@@ -607,7 +400,7 @@ export const productSubscriberHighlights = [
|
||||
|
||||
This returns the created product. In the next section, you’ll implement the API route to fetch the products of a store.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
|
||||
---
|
||||
@@ -616,16 +409,22 @@ export const productSubscriberHighlights = [
|
||||
|
||||
To allow admin users to view their store’s products, create an API route that uses the remote query to fetch the products based on the logged-in user’s store.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Retrieving module relationship data using the remote query is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "/v2/basics/api-routes",
|
||||
href: "!docs!/basics/api-routes",
|
||||
title: "API Routes",
|
||||
text: "Learn how to create an API Route in Medusa.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/v2/advanced-development/modules/remote-query",
|
||||
href: "!docs!/advanced-development/modules/remote-query",
|
||||
title: "Remote Query",
|
||||
text: "Use the remote query to fetch data across modules.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -633,14 +432,14 @@ To allow admin users to view their store’s products, create an API route that
|
||||
},
|
||||
]} />
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/api/admin/marketplace/products/route.ts` with the following content:
|
||||
|
||||
export const productRoutesHighlights = [
|
||||
["26", "", "Retrieve the store of the logged-in user."],
|
||||
["37", "", "Build a query that retrieves the products of that store."],
|
||||
["49", "", "Retrieve the products using remote query."]
|
||||
["26", "storeUsers", "Retrieve the store of the logged-in user."],
|
||||
["38", "query", "Build a query that retrieves the products of that store."],
|
||||
["50", "remoteQuery", "Retrieve the products using remote query."]
|
||||
]
|
||||
|
||||
```ts title="src/api/admin/marketplace/products/route.ts" highlights={productRoutesHighlights} collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
||||
@@ -656,7 +455,7 @@ export const productRoutesHighlights = [
|
||||
} from "@medusajs/utils"
|
||||
import MarketplaceModuleService
|
||||
from "../../../../modules/marketplace/service"
|
||||
|
||||
|
||||
export async function GET(
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
@@ -668,18 +467,19 @@ export const productRoutesHighlights = [
|
||||
const remoteQuery: RemoteQueryFunction = req.scope.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_QUERY
|
||||
)
|
||||
|
||||
const storeUsers = await marketplaceModuleService.list({
|
||||
user_id: req.auth.actor_id,
|
||||
})
|
||||
|
||||
|
||||
const storeUsers = await marketplaceModuleService
|
||||
.listStoreUsers({
|
||||
user_id: req.auth_context.actor_id,
|
||||
})
|
||||
|
||||
if (!storeUsers.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"This user doesn't have an associated store."
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "store_product",
|
||||
fields: [
|
||||
@@ -691,9 +491,9 @@ export const productRoutesHighlights = [
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
|
||||
|
||||
res.json({
|
||||
store_id: storeUsers[0].store_id,
|
||||
products: result.map((data) => data.product),
|
||||
@@ -741,39 +541,39 @@ export const productRoutesHighlights = [
|
||||
|
||||
This will return the product you created in the previous section, if the `{jwt_token}` belongs to the user of the same store ID.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Split Orders Based on Stores
|
||||
|
||||
An order may contain items from different stores. To ensure that users can only view and manage their orders, create a subscriber that listens to the `order.placed` event and handles splitting the order into multiple orders based on the items’ stores.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
While this section showcases the implementation, the `order.placed` event is still not emitted in Medusa V2.
|
||||
The `order.placed` event is still not emitted in Medusa V2.
|
||||
|
||||
</Note>
|
||||
|
||||
An order may contain items from different stores. To ensure that users can only view and manage their orders, create a subscriber that listens to the `order.placed` event and handles splitting the order into multiple orders based on the items’ stores.
|
||||
|
||||
<Card
|
||||
href="/v2/basics/events-and-subscribers"
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/subscribers/order-created.ts` with the following content:
|
||||
|
||||
export const orderSubscriberHighlights = [
|
||||
["35", "", "Loop over the created order’s items."],
|
||||
["57", "", "Group the items by their store ID."],
|
||||
["72", "", "If the items have the same store ID, then associate the created order with the store."],
|
||||
["88", "", "If there are items from more than one store in the order, create child orders and associate each of them with the store."],
|
||||
["91", "parent_order_id", "The `parent_order_id` field in the `StoreOrder` data model points to the original order."]
|
||||
["72", "createStoreOrders", "If the items have the same store ID, then associate the created order with the store."],
|
||||
["88", "createStoreOrders", "If there are items from more than one store in the order, create child orders and associate each of them with the store."],
|
||||
["91", "parent_order_id", "The `parent_order_id` property in the `StoreOrder` data model points to the original order."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/order-created.ts" highlights={orderSubscriberHighlights} collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
||||
@@ -789,12 +589,12 @@ export const orderSubscriberHighlights = [
|
||||
import MarketplaceModuleService
|
||||
from "../modules/marketplace/service"
|
||||
import { createOrdersWorkflow } from "@medusajs/core-flows"
|
||||
|
||||
|
||||
export default async function orderCreatedHandler({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { id } = data.data || data
|
||||
const { id } = "data" in data ? data.data : data
|
||||
const orderModuleService: IOrderModuleService =
|
||||
container.resolve(
|
||||
ModuleRegistrationName.ORDER
|
||||
@@ -804,23 +604,23 @@ export const orderSubscriberHighlights = [
|
||||
container.resolve(
|
||||
"marketplaceModuleService"
|
||||
)
|
||||
|
||||
|
||||
const storeToOrders: Record<string, CreateOrderDTO> = {}
|
||||
|
||||
const order = await orderModuleService.retrieve(id, {
|
||||
|
||||
const order = await orderModuleService.retrieveOrder(id, {
|
||||
relations: ["items"],
|
||||
})
|
||||
|
||||
|
||||
await Promise.all(order.items?.map(async (item) => {
|
||||
const storeProduct = await marketplaceModuleService
|
||||
.listStoreProducts({
|
||||
product_id: item.product_id,
|
||||
})
|
||||
|
||||
|
||||
if (!storeProduct.length) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const storeId = storeProduct[0].store_id
|
||||
|
||||
if (!storeToOrders[storeId]) {
|
||||
@@ -830,32 +630,32 @@ export const orderSubscriberHighlights = [
|
||||
items: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const { id, ...itemDetails } = item
|
||||
|
||||
|
||||
storeToOrders[storeId].items.push(itemDetails)
|
||||
}))
|
||||
|
||||
|
||||
const storeToOrdersKeys = Object.keys(storeToOrders)
|
||||
|
||||
|
||||
if (!storeToOrdersKeys.length) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
storeToOrdersKeys.length === 1 &&
|
||||
storeToOrders[0].items.length === order.items.length
|
||||
) {
|
||||
// The order is composed of items from one store, so
|
||||
// associate the order as-is with the store.
|
||||
await marketplaceModuleService.createStoreOrder({
|
||||
await marketplaceModuleService.createStoreOrders({
|
||||
store_id: storeToOrdersKeys[0],
|
||||
order_id: order.id,
|
||||
})
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// create store orders for each child order
|
||||
await Promise.all(
|
||||
storeToOrdersKeys.map(async (storeId) => {
|
||||
@@ -863,8 +663,8 @@ export const orderSubscriberHighlights = [
|
||||
.run({
|
||||
input: storeToOrders[storeId],
|
||||
})
|
||||
|
||||
await marketplaceModuleService.createStoreOrder({
|
||||
|
||||
await marketplaceModuleService.createStoreOrders({
|
||||
store_id: storeId,
|
||||
order_id: result.id,
|
||||
parent_order_id: order.id,
|
||||
@@ -872,7 +672,7 @@ export const orderSubscriberHighlights = [
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "order.placed",
|
||||
}
|
||||
@@ -883,13 +683,13 @@ export const orderSubscriberHighlights = [
|
||||
- Loop over the created order’s items.
|
||||
- Group the items by their store ID.
|
||||
- If the items have the same store ID, then associate the created order with the store.
|
||||
- If there are items from more than one store in the order, create child orders and associate each of them with the store. Here, you use the `parent_order_id` field in the `StoreOrder` data model to point to the original order.
|
||||
- If there are items from more than one store in the order, create child orders and associate each of them with the store. Here, you use the `parent_order_id` property in the `StoreOrder` data model to point to the original order.
|
||||
|
||||
To test this out, create an order in your store. That will run the subscriber and create the child orders.
|
||||
|
||||
The next section covers how to retrieve the store’s orders.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
|
||||
---
|
||||
@@ -898,16 +698,22 @@ export const orderSubscriberHighlights = [
|
||||
|
||||
Similar to products, to allow admin users to view their store’s orders, create an API route that uses the remote query to fetch the orders based on the logged-in user’s store.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Retrieving module relationship data using the remote query is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "/v2/basics/api-routes",
|
||||
href: "!docs!/basics/api-routes",
|
||||
title: "API Routes",
|
||||
text: "Learn how to create an API Route in Medusa.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/v2/advanced-development/modules/remote-query",
|
||||
href: "!docs!/advanced-development/modules/remote-query",
|
||||
title: "Remote Query",
|
||||
text: "Use the remote query to fetch data across modules.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -915,7 +721,7 @@ Similar to products, to allow admin users to view their store’s orders, create
|
||||
},
|
||||
]} />
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/api/admin/marketplace/orders/route.ts` with the following content:
|
||||
|
||||
@@ -937,7 +743,7 @@ export const orderRoutesHighlights = [
|
||||
} from "@medusajs/utils"
|
||||
import MarketplaceModuleService
|
||||
from "../../../../modules/marketplace/service"
|
||||
|
||||
|
||||
export async function GET(
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
@@ -949,11 +755,11 @@ export const orderRoutesHighlights = [
|
||||
const remoteQuery: RemoteQueryFunction = req.scope.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_QUERY
|
||||
)
|
||||
|
||||
const storeUsers = await marketplaceModuleService.list({
|
||||
user_id: req.auth.actor_id,
|
||||
|
||||
const storeUsers = await marketplaceModuleService.listStoreUsers({
|
||||
user_id: req.auth_context.actor_id,
|
||||
})
|
||||
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "store_order",
|
||||
fields: [
|
||||
@@ -965,14 +771,13 @@ export const orderRoutesHighlights = [
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
|
||||
|
||||
res.json({
|
||||
orders: result.map((data) => data.order),
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This creates a `GET` API route at `/admin/marketplace/orders`. In the API route, you:
|
||||
@@ -990,7 +795,7 @@ export const orderRoutesHighlights = [
|
||||
|
||||
This will return the orders you created in the previous section if the `{jwt_token}` belongs to the user of the same store ID.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -114,15 +114,11 @@ Using the tax-inclusive feature, merchants can specify prices including taxes pe
|
||||
}
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
href: "/storefront-development/products/price",
|
||||
title: "Display Product Price in Storefront",
|
||||
text: "Learn how to display the correct product price in a storefront.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false,
|
||||
badge: {
|
||||
variant: "blue",
|
||||
children: "Guide Soon"
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ You also have freedom in how you choose to implement the storefront, allowing yo
|
||||
|
||||
## Store Personalized Data
|
||||
|
||||
The Cart Module's `LineItem` data model has a `metadata` field that holds any custom data. You can pass the customer's customization in that field when adding a product to the cart.
|
||||
The Cart Module's `LineItem` data model has a `metadata` property that holds any custom data. You can pass the customer's customization in the request body's `metadata` field when adding a product to the cart.
|
||||
|
||||
For example, if you’re asking customers to enter a message to put in a letter they’re purchasing, use the `metadata` attribute of the `LineItem` data model to set the personalized information entered by the customer:
|
||||
|
||||
@@ -81,9 +81,9 @@ Medusa provides a Next.js Starter storefront with basic ecommerce functionalitie
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/storefront-development/tips",
|
||||
title: "Build Your Own Storefront",
|
||||
text: "Find tips on how to create a storefront.",
|
||||
href: "/storefront-development",
|
||||
title: "Storefront Development",
|
||||
text: "Find guides for your storefront development.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ Medusa's modular architecture removes any restrictions you may have while making
|
||||
|
||||
POS systems make the checkout process smoother by integrating a barcode scanner. Merchants scan a product by its barcode to check its details or add it to the customer's purchase.
|
||||
|
||||
The Product Module's `ProductVariant` data model has the fields to implement this integration, mainly the `barcode` attribute. Other notable fields include `ean`, `upc`, and `hs_code`, among others.
|
||||
The Product Module's `ProductVariant` data model has the properties to implement this integration, mainly the `barcode` attribute. Other notable properties include `ean`, `upc`, and `hs_code`, among others.
|
||||
|
||||
To search through product variants by their barcode, create a custom API Route and call it within your POS.
|
||||
|
||||
@@ -53,14 +53,14 @@ To search through product variants by their barcode, create a custom API Route a
|
||||
|
||||
<CardList itemsPerRow={2} items={[
|
||||
{
|
||||
href: "#",
|
||||
href: "/commerce-modules/product",
|
||||
title: "Product Module",
|
||||
text: "Learn about the Product Module.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "https://docs.medusajs.com/api/admin",
|
||||
href: "!docs!/basics/api-routes",
|
||||
title: "Create API Route",
|
||||
text: "Learn how to create an API Route.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -100,7 +100,7 @@ To search through product variants by their barcode, create a custom API Route a
|
||||
|
||||
// retrieve product variants by barcode
|
||||
const productVariants = await productModuleService
|
||||
.listVariants({
|
||||
.listProductVariants({
|
||||
// @ts-ignore
|
||||
barcode,
|
||||
})
|
||||
|
||||
@@ -141,9 +141,9 @@ Medusa provides a Next.js Starter. Since you've customized your Medusa project,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/storefront-development/tips",
|
||||
href: "/storefront-development",
|
||||
title: "Option 2: Build Custom Storefront",
|
||||
text: "Find tips to build your own storefront.",
|
||||
text: "Find guides for your storefront development.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user