fix(medusa): Add inventory decoration for cart endpoints (#5187)
**What** - decorate item totals for `cart.item.variant` when adding a line-item and retrieving a cart closes #5181
This commit is contained in:
1 parent
ca05436fc1
commit
9ff22110a6
19 files changed
+370
-77
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): decorate inventory for variants returned through carts
|
||||||
@@ -234,6 +234,54 @@ describe("/store/carts", () => {
|
|||||||
expect(count).toEqual(0)
|
expect(count).toEqual(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should decorate line item variant inventory_quantity when creating a line-item", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const cartId = "test-cart"
|
||||||
|
|
||||||
|
// Add standard line item to cart
|
||||||
|
const addCart = await api
|
||||||
|
.post(
|
||||||
|
`/store/carts/${cartId}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: variantId,
|
||||||
|
quantity: 3,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(addCart.status).toEqual(200)
|
||||||
|
expect(addCart.data.cart.items[0].variant.inventory_quantity).toEqual(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should decorate line item variant inventory_quantity when getting cart", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const cartId = "test-cart"
|
||||||
|
|
||||||
|
// Add standard line item to cart
|
||||||
|
await api
|
||||||
|
.post(
|
||||||
|
`/store/carts/${cartId}/line-items`,
|
||||||
|
{
|
||||||
|
variant_id: variantId,
|
||||||
|
quantity: 3,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
const cartResponse = await api
|
||||||
|
.get(`/store/carts/${cartId}`, { withCredentials: true })
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(cartResponse.status).toEqual(200)
|
||||||
|
expect(
|
||||||
|
cartResponse.data.cart.items[0].variant.inventory_quantity
|
||||||
|
).toEqual(5)
|
||||||
|
})
|
||||||
|
|
||||||
it("fails to add a item on the cart if the inventory isn't enough", async () => {
|
it("fails to add a item on the cart if the inventory isn't enough", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ const adminSeeder = require("../../../../helpers/admin-seeder")
|
|||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
const { simpleProductFactory } = require("../../../../factories")
|
const {
|
||||||
|
simpleProductFactory,
|
||||||
|
simpleSalesChannelFactory,
|
||||||
|
} = require("../../../../factories")
|
||||||
|
|
||||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||||
|
|
||||||
@@ -53,7 +56,17 @@ describe("Get products", () => {
|
|||||||
"productVariantInventoryService"
|
"productVariantInventoryService"
|
||||||
)
|
)
|
||||||
const inventoryService = appContainer.resolve("inventoryService")
|
const inventoryService = appContainer.resolve("inventoryService")
|
||||||
|
const locationService = appContainer.resolve("stockLocationService")
|
||||||
|
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||||
|
const salesChannelLocationService = appContainer.resolve(
|
||||||
|
"salesChannelLocationService"
|
||||||
|
)
|
||||||
|
|
||||||
|
const salesChannel = await simpleSalesChannelFactory(dbConnection, {
|
||||||
|
is_default: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const location = await locationService.create({ name: "test-location" })
|
||||||
await simpleProductFactory(
|
await simpleProductFactory(
|
||||||
dbConnection,
|
dbConnection,
|
||||||
{
|
{
|
||||||
@@ -63,7 +76,11 @@ describe("Get products", () => {
|
|||||||
},
|
},
|
||||||
100
|
100
|
||||||
)
|
)
|
||||||
|
await salesChannelService.addProducts(salesChannel.id, [productId])
|
||||||
|
await salesChannelLocationService.associateLocation(
|
||||||
|
salesChannel.id,
|
||||||
|
location.id
|
||||||
|
)
|
||||||
invItem = await inventoryService.createInventoryItem({
|
invItem = await inventoryService.createInventoryItem({
|
||||||
sku: "test-sku",
|
sku: "test-sku",
|
||||||
})
|
})
|
||||||
@@ -72,33 +89,62 @@ describe("Get products", () => {
|
|||||||
variantId,
|
variantId,
|
||||||
invItem.id
|
invItem.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await inventoryService.createInventoryLevel({
|
||||||
|
inventory_item_id: invItem.id,
|
||||||
|
location_id: location.id,
|
||||||
|
stocked_quantity: 100,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("Expands inventory items when getting product with expand parameters", async () => {
|
describe("/store/products/:id", () => {
|
||||||
const api = useApi()
|
it("Expands inventory items when getting product with expand parameters", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
const res = await api.get(
|
const res = await api.get(
|
||||||
`/store/products/${productId}?expand=variants,variants.inventory_items`,
|
`/store/products/${productId}?expand=variants,variants.inventory_items`
|
||||||
adminHeaders
|
)
|
||||||
)
|
|
||||||
|
|
||||||
expect(res.status).toEqual(200)
|
expect(res.status).toEqual(200)
|
||||||
expect(res.data.product).toEqual(
|
expect(res.data.product).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productId,
|
id: productId,
|
||||||
variants: [
|
variants: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: variantId,
|
id: variantId,
|
||||||
inventory_items: [
|
inventory_items: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
inventory_item_id: invItem.id,
|
inventory_item_id: invItem.id,
|
||||||
variant_id: variantId,
|
variant_id: variantId,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({})
|
expect.objectContaining({})
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("/admin/products/:id", () => {
|
||||||
|
it("should get inventory quantity for products fetched through the admin api", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const res = await api.get(`/admin/products/${productId}`, adminHeaders)
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200)
|
||||||
|
expect(res.data.product).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: productId,
|
||||||
|
variants: [
|
||||||
|
expect.objectContaining({
|
||||||
|
id: variantId,
|
||||||
|
inventory_quantity: 100,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
expect.objectContaining({})
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -11,6 +11,7 @@ import path from "path"
|
|||||||
import adminSeeder from "../../../../helpers/admin-seeder"
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
|
import { createDefaultRuleTypes } from "../../../helpers/create-default-rule-types"
|
||||||
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
|
import { createVariantPriceSet } from "../../../helpers/create-variant-price-set"
|
||||||
|
import { AxiosInstance } from "axios"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ describe("[Product & Pricing Module] POST /admin/products/:id/variants/:id", ()
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should create product variant price sets and prices", async () => {
|
it("should create product variant price sets and prices", async () => {
|
||||||
const api = useApi()
|
const api = useApi()! as AxiosInstance
|
||||||
const data = {
|
const data = {
|
||||||
title: "test variant update",
|
title: "test variant update",
|
||||||
prices: [
|
prices: [
|
||||||
@@ -144,7 +145,7 @@ describe("[Product & Pricing Module] POST /admin/products/:id/variants/:id", ()
|
|||||||
|
|
||||||
const moneyAmountToUpdate = priceSet.money_amounts?.[0]
|
const moneyAmountToUpdate = priceSet.money_amounts?.[0]
|
||||||
|
|
||||||
const api = useApi()
|
const api = useApi()! as AxiosInstance
|
||||||
const data = {
|
const data = {
|
||||||
title: "test variant update",
|
title: "test variant update",
|
||||||
prices: [
|
prices: [
|
||||||
@@ -202,7 +203,7 @@ describe("[Product & Pricing Module] POST /admin/products/:id/variants/:id", ()
|
|||||||
prices: [],
|
prices: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const api = useApi()
|
const api = useApi()! as AxiosInstance
|
||||||
const data = {
|
const data = {
|
||||||
title: "test variant update",
|
title: "test variant update",
|
||||||
prices: [
|
prices: [
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { MedusaError } from "@medusajs/utils"
|
import {
|
||||||
|
PricingService,
|
||||||
|
ProductService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
SalesChannelService,
|
||||||
|
} from "../../../../services"
|
||||||
|
|
||||||
import IsolateProductDomainFeatureFlag from "../../../../loaders/feature-flags/isolate-product-domain"
|
import IsolateProductDomainFeatureFlag from "../../../../loaders/feature-flags/isolate-product-domain"
|
||||||
import { PricingService, ProductService } from "../../../../services"
|
import { MedusaError } from "@medusajs/utils"
|
||||||
import { FindParams } from "../../../../types/common"
|
import { FindParams } from "../../../../types/common"
|
||||||
import { defaultAdminProductRemoteQueryObject } from "./index"
|
import { defaultAdminProductRemoteQueryObject } from "./index"
|
||||||
|
|
||||||
@@ -63,6 +69,12 @@ export default async (req, res) => {
|
|||||||
const pricingService: PricingService = req.scope.resolve("pricingService")
|
const pricingService: PricingService = req.scope.resolve("pricingService")
|
||||||
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
const salesChannelService: SalesChannelService = req.scope.resolve(
|
||||||
|
"salesChannelService"
|
||||||
|
)
|
||||||
|
|
||||||
let rawProduct
|
let rawProduct
|
||||||
if (featureFlagRouter.isFeatureEnabled(IsolateProductDomainFeatureFlag.key)) {
|
if (featureFlagRouter.isFeatureEnabled(IsolateProductDomainFeatureFlag.key)) {
|
||||||
rawProduct = await getProductWithIsolatedProductModule(
|
rawProduct = await getProductWithIsolatedProductModule(
|
||||||
@@ -81,10 +93,29 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const product = rawProduct
|
const product = rawProduct
|
||||||
|
|
||||||
|
const decoratePromises: Promise<any>[] = []
|
||||||
if (shouldSetPricing) {
|
if (shouldSetPricing) {
|
||||||
await pricingService.setAdminProductPricing([product])
|
decoratePromises.push(pricingService.setAdminProductPricing([product]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldSetAvailability =
|
||||||
|
req.retrieveConfig.relations?.includes("variants")
|
||||||
|
|
||||||
|
if (shouldSetAvailability) {
|
||||||
|
const [salesChannelsIds] = await salesChannelService.listAndCount(
|
||||||
|
{},
|
||||||
|
{ select: ["id"] }
|
||||||
|
)
|
||||||
|
|
||||||
|
decoratePromises.push(
|
||||||
|
productVariantInventoryService.setProductAvailability(
|
||||||
|
[product],
|
||||||
|
salesChannelsIds.map((salesChannel) => salesChannel.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await Promise.all(decoratePromises)
|
||||||
|
|
||||||
res.json({ product })
|
res.json({ product })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IdMap } from "medusa-test-utils"
|
|
||||||
import { request } from "../../../../../helpers/test-request"
|
|
||||||
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
import { LineItemServiceMock } from "../../../../../services/__mocks__/line-item"
|
import { LineItemServiceMock } from "../../../../../services/__mocks__/line-item"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
|
||||||
describe("POST /store/carts", () => {
|
describe("POST /store/carts", () => {
|
||||||
describe("successfully creates a cart", () => {
|
describe("successfully creates a cart", () => {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { IsOptional, IsString } from "class-validator"
|
import { IsOptional, IsString } from "class-validator"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
|
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
@@ -66,6 +69,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
await manager.transaction(async (m) => {
|
await manager.transaction(async (m) => {
|
||||||
const txCartService = cartService.withTransaction(m)
|
const txCartService = cartService.withTransaction(m)
|
||||||
@@ -91,6 +96,11 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(data, []) })
|
res.status(200).json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { MedusaContainer } from "@medusajs/modules-sdk"
|
|
||||||
import {
|
import {
|
||||||
createCart as createCartWorkflow,
|
CartService,
|
||||||
Workflows,
|
LineItemService,
|
||||||
} from "@medusajs/workflows"
|
ProductVariantInventoryService,
|
||||||
import { Type } from "class-transformer"
|
RegionService,
|
||||||
|
} from "../../../../services"
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsInt,
|
IsInt,
|
||||||
@@ -12,21 +12,23 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { isDefined, MedusaError } from "medusa-core-utils"
|
import { MedusaError, isDefined } from "medusa-core-utils"
|
||||||
import reqIp from "request-ip"
|
|
||||||
import { EntityManager } from "typeorm"
|
|
||||||
import { FlagRouter } from "@medusajs/utils"
|
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
|
||||||
import { LineItem } from "../../../../models"
|
|
||||||
import {
|
import {
|
||||||
CartService,
|
Workflows,
|
||||||
LineItemService,
|
createCart as createCartWorkflow,
|
||||||
RegionService,
|
} from "@medusajs/workflows"
|
||||||
} from "../../../../services"
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
|
|
||||||
import { CartCreateProps } from "../../../../types/cart"
|
import { CartCreateProps } from "../../../../types/cart"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { EntityManager } from "typeorm"
|
||||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||||
|
import { FlagRouter } from "@medusajs/utils"
|
||||||
|
import { LineItem } from "../../../../models"
|
||||||
|
import { MedusaContainer } from "@medusajs/modules-sdk"
|
||||||
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
import reqIp from "request-ip"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /store/carts
|
* @oas [post] /store/carts
|
||||||
@@ -82,6 +84,8 @@ export default async (req, res) => {
|
|||||||
const entityManager: EntityManager = req.scope.resolve("manager")
|
const entityManager: EntityManager = req.scope.resolve("manager")
|
||||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
const validated = req.validatedBody as StorePostCartReq
|
const validated = req.validatedBody as StorePostCartReq
|
||||||
|
|
||||||
@@ -219,6 +223,11 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
cart.items.map((i) => i.variant),
|
||||||
|
cart.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(cart, []) })
|
res.status(200).json({ cart: cleanResponseData(cart, []) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ import { FlagRouter } from "@medusajs/utils"
|
|||||||
import { AwilixContainer } from "awilix"
|
import { AwilixContainer } from "awilix"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { Cart } from "../../../../../../models"
|
import { Cart } from "../../../../../../models"
|
||||||
import { CartService, LineItemService } from "../../../../../../services"
|
import {
|
||||||
|
CartService,
|
||||||
|
LineItemService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../../../services"
|
||||||
import { WithRequiredProperty } from "../../../../../../types/common"
|
import { WithRequiredProperty } from "../../../../../../types/common"
|
||||||
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
|
import { IdempotencyCallbackResult } from "../../../../../../types/idempotency-key"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "../../index"
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "../../index"
|
||||||
|
import SalesChannelFeatureFlag from "../../../../../../loaders/feature-flags/sales-channels"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
export const CreateLineItemSteps = {
|
export const CreateLineItemSteps = {
|
||||||
STARTED: "started",
|
STARTED: "started",
|
||||||
@@ -26,6 +32,9 @@ export async function handleAddOrUpdateLineItem(
|
|||||||
const lineItemService: LineItemService = container.resolve("lineItemService")
|
const lineItemService: LineItemService = container.resolve("lineItemService")
|
||||||
const featureFlagRouter: FlagRouter = container.resolve("featureFlagRouter")
|
const featureFlagRouter: FlagRouter = container.resolve("featureFlagRouter")
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
container.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
const txCartService = cartService.withTransaction(manager)
|
const txCartService = cartService.withTransaction(manager)
|
||||||
|
|
||||||
let cart = await txCartService.retrieve(cartId, {
|
let cart = await txCartService.retrieve(cartId, {
|
||||||
@@ -43,17 +52,30 @@ export async function handleAddOrUpdateLineItem(
|
|||||||
validateSalesChannels: featureFlagRouter.isFeatureEnabled("sales_channels"),
|
validateSalesChannels: featureFlagRouter.isFeatureEnabled("sales_channels"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const relations = [
|
||||||
|
...defaultStoreCartRelations,
|
||||||
|
"billing_address",
|
||||||
|
"region.payment_providers",
|
||||||
|
"payment_sessions",
|
||||||
|
"customer",
|
||||||
|
]
|
||||||
|
|
||||||
|
const shouldSetAvailability =
|
||||||
|
relations?.some((rel) => rel.includes("variant")) &&
|
||||||
|
featureFlagRouter.isFeatureEnabled(SalesChannelFeatureFlag.key)
|
||||||
|
|
||||||
cart = await txCartService.retrieveWithTotals(cart.id, {
|
cart = await txCartService.retrieveWithTotals(cart.id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: [
|
relations,
|
||||||
...defaultStoreCartRelations,
|
|
||||||
"billing_address",
|
|
||||||
"region.payment_providers",
|
|
||||||
"payment_sessions",
|
|
||||||
"customer",
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (shouldSetAvailability) {
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
cart.items.map((i) => i.variant),
|
||||||
|
cart.sales_channel_id!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (cart.payment_sessions?.length) {
|
if (cart.payment_sessions?.length) {
|
||||||
await txCartService.setPaymentSessions(
|
await txCartService.setPaymentSessions(
|
||||||
cart as WithRequiredProperty<Cart, "total">
|
cart as WithRequiredProperty<Cart, "total">
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import IdempotencyKeyService from "../../../../services/idempotency-key"
|
import IdempotencyKeyService from "../../../../services/idempotency-key"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
@@ -55,6 +59,10 @@ export default async (req, res) => {
|
|||||||
const idempotencyKeyService: IdempotencyKeyService = req.scope.resolve(
|
const idempotencyKeyService: IdempotencyKeyService = req.scope.resolve(
|
||||||
"idempotencyKeyService"
|
"idempotencyKeyService"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
|
||||||
const headerKey = req.get("Idempotency-Key") || ""
|
const headerKey = req.get("Idempotency-Key") || ""
|
||||||
@@ -98,6 +106,11 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
cart.items.map((i) => i.variant),
|
||||||
|
cart.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response_code: 200,
|
response_code: 200,
|
||||||
response_body: { cart },
|
response_body: { cart },
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { EntityManager } from "typeorm"
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +57,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
await manager.transaction(async (m) => {
|
await manager.transaction(async (m) => {
|
||||||
// Remove the discount
|
// Remove the discount
|
||||||
@@ -73,5 +79,10 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(data, []) })
|
res.status(200).json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { EntityManager } from "typeorm"
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +57,9 @@ export default async (req, res) => {
|
|||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
await manager.transaction(async (m) => {
|
await manager.transaction(async (m) => {
|
||||||
const cartServiceTx = cartService.withTransaction(m)
|
const cartServiceTx = cartService.withTransaction(m)
|
||||||
|
|
||||||
@@ -74,5 +81,10 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(data, []) })
|
res.status(200).json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
@@ -52,6 +56,9 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
await manager.transaction(async (transactionManager) => {
|
await manager.transaction(async (transactionManager) => {
|
||||||
return await cartService
|
return await cartService
|
||||||
@@ -64,5 +71,10 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(data, []) })
|
res.status(200).json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import { CartService } from "../../../../services"
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +55,9 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const cart = await cartService.retrieve(id, {
|
||||||
select: ["id", "customer_id"],
|
select: ["id", "customer_id"],
|
||||||
@@ -69,7 +77,27 @@ export default async (req, res) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const shouldSetAvailability = req.retrieveConfig.relations?.some((rel) =>
|
||||||
|
rel.includes("variant")
|
||||||
|
)
|
||||||
|
|
||||||
|
const select = [...req.retrieveConfig.select]
|
||||||
|
const salesChannelsEnabled = featureFlagRouter.isFeatureEnabled(
|
||||||
|
SalesChannelFeatureFlag.key
|
||||||
|
)
|
||||||
|
|
||||||
|
if (salesChannelsEnabled) {
|
||||||
|
select.push("sales_channel_id")
|
||||||
|
}
|
||||||
|
|
||||||
const data = await cartService.retrieveWithTotals(id, req.retrieveConfig)
|
const data = await cartService.retrieveWithTotals(id, req.retrieveConfig)
|
||||||
|
|
||||||
|
if (shouldSetAvailability) {
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
res.json({ cart: cleanResponseData(data, []) })
|
res.json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
|
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { IsString } from "class-validator"
|
import { IsString } from "class-validator"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
@@ -67,6 +70,9 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
await manager.transaction(async (transactionManager) => {
|
await manager.transaction(async (transactionManager) => {
|
||||||
return await cartService
|
return await cartService
|
||||||
@@ -79,6 +85,11 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(data, []) })
|
res.status(200).json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsEmail,
|
IsEmail,
|
||||||
@@ -7,15 +11,14 @@ import {
|
|||||||
} from "class-validator"
|
} from "class-validator"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
|
|
||||||
import { Type } from "class-transformer"
|
|
||||||
import { EntityManager } from "typeorm"
|
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
import { AddressPayload } from "../../../../types/common"
|
import { AddressPayload } from "../../../../types/common"
|
||||||
|
import { EntityManager } from "typeorm"
|
||||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||||
import { IsType } from "../../../../utils/validators/is-type"
|
import { IsType } from "../../../../utils/validators/is-type"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
|
||||||
import IsolateProductDomainFeatureFlag from "../../../../loaders/feature-flags/isolate-product-domain"
|
import IsolateProductDomainFeatureFlag from "../../../../loaders/feature-flags/isolate-product-domain"
|
||||||
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
|
import { Type } from "class-transformer"
|
||||||
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /store/carts/{id}
|
* @oas [post] /store/carts/{id}
|
||||||
@@ -79,6 +82,9 @@ export default async (req, res) => {
|
|||||||
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
if (req.user?.customer_id) {
|
if (req.user?.customer_id) {
|
||||||
validated.customer_id = req.user.customer_id
|
validated.customer_id = req.user.customer_id
|
||||||
}
|
}
|
||||||
@@ -111,6 +117,11 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.json({ cart: cleanResponseData(data, []) })
|
res.json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { IsInt, IsOptional } from "class-validator"
|
import { IsInt, IsOptional } from "class-validator"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
|
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { MedusaError } from "medusa-core-utils"
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
@@ -70,6 +73,9 @@ export default async (req, res) => {
|
|||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
await manager.transaction(async (m) => {
|
await manager.transaction(async (m) => {
|
||||||
// If the quantity is 0 that is effectively deletion
|
// If the quantity is 0 that is effectively deletion
|
||||||
if (validated.quantity === 0) {
|
if (validated.quantity === 0) {
|
||||||
@@ -115,6 +121,11 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(data, []) })
|
res.status(200).json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { IsObject } from "class-validator"
|
import {
|
||||||
|
CartService,
|
||||||
|
ProductVariantInventoryService,
|
||||||
|
} from "../../../../services"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
import { IsObject } from "class-validator"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,6 +72,8 @@ export default async (req, res) => {
|
|||||||
const validated = req.validatedBody
|
const validated = req.validatedBody
|
||||||
|
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
const productVariantInventoryService: ProductVariantInventoryService =
|
||||||
|
req.scope.resolve("productVariantInventoryService")
|
||||||
|
|
||||||
const manager: EntityManager = req.scope.resolve("manager")
|
const manager: EntityManager = req.scope.resolve("manager")
|
||||||
await manager.transaction(async (transactionManager) => {
|
await manager.transaction(async (transactionManager) => {
|
||||||
@@ -84,6 +90,11 @@ export default async (req, res) => {
|
|||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await productVariantInventoryService.setVariantAvailability(
|
||||||
|
data.items.map((i) => i.variant),
|
||||||
|
data.sales_channel_id!
|
||||||
|
)
|
||||||
|
|
||||||
res.status(200).json({ cart: cleanResponseData(data, []) })
|
res.status(200).json({ cart: cleanResponseData(data, []) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MedusaError } from "medusa-core-utils"
|
|
||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
export const carts = {
|
export const carts = {
|
||||||
emptyCart: {
|
emptyCart: {
|
||||||
@@ -66,6 +66,7 @@ export const carts = {
|
|||||||
id: IdMap.getId("regionCart"),
|
id: IdMap.getId("regionCart"),
|
||||||
name: "Product 1",
|
name: "Product 1",
|
||||||
region_id: IdMap.getId("testRegion"),
|
region_id: IdMap.getId("testRegion"),
|
||||||
|
items: [],
|
||||||
},
|
},
|
||||||
frCart: {
|
frCart: {
|
||||||
id: IdMap.getId("fr-cart"),
|
id: IdMap.getId("fr-cart"),
|
||||||
|
|||||||
Reference in new issue
Block a user