|
|
|
@@ -14684,7 +14684,7 @@ The Index Module solves this problem by ingesting data into a central data store
|
|
|
|
|
|
|
|
|
|
### Ingested Data Models
|
|
|
|
|
|
|
|
|
|
All core data models in Medusa are ingested, including `Product`, `Price`, `SalesChannel`, and more. You can also index custom data models if they are linked to an ingested data model. You'll learn more about this in the [Ingest Custom Data Models](#how-to-ingest-custom-data-models) section.
|
|
|
|
|
Data models are ingested if they're linked to other data models with the `filterable` property set, as you'll learn in the [Ingest Custom Data Models](#how-to-ingest-custom-data-models) section. Medusa also ingests some core data models by default, such as `Product`, `ProductVariant`, `Price`, and `SalesChannel`.
|
|
|
|
|
|
|
|
|
|
Prior to [Medusa v2.10.2](https://github.com/medusajs/medusa/releases/tag/v2.10.2), only the `Product`, `ProductVariant`, `Price`, and `SalesChannel` data models were ingested. Make sure to update to the latest version to ingest all core data models.
|
|
|
|
|
|
|
|
|
@@ -14720,7 +14720,7 @@ npx medusa db:migrate
|
|
|
|
|
|
|
|
|
|
### Ingest Data
|
|
|
|
|
|
|
|
|
|
The Index Module only ingests data when you start your Medusa server. So, to ingest the [currently supported data models](#ingested-data-models), start the Medusa application:
|
|
|
|
|
The Index Module only ingests data when you start your Medusa server. So, to [ingest data models](#ingested-data-models), start the Medusa application:
|
|
|
|
|
|
|
|
|
|
```bash npm2yarn
|
|
|
|
|
npm run dev
|
|
|
|
@@ -14825,11 +14825,11 @@ The `index` method accepts an object with the same properties as the `graph` met
|
|
|
|
|
|
|
|
|
|
## How to Ingest Custom Data Models
|
|
|
|
|
|
|
|
|
|
Aside from the [core data models](#ingested-data-models), you can also ingest your own custom data models into the Index Module. You can do so by defining a link between your custom data model and one of the core data models, and setting the `filterable` property in the link definition.
|
|
|
|
|
You can ingest core and custom data models into the Index Module. You can do so by defining a link between your custom data model and one of the core data models, and setting the `filterable` property in the link definition.
|
|
|
|
|
|
|
|
|
|
Read-only links are not supported by the Index Module.
|
|
|
|
|
|
|
|
|
|
For example, assuming you have a Brand Module with a Brand data model (as explained in the [Customizations](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)), you can ingest it into the Index Module using the `filterable` property in its link definition to the Product data model:
|
|
|
|
|
For example, assuming you have a Brand Module with a Brand data model (as explained in the [Customizations](https://docs.medusajs.com/learn/customization/custom-features/module/index.html.md)), you can ingest it into the Index Module using the `filterable` property in its link definition to the `Product` data model:
|
|
|
|
|
|
|
|
|
|
```ts title="src/links/product-brand.ts" highlights={filterableHighlights}
|
|
|
|
|
import BrandModule from "../modules/brand"
|
|
|
|
@@ -24296,15 +24296,15 @@ In this chapter, you'll learn how to install and run a Medusa application.
|
|
|
|
|
|
|
|
|
|
## Get Started with Cloud
|
|
|
|
|
|
|
|
|
|
Cloud is Medusa's PaaS platform that allows you to deploy and manage production-ready Medusa applications with ease. Benefit from features like zero-configuration deployments, automatic scaling, and GitHub integration to streamline your development workflow.
|
|
|
|
|
[Cloud](https://docs.medusajs.com/cloud/index.html.md) is Medusa's PaaS platform that allows you to deploy and manage production-ready Medusa applications with ease. Benefit from features like zero-configuration deployments, automatic scaling, and GitHub integration to streamline your development workflow.
|
|
|
|
|
|
|
|
|
|
Refer to the [Sign Up](https://docs.medusajs.com/cloud/sign-up/index.html.md) guide to create your first Medusa project in minutes.
|
|
|
|
|
[Sign up](http://cloud.medusajs.com/signup) with Cloud and create your first Medusa project in minutes.
|
|
|
|
|
|
|
|
|
|
***
|
|
|
|
|
|
|
|
|
|
## Create Medusa Application Locally
|
|
|
|
|
|
|
|
|
|
A Medusa application is made up of a Node.js server and an admin dashboard. You can optionally install the [Next.js Starter Storefront](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) separately either while installing the Medusa application or at a later point.
|
|
|
|
|
A Medusa application is made up of a Node.js server and a Vite admin dashboard. You can optionally install the [Next.js Starter Storefront](https://docs.medusajs.com/resources/nextjs-starter/index.html.md) separately either while installing the Medusa application or at a later point.
|
|
|
|
|
|
|
|
|
|
While this is the recommended way to create a Medusa application, you can alternatively [install a Medusa application with Docker](https://docs.medusajs.com/learn/installation/docker/index.html.md).
|
|
|
|
|
|
|
|
|
@@ -65737,7 +65737,7 @@ export const TierRule = model.define("tier_rule", {
|
|
|
|
|
{
|
|
|
|
|
on: ["tier_id", "currency_code"],
|
|
|
|
|
unique: true,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -65876,7 +65876,7 @@ import CustomerModule from "@medusajs/medusa/customer"
|
|
|
|
|
export default defineLink(
|
|
|
|
|
{
|
|
|
|
|
linkable: TierModule.linkable.tier,
|
|
|
|
|
filterable: ["id"]
|
|
|
|
|
filterable: ["id"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
linkable: CustomerModule.linkable.customer,
|
|
|
|
@@ -66018,7 +66018,7 @@ export const createTierRulesStep = createStep(
|
|
|
|
|
const tierModuleService = container.resolve(TIER_MODULE)
|
|
|
|
|
|
|
|
|
|
const createdRules = await tierModuleService.createTierRules(
|
|
|
|
|
input.tier_rules.map(rule => ({
|
|
|
|
|
input.tier_rules.map((rule) => ({
|
|
|
|
|
tier_id: input.tier_id,
|
|
|
|
|
min_purchase_value: rule.min_purchase_value,
|
|
|
|
|
currency_code: rule.currency_code,
|
|
|
|
@@ -66033,7 +66033,7 @@ export const createTierRulesStep = createStep(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tierModuleService = container.resolve(TIER_MODULE)
|
|
|
|
|
await tierModuleService.deleteTierRules(createdRules.map(rule => rule.id))
|
|
|
|
|
await tierModuleService.deleteTierRules(createdRules.map((rule) => rule.id))
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
@@ -66082,7 +66082,7 @@ export const createTierWorkflow = createWorkflow(
|
|
|
|
|
},
|
|
|
|
|
options: {
|
|
|
|
|
throwIfKeyNotFound: true,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
// Create the tier
|
|
|
|
@@ -67005,7 +67005,7 @@ export const deleteTierRulesStep = createStep(
|
|
|
|
|
const tierModuleService: TierModuleService = container.resolve(TIER_MODULE)
|
|
|
|
|
// Restore deleted rules
|
|
|
|
|
await tierModuleService.createTierRules(
|
|
|
|
|
compensationData.map(rule => ({
|
|
|
|
|
compensationData.map((rule) => ({
|
|
|
|
|
tier_id: rule.tier_id,
|
|
|
|
|
min_purchase_value: rule.min_purchase_value,
|
|
|
|
|
currency_code: rule.currency_code,
|
|
|
|
@@ -67060,7 +67060,7 @@ export const updateTierWorkflow = createWorkflow(
|
|
|
|
|
},
|
|
|
|
|
options: {
|
|
|
|
|
throwIfKeyNotFound: true,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
// Validate promotion if provided
|
|
|
|
|
when({ input }, (data) => !!data.input.promo_id)
|
|
|
|
@@ -67073,7 +67073,7 @@ export const updateTierWorkflow = createWorkflow(
|
|
|
|
|
},
|
|
|
|
|
options: {
|
|
|
|
|
throwIfKeyNotFound: true,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}).config({ name: "retrieve-promotion" })
|
|
|
|
|
})
|
|
|
|
|
// Update the tier
|
|
|
|
@@ -67089,10 +67089,10 @@ export const updateTierWorkflow = createWorkflow(
|
|
|
|
|
const ids = transform({
|
|
|
|
|
tiers,
|
|
|
|
|
}, (data) => {
|
|
|
|
|
return (data.tiers[0].tier_rules?.map(rule => rule?.id) || []) as string[]
|
|
|
|
|
return (data.tiers[0].tier_rules?.map((rule) => rule?.id) || []) as string[]
|
|
|
|
|
})
|
|
|
|
|
deleteTierRulesStep({
|
|
|
|
|
ids
|
|
|
|
|
ids,
|
|
|
|
|
})
|
|
|
|
|
return createTierRulesStep({
|
|
|
|
|
tier_id: input.id,
|
|
|
|
@@ -67205,8 +67205,8 @@ export default defineMiddlewares({
|
|
|
|
|
matcher: "/admin/tiers/:id",
|
|
|
|
|
methods: ["POST"],
|
|
|
|
|
middlewares: [validateAndTransformBody(UpdateTierSchema)],
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -67288,7 +67288,7 @@ export default defineMiddlewares({
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -67319,8 +67319,8 @@ export default config({
|
|
|
|
|
// ...
|
|
|
|
|
{
|
|
|
|
|
resolve: "@medusajs/index",
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -67426,7 +67426,7 @@ const { data: storeData } = useQuery({
|
|
|
|
|
|
|
|
|
|
const updateTierMutation = useMutation({
|
|
|
|
|
mutationFn: async (data: EditTierFormData) => {
|
|
|
|
|
if (!tier) return
|
|
|
|
|
if (!tier) {return}
|
|
|
|
|
return await sdk.client.fetch(`/admin/tiers/${tier.id}`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: data,
|
|
|
|
@@ -67984,7 +67984,7 @@ In `src/admin/routes/tiers/page.tsx`, find the `onRowClick` handler and replace
|
|
|
|
|
```tsx title="src/admin/routes/tiers/page.tsx"
|
|
|
|
|
onRowClick: (_event, row) => {
|
|
|
|
|
navigate(`/tiers/${row.id}`)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You navigate to the tier details page when the user clicks on a tier in the tiers list.
|
|
|
|
@@ -68042,12 +68042,12 @@ class TierModuleService extends MedusaService({
|
|
|
|
|
}) {
|
|
|
|
|
async calculateQualifyingTier(
|
|
|
|
|
currencyCode: string,
|
|
|
|
|
purchaseValue: number,
|
|
|
|
|
purchaseValue: number
|
|
|
|
|
) {
|
|
|
|
|
const rules = await this.listTierRules(
|
|
|
|
|
{
|
|
|
|
|
currency_code: currencyCode,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!rules || rules.length === 0) {
|
|
|
|
@@ -68205,7 +68205,7 @@ export const updateCustomerTierOnOrderWorkflow = createWorkflow(
|
|
|
|
|
},
|
|
|
|
|
options: {
|
|
|
|
|
throwIfKeyNotFound: true,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const validatedCustomer = validateCustomerStep({
|
|
|
|
@@ -68223,8 +68223,8 @@ export const updateCustomerTierOnOrderWorkflow = createWorkflow(
|
|
|
|
|
$nin: [
|
|
|
|
|
OrderStatus.CANCELED,
|
|
|
|
|
OrderStatus.DRAFT,
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}).config({ name: "completed-orders" })
|
|
|
|
|
|
|
|
|
@@ -68638,8 +68638,8 @@ updateCartPromotionsWorkflow.hooks.validate(async ({ input, cart }, { container
|
|
|
|
|
entity: "tier",
|
|
|
|
|
fields: ["id", "promo_id"],
|
|
|
|
|
filters: {
|
|
|
|
|
promo_id: promotions.map((p) => p.id)
|
|
|
|
|
}
|
|
|
|
|
promo_id: promotions.map((p) => p.id),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Validate each promotion being added
|
|
|
|
@@ -68714,8 +68714,8 @@ completeCartWorkflow.hooks.validate(async ({ cart }, { container }) => {
|
|
|
|
|
entity: "tier",
|
|
|
|
|
fields: ["id", "promo_id"],
|
|
|
|
|
filters: {
|
|
|
|
|
promo_id: detailedCart.promotions.map((p) => p?.id).filter(Boolean) as string[]
|
|
|
|
|
}
|
|
|
|
|
promo_id: detailedCart.promotions.map((p) => p?.id).filter(Boolean) as string[],
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Validate that if a tier promotion is applied, the customer belongs to that tier
|
|
|
|
@@ -68761,7 +68761,7 @@ class TierModuleService extends MedusaService({
|
|
|
|
|
// ...
|
|
|
|
|
async calculateNextTierUpgrade(
|
|
|
|
|
currencyCode: string,
|
|
|
|
|
currentPurchaseValue: number,
|
|
|
|
|
currentPurchaseValue: number
|
|
|
|
|
) {
|
|
|
|
|
const rules = await this.listTierRules(
|
|
|
|
|
{
|
|
|
|
@@ -68866,7 +68866,7 @@ export async function GET(
|
|
|
|
|
|
|
|
|
|
// Get currency code from cart or region context
|
|
|
|
|
// Try to get from cart first, then region
|
|
|
|
|
let regionId = req.validatedQuery.region_id
|
|
|
|
|
const regionId = req.validatedQuery.region_id
|
|
|
|
|
|
|
|
|
|
// Calculate total purchase value
|
|
|
|
|
const { data: orders } = await query.graph({
|
|
|
|
@@ -68879,7 +68879,7 @@ export async function GET(
|
|
|
|
|
$nin: [
|
|
|
|
|
OrderStatus.CANCELED,
|
|
|
|
|
OrderStatus.DRAFT,
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
@@ -68911,7 +68911,7 @@ export async function GET(
|
|
|
|
|
const currentTier = customer.tier || null
|
|
|
|
|
|
|
|
|
|
// Determine next tier upgrade
|
|
|
|
|
let nextTierUpgrade = await tierModuleService.calculateNextTierUpgrade(
|
|
|
|
|
const nextTierUpgrade = await tierModuleService.calculateNextTierUpgrade(
|
|
|
|
|
currencyCode as string,
|
|
|
|
|
totalPurchaseValue
|
|
|
|
|
)
|
|
|
|
@@ -69018,9 +69018,9 @@ export const retrieveCustomerNextTier =
|
|
|
|
|
const authHeaders = await getAuthHeaders()
|
|
|
|
|
const region = await getRegion(countryCode)
|
|
|
|
|
|
|
|
|
|
if (!region) return null
|
|
|
|
|
if (!region) {return null}
|
|
|
|
|
|
|
|
|
|
if (!authHeaders) return null
|
|
|
|
|
if (!authHeaders) {return null}
|
|
|
|
|
|
|
|
|
|
const headers = {
|
|
|
|
|
...authHeaders,
|
|
|
|
@@ -69037,7 +69037,7 @@ export const retrieveCustomerNextTier =
|
|
|
|
|
next,
|
|
|
|
|
query: {
|
|
|
|
|
region_id: region.id,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.then((data) => data)
|
|
|
|
|
.catch(() => null)
|
|
|
|
@@ -94107,7 +94107,7 @@ To create the workflow, create the file `src/workflows/sync-products.ts` with th
|
|
|
|
|
import {
|
|
|
|
|
createWorkflow,
|
|
|
|
|
transform,
|
|
|
|
|
WorkflowResponse
|
|
|
|
|
WorkflowResponse,
|
|
|
|
|
} from "@medusajs/framework/workflows-sdk"
|
|
|
|
|
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
|
|
|
|
import { syncProductsStep, SyncProductsStepInput } from "./steps/sync-products"
|
|
|
|
@@ -94123,11 +94123,11 @@ export const syncProductsWorkflow = createWorkflow(
|
|
|
|
|
"sync-products",
|
|
|
|
|
({ filters, limit, offset }: SyncProductsWorkflowInput) => {
|
|
|
|
|
const productFilters = transform({
|
|
|
|
|
filters
|
|
|
|
|
filters,
|
|
|
|
|
}, (data) => {
|
|
|
|
|
return {
|
|
|
|
|
status: ProductStatus.PUBLISHED,
|
|
|
|
|
...data.filters
|
|
|
|
|
...data.filters,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
const { data, metadata } = useQueryGraphStep({
|
|
|
|
@@ -101971,7 +101971,7 @@ To create the workflow, create the file `src/workflows/sync-products.ts` with th
|
|
|
|
|
import {
|
|
|
|
|
createWorkflow,
|
|
|
|
|
transform,
|
|
|
|
|
WorkflowResponse
|
|
|
|
|
WorkflowResponse,
|
|
|
|
|
} from "@medusajs/framework/workflows-sdk"
|
|
|
|
|
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
|
|
|
|
import { syncProductsStep, SyncProductsStepInput } from "./steps/sync-products"
|
|
|
|
@@ -101987,11 +101987,11 @@ export const syncProductsWorkflow = createWorkflow(
|
|
|
|
|
"sync-products",
|
|
|
|
|
({ filters, limit, offset }: SyncProductsWorkflowInput) => {
|
|
|
|
|
const productFilters = transform({
|
|
|
|
|
filters
|
|
|
|
|
filters,
|
|
|
|
|
}, (data) => {
|
|
|
|
|
return {
|
|
|
|
|
status: ProductStatus.PUBLISHED,
|
|
|
|
|
...data.filters
|
|
|
|
|
...data.filters,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
const { data, metadata } = useQueryGraphStep({
|
|
|
|
|