docs: improved commerce module docs [2/n] (#9501)
Improve + add docs for commerce modules from currency to inventory [2/n]
This commit is contained in:
@@ -13,16 +13,15 @@ In this guide, you’ll find common examples of how you can use the Currency Mod
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICurrencyModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService: ICurrencyModuleService = req.scope.resolve(
|
||||
const currencyModuleService = req.scope.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
@@ -59,16 +58,15 @@ export async function GET(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICurrencyModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService: ICurrencyModuleService = req.scope.resolve(
|
||||
const currencyModuleService = req.scope.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Currency Module is the `@medusajs/medusa/currency` NPM package that provides currency-related features in your Medusa and Node.js applications.
|
||||
The Currency Module provides currency-related features in your Medusa and Node.js applications.
|
||||
|
||||
## How to Use Currency Module's Service
|
||||
|
||||
@@ -15,18 +15,33 @@ You can use the Currency Module's main service by resolving from the Medusa cont
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const currencyModuleService = container.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
const currencies = await currencyModuleService.listCurrencies()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICurrencyModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService: ICurrencyModuleService = req.scope.resolve(
|
||||
const currencyModuleService = req.scope.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
@@ -39,35 +54,17 @@ export async function GET(
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { ICurrencyModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const currencyModuleService: ICurrencyModuleService = container.resolve(
|
||||
const currencyModuleService = container.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
const currencies = await currencyModuleService.listCurrencies()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { ICurrencyModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const currencyModuleService: ICurrencyModuleService = container.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
const currencies = await currencyModuleService.listCurrencies()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
@@ -87,12 +84,12 @@ const currency = await currencyModuleService.retrieveCurrency("usd")
|
||||
|
||||
### Support Currencies in Modules
|
||||
|
||||
Other commerce modules use currency codes in their data models or operations. You can use the Currency Module to retrieve a currency code and its details.
|
||||
Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
|
||||
|
||||
An example with the Region Module:
|
||||
|
||||
```ts
|
||||
const region = await regionModuleService.retrieveCurrency("reg_123")
|
||||
const region = await regionModuleService.retrieveRegion("reg_123")
|
||||
const currency = await currencyModuleService.retrieveCurrency(
|
||||
region.currency_code
|
||||
)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Currency Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document showcases the link modules defined between the Currency Module and other commerce modules.
|
||||
|
||||
## Store Module
|
||||
|
||||
A store has a default currency. Medusa defines a link module that builds a relationship between the `Store` and `Currency` data models.
|
||||
|
||||

|
||||
@@ -18,6 +18,6 @@ When this or another guest customer registers an account with the same email, a
|
||||
|
||||
## Email Uniqueness
|
||||
|
||||
The above behavior means that two `Customer` records may exist of the same email. However, the main difference is the `has_account` property's value.
|
||||
The above behavior means that two `Customer` records may exist with the same email. However, the main difference is the `has_account` property's value.
|
||||
|
||||
So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
|
||||
|
||||
@@ -13,13 +13,12 @@ In this guide, you’ll find common examples of how you can use the Customer Mod
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService: ICustomerModuleService = request.scope.resolve(
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
@@ -68,13 +67,12 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService: ICustomerModuleService = request.scope.resolve(
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
@@ -119,13 +117,12 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService: ICustomerModuleService = request.scope.resolve(
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
@@ -168,13 +165,12 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService: ICustomerModuleService = request.scope.resolve(
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
|
||||
684
www/apps/resources/app/commerce-modules/customer/extend/page.mdx
Normal file
684
www/apps/resources/app/commerce-modules/customer/extend/page.mdx
Normal file
@@ -0,0 +1,684 @@
|
||||
import { Prerequisites } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Extend Customer Data Model`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this documentation, you'll learn how to extend a data model of the Customer Module to add a custom property.
|
||||
|
||||
You'll create a `Custom` data model in a module. This data model will have a `custom_name` property, which is the property you want to add to the [Customer data model](/references/customer/models/Customer) defined in the Customer Module.
|
||||
|
||||
You'll then learn how to:
|
||||
|
||||
- Link the `Custom` data model to the `Customer` data model.
|
||||
- Set the `custom_name` property when a customer is created or updated using Medusa's API routes.
|
||||
- Retrieve the `custom_name` property with the customer's details, in custom or existing API routes.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Similar steps can bee applied to the `CustomerAddress` data model.
|
||||
|
||||
</Note>
|
||||
|
||||
## Step 1: Define Custom Data Model
|
||||
|
||||
Consider you have a Hello Module defined in the `/src/modules/hello` directory.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
If you don't have a module, follow [this guide](!docs!/basics/modules) to create one.
|
||||
|
||||
</Note>
|
||||
|
||||
To add the `custom_name` property to the `Customer` data model, you'll create in the Hello Module a data model that has the `custom_name` property.
|
||||
|
||||
Create the file `src/modules/hello/models/custom.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/hello/models/custom.ts"
|
||||
import { model } from "@medusajs/framework/utils"
|
||||
|
||||
export const Custom = model.define("custom", {
|
||||
id: model.id().primaryKey(),
|
||||
custom_name: model.text(),
|
||||
})
|
||||
```
|
||||
|
||||
This creates a `Custom` data model that has the `id` and `custom_name` properties.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn more about data models in [this guide](!docs!/data-models).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Define Link to Customer Data Model
|
||||
|
||||
Next, you'll define a module link between the `Custom` and `Customer` data model. A module link allows you to form a relation between two data models of separate modules while maintaining module isolation.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn more about module links in [this guide](!docs!/module-links).
|
||||
|
||||
</Note>
|
||||
|
||||
Create the file `src/links/customer-custom.ts` with the following content:
|
||||
|
||||
```ts title="src/links/customer-custom.ts"
|
||||
import { defineLink } from "@medusajs/framework/utils"
|
||||
import HelloModule from "../modules/hello"
|
||||
import CustomerModule from "@medusajs/medusa/customer"
|
||||
|
||||
export default defineLink(
|
||||
CustomerModule.linkable.customer,
|
||||
HelloModule.linkable.custom
|
||||
)
|
||||
```
|
||||
|
||||
This defines a link between the `Customer` and `Custom` data models. Using this link, you'll later query data across the modules, and link records of each data model.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Generate and Run Migrations
|
||||
|
||||
<Prerequisites
|
||||
items={[
|
||||
{
|
||||
text: "Module must be registered in medusa-config.js",
|
||||
link: "!docs!/basics/modules#4-add-module-to-configurations"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
To reflect the `Custom` data model in the database, generate a migration that defines the table to be created for it.
|
||||
|
||||
Run the following command in your Medusa project's root:
|
||||
|
||||
```bash
|
||||
npx medusa db:generate helloModuleService
|
||||
```
|
||||
|
||||
Where `helloModuleService` is your module's name.
|
||||
|
||||
Then, run the `db:migrate` command to run the migrations and create a table in the database for the link between the `Customer` and `Custom` data models:
|
||||
|
||||
```bash
|
||||
npx medusa db:migrate
|
||||
```
|
||||
|
||||
A table for the link is now created in the database. You can now retrieve and manage the link between records of the data models.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Consume customersCreated Workflow Hook
|
||||
|
||||
When a customer is created, you also want to create a `Custom` record and set the `custom_name` property, then create a link between the `Customer` and `Custom` records.
|
||||
|
||||
To do that, you'll consume the [customersCreated](/resources/references/medusa-workflows/createCustomersWorkflow#customerscreated) hook of the [createCustomersWorkflow](/references/medusa-workflows/createCustomersWorkflow). This workflow is executed in the [Create Customer Admin API route](!api!/admin#customers_postcustomers)
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn more about workflow hooks in [this guide](!docs!/advanced-development/workflows/workflow-hooks).
|
||||
|
||||
</Note>
|
||||
|
||||
The API route accepts in its request body an `additional_data` parameter. You can pass in it custom data, which is passed to the workflow hook handler.
|
||||
|
||||
### Add custom_name to Additional Data Validation
|
||||
|
||||
To pass the `custom_name` in the `additional_data` parameter, you must add a validation rule that tells the Medusa application about this custom property.
|
||||
|
||||
Create the file `src/api/middlewares.ts` with the following content:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import { z } from "zod"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
{
|
||||
method: "POST",
|
||||
matcher: "/admin/customers",
|
||||
additionalDataValidator: {
|
||||
custom_name: z.string().optional(),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
The `additional_data` parameter validation is customized using the `defineMiddlewares` utility function. In the routes middleware configuration object, the `additionalDataValidator` property accepts [Zod](https://zod.dev/) validaiton rules.
|
||||
|
||||
In the snippet above, you add a validation rule indicating that `custom_name` is a string that can be passed in the `additional_data` object.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn more about additional data validation in [this guide](!docs!/advanced-development/api-routes/additional-data).
|
||||
|
||||
</Note>
|
||||
|
||||
### Create Workflow to Create Custom Record
|
||||
|
||||
You'll now create a workflow that will be used in the hook handler.
|
||||
|
||||
This workflow will create a `Custom` record, then link it to the customer.
|
||||
|
||||
Start by creating the step that creates the `Custom` record. Create the file `src/workflows/create-custom-from-customer/steps/create-custom.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/create-custom-from-customer/steps/create-custom.ts"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import HelloModuleService from "../../../modules/hello/service"
|
||||
import { HELLO_MODULE } from "../../../modules/hello"
|
||||
|
||||
type CreateCustomStepInput = {
|
||||
custom_name?: string
|
||||
}
|
||||
|
||||
export const createCustomStep = createStep(
|
||||
"create-custom",
|
||||
async (data: CreateCustomStepInput, { container }) => {
|
||||
if (!data.custom_name) {
|
||||
return
|
||||
}
|
||||
|
||||
const helloModuleService: HelloModuleService = container.resolve(
|
||||
HELLO_MODULE
|
||||
)
|
||||
|
||||
const custom = await helloModuleService.createCustoms(data)
|
||||
|
||||
return new StepResponse(custom, custom)
|
||||
},
|
||||
async (custom, { container }) => {
|
||||
const helloModuleService: HelloModuleService = container.resolve(
|
||||
HELLO_MODULE
|
||||
)
|
||||
|
||||
await helloModuleService.deleteCustoms(custom.id)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the step, you resolve the Hello Module's main service and create a `Custom` record.
|
||||
|
||||
In the compensation function that undoes the step's actions in case of an error, you delete the created record.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Learn more about compensation functions in [this guide](!docs!/advanced-development/workflows/compensation-function).
|
||||
|
||||
</Note>
|
||||
|
||||
Then, create the workflow at `src/workflows/create-custom-from-customer/index.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/create-custom-from-customer/index.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { createWorkflow, transform, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { CustomerDTO } from "@medusajs/framework/types"
|
||||
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { HELLO_MODULE } from "../../modules/hello"
|
||||
import { createCustomStep } from "./steps/create-custom"
|
||||
|
||||
export type CreateCustomFromCustomerWorkflowInput = {
|
||||
customer: CustomerDTO
|
||||
additional_data?: {
|
||||
custom_name?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const createCustomFromCustomerWorkflow = createWorkflow(
|
||||
"create-custom-from-customer",
|
||||
(input: CreateCustomFromCustomerWorkflowInput) => {
|
||||
const customName = transform(
|
||||
{
|
||||
input,
|
||||
},
|
||||
(data) => data.input.additional_data.custom_name || ""
|
||||
)
|
||||
|
||||
const custom = createCustomStep({
|
||||
custom_name: customName,
|
||||
})
|
||||
|
||||
when(({ custom }), ({ custom }) => custom !== undefined)
|
||||
.then(() => {
|
||||
createRemoteLinkStep([{
|
||||
[Modules.CUSTOMER]: {
|
||||
customer_id: input.customer.id,
|
||||
},
|
||||
[HELLO_MODULE]: {
|
||||
custom_id: custom.id,
|
||||
},
|
||||
}])
|
||||
})
|
||||
|
||||
return new WorkflowResponse({
|
||||
custom,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The workflow accepts as an input the created customer and the `additional_data` parameter passed in the request. This is the same input that the `customersCreated` hook accepts.
|
||||
|
||||
In the workflow, you:
|
||||
|
||||
1. Use the `transform` utility to get the value of `custom_name` based on whether it's set in `additional_data`. Learn more about why you can't use conditional operators in a workflow without using `transform` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
|
||||
2. Create the `Custom` record using the `createCustomStep`.
|
||||
3. Use the `when-then` utility to link the customer to the `Custom` record if it was created. Learn more about why you can't use if-then conditions in a workflow without using `when-then` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
|
||||
|
||||
You'll next call the workflow in the hook handler.
|
||||
|
||||
### Consume Workflow Hook
|
||||
|
||||
You can now consume the `customersCreated` hook, which is executed in the `createCustomersWorkflow` after the customer is created.
|
||||
|
||||
To consume the hook, create the file `src/workflow/hooks/customer-created.ts` with the following content:
|
||||
|
||||
```ts title="src/workflow/hooks/customer-created.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import { createCustomersWorkflow } from "@medusajs/medusa/core-flows"
|
||||
import {
|
||||
createCustomFromCustomerWorkflow,
|
||||
CreateCustomFromCustomerWorkflowInput,
|
||||
} from "../create-custom-from-customer"
|
||||
|
||||
createCustomersWorkflow.hooks.customersCreated(
|
||||
async ({ customers, additional_data }, { container }) => {
|
||||
const workflow = createCustomFromCustomerWorkflow(container)
|
||||
|
||||
for (const customer of customers) {
|
||||
await workflow.run({
|
||||
input: {
|
||||
customer,
|
||||
additional_data,
|
||||
} as CreateCustomFromCustomerWorkflowInput,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The hook handler executes the `createCustomFromCustomerWorkflow`, passing it its input.
|
||||
|
||||
### Test it Out
|
||||
|
||||
To test it out, send a `POST` request to `/admin/customers` to create a customer, passing `custom_name` in `additional_data`:
|
||||
|
||||
```bash
|
||||
curl --location 'localhost:9000/admin/customers' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer {token}' \
|
||||
--data-raw '{
|
||||
"email": "customer@gmail.com",
|
||||
"additional_data": {
|
||||
"custom_name": "test"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Make sure to replace `{token}` with an admin user's JWT token. Learn how to retrieve it in the [API reference](!api!/admin#1-bearer-authorization-with-jwt-tokens).
|
||||
|
||||
The request will return the customer's details. You'll learn how to retrive the `custom_name` property with the customer's details in the next section.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Retrieve custom_name with Customer Details
|
||||
|
||||
When you extend an existing data model through links, you also want to retrieve the custom properties with the data model.
|
||||
|
||||
### Retrieve in API Routes
|
||||
|
||||
To retrieve the `custom_name` property when you're retrieving the customer through API routes, such as the [Get Customer API Route](!api!/admin#customers_getcustomersid), pass in the `fields` query parameter `+custom.*`, which retrieves the linked `Custom` record's details.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The `+` prefix in `+custom.*` indicates that the relation should be retrieved with the default customer fields. Learn more about selecting fields and relations in the [API reference](!api!/admin#select-fields-and-relations).
|
||||
|
||||
</Note>
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
curl -X POST 'localhost:9000/admin/customers/{customer_id}?fields=+custom.*' \
|
||||
-H 'Authorization: Bearer {token}'
|
||||
```
|
||||
|
||||
Make sure to replace `{customer_id}` with the customer's ID, and `{token}` with an admin user's JWT token.
|
||||
|
||||
Among the returned `customer` object, you'll find a `custom` property which holds the details of the linked `Custom` record:
|
||||
|
||||
```json
|
||||
{
|
||||
"customer": {
|
||||
// ...
|
||||
"custom": {
|
||||
"id": "01J9NP7ANXDZ0EAYF0956ZE1ZA",
|
||||
"custom_name": "test",
|
||||
"created_at": "2024-10-08T09:09:06.877Z",
|
||||
"updated_at": "2024-10-08T09:09:06.877Z",
|
||||
"deleted_at": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve using Query
|
||||
|
||||
You can also retrieve the `Custom` record linked to a customer in your code using [Query](!docs!/advanced-development/module-links/query).
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
const { data: [customer] } = await query.graph({
|
||||
entity: "customer",
|
||||
fields: ["*", "custom.*"],
|
||||
filters: {
|
||||
id: customer_id,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Learn more about how to use Query in [this guide](!docs!/advanced-development/module-links/query).
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Consume customersUpdated Workflow Hook
|
||||
|
||||
Similar to the `customersCreated` hook, you'll consume the [customersUpdated](/references/medusa-workflows/updateCustomersWorkflow#customersUpdated) hook of the [updateCustomersWorkflow](/references/medusa-workflows/updateCustomersWorkflow) to update `custom_name` when the customer is updated.
|
||||
|
||||
The `updateCustomersWorkflow` is executed by the [Update Customer API route](!api!/admin#customers_postcustomersid), which accepts the `additional_data` parameter to pass custom data to the hook.
|
||||
|
||||
### Add custom_name to Additional Data Validation
|
||||
|
||||
To allow passing `custom_name` in the `additional_data` parameter of the update customer route, add in `src/api/middlewares.ts` a new route middleware configuration object:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { defineMiddlewares } from "@medusajs/medusa"
|
||||
import { z } from "zod"
|
||||
|
||||
export default defineMiddlewares({
|
||||
routes: [
|
||||
// ...
|
||||
{
|
||||
method: "POST",
|
||||
matcher: "/admin/customers/:id",
|
||||
additionalDataValidator: {
|
||||
custom_name: z.string().nullish(),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
The validation schema is the similar to that of the Create Customer API route, except you can pass a `null` value for `custom_name` to remove or unset the `custom_name`'s value.
|
||||
|
||||
### Create Workflow to Update Custom Record
|
||||
|
||||
Next, you'll create a workflow that creates, updates, or deletes `Custom` records based on the provided `additional_data` parameter:
|
||||
|
||||
1. If `additional_data.custom_name` is set and it's `null`, the `Custom` record linked to the customer is deleted.
|
||||
2. If `additional_data.custom_name` is set and the customer doesn't have a linked `Custom` record, a new record is created and linked to the customer.
|
||||
3. If `additional_data.custom_name` is set and the customer has a linked `Custom` record, the `custom_name` property of the `Custom` record is updated.
|
||||
|
||||
Start by creating the step that updates a `Custom` record. Create the file `src/workflows/update-custom-from-customer/steps/update-custom.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/update-custom-from-customer/steps/update-custom.ts"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { HELLO_MODULE } from "../../../modules/hello"
|
||||
import HelloModuleService from "../../../modules/hello/service"
|
||||
|
||||
type UpdateCustomStepInput = {
|
||||
id: string
|
||||
custom_name: string
|
||||
}
|
||||
|
||||
export const updateCustomStep = createStep(
|
||||
"update-custom",
|
||||
async ({ id, custom_name }: UpdateCustomStepInput, { container }) => {
|
||||
const helloModuleService: HelloModuleService = container.resolve(
|
||||
HELLO_MODULE
|
||||
)
|
||||
|
||||
const prevData = await helloModuleService.retrieveCustom(id)
|
||||
|
||||
const custom = await helloModuleService.updateCustoms({
|
||||
id,
|
||||
custom_name,
|
||||
})
|
||||
|
||||
return new StepResponse(custom, prevData)
|
||||
},
|
||||
async (prevData, { container }) => {
|
||||
const helloModuleService: HelloModuleService = container.resolve(
|
||||
HELLO_MODULE
|
||||
)
|
||||
|
||||
await helloModuleService.updateCustoms(prevData)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In this step, you update a `Custom` record. In the compensation function, you revert the update.
|
||||
|
||||
Next, you'll create the step that deletes a `Custom` record. Create the file `src/workflows/update-custom-from-customer/steps/delete-custom.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/update-custom-from-customer/steps/delete-custom.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { Custom } from "../../../modules/hello/models/custom"
|
||||
import { InferTypeOf } from "@medusajs/framework/types"
|
||||
import HelloModuleService from "../../../modules/hello/service"
|
||||
import { HELLO_MODULE } from "../../../modules/hello"
|
||||
|
||||
type DeleteCustomStepInput = {
|
||||
custom: InferTypeOf<typeof Custom>
|
||||
}
|
||||
|
||||
export const deleteCustomStep = createStep(
|
||||
"delete-custom",
|
||||
async ({ custom }: DeleteCustomStepInput, { container }) => {
|
||||
const helloModuleService: HelloModuleService = container.resolve(
|
||||
HELLO_MODULE
|
||||
)
|
||||
|
||||
await helloModuleService.deleteCustoms(custom.id)
|
||||
|
||||
return new StepResponse(custom, custom)
|
||||
},
|
||||
async (custom, { container }) => {
|
||||
const helloModuleService: HelloModuleService = container.resolve(
|
||||
HELLO_MODULE
|
||||
)
|
||||
|
||||
await helloModuleService.createCustoms(custom)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In this step, you delete a `Custom` record. In the compensation function, you create it again.
|
||||
|
||||
Finally, you'll create the workflow. Create the file `src/workflows/update-custom-from-customer/index.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/update-custom-from-customer/index.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||
import { CustomerDTO } from "@medusajs/framework/types"
|
||||
import { createWorkflow, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
|
||||
import { createRemoteLinkStep, dismissRemoteLinkStep, useRemoteQueryStep } from "@medusajs/medusa/core-flows"
|
||||
import { createCustomStep } from "../create-custom-from-customer/steps/create-custom"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { HELLO_MODULE } from "../../modules/hello"
|
||||
import { deleteCustomStep } from "./steps/delete-custom"
|
||||
import { updateCustomStep } from "./steps/update-custom"
|
||||
|
||||
export type UpdateCustomFromCustomerStepInput = {
|
||||
customer: CustomerDTO
|
||||
additional_data?: {
|
||||
custom_name?: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export const updateCustomFromCustomerWorkflow = createWorkflow(
|
||||
"update-custom-from-customer",
|
||||
(input: UpdateCustomFromCustomerStepInput) => {
|
||||
const customerData = useRemoteQueryStep({
|
||||
entry_point: "customer",
|
||||
fields: ["custom.*"],
|
||||
variables: {
|
||||
filters: {
|
||||
id: input.customer.id,
|
||||
},
|
||||
},
|
||||
list: false,
|
||||
})
|
||||
|
||||
// TODO create, update, or delete Custom record
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The workflow accepts the same input as the `customersUpdated` workflow hook handler would.
|
||||
|
||||
In the workflow, you retrieve the customer's linked `Custom` record using Query.
|
||||
|
||||
Next, replace the `TODO` with the following:
|
||||
|
||||
```ts title="src/workflows/update-custom-from-customer/index.ts"
|
||||
const created = when({
|
||||
input,
|
||||
customerData,
|
||||
}, (data) =>
|
||||
!data.customerData.custom &&
|
||||
data.input.additional_data?.custom_name?.length > 0
|
||||
)
|
||||
.then(() => {
|
||||
const custom = createCustomStep({
|
||||
custom_name: input.additional_data.custom_name,
|
||||
})
|
||||
|
||||
createRemoteLinkStep([{
|
||||
[Modules.CUSTOMER]: {
|
||||
customer_id: input.customer.id,
|
||||
},
|
||||
[HELLO_MODULE]: {
|
||||
custom_id: custom.id,
|
||||
},
|
||||
}])
|
||||
|
||||
return custom
|
||||
})
|
||||
|
||||
// TODO update, or delete Custom record
|
||||
```
|
||||
|
||||
Using the `when-then` utility, you check if the customer doesn't have a linked `Custom` record and the `custom_name` property is set. If so, you create a `Custom` record and link it to the customer.
|
||||
|
||||
To create the `Custom` record, you use the `createCustomStep` you created in an earlier section.
|
||||
|
||||
Next, replace the new `TODO` with the following:
|
||||
|
||||
```ts title="src/workflows/update-custom-from-customer/index.ts"
|
||||
const deleted = when({
|
||||
input,
|
||||
customerData,
|
||||
}, (data) =>
|
||||
data.customerData.custom && (
|
||||
data.input.additional_data?.custom_name === null ||
|
||||
data.input.additional_data?.custom_name.length === 0
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
deleteCustomStep({
|
||||
custom: customerData.custom,
|
||||
})
|
||||
|
||||
dismissRemoteLinkStep({
|
||||
[HELLO_MODULE]: {
|
||||
custom_id: customerData.custom.id,
|
||||
},
|
||||
})
|
||||
|
||||
return customerData.custom.id
|
||||
})
|
||||
|
||||
// TODO delete Custom record
|
||||
```
|
||||
|
||||
Using the `when-then` utility, you check if the customer has a linked `Custom` record and `custom_name` is `null` or an empty string. If so, you delete the linked `Custom` record and dismiss its links.
|
||||
|
||||
Finally, replace the new `TODO` with the following:
|
||||
|
||||
```ts title="src/workflows/update-custom-from-customer/index.ts"
|
||||
const updated = when({
|
||||
input,
|
||||
customerData,
|
||||
}, (data) => data.customerData.custom && data.input.additional_data?.custom_name?.length > 0)
|
||||
.then(() => {
|
||||
const custom = updateCustomStep({
|
||||
id: customerData.custom.id,
|
||||
custom_name: input.additional_data.custom_name,
|
||||
})
|
||||
|
||||
return custom
|
||||
})
|
||||
|
||||
return new WorkflowResponse({
|
||||
created,
|
||||
updated,
|
||||
deleted,
|
||||
})
|
||||
```
|
||||
|
||||
Using the `when-then` utility, you check if the customer has a linked `Custom` record and `custom_name` is passed in the `additional_data`. If so, you update the linked `Custom` recod.
|
||||
|
||||
You return in the workflow response the created, updated, and deleted `Custom` record.
|
||||
|
||||
### Consume customersUpdated Workflow Hook
|
||||
|
||||
You can now consume the `customersUpdated` and execute the workflow you created.
|
||||
|
||||
Create the file `src/workflows/hooks/customer-updated.ts` with the following content:
|
||||
|
||||
```ts title="src/workflows/hooks/customer-updated.ts"
|
||||
import { updateCustomersWorkflow } from "@medusajs/medusa/core-flows"
|
||||
import {
|
||||
UpdateCustomFromCustomerStepInput,
|
||||
updateCustomFromCustomerWorkflow,
|
||||
} from "../update-custom-from-customer"
|
||||
|
||||
updateCustomersWorkflow.hooks.customersUpdated(
|
||||
async ({ customers, additional_data }, { container }) => {
|
||||
const workflow = updateCustomFromCustomerWorkflow(container)
|
||||
|
||||
for (const customer of customers) {
|
||||
await workflow.run({
|
||||
input: {
|
||||
customer,
|
||||
additional_data,
|
||||
} as UpdateCustomFromCustomerStepInput,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the workflow hook handler, you execute the workflow, passing it the hook's input.
|
||||
|
||||
### Test it Out
|
||||
|
||||
To test it out, send a `POST` request to `/admin/customers/:id` to update a customer, passing `custom_name` in `additional_data`:
|
||||
|
||||
```bash
|
||||
curl -X POST 'localhost:9000/admin/customers/{customer_id}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Authorization: Bearer {token}' \
|
||||
--data '{
|
||||
"additional_data": {
|
||||
"custom_name": "test3"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Make sure to replace `{customer_id}` with the customer's ID, and `{token}` with the JWT token of an admin user.
|
||||
|
||||
The request will return the customer's details with the updated `custom` linked record.
|
||||
@@ -6,7 +6,7 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Customer Module is the `@medusajs/medusa/customer` NPM package that provides customer-related features in your Medusa and Node.js applications.
|
||||
The Customer Module provides customer-related features in your Medusa and Node.js applications.
|
||||
|
||||
## How to Use Customer Module's Service
|
||||
|
||||
@@ -15,15 +15,30 @@ You can use the Customer Module's main service by resolving from the Medusa cont
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const customerModuleService = container.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService: ICustomerModuleService = request.scope.resolve(
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
@@ -36,35 +51,17 @@ export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const customerModuleService: ICustomerModuleService = container.resolve(
|
||||
const customerModuleService = container.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { ICustomerModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const customerModuleService: ICustomerModuleService = container.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
@@ -76,7 +73,7 @@ const step1 = createStep("step-1", async (_, { container }) => {
|
||||
|
||||
### Customer Management
|
||||
|
||||
With the Customer Module, store and manage customers in your store.
|
||||
With the Customer Module, store and manage customers.
|
||||
|
||||
```ts
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
@@ -88,7 +85,9 @@ const customer = await customerModuleService.createCustomers({
|
||||
|
||||
### Customer Organization
|
||||
|
||||
You can organize customers into groups. This has a lot of benefits and supports more use cases, such as provide discounts for specific customer groups using the Promotion Module.
|
||||
Organize customers into groups.
|
||||
|
||||
This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the Promotion Module.
|
||||
|
||||
```ts
|
||||
const customerGroup = await customerModuleService.createCustomerGroups({
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Customer Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document showcases the link modules defined between the Customer Module and other commerce modules.
|
||||
|
||||
## Cart Module
|
||||
|
||||
A customer has a cart to purchase items from the store. Medusa defines a link module that builds a relationship between the `Customer` and the `Cart` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Order Module
|
||||
|
||||
A customer is associated with the orders they place. Medusa defines a link module that builds a relationship between the `Customer` and the `Order` data models.
|
||||
|
||||

|
||||
|
||||
@@ -10,7 +10,7 @@ In this document, you’ll learn what a fulfillment module provider is.
|
||||
|
||||
A fulfillment module provider handles fulfilling items, typically using a third-party integration.
|
||||
|
||||
Fulfillment module providers are stored and represented by the [FulfillmentProvider data model](/references/fulfillment/models/FulfillmentProvider).
|
||||
Fulfillment module providers registered in the Fulfillment Module's [options](../module-options/page.mdx) are stored and represented by the [FulfillmentProvider data model](/references/fulfillment/models/FulfillmentProvider).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ A fulfillment is the shipping and delivery of one or more items to the customer.
|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Processing
|
||||
## Fulfillment Processing by a Fulfillment Provider
|
||||
|
||||
A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
export const metadata = {
|
||||
title: `Links between Fulfillment Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document showcases the module links defined between the Fulfillment Module and other commerce modules.
|
||||
|
||||
## Order Module
|
||||
|
||||
The Order Module provides order-management functionalities.
|
||||
|
||||
Medusa defines a link between the `Fulfillment` and `Order` data models. A fulfillment is created for an orders' items.
|
||||
|
||||

|
||||
|
||||
A fulfillment is also created for a return's items. So, Medusa defines a link between the `Fulfillment` and `Return` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Pricing Module
|
||||
|
||||
The Pricing Module provides features to store, manage, and retrieve the best prices in a specified context.
|
||||
|
||||
Medusa defines a link between the `PriceSet` and `ShippingOption` data models. A shipping option's price is stored as a price set.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Stock Location Module
|
||||
|
||||
The Stock Location Module provides features to manage stock locations in a store.
|
||||
|
||||
Medusa defines a link between the `FulfillmentSet` and `StockLocation` data models. A fulfillment set can be conditioned to a specific stock location.
|
||||
|
||||

|
||||
|
||||
Medusa also defines a link between the `FulfillmentProvider` and `StockLocation` data models to indicate the providers that can be used in a location.
|
||||
|
||||

|
||||
@@ -6,7 +6,7 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Fulfillment Module is the `@medusajs/medusa/fulfillment` NPM package that provides fulfillment-related features in your Medusa and Node.js applications.
|
||||
The Fulfillment Module provides fulfillment-related features in your Medusa and Node.js applications.
|
||||
|
||||
## How to Use Fulfillment Module's Service
|
||||
|
||||
@@ -15,18 +15,33 @@ You can use the Fulfillment Module's main service by resolving from the Medusa c
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const fulfillmentModuleService = container.resolve(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
|
||||
const fulfillments = await fulfillmentModuleService.listFulfillments()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IFulfillmentModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const fulfillmentModuleService: IFulfillmentModuleService = req.scope.resolve(
|
||||
const fulfillmentModuleService = req.scope.resolve(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
|
||||
@@ -39,35 +54,17 @@ export async function GET(
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { IFulfillmentModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const fulfillmentModuleService: IFulfillmentModuleService = container.resolve(
|
||||
const fulfillmentModuleService = container.resolve(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
|
||||
const fulfillments = await fulfillmentModuleService.listFulfillments()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IFulfillmentModuleService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const fulfillmentModuleService: IFulfillmentModuleService = container.resolve(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
|
||||
const fulfillments = await fulfillmentModuleService.listFulfillments()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Fulfillment Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document showcases the link modules defined between the Fulfillment Module and other commerce modules.
|
||||
|
||||
## Order Module
|
||||
|
||||
A fulfillment is created for an orders' items. Medusa defines a link module that builds a relationship between the `Fulfillment` and `Order` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Pricing Module
|
||||
|
||||
A shipping option's price is stored as a price set. Medusa defines a link module that builds a relationship between the `PriceSet` and `ShippingOption` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Stock Location Module
|
||||
|
||||
A fulfillment set can be conditioned to a specific stock location. Medusa defines a link module that builds a relationship between the `FulfillmentSet` and `StockLocation` data models.
|
||||
|
||||

|
||||
@@ -45,7 +45,7 @@ These rules are represented by the [ShippingOptionRule data model](/references/f
|
||||
|
||||

|
||||
|
||||
A shipping option can have multiple rules. For example, a shipping option is available if the customer belongs to the VIP group and the total weight is less than 2000g.
|
||||
A shipping option can have multiple rules. For example, you can add rules to a shipping option so that it's available if the customer belongs to the VIP group and the total weight is less than 2000g.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -34,6 +34,6 @@ The inventory level's location is determined by the `location_id` property. Medu
|
||||
|
||||
## ReservationItem
|
||||
|
||||
A reservation item, represented by the `ReservationItem` data model, represents unavailable quantity of an inventory item in a location. It's used when an order is placed but not fulfilled yet.
|
||||
A reservation item, represented by the [ReservationItem](/references/inventory-next/models/ReservationItem) data model, represents unavailable quantity of an inventory item in a location. It's used when an order is placed but not fulfilled yet.
|
||||
|
||||
The reserved quantity is associated with a location, so it has a similar relation to that of the `InventoryLevel` with the Stock Location Module.
|
||||
|
||||
@@ -13,16 +13,15 @@ In this document, you’ll find common examples of how you can use the Inventory
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -68,16 +67,15 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -114,16 +112,15 @@ export async function GET(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -170,16 +167,15 @@ export async function GET(request: Request, { params }: ContextType) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -225,16 +221,15 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -280,16 +275,15 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -335,16 +329,15 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -392,16 +385,15 @@ export async function POST(request: Request) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -479,16 +471,15 @@ export async function POST(request: Request, { params }: ContextType) {
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
|
||||
@@ -47,3 +47,7 @@ When an item in an order is fulfilled and the associated variant has its `manage
|
||||
When an item in an order is returned and the associated variant has its `manage_inventory` property set to `true`, the Medusa application increments the `stocked_quantity` of the inventory item's level with the returned quantity.
|
||||
|
||||

|
||||
|
||||
### Dismissed Returned Items
|
||||
|
||||
If a returned item is considered damanged or its dismissed, its quantity doesn't increment the `stocked_quantity` of the inventory item's level.
|
||||
@@ -0,0 +1,15 @@
|
||||
export const metadata = {
|
||||
title: `Links between Inventory Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document showcases the module links defined between the Inventory Module and other commerce modules.
|
||||
|
||||
## Product Module
|
||||
|
||||
Each product variant has different inventory details. Medusa defines a link between the `ProductVariant` and `InventoryItem` data models.
|
||||
|
||||

|
||||
|
||||
A product variant whose `manage_inventory` property is enabled has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
|
||||
@@ -6,7 +6,7 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Inventory Module is the `@medusajs/medusa/inventory-next` NPM package that provides inventory-related features in your Medusa and Node.js applications.
|
||||
The Inventory Module provides inventory-related features in your Medusa and Node.js applications.
|
||||
|
||||
## How to Use Inventory Module's Service
|
||||
|
||||
@@ -15,18 +15,33 @@ You can use the Inventory Module's main service by resolving from the Medusa con
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const inventoryModuleService = container.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryItems = await inventoryModuleService.listInventoryItems({})
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService = request.scope.resolve(
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
@@ -39,35 +54,17 @@ export async function GET(
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const inventoryModuleService: IInventoryService = container.resolve(
|
||||
const inventoryModuleService = container.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryItems = await inventoryModuleService.listInventoryItems({})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { IInventoryService } from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const inventoryModuleService: IInventoryService = container.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryItems = await inventoryModuleService.listInventoryItems({})
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
@@ -93,7 +90,9 @@ const inventoryItem = await inventoryModuleService.createInventoryItems({
|
||||
|
||||
### Inventory Across Locations
|
||||
|
||||
Inventory items' quantities are set per locations through inventory levels. This gives you more flexibility in managing the quantity of a stock-kept item across different locations, such as different warehouses.
|
||||
Inventory items' quantities are set per locations through inventory levels.
|
||||
|
||||
This gives you more flexibility in managing the quantity of a stock-kept item across different locations, such as different warehouses.
|
||||
|
||||
```ts
|
||||
const inventoryLevel = await inventoryModuleService.createInventoryLevels([
|
||||
@@ -107,7 +106,9 @@ const inventoryLevel = await inventoryModuleService.createInventoryLevels([
|
||||
|
||||
### Reservation Management
|
||||
|
||||
Reserve quantities of inventory items at specific locations for orders or other purposes. The reserved quantity isn't considered for purchase, but can be deleted to revert the reservation.
|
||||
Reserve quantities of inventory items at specific locations for orders or other purposes.
|
||||
|
||||
The reserved quantity isn't considered for purchase, but can be deleted to revert the reservation.
|
||||
|
||||
```ts
|
||||
const reservationItem = await inventoryModuleService.createReservationItems([
|
||||
@@ -121,7 +122,9 @@ const reservationItem = await inventoryModuleService.createReservationItems([
|
||||
|
||||
### Check Inventory Availability
|
||||
|
||||
Check whether an inventory item has the necessary quantity for purchase. Any reserved quantity is considered unavailable.
|
||||
Check whether an inventory item has the necessary quantity for purchase.
|
||||
|
||||
Any reserved quantity is considered unavailable.
|
||||
|
||||
```ts
|
||||
const isAvailable = await inventoryModuleService.confirmInventory(
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Inventory Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document showcases the link modules defined between the Inventory Module and other commerce modules.
|
||||
|
||||
## Product Module
|
||||
|
||||
Each product variant has different inventory details. Medusa defines a link module that builds a relationship between the `ProductVariant` and `InventoryItem` data models.
|
||||
|
||||

|
||||
|
||||
A product variant, whose `manage_inventory` property is enabled, has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
|
||||
|
||||
---
|
||||
|
||||
## Stock Location Module
|
||||
|
||||
Reservation items and inventory items are associated with a location. The Medusa application forms a relation between the `ReservationItem` and `StockLocation` data models, and the `InventoryLevel` and `StockLocation` data models.
|
||||
|
||||

|
||||
|
||||
The stock location provides address details of the location.
|
||||
Reference in New Issue
Block a user