feat(orchestration,core-flows,medusa,product,types,utils): product import/export uses workflows (#5811)

This commit is contained in:
Riqwan Thamir
2023-12-12 12:09:25 +00:00
committed by GitHub
parent 6f96ced40f
commit 07107f3565
30 changed files with 1601 additions and 357 deletions
@@ -19,7 +19,7 @@ import { pickBy } from "lodash"
import { ProductStatus } from "../../../../models"
import PriceListService from "../../../../services/price-list"
import { FilterableProductProps } from "../../../../types/product"
import { listAndCountProductWithIsolatedProductModule } from "../products/list-products"
import { listProducts } from "../../../../utils"
/**
* @oas [get] /admin/price-lists/{id}/products
@@ -196,8 +196,8 @@ export default async (req: Request, res) => {
}
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
;[products, count] = await listAndCountProductWithIsolatedProductModule(
req,
;[products, count] = await listProducts(
req.scope,
filterableFields,
req.listConfig
)
@@ -1,5 +1,5 @@
import { Workflows, createProducts } from "@medusajs/core-flows"
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
import { createProducts, Workflows } from "@medusajs/core-flows"
import {
IsArray,
IsBoolean,
@@ -45,7 +45,7 @@ import { EntityManager } from "typeorm"
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
import { ProductStatus } from "../../../../models"
import { Logger } from "../../../../types/global"
import { validator } from "../../../../utils"
import { retrieveProduct, validator } from "../../../../utils"
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
/**
@@ -257,7 +257,11 @@ export default async (req, res) => {
let rawProduct
if (isMedusaV2Enabled) {
rawProduct = await getProductWithIsolatedProductModule(req, product.id)
rawProduct = await retrieveProduct(
req.scope,
product.id,
defaultAdminProductRemoteQueryObject
)
} else {
rawProduct = await productService.retrieve(product.id, {
select: defaultAdminProductFields,
@@ -272,26 +276,6 @@ export default async (req, res) => {
res.json({ product: pricedProduct })
}
async function getProductWithIsolatedProductModule(req, id) {
// TODO: Add support for fields/expands
const remoteQuery = req.scope.resolve("remoteQuery")
const variables = { id }
const query = {
product: {
__args: variables,
...defaultAdminProductRemoteQueryObject,
},
}
const [product] = await remoteQuery(query)
product.profile_id = product.profile?.id
return product
}
class ProductVariantOptionReq {
@IsString()
value: string
@@ -1,6 +1,7 @@
import { CreateProductVariants } from "@medusajs/core-flows"
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
import { FlagRouter, MedusaV2Flag } from "@medusajs/utils"
import { CreateProductVariants } from "@medusajs/core-flows"
import { Type } from "class-transformer"
import {
IsArray,
IsBoolean,
@@ -10,7 +11,12 @@ import {
IsString,
ValidateNested,
} from "class-validator"
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { EntityManager } from "typeorm"
import {
defaultAdminProductFields,
defaultAdminProductRelations,
defaultAdminProductRemoteQueryObject,
} from "."
import {
PricingService,
ProductService,
@@ -21,10 +27,7 @@ import {
CreateProductVariantInput,
ProductVariantPricesCreateReq,
} from "../../../../types/product-variant"
import { Type } from "class-transformer"
import { EntityManager } from "typeorm"
import { validator } from "../../../../utils/validator"
import { getProductWithIsolatedProductModule } from "./get-product"
import { retrieveProduct, validator } from "../../../../utils"
import { createVariantsTransaction } from "./transaction/create-product-variant"
/**
@@ -157,10 +160,10 @@ export default async (req, res) => {
},
})
rawProduct = await getProductWithIsolatedProductModule(
req,
rawProduct = await retrieveProduct(
req.scope,
id,
req.retrieveConfig
defaultAdminProductRemoteQueryObject
)
} else {
await manager.transaction(async (transactionManager) => {
@@ -5,8 +5,9 @@ import {
SalesChannelService,
} from "../../../../services"
import { MedusaError, MedusaV2Flag, promiseAll } from "@medusajs/utils"
import { MedusaV2Flag, promiseAll } from "@medusajs/utils"
import { FindParams } from "../../../../types/common"
import { retrieveProduct } from "../../../../utils"
import { defaultAdminProductRemoteQueryObject } from "./index"
/**
@@ -76,10 +77,10 @@ export default async (req, res) => {
let rawProduct
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
rawProduct = await getProductWithIsolatedProductModule(
req,
rawProduct = await retrieveProduct(
req.scope,
id,
req.retrieveConfig
defaultAdminProductRemoteQueryObject
)
} else {
rawProduct = await productService.retrieve(id, req.retrieveConfig)
@@ -118,35 +119,4 @@ export default async (req, res) => {
res.json({ product })
}
export async function getProductWithIsolatedProductModule(
req,
id,
retrieveConfig
) {
// TODO: Add support for fields/expands
const remoteQuery = req.scope.resolve("remoteQuery")
const variables = { id }
const query = {
product: {
__args: variables,
...defaultAdminProductRemoteQueryObject,
},
}
const [product] = await remoteQuery(query)
if (!product) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Product with id: ${id} not found`
)
}
product.profile_id = product.profile?.id
return product
}
export class AdminGetProductParams extends FindParams {}
@@ -1,23 +1,19 @@
import { IsNumber, IsOptional, IsString } from "class-validator"
import {
PriceListService,
PricingService,
ProductService,
ProductVariantInventoryService,
SalesChannelService,
} from "../../../../services"
import {
IInventoryService,
IPricingModuleService,
IProductModuleService,
} from "@medusajs/types"
import { MedusaV2Flag, promiseAll } from "@medusajs/utils"
import { listProducts } from "../../../../utils"
import { IInventoryService } from "@medusajs/types"
import { MedusaV2Flag } from "@medusajs/utils"
import { Type } from "class-transformer"
import { Product } from "../../../../models"
import { PricedProduct } from "../../../../types/pricing"
import { FilterableProductProps } from "../../../../types/product"
import { defaultAdminProductRemoteQueryObject } from "./index"
/**
* @oas [get] /admin/products
@@ -252,12 +248,11 @@ export default async (req, res) => {
let count
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
const [products, count_] =
await listAndCountProductWithIsolatedProductModule(
req,
req.filterableFields,
req.listConfig
)
const [products, count_] = await listProducts(
req.scope,
req.filterableFields,
req.listConfig
)
rawProducts = products
count = count_
@@ -305,171 +300,6 @@ export default async (req, res) => {
})
}
async function getVariantsFromPriceList(req, priceListId) {
const remoteQuery = req.scope.resolve("remoteQuery")
const pricingModuleService: IPricingModuleService = req.scope.resolve(
"pricingModuleService"
)
const productModuleService: IProductModuleService = req.scope.resolve(
"productModuleService"
)
const [priceList] = await pricingModuleService.listPriceLists(
{ id: [priceListId] },
{
relations: [
"price_set_money_amounts",
"price_set_money_amounts.price_set",
],
select: ["price_set_money_amounts.price_set.id"],
}
)
const priceSetIds = priceList.price_set_money_amounts?.map(
(psma) => psma.price_set?.id
)
const query = {
product_variant_price_set: {
__args: {
price_set_id: priceSetIds,
},
fields: ["variant_id", "price_set_id"],
},
}
const variantPriceSets = await remoteQuery(query)
const variantIds = variantPriceSets.map((vps) => vps.variant_id)
return await productModuleService.listVariants(
{
id: variantIds,
},
{
select: ["product_id"],
}
)
}
export async function listAndCountProductWithIsolatedProductModule(
req,
filterableFields,
listConfig
) {
// TODO: Add support for fields/expands
const remoteQuery = req.scope.resolve("remoteQuery")
const featureFlagRouter = req.scope.resolve("featureFlagRouter")
const productIdsFilter: Set<string> = new Set()
const variantIdsFilter: Set<string> = new Set()
const promises: Promise<void>[] = []
// This is not the best way of handling cross filtering but for now I would say it is fine
const salesChannelIdFilter = filterableFields.sales_channel_id
delete filterableFields.sales_channel_id
if (salesChannelIdFilter) {
const salesChannelService = req.scope.resolve(
"salesChannelService"
) as SalesChannelService
promises.push(
salesChannelService
.listProductIdsBySalesChannelIds(salesChannelIdFilter)
.then((productIdsInSalesChannel) => {
let filteredProductIds =
productIdsInSalesChannel[salesChannelIdFilter]
if (filterableFields.id) {
filterableFields.id = Array.isArray(filterableFields.id)
? filterableFields.id
: [filterableFields.id]
const salesChannelProductIdsSet = new Set(filteredProductIds)
filteredProductIds = filterableFields.id.filter((productId) =>
salesChannelProductIdsSet.has(productId)
)
}
filteredProductIds.map((id) => productIdsFilter.add(id))
})
)
}
const priceListId = filterableFields.price_list_id
delete filterableFields.price_list_id
if (priceListId) {
if (featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)) {
const variants = await getVariantsFromPriceList(req, priceListId)
variants.forEach((pv) => variantIdsFilter.add(pv.id))
} else {
// TODO: it is working but validate the behaviour.
// e.g pricing context properly set.
// At the moment filtering by price list but not having any customer id or
// include discount forces the query to filter with price list id is null
const priceListService = req.scope.resolve(
"priceListService"
) as PriceListService
promises.push(
priceListService
.listPriceListsVariantIdsMap(priceListId)
.then((priceListVariantIdsMap) => {
priceListVariantIdsMap[priceListId].map((variantId) =>
variantIdsFilter.add(variantId)
)
})
)
}
}
const discountConditionId = filterableFields.discount_condition_id
delete filterableFields.discount_condition_id
if (discountConditionId) {
// TODO implement later
}
await promiseAll(promises)
if (productIdsFilter.size > 0) {
filterableFields.id = Array.from(productIdsFilter)
}
if (variantIdsFilter.size > 0) {
filterableFields.variants = { id: Array.from(variantIdsFilter) }
}
const variables = {
filters: filterableFields,
order: listConfig.order,
skip: listConfig.skip,
take: listConfig.take,
}
const query = {
product: {
__args: variables,
...defaultAdminProductRemoteQueryObject,
},
}
const {
rows: products,
metadata: { count },
} = await remoteQuery(query)
products.forEach((product) => {
product.profile_id = product.profile?.id
})
return [products, count]
}
/**
* Parameters used to filter and configure the pagination of the retrieved products.
*/
@@ -1,3 +1,4 @@
import { Workflows, updateProducts } from "@medusajs/core-flows"
import { DistributedTransaction } from "@medusajs/orchestration"
import {
FlagRouter,
@@ -5,7 +6,6 @@ import {
MedusaV2Flag,
promiseAll,
} from "@medusajs/utils"
import { updateProducts, Workflows } from "@medusajs/core-flows"
import { Type } from "class-transformer"
import {
IsArray,
@@ -45,6 +45,7 @@ import {
ProductVariantPricesUpdateReq,
UpdateProductVariantInput,
} from "../../../../types/product-variant"
import { retrieveProduct } from "../../../../utils"
import {
createVariantsTransaction,
revertVariantTransaction,
@@ -292,7 +293,11 @@ export default async (req, res) => {
let rawProduct
if (isMedusaV2Enabled) {
rawProduct = await getProductWithIsolatedProductModule(req, id)
rawProduct = await retrieveProduct(
req.scope,
id,
defaultAdminProductRemoteQueryObject
)
} else {
rawProduct = await productService.retrieve(id, {
select: defaultAdminProductFields,
@@ -305,26 +310,6 @@ export default async (req, res) => {
res.json({ product })
}
async function getProductWithIsolatedProductModule(req, id) {
// TODO: Add support for fields/expands
const remoteQuery = req.scope.resolve("remoteQuery")
const variables = { id }
const query = {
product: {
__args: variables,
...defaultAdminProductRemoteQueryObject,
},
}
const [product] = await remoteQuery(query)
product.profile_id = product.profile?.id
return product
}
class ProductVariantOptionReq {
@IsString()
value: string