feat(orchestration,core-flows,medusa,product,types,utils): product import/export uses workflows (#5811)
This commit is contained in:
@@ -23,7 +23,6 @@ export async function createProductVariantsPrepareData({
|
||||
container,
|
||||
data,
|
||||
}: WorkflowArguments<WorkflowTypes.ProductWorkflow.CreateProductVariantsWorkflowInputDTO>): Promise<CreateProductVariantsPreparedData> {
|
||||
const featureFlagRouter = container.resolve("featureFlagRouter")
|
||||
const productVariants: ProductWorkflow.CreateProductVariantsInputDTO[] =
|
||||
data.productVariants || []
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ export async function updateProductVariants({
|
||||
const productModuleService: ProductTypes.IProductModuleService =
|
||||
container.resolve(ModulesDefinition[Modules.PRODUCT].registrationName)
|
||||
|
||||
for (const [product_id, variantsUpdateData = []] of productVariantsMap) {
|
||||
for (const [
|
||||
product_id,
|
||||
variantsUpdateData = [],
|
||||
] of productVariantsMap) {
|
||||
updateVariantsData.push(
|
||||
...(variantsUpdateData as unknown as UpdateProductVariantOnlyDTO[]).map(
|
||||
(update) => ({ ...update, product_id })
|
||||
|
||||
@@ -95,7 +95,9 @@ export async function upsertVariantPrices({
|
||||
min_quantity: price.min_quantity,
|
||||
max_quantity: price.max_quantity,
|
||||
amount: price.amount,
|
||||
currency_code: region_currency_code ?? price.currency_code,
|
||||
currency_code: (
|
||||
region_currency_code ?? price.currency_code
|
||||
).toLowerCase(),
|
||||
}
|
||||
|
||||
moneyAmountsToUpdate.push(priceToUpdate)
|
||||
@@ -104,7 +106,9 @@ export async function upsertVariantPrices({
|
||||
min_quantity: price.min_quantity,
|
||||
max_quantity: price.max_quantity,
|
||||
amount: price.amount,
|
||||
currency_code: region_currency_code ?? price.currency_code,
|
||||
currency_code: (
|
||||
region_currency_code ?? price.currency_code
|
||||
).toLowerCase(),
|
||||
rules: region_rules ?? {},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { FlagRouter } from "@medusajs/utils"
|
||||
import { Request } from "express"
|
||||
import { IdMap, MockManager } from "medusa-test-utils"
|
||||
import { AdminPostBatchesReq, defaultAdminProductRelations, } from "../../../../api"
|
||||
import {
|
||||
AdminPostBatchesReq,
|
||||
defaultAdminProductRelations,
|
||||
} from "../../../../api"
|
||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||
import { User } from "../../../../models"
|
||||
import { BatchJobStatus } from "../../../../types/batch-job"
|
||||
@@ -117,6 +120,7 @@ describe("Product export strategy", () => {
|
||||
batchJobService: batchJobServiceMock as any,
|
||||
productService: productServiceMock as any,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
remoteQuery: (() => {}) as any,
|
||||
})
|
||||
|
||||
it("should generate the appropriate template", async () => {
|
||||
@@ -264,6 +268,7 @@ describe("Product export strategy", () => {
|
||||
productService: productServiceWithNoDataMock as any,
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
remoteQuery: (() => {}) as any,
|
||||
})
|
||||
|
||||
await productExportStrategy.prepareBatchJobForProcessing(
|
||||
@@ -283,6 +288,7 @@ describe("Product export strategy", () => {
|
||||
productService: productServiceMock as any,
|
||||
manager: MockManager,
|
||||
featureFlagRouter: new FlagRouter({}),
|
||||
remoteQuery: (() => {}) as any,
|
||||
})
|
||||
|
||||
await productExportStrategy.prepareBatchJobForProcessing(
|
||||
@@ -381,6 +387,7 @@ describe("Product export strategy with sales Channels", () => {
|
||||
fileService: fileServiceMock as any,
|
||||
batchJobService: batchJobServiceMock as any,
|
||||
productService: productServiceMock as any,
|
||||
remoteQuery: (() => {}) as any,
|
||||
featureFlagRouter: new FlagRouter({
|
||||
[SalesChannelFeatureFlag.key]: true,
|
||||
}),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { FlagRouter, MedusaV2Flag, createContainerLike } from "@medusajs/utils"
|
||||
import { humanizeAmount } from "medusa-core-utils"
|
||||
|
||||
import { FlagRouter } from "@medusajs/utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { defaultAdminProductRelations } from "../../../api"
|
||||
import { AbstractBatchJobStrategy, IFileService } from "../../../interfaces"
|
||||
import ProductCategoryFeatureFlag from "../../../loaders/feature-flags/product-categories"
|
||||
@@ -11,19 +10,19 @@ import { Product, ProductVariant } from "../../../models"
|
||||
import { BatchJobService, ProductService } from "../../../services"
|
||||
import { BatchJobStatus, CreateBatchJobInput } from "../../../types/batch-job"
|
||||
import { FindProductConfig } from "../../../types/product"
|
||||
import { csvCellContentFormatter } from "../../../utils"
|
||||
import { csvCellContentFormatter, listProducts } from "../../../utils"
|
||||
import { prepareListQuery } from "../../../utils/get-query-config"
|
||||
import {
|
||||
DynamicProductExportDescriptor,
|
||||
ProductExportBatchJob,
|
||||
ProductExportBatchJobContext,
|
||||
ProductExportInjectedDependencies,
|
||||
ProductExportPriceData,
|
||||
DynamicProductExportDescriptor,
|
||||
ProductExportBatchJob,
|
||||
ProductExportBatchJobContext,
|
||||
ProductExportInjectedDependencies,
|
||||
ProductExportPriceData,
|
||||
} from "./types"
|
||||
import {
|
||||
productCategoriesColumnsDefinition,
|
||||
productColumnsDefinition,
|
||||
productSalesChannelColumnsDefinition,
|
||||
productCategoriesColumnsDefinition,
|
||||
productColumnsDefinition,
|
||||
productSalesChannelColumnsDefinition,
|
||||
} from "./types/columns-definition"
|
||||
|
||||
export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
@@ -37,6 +36,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
protected readonly productService_: ProductService
|
||||
protected readonly fileService_: IFileService
|
||||
protected readonly featureFlagRouter_: FlagRouter
|
||||
protected readonly remoteQuery_: any
|
||||
|
||||
protected readonly defaultRelations_ = [
|
||||
...defaultAdminProductRelations,
|
||||
@@ -68,6 +68,7 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
productService,
|
||||
fileService,
|
||||
featureFlagRouter,
|
||||
remoteQuery,
|
||||
}: ProductExportInjectedDependencies) {
|
||||
super({
|
||||
manager,
|
||||
@@ -75,8 +76,10 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
productService,
|
||||
fileService,
|
||||
featureFlagRouter,
|
||||
remoteQuery,
|
||||
})
|
||||
|
||||
this.remoteQuery_ = remoteQuery
|
||||
this.manager_ = manager
|
||||
this.batchJobService_ = batchJobService
|
||||
this.productService_ = productService
|
||||
@@ -135,6 +138,10 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
}
|
||||
|
||||
async preProcessBatchJob(batchJobId: string): Promise<void> {
|
||||
const isMedusaV2Enabled = this.featureFlagRouter_.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
return await this.atomicPhase_(async (transactionManager) => {
|
||||
const batchJob = (await this.batchJobService_
|
||||
.withTransaction(transactionManager)
|
||||
@@ -144,12 +151,29 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
const limit = batchJob.context?.list_config?.take ?? this.DEFAULT_LIMIT
|
||||
|
||||
const { list_config = {}, filterable_fields = {} } = batchJob.context
|
||||
const [productList, count] = await this.productService_
|
||||
.withTransaction(transactionManager)
|
||||
.listAndCount(filterable_fields, {
|
||||
...(list_config ?? {}),
|
||||
take: Math.min(batchJob.context.batch_size ?? Infinity, limit),
|
||||
} as FindProductConfig)
|
||||
let productList: Product[] = []
|
||||
let count = 0
|
||||
const container = createContainerLike(
|
||||
this.__container__
|
||||
) as MedusaContainer
|
||||
|
||||
if (isMedusaV2Enabled) {
|
||||
;[productList, count] = await listProducts(
|
||||
container,
|
||||
filterable_fields,
|
||||
{
|
||||
...list_config,
|
||||
take: null,
|
||||
}
|
||||
)
|
||||
} else {
|
||||
;[productList, count] = await this.productService_
|
||||
.withTransaction(transactionManager)
|
||||
.listAndCount(filterable_fields, {
|
||||
...(list_config ?? {}),
|
||||
take: Math.min(batchJob.context.batch_size ?? Infinity, limit),
|
||||
} as FindProductConfig)
|
||||
}
|
||||
|
||||
const productCount = batchJob.context?.batch_size ?? count
|
||||
let products: Product[] = productList
|
||||
@@ -162,13 +186,25 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
|
||||
while (offset < productCount) {
|
||||
if (!products?.length) {
|
||||
products = await this.productService_
|
||||
.withTransaction(transactionManager)
|
||||
.list(filterable_fields, {
|
||||
...list_config,
|
||||
skip: offset,
|
||||
take: Math.min(productCount - offset, limit),
|
||||
} as FindProductConfig)
|
||||
if (isMedusaV2Enabled) {
|
||||
;[productList, count] = await listProducts(
|
||||
container,
|
||||
filterable_fields,
|
||||
{
|
||||
...list_config,
|
||||
skip: offset,
|
||||
take: Math.min(productCount - offset, limit),
|
||||
}
|
||||
)
|
||||
} else {
|
||||
products = await this.productService_
|
||||
.withTransaction(transactionManager)
|
||||
.list(filterable_fields, {
|
||||
...list_config,
|
||||
skip: offset,
|
||||
take: Math.min(productCount - offset, limit),
|
||||
} as FindProductConfig)
|
||||
}
|
||||
}
|
||||
|
||||
const shapeData = this.getProductRelationsDynamicColumnsShape(products)
|
||||
@@ -669,7 +705,8 @@ export default class ProductExportStrategy extends AbstractBatchJobStrategy {
|
||||
if (
|
||||
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key)
|
||||
) {
|
||||
const salesChannelCount = product?.sales_channels?.length ?? 0
|
||||
const salesChannelCount =
|
||||
(product as Product)?.sales_channels?.length ?? 0
|
||||
salesChannelsColumnCount = Math.max(
|
||||
salesChannelsColumnCount,
|
||||
salesChannelCount
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
import { computerizeAmount, MedusaError } from "medusa-core-utils"
|
||||
import {
|
||||
CreateProductVariants,
|
||||
UpdateProductVariants,
|
||||
createProducts,
|
||||
updateProducts,
|
||||
} from "@medusajs/core-flows"
|
||||
import { ProductWorkflow } from "@medusajs/types"
|
||||
import { FlagRouter, MedusaV2Flag, promiseAll } from "@medusajs/utils"
|
||||
import { MedusaError, computerizeAmount } from "medusa-core-utils"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
import { FlagRouter, promiseAll } from "@medusajs/utils"
|
||||
import { AbstractBatchJobStrategy, IFileService } from "../../../interfaces"
|
||||
import ProductCategoryFeatureFlag from "../../../loaders/feature-flags/product-categories"
|
||||
import SalesChannelFeatureFlag from "../../../loaders/feature-flags/sales-channels"
|
||||
@@ -109,7 +115,6 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
})
|
||||
|
||||
this.featureFlagRouter_ = featureFlagRouter
|
||||
|
||||
this.manager_ = manager
|
||||
this.fileService_ = fileService
|
||||
this.batchJobService_ = batchJobService
|
||||
@@ -168,7 +173,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
const variantsUpdate: TParsedProductImportRowData[] = []
|
||||
|
||||
for (const row of csvData) {
|
||||
if ((row["variant.prices"] as Record<string, any>[]).length) {
|
||||
if ((row["variant.prices"] as Record<string, any>[])?.length) {
|
||||
await this.prepareVariantPrices(row)
|
||||
}
|
||||
|
||||
@@ -278,6 +283,7 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
|
||||
let totalOperationCount = 0
|
||||
const operationsCounts = {}
|
||||
|
||||
Object.keys(ops).forEach((key) => {
|
||||
operationsCounts[key] = ops[key].length
|
||||
totalOperationCount += ops[key].length
|
||||
@@ -417,6 +423,10 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
return
|
||||
}
|
||||
|
||||
const isMedusaV2Enabled = this.featureFlagRouter_.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
const transactionManager = this.transactionManager_ ?? this.manager_
|
||||
|
||||
const productOps = await this.downloadImportOpsFile(
|
||||
@@ -468,10 +478,27 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: we should only pass the expected data and should not have to cast the entire object. Here we are passing everything contained in productData
|
||||
await productServiceTx.create(
|
||||
productData as unknown as CreateProductInput
|
||||
)
|
||||
if (isMedusaV2Enabled) {
|
||||
const createProductWorkflow = createProducts(this.__container__)
|
||||
|
||||
const input = {
|
||||
products: [
|
||||
productData,
|
||||
] as unknown as ProductWorkflow.CreateProductInputDTO[],
|
||||
}
|
||||
|
||||
await createProductWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager: transactionManager,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// TODO: we should only pass the expected data and should not have to cast the entire object. Here we are passing everything contained in productData
|
||||
await productServiceTx.create(
|
||||
productData as unknown as CreateProductInput
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
ProductImportStrategy.throwDescriptiveError(productOp, e.message)
|
||||
}
|
||||
@@ -490,6 +517,10 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
return
|
||||
}
|
||||
|
||||
const isMedusaV2Enabled = this.featureFlagRouter_.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
const transactionManager = this.transactionManager_ ?? this.manager_
|
||||
const productOps = await this.downloadImportOpsFile(
|
||||
batchJob,
|
||||
@@ -541,11 +572,31 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: we should only pass the expected data. Here we are passing everything contained in productData
|
||||
await productServiceTx.update(
|
||||
productOp["product.id"] as string,
|
||||
productData
|
||||
)
|
||||
if (isMedusaV2Enabled) {
|
||||
const updateProductWorkflow = updateProducts(this.__container__)
|
||||
|
||||
const input = {
|
||||
products: [
|
||||
{
|
||||
id: productOp["product.id"] as string,
|
||||
...productData,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await updateProductWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager: transactionManager,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// TODO: we should only pass the expected data. Here we are passing everything contained in productData
|
||||
await productServiceTx.update(
|
||||
productOp["product.id"] as string,
|
||||
productData
|
||||
)
|
||||
}
|
||||
} catch (e) {
|
||||
ProductImportStrategy.throwDescriptiveError(productOp, e.message)
|
||||
}
|
||||
@@ -565,6 +616,10 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
return
|
||||
}
|
||||
|
||||
const isMedusaV2Enabled = this.featureFlagRouter_.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
const transactionManager = this.transactionManager_ ?? this.manager_
|
||||
|
||||
const variantOps = await this.downloadImportOpsFile(
|
||||
@@ -600,9 +655,30 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
delete variant.id
|
||||
delete variant.product
|
||||
|
||||
await this.productVariantService_
|
||||
.withTransaction(transactionManager)
|
||||
.create(product!, variant as unknown as CreateProductVariantInput)
|
||||
if (isMedusaV2Enabled) {
|
||||
const createProductVariantsWorkflow =
|
||||
CreateProductVariants.createProductVariants(this.__container__)
|
||||
|
||||
const input: ProductWorkflow.CreateProductVariantsWorkflowInputDTO = {
|
||||
productVariants: [
|
||||
{
|
||||
...variant,
|
||||
product_id: product.id,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await createProductVariantsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager: transactionManager,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await this.productVariantService_
|
||||
.withTransaction(transactionManager)
|
||||
.create(product!, variant as unknown as CreateProductVariantInput)
|
||||
}
|
||||
|
||||
await this.updateProgress(batchJob.id)
|
||||
} catch (e) {
|
||||
@@ -621,6 +697,10 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
return
|
||||
}
|
||||
|
||||
const isMedusaV2Enabled = this.featureFlagRouter_.isFeatureEnabled(
|
||||
MedusaV2Flag.key
|
||||
)
|
||||
|
||||
const transactionManager = this.transactionManager_ ?? this.manager_
|
||||
|
||||
const variantOps = await this.downloadImportOpsFile(
|
||||
@@ -637,15 +717,36 @@ class ProductImportStrategy extends AbstractBatchJobStrategy {
|
||||
variantOp["product.handle"] as string
|
||||
)
|
||||
|
||||
await this.prepareVariantOptions(variantOp, product.id)
|
||||
await this.prepareVariantOptions(variantOp, product.id!)
|
||||
|
||||
const updateData = transformVariantData(variantOp)
|
||||
delete updateData.product
|
||||
delete updateData["product.handle"]
|
||||
|
||||
await this.productVariantService_
|
||||
.withTransaction(transactionManager)
|
||||
.update(variantOp["variant.id"] as string, updateData)
|
||||
if (isMedusaV2Enabled) {
|
||||
const updateProductVariantsWorkflow =
|
||||
UpdateProductVariants.updateProductVariants(this.__container__)
|
||||
|
||||
const input: ProductWorkflow.UpdateProductVariantsWorkflowInputDTO = {
|
||||
productVariants: [
|
||||
{
|
||||
id: variantOp["variant.id"] as string,
|
||||
...updateData,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
await updateProductVariantsWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager: transactionManager,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await this.productVariantService_
|
||||
.withTransaction(transactionManager)
|
||||
.update(variantOp["variant.id"] as string, updateData)
|
||||
}
|
||||
} catch (e) {
|
||||
ProductImportStrategy.throwDescriptiveError(variantOp, e.message)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RemoteQueryFunction } from "@medusajs/types"
|
||||
import { FlagRouter } from "@medusajs/utils"
|
||||
import { FileService } from "medusa-interfaces"
|
||||
import { EntityManager } from "typeorm"
|
||||
@@ -5,14 +6,14 @@ import { IFileService } from "../../../../interfaces"
|
||||
import { CsvSchema, CsvSchemaColumn } from "../../../../interfaces/csv-parser"
|
||||
import { BatchJob, Product, ProductVariant } from "../../../../models"
|
||||
import {
|
||||
BatchJobService,
|
||||
ProductCategoryService,
|
||||
ProductCollectionService,
|
||||
ProductService,
|
||||
ProductVariantService,
|
||||
RegionService,
|
||||
SalesChannelService,
|
||||
ShippingProfileService,
|
||||
BatchJobService,
|
||||
ProductCategoryService,
|
||||
ProductCollectionService,
|
||||
ProductService,
|
||||
ProductVariantService,
|
||||
RegionService,
|
||||
SalesChannelService,
|
||||
ShippingProfileService,
|
||||
} from "../../../../services"
|
||||
import { Selector } from "../../../../types/common"
|
||||
|
||||
@@ -22,6 +23,7 @@ export type ProductExportInjectedDependencies = {
|
||||
productService: ProductService
|
||||
fileService: IFileService
|
||||
featureFlagRouter: FlagRouter
|
||||
remoteQuery: RemoteQueryFunction
|
||||
}
|
||||
|
||||
export type ProductExportBatchJobContext = {
|
||||
|
||||
@@ -11,6 +11,7 @@ export * from "./is-object"
|
||||
export * from "./is-string"
|
||||
export * from "./omit-deep"
|
||||
export * from "./product-category"
|
||||
export * from "./queries"
|
||||
export * from "./remote-query-fetch-data"
|
||||
export * from "./remove-undefined-properties"
|
||||
export * from "./set-metadata"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./products"
|
||||
@@ -0,0 +1,34 @@
|
||||
import { MedusaContainer, ProductVariantDTO } from "@medusajs/types"
|
||||
|
||||
export async function getVariantsFromPriceList(
|
||||
container: MedusaContainer,
|
||||
priceListId: string
|
||||
) {
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
|
||||
const query = {
|
||||
price_list: {
|
||||
__args: { id: [priceListId] },
|
||||
price_set_money_amounts: {
|
||||
price_set: {
|
||||
variant_link: { variant: { fields: ["id", "product_id"] } },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const priceLists = await remoteQuery(query)
|
||||
const variants: ProductVariantDTO[] = []
|
||||
|
||||
priceLists.forEach((priceList) => {
|
||||
priceList.price_set_money_amounts?.forEach((psma) => {
|
||||
const variant = psma.price_set?.variant_link?.variant
|
||||
|
||||
if (variant) {
|
||||
variants.push(variant)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return variants
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./get-variants-from-price-list"
|
||||
export * from "./list-products"
|
||||
export * from "./retrieve-product"
|
||||
@@ -0,0 +1,248 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { MedusaV2Flag, promiseAll } from "@medusajs/utils"
|
||||
|
||||
import { PriceListService, SalesChannelService } from "../../../services"
|
||||
import { getVariantsFromPriceList } from "./get-variants-from-price-list"
|
||||
|
||||
export async function listProducts(
|
||||
container: MedusaContainer,
|
||||
filterableFields,
|
||||
listConfig
|
||||
) {
|
||||
// TODO: Add support for fields/expands
|
||||
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
const featureFlagRouter = container.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 = container.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(container, 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 = container.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]
|
||||
}
|
||||
|
||||
export const defaultAdminProductRemoteQueryObject = {
|
||||
fields: [
|
||||
"id",
|
||||
"title",
|
||||
"subtitle",
|
||||
"status",
|
||||
"external_id",
|
||||
"description",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"discountable",
|
||||
"thumbnail",
|
||||
"collection_id",
|
||||
"type_id",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
"width",
|
||||
"hs_code",
|
||||
"origin_country",
|
||||
"mid_code",
|
||||
"material",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
],
|
||||
images: {
|
||||
fields: ["id", "created_at", "updated_at", "deleted_at", "url", "metadata"],
|
||||
},
|
||||
tags: {
|
||||
fields: ["id", "created_at", "updated_at", "deleted_at", "value"],
|
||||
},
|
||||
|
||||
type: {
|
||||
fields: ["id", "created_at", "updated_at", "deleted_at", "value"],
|
||||
},
|
||||
|
||||
collection: {
|
||||
fields: ["title", "handle", "id", "created_at", "updated_at", "deleted_at"],
|
||||
},
|
||||
|
||||
categories: {
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"handle",
|
||||
"is_active",
|
||||
"is_internal",
|
||||
"parent_category_id",
|
||||
],
|
||||
},
|
||||
|
||||
options: {
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"title",
|
||||
"product_id",
|
||||
"metadata",
|
||||
],
|
||||
values: {
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"value",
|
||||
"option_id",
|
||||
"variant_id",
|
||||
"metadata",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
variants: {
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"title",
|
||||
"product_id",
|
||||
"sku",
|
||||
"barcode",
|
||||
"ean",
|
||||
"upc",
|
||||
"variant_rank",
|
||||
"inventory_quantity",
|
||||
"allow_backorder",
|
||||
"manage_inventory",
|
||||
"hs_code",
|
||||
"origin_country",
|
||||
"mid_code",
|
||||
"material",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
"width",
|
||||
"metadata",
|
||||
],
|
||||
|
||||
options: {
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"value",
|
||||
"option_id",
|
||||
"variant_id",
|
||||
"metadata",
|
||||
],
|
||||
},
|
||||
},
|
||||
profile: {
|
||||
fields: ["id", "created_at", "updated_at", "deleted_at", "name", "type"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
export async function retrieveProduct(container, id, remoteQueryObject = {}) {
|
||||
// TODO: Add support for fields/expands
|
||||
const remoteQuery = container.resolve("remoteQuery")
|
||||
|
||||
const variables = { id }
|
||||
|
||||
const query = {
|
||||
product: {
|
||||
__args: variables,
|
||||
...remoteQueryObject,
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||
import { WorkflowDefinition, WorkflowManager } from "./workflow-manager"
|
||||
import { createContainerLike, createMedusaContainer } from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
|
||||
import { DistributedTransaction } from "../transaction"
|
||||
import { asValue } from "awilix"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { WorkflowDefinition, WorkflowManager } from "./workflow-manager"
|
||||
|
||||
export class GlobalWorkflow extends WorkflowManager {
|
||||
protected static workflows: Map<string, WorkflowDefinition> = new Map()
|
||||
@@ -16,17 +16,17 @@ export class GlobalWorkflow extends WorkflowManager {
|
||||
) {
|
||||
super()
|
||||
|
||||
const container = createMedusaContainer()
|
||||
let container
|
||||
|
||||
// Medusa container
|
||||
if (!Array.isArray(modulesLoaded) && modulesLoaded) {
|
||||
const cradle = modulesLoaded.cradle
|
||||
for (const key in cradle) {
|
||||
container.register(key, asValue(cradle[key]))
|
||||
if (!("cradle" in modulesLoaded)) {
|
||||
container = createContainerLike(modulesLoaded)
|
||||
} else {
|
||||
container = modulesLoaded
|
||||
}
|
||||
}
|
||||
// Array of modules
|
||||
else if (modulesLoaded?.length) {
|
||||
} else if (Array.isArray(modulesLoaded) && modulesLoaded.length) {
|
||||
container = createMedusaContainer()
|
||||
|
||||
for (const mod of modulesLoaded) {
|
||||
const registrationName = mod.__definition.registrationName
|
||||
container.register(registrationName, asValue(mod))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Context, LoadedModule, MedusaContainer } from "@medusajs/types"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { createContainerLike, createMedusaContainer } from "@medusajs/utils"
|
||||
import { asValue } from "awilix"
|
||||
import {
|
||||
DistributedTransaction,
|
||||
@@ -27,7 +27,7 @@ export class LocalWorkflow {
|
||||
|
||||
constructor(
|
||||
workflowId: string,
|
||||
modulesLoaded?: LoadedModule[] | MedusaContainer
|
||||
modulesLoaded: LoadedModule[] | MedusaContainer
|
||||
) {
|
||||
const globalWorkflow = WorkflowManager.getWorkflow(workflowId)
|
||||
if (!globalWorkflow) {
|
||||
@@ -39,17 +39,17 @@ export class LocalWorkflow {
|
||||
this.workflow = globalWorkflow
|
||||
this.handlers = new Map(globalWorkflow.handlers_)
|
||||
|
||||
const container = createMedusaContainer()
|
||||
let container
|
||||
|
||||
// Medusa container
|
||||
if (!Array.isArray(modulesLoaded) && modulesLoaded) {
|
||||
const cradle = modulesLoaded.cradle
|
||||
for (const key of Object.keys(cradle ?? {})) {
|
||||
container.register(key, asValue(cradle[key]))
|
||||
if (!("cradle" in modulesLoaded)) {
|
||||
container = createContainerLike(modulesLoaded)
|
||||
} else {
|
||||
container = modulesLoaded
|
||||
}
|
||||
}
|
||||
// Array of modules
|
||||
else if (modulesLoaded?.length) {
|
||||
} else if (Array.isArray(modulesLoaded) && modulesLoaded.length) {
|
||||
container = createMedusaContainer()
|
||||
|
||||
for (const mod of modulesLoaded) {
|
||||
const registrationName = mod.__definition.registrationName
|
||||
container.register(registrationName, asValue(mod))
|
||||
|
||||
@@ -67,15 +67,15 @@ import {
|
||||
MedusaError,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
CreateProductOptionValueDTO,
|
||||
UpdateProductOptionValueDTO,
|
||||
} from "../types/services/product-option-value"
|
||||
import {
|
||||
entityNameToLinkableKeysMap,
|
||||
joinerConfig,
|
||||
LinkableKeys,
|
||||
} from "./../joiner-config"
|
||||
import {
|
||||
CreateProductOptionValueDTO,
|
||||
UpdateProductOptionValueDTO,
|
||||
} from "../types/services/product-option-value"
|
||||
|
||||
type InjectedDependencies = {
|
||||
baseRepository: DAL.RepositoryService
|
||||
@@ -1063,6 +1063,10 @@ export default class ProductModuleService<
|
||||
|
||||
const existingProductVariantsMap = new Map<string, ProductVariant[]>(
|
||||
data.map((productData) => {
|
||||
if (productData.variants === undefined) {
|
||||
return [productData.id, []]
|
||||
}
|
||||
|
||||
const productVariantsForProduct = existingProductVariants.filter(
|
||||
(variant) => variant.product_id === productData.id
|
||||
)
|
||||
|
||||
@@ -4,3 +4,7 @@ export type MedusaContainer = AwilixContainer & {
|
||||
registerAdd: <T>(name: string, registration: T) => MedusaContainer
|
||||
createScope: () => MedusaContainer
|
||||
}
|
||||
|
||||
export type ContainerLike = {
|
||||
resolve<T = unknown>(key: string): T
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ContainerLike } from "@medusajs/types"
|
||||
|
||||
export function createContainerLike(obj): ContainerLike {
|
||||
return {
|
||||
resolve(key: string) {
|
||||
return obj[key]
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export * from "./array-difference"
|
||||
export * from "./build-query"
|
||||
export * from "./camel-to-snake-case"
|
||||
export * from "./container"
|
||||
export * from "./create-container-like"
|
||||
export * from "./deduplicate"
|
||||
export * from "./deep-equal-obj"
|
||||
export * from "./errors"
|
||||
|
||||
Reference in New Issue
Block a user