feat(workflows): update product workflow (#4982)
**What** - added "update product" workflow Co-authored-by: Riqwan Thamir <5105988+riqwan@users.noreply.github.com>
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
import { DistributedTransaction } from "@medusajs/orchestration"
|
||||
import { FlagRouter, MedusaError } from "@medusajs/utils"
|
||||
import { Workflows, updateProducts } from "@medusajs/workflows"
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
@@ -11,7 +15,13 @@ import {
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
import {
|
||||
defaultAdminProductFields,
|
||||
defaultAdminProductRelations,
|
||||
defaultAdminProductRemoteQueryObject,
|
||||
} from "."
|
||||
import { ProductStatus, ProductVariant } from "../../../../models"
|
||||
import {
|
||||
PricingService,
|
||||
@@ -35,15 +45,13 @@ import {
|
||||
revertVariantTransaction,
|
||||
} from "./transaction/create-product-variant"
|
||||
|
||||
import { DistributedTransaction } from "@medusajs/orchestration"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { Type } from "class-transformer"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||
import { ProductVariantRepository } from "../../../../repositories/product-variant"
|
||||
import { Logger } from "../../../../types/global"
|
||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||
|
||||
import IsolateProductDomainFeatureFlag from "../../../../loaders/feature-flags/isolate-product-domain"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
@@ -129,134 +137,192 @@ export default async (req, res) => {
|
||||
req.scope.resolve("inventoryService")
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
const productServiceTx = productService.withTransaction(transactionManager)
|
||||
|
||||
const { variants } = validated
|
||||
delete validated.variants
|
||||
const productModuleService = req.scope.resolve("productModuleService")
|
||||
|
||||
const product = await productServiceTx.update(id, validated)
|
||||
const featureFlagRouter: FlagRouter = req.scope.resolve("featureFlagRouter")
|
||||
const isWorkflowEnabled = featureFlagRouter.isFeatureEnabled({
|
||||
workflows: Workflows.UpdateProducts,
|
||||
})
|
||||
|
||||
if (!variants) {
|
||||
return
|
||||
if (isWorkflowEnabled && !productModuleService) {
|
||||
logger.warn(
|
||||
`Cannot run ${Workflows.UpdateProducts} workflow without '@medusajs/product' installed`
|
||||
)
|
||||
}
|
||||
|
||||
if (isWorkflowEnabled && !!productModuleService) {
|
||||
const updateProductWorkflow = updateProducts(req.scope)
|
||||
|
||||
const input = {
|
||||
products: [
|
||||
{ id, ...validated },
|
||||
] as WorkflowTypes.ProductWorkflow.UpdateProductInputDTO[],
|
||||
}
|
||||
|
||||
const variantRepo = manager.withRepository(productVariantRepo)
|
||||
const productVariants = await productVariantService
|
||||
.withTransaction(transactionManager)
|
||||
.list(
|
||||
{ product_id: id },
|
||||
{
|
||||
select: variantRepo.metadata.columns.map(
|
||||
(c) => c.propertyName
|
||||
) as (keyof ProductVariant)[],
|
||||
const { result } = await updateProductWorkflow.run({
|
||||
input,
|
||||
context: {
|
||||
manager: manager,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
const productServiceTx =
|
||||
productService.withTransaction(transactionManager)
|
||||
|
||||
const { variants } = validated
|
||||
delete validated.variants
|
||||
|
||||
const product = await productServiceTx.update(id, validated)
|
||||
|
||||
if (!variants) {
|
||||
return
|
||||
}
|
||||
|
||||
const variantRepo = manager.withRepository(productVariantRepo)
|
||||
const productVariants = await productVariantService
|
||||
.withTransaction(transactionManager)
|
||||
.list(
|
||||
{ product_id: id },
|
||||
{
|
||||
select: variantRepo.metadata.columns.map(
|
||||
(c) => c.propertyName
|
||||
) as (keyof ProductVariant)[],
|
||||
}
|
||||
)
|
||||
|
||||
const productVariantMap = new Map(productVariants.map((v) => [v.id, v]))
|
||||
const variantWithIdSet = new Set()
|
||||
|
||||
const variantIdsNotBelongingToProduct: string[] = []
|
||||
const variantsToUpdate: {
|
||||
variant: ProductVariant
|
||||
updateData: UpdateProductVariantInput
|
||||
}[] = []
|
||||
const variantsToCreate: ProductVariantReq[] = []
|
||||
|
||||
// Preparing the data step
|
||||
for (const [variantRank, variant] of variants.entries()) {
|
||||
if (!variant.id) {
|
||||
Object.assign(variant, {
|
||||
variant_rank: variantRank,
|
||||
options: variant.options || [],
|
||||
prices: variant.prices || [],
|
||||
})
|
||||
variantsToCreate.push(variant)
|
||||
continue
|
||||
}
|
||||
)
|
||||
|
||||
const productVariantMap = new Map(productVariants.map((v) => [v.id, v]))
|
||||
const variantWithIdSet = new Set()
|
||||
// Will be used to find the variants that should be removed during the next steps
|
||||
variantWithIdSet.add(variant.id)
|
||||
|
||||
const variantIdsNotBelongingToProduct: string[] = []
|
||||
const variantsToUpdate: {
|
||||
variant: ProductVariant
|
||||
updateData: UpdateProductVariantInput
|
||||
}[] = []
|
||||
const variantsToCreate: ProductVariantReq[] = []
|
||||
if (!productVariantMap.has(variant.id)) {
|
||||
variantIdsNotBelongingToProduct.push(variant.id)
|
||||
continue
|
||||
}
|
||||
|
||||
// Preparing the data step
|
||||
for (const [variantRank, variant] of variants.entries()) {
|
||||
if (!variant.id) {
|
||||
const productVariant = productVariantMap.get(variant.id)!
|
||||
Object.assign(variant, {
|
||||
variant_rank: variantRank,
|
||||
options: variant.options || [],
|
||||
prices: variant.prices || [],
|
||||
product_id: productVariant.product_id,
|
||||
})
|
||||
variantsToCreate.push(variant)
|
||||
continue
|
||||
variantsToUpdate.push({ variant: productVariant, updateData: variant })
|
||||
}
|
||||
|
||||
// Will be used to find the variants that should be removed during the next steps
|
||||
variantWithIdSet.add(variant.id)
|
||||
|
||||
if (!productVariantMap.has(variant.id)) {
|
||||
variantIdsNotBelongingToProduct.push(variant.id)
|
||||
continue
|
||||
if (variantIdsNotBelongingToProduct.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Variants with id: ${variantIdsNotBelongingToProduct.join(
|
||||
", "
|
||||
)} are not associated with this product`
|
||||
)
|
||||
}
|
||||
|
||||
const productVariant = productVariantMap.get(variant.id)!
|
||||
Object.assign(variant, {
|
||||
variant_rank: variantRank,
|
||||
product_id: productVariant.product_id,
|
||||
})
|
||||
variantsToUpdate.push({ variant: productVariant, updateData: variant })
|
||||
}
|
||||
const allVariantTransactions: DistributedTransaction[] = []
|
||||
const transactionDependencies = {
|
||||
manager: transactionManager,
|
||||
inventoryService,
|
||||
productVariantInventoryService,
|
||||
productVariantService,
|
||||
}
|
||||
|
||||
if (variantIdsNotBelongingToProduct.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Variants with id: ${variantIdsNotBelongingToProduct.join(
|
||||
", "
|
||||
)} are not associated with this product`
|
||||
const productVariantServiceTx =
|
||||
productVariantService.withTransaction(transactionManager)
|
||||
|
||||
// Delete the variant that does not exist anymore from the provided variants
|
||||
const variantIdsToDelete = [...productVariantMap.keys()].filter(
|
||||
(variantId) => !variantWithIdSet.has(variantId)
|
||||
)
|
||||
}
|
||||
|
||||
const allVariantTransactions: DistributedTransaction[] = []
|
||||
const transactionDependencies = {
|
||||
manager: transactionManager,
|
||||
inventoryService,
|
||||
productVariantInventoryService,
|
||||
productVariantService,
|
||||
}
|
||||
|
||||
const productVariantServiceTx =
|
||||
productVariantService.withTransaction(transactionManager)
|
||||
|
||||
// Delete the variant that does not exist anymore from the provided variants
|
||||
const variantIdsToDelete = [...productVariantMap.keys()].filter(
|
||||
(variantId) => !variantWithIdSet.has(variantId)
|
||||
)
|
||||
|
||||
if (variantIdsToDelete) {
|
||||
await productVariantServiceTx.delete(variantIdsToDelete)
|
||||
}
|
||||
|
||||
if (variantsToUpdate.length) {
|
||||
await productVariantServiceTx.update(variantsToUpdate)
|
||||
}
|
||||
|
||||
if (variantsToCreate.length) {
|
||||
try {
|
||||
const varTransaction = await createVariantsTransaction(
|
||||
transactionDependencies,
|
||||
product.id,
|
||||
variantsToCreate as CreateProductVariantInput[]
|
||||
)
|
||||
allVariantTransactions.push(varTransaction)
|
||||
} catch (e) {
|
||||
await Promise.all(
|
||||
allVariantTransactions.map(async (transaction) => {
|
||||
await revertVariantTransaction(
|
||||
transactionDependencies,
|
||||
transaction
|
||||
).catch(() => logger.warn("Transaction couldn't be reverted."))
|
||||
})
|
||||
)
|
||||
|
||||
throw e
|
||||
if (variantIdsToDelete) {
|
||||
await productVariantServiceTx.delete(variantIdsToDelete)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const rawProduct = await productService.retrieve(id, {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
})
|
||||
if (variantsToUpdate.length) {
|
||||
await productVariantServiceTx.update(variantsToUpdate)
|
||||
}
|
||||
|
||||
if (variantsToCreate.length) {
|
||||
try {
|
||||
const varTransaction = await createVariantsTransaction(
|
||||
transactionDependencies,
|
||||
product.id,
|
||||
variantsToCreate as CreateProductVariantInput[]
|
||||
)
|
||||
allVariantTransactions.push(varTransaction)
|
||||
} catch (e) {
|
||||
await Promise.all(
|
||||
allVariantTransactions.map(async (transaction) => {
|
||||
await revertVariantTransaction(
|
||||
transactionDependencies,
|
||||
transaction
|
||||
).catch(() => logger.warn("Transaction couldn't be reverted."))
|
||||
})
|
||||
)
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let rawProduct
|
||||
|
||||
if (featureFlagRouter.isFeatureEnabled(IsolateProductDomainFeatureFlag.key)) {
|
||||
rawProduct = await getProductWithIsolatedProductModule(req, id)
|
||||
} else {
|
||||
rawProduct = await productService.retrieve(id, {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
})
|
||||
}
|
||||
|
||||
const [product] = await pricingService.setProductPrices([rawProduct])
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user