Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(product): Update full descendant tree mpath when updating parent category id #10144

Merged
merged 6 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mighty-years-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/product": patch
---

fix(product): Update full descendant true when update parent category id
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,109 @@ moduleIntegrationTestRunner<Service>({
])
)
})

it(`should update the mpath of the fulle descenden tree successfully when moving the grand parent in the hierarchy`, async () => {
adrien2p marked this conversation as resolved.
Show resolved Hide resolved
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", () => {
Expand Down
89 changes: 73 additions & 16 deletions packages/modules/product/src/repositories/product-category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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]))

Expand Down Expand Up @@ -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,
})
Expand Down Expand Up @@ -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>
)
})
)

Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
)
}

Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
Loading