fix(product): Update full descendant tree mpath when updating parent category id (#10144)
FIXES FRMW-2774 **What** When updating the parent category id, all descendant mpath should be re computed
This commit is contained in:
committed by
GitHub
parent
59bf9afd48
commit
1f44281ed6
5
.changeset/mighty-years-crash.md
Normal file
5
.changeset/mighty-years-crash.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/product": patch
|
||||
---
|
||||
|
||||
fix(product): Update full descendant true when update parent category id
|
||||
@@ -1129,6 +1129,109 @@ moduleIntegrationTestRunner<Service>({
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it(`should update the mpath of the full descendent tree successfully when moving the grand parent in the hierarchy`, async () => {
|
||||
for (const entry of eletronicsCategoriesData) {
|
||||
await service.create([entry])
|
||||
}
|
||||
|
||||
let [productCategory] = await service.list(
|
||||
{
|
||||
id: "laptops",
|
||||
},
|
||||
{
|
||||
select: ["id", "handle"],
|
||||
}
|
||||
)
|
||||
|
||||
await service.update([
|
||||
{
|
||||
id: productCategory.id,
|
||||
parent_category_id: "gaming-desktops",
|
||||
},
|
||||
])
|
||||
;[productCategory] = await service.list({
|
||||
id: "laptops",
|
||||
include_descendants_tree: true,
|
||||
})
|
||||
|
||||
expect(productCategory).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "laptops",
|
||||
mpath: "electronics.computers.desktops.gaming-desktops.laptops",
|
||||
parent_category_id: "gaming-desktops",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "gaming-laptops",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.gaming-laptops",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "budget-gaming",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.gaming-laptops.budget-gaming",
|
||||
parent_category_id: "gaming-laptops",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "high-performance",
|
||||
parent_category_id: "gaming-laptops",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.gaming-laptops.high-performance",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "4k-gaming",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.gaming-laptops.high-performance.4k-gaming",
|
||||
parent_category_id: "high-performance",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "vr-ready",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.gaming-laptops.high-performance.vr-ready",
|
||||
parent_category_id: "high-performance",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "ultrabooks",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.ultrabooks",
|
||||
parent_category_id: "laptops",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "convertible-ultrabooks",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.ultrabooks.convertible-ultrabooks",
|
||||
parent_category_id: "ultrabooks",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "detachable-ultrabooks",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.ultrabooks.convertible-ultrabooks.detachable-ultrabooks",
|
||||
parent_category_id: "convertible-ultrabooks",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "touchscreen-ultrabooks",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.ultrabooks.convertible-ultrabooks.touchscreen-ultrabooks",
|
||||
parent_category_id: "convertible-ultrabooks",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "thin-light",
|
||||
mpath:
|
||||
"electronics.computers.desktops.gaming-desktops.laptops.ultrabooks.thin-light",
|
||||
parent_category_id: "ultrabooks",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
} from "@medusajs/framework/types"
|
||||
import { DALUtils, isDefined, MedusaError } from "@medusajs/framework/utils"
|
||||
import {
|
||||
EntityDTO,
|
||||
LoadStrategy,
|
||||
FilterQuery as MikroFilterQuery,
|
||||
FindOptions as MikroOptions,
|
||||
LoadStrategy,
|
||||
RequiredEntityData,
|
||||
} from "@mikro-orm/core"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductCategory } from "@models"
|
||||
@@ -121,20 +123,26 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
ancestors?: boolean
|
||||
},
|
||||
productCategories: ProductCategory[],
|
||||
findOptions: DAL.FindOptions<ProductCategory> = { where: {} },
|
||||
findOptions: DAL.FindOptions<ProductCategory> & {
|
||||
serialize?: boolean
|
||||
} = { where: {} },
|
||||
context: Context = {}
|
||||
): Promise<ProductCategory[]> {
|
||||
const { serialize = true } = findOptions
|
||||
delete findOptions.serialize
|
||||
|
||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||
|
||||
// We dont want to get the relations as we will fetch all the categories and build the tree manually
|
||||
let relationIndex =
|
||||
findOptions.options?.populate?.indexOf("parent_category")
|
||||
findOptions.options?.populate?.indexOf("parent_category") ?? -1
|
||||
const shouldPopulateParent = relationIndex !== -1
|
||||
if (shouldPopulateParent && include.ancestors) {
|
||||
findOptions.options!.populate!.splice(relationIndex as number, 1)
|
||||
}
|
||||
|
||||
relationIndex = findOptions.options?.populate?.indexOf("category_children")
|
||||
relationIndex =
|
||||
findOptions.options?.populate?.indexOf("category_children") ?? -1
|
||||
const shouldPopulateChildren = relationIndex !== -1
|
||||
|
||||
if (shouldPopulateChildren && include.descendants) {
|
||||
@@ -171,9 +179,11 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
delete where.mpath
|
||||
delete where.parent_category_id
|
||||
|
||||
const categoriesInTree = await this.serialize<ProductCategory[]>(
|
||||
await manager.find(ProductCategory, where, options)
|
||||
)
|
||||
const categoriesInTree = serialize
|
||||
? await this.serialize<ProductCategory[]>(
|
||||
await manager.find(ProductCategory, where, options)
|
||||
)
|
||||
: await manager.find(ProductCategory, where, options)
|
||||
|
||||
const categoriesById = new Map(categoriesInTree.map((cat) => [cat.id, cat]))
|
||||
|
||||
@@ -352,7 +362,7 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
|
||||
const categories = await Promise.all(
|
||||
data.map(async (entry, i) => {
|
||||
const categoryData: Partial<ProductCategory> = { ...entry }
|
||||
const categoryData: Partial<EntityDTO<ProductCategory>> = { ...entry }
|
||||
const siblingsCount = await manager.count(ProductCategory, {
|
||||
parent_category_id: categoryData?.parent_category_id || null,
|
||||
})
|
||||
@@ -387,7 +397,10 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
categoryData.mpath = parentCategory.mpath
|
||||
}
|
||||
|
||||
return manager.create(ProductCategory, categoryData as ProductCategory)
|
||||
return manager.create(
|
||||
ProductCategory,
|
||||
categoryData as RequiredEntityData<ProductCategory>
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@@ -402,10 +415,10 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
const manager = super.getActiveManager<SqlEntityManager>(context)
|
||||
const categories = await Promise.all(
|
||||
data.map(async (entry, i) => {
|
||||
const categoryData: Partial<ProductCategory> = { ...entry }
|
||||
const productCategory = await manager.findOne(ProductCategory, {
|
||||
const categoryData: Partial<EntityDTO<ProductCategory>> = { ...entry }
|
||||
let productCategory = (await manager.findOne(ProductCategory, {
|
||||
id: categoryData.id,
|
||||
})
|
||||
})) as ProductCategory
|
||||
|
||||
if (!productCategory) {
|
||||
throw new MedusaError(
|
||||
@@ -438,17 +451,59 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
if (categoryData.parent_category_id === null) {
|
||||
categoryData.mpath = ""
|
||||
} else {
|
||||
productCategory = (
|
||||
await this.buildProductCategoriesWithTree(
|
||||
{
|
||||
descendants: true,
|
||||
},
|
||||
[productCategory],
|
||||
{
|
||||
where: { id: productCategory.id },
|
||||
serialize: false,
|
||||
},
|
||||
context
|
||||
)
|
||||
)[0]
|
||||
|
||||
const newParentCategory = await manager.findOne(
|
||||
ProductCategory,
|
||||
categoryData.parent_category_id
|
||||
)
|
||||
|
||||
if (!newParentCategory) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
`Parent category with id: '${categoryData.parent_category_id}' does not exist`
|
||||
)
|
||||
}
|
||||
categoryData.mpath = `${newParentCategory.mpath}.${productCategory.id}`
|
||||
|
||||
const categoryDataChildren =
|
||||
categoryData.category_children?.flatMap(
|
||||
(child) => child.category_children ?? []
|
||||
)
|
||||
|
||||
const categoryDataChildrenMap = new Map(
|
||||
categoryDataChildren?.map((child) => [child.id, child])
|
||||
)
|
||||
|
||||
function updateMpathRecursively(
|
||||
category: ProductCategory,
|
||||
newBaseMpath: string
|
||||
) {
|
||||
const newMpath = `${newBaseMpath}.${category.id}`
|
||||
category.mpath = newMpath
|
||||
for (let child of category.category_children) {
|
||||
child = manager.getReference(ProductCategory, child.id)
|
||||
manager.assign(
|
||||
child,
|
||||
categoryDataChildrenMap.get(child.id) ?? {}
|
||||
)
|
||||
updateMpathRecursively(child, newMpath)
|
||||
}
|
||||
}
|
||||
|
||||
updateMpathRecursively(productCategory!, newParentCategory.mpath!)
|
||||
// categoryData.mpath = `${newParentCategory.mpath}.${productCategory.id}`
|
||||
}
|
||||
|
||||
// Rerank the siblings in the new parent
|
||||
@@ -491,7 +546,9 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
await this.rerankAllSiblings(
|
||||
manager,
|
||||
productCategory,
|
||||
categoryData as ProductCategory
|
||||
categoryData as Partial<EntityDTO<ProductCategory>> & {
|
||||
rank: number
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -529,7 +586,7 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
|
||||
protected async rerankSiblingsAfterCreation(
|
||||
manager: SqlEntityManager,
|
||||
addedSibling: Partial<ProductCategory>
|
||||
addedSibling: Partial<EntityDTO<ProductCategory>>
|
||||
) {
|
||||
const affectedSiblings = await manager.find(ProductCategory, {
|
||||
parent_category_id: addedSibling.parent_category_id,
|
||||
@@ -547,7 +604,7 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
|
||||
protected async rerankAllSiblings(
|
||||
manager: SqlEntityManager,
|
||||
originalSibling: Partial<ProductCategory> & { rank: number },
|
||||
updatedSibling: Partial<ProductCategory> & { rank: number }
|
||||
updatedSibling: Partial<EntityDTO<ProductCategory>> & { rank: number }
|
||||
) {
|
||||
if (originalSibling.rank === updatedSibling.rank) {
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user