feat(origin-goods): include merged secondary references in tree stats
This commit is contained in:
@@ -77,4 +77,69 @@ describe('OriginGoodsService', () => {
|
||||
expect(result.total).toBe(0);
|
||||
expect(result.items.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('getTree merged references', () => {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
function findOgNode(
|
||||
treeResponse: { tree: any[] },
|
||||
ogId: string,
|
||||
): { configuredCount: number; configuredCountries: string[] } {
|
||||
let found: { configuredCount: number; configuredCountries: string[] } | null = null;
|
||||
const walk = (nodes: any[]) => {
|
||||
for (const n of nodes) {
|
||||
const hit = (n.originGoods ?? []).find((o: any) => o.id === ogId);
|
||||
if (hit) {
|
||||
found = hit;
|
||||
return;
|
||||
}
|
||||
if (n.children?.length) walk(n.children);
|
||||
}
|
||||
};
|
||||
walk(treeResponse.tree);
|
||||
if (!found) throw new Error(`og node ${ogId} not found in tree`);
|
||||
return found;
|
||||
}
|
||||
|
||||
it('counts secondary references as configured', async () => {
|
||||
const sdsCat = `tree-cat-${stamp}`;
|
||||
await prisma.originGood.createMany({
|
||||
data: [
|
||||
{ sdsGoodId: `tree-a-${stamp}`, goodName: `Tree A ${stamp}`, sdsCategoryId: sdsCat },
|
||||
{ sdsGoodId: `tree-b-${stamp}`, goodName: `Tree B ${stamp}`, sdsCategoryId: sdsCat },
|
||||
],
|
||||
});
|
||||
createdSds.push(`tree-a-${stamp}`, `tree-b-${stamp}`);
|
||||
const originA = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: `tree-a-${stamp}` } });
|
||||
const originB = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: `tree-b-${stamp}` } });
|
||||
|
||||
const cat = await prisma.category.create({
|
||||
data: { categoryName: `Tree Cat ${stamp}`, sdsCategoryId: sdsCat },
|
||||
});
|
||||
const country = await prisma.country.create({
|
||||
data: { countryName: `Tree Country ${stamp}` },
|
||||
});
|
||||
const good = await prisma.good.create({
|
||||
data: {
|
||||
goodName: `Tree Good ${stamp}`,
|
||||
originGoodId: originA.id,
|
||||
countryId: country.id,
|
||||
categoryId: cat.id,
|
||||
},
|
||||
});
|
||||
await prisma.goodOriginGood.create({
|
||||
data: { goodId: good.id, originGoodId: originB.id },
|
||||
});
|
||||
|
||||
try {
|
||||
const tree = await service.getTree();
|
||||
const nodeB = findOgNode(tree, originB.id.toString());
|
||||
expect(nodeB.configuredCount).toBeGreaterThanOrEqual(1);
|
||||
expect(nodeB.configuredCountries).toContain(`Tree Country ${stamp}`);
|
||||
} finally {
|
||||
await prisma.good.delete({ where: { id: good.id } }).catch(() => undefined);
|
||||
await prisma.country.delete({ where: { id: country.id } }).catch(() => undefined);
|
||||
await prisma.category.delete({ where: { id: cat.id } }).catch(() => undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -114,7 +114,7 @@ export class OriginGoodsService {
|
||||
* under a synthetic "未分类" root node.
|
||||
*/
|
||||
async getTree(): Promise<OriginGoodsTreeResponse> {
|
||||
const [allCategories, allOriginGoods, configCounts, goodsWithCountries, goodsWithTags] =
|
||||
const [allCategories, allOriginGoods, configCounts, goodsWithCountries, goodsWithTags, mergedCounts, mergedWithCountries] =
|
||||
await Promise.all([
|
||||
this.prisma.category.findMany({
|
||||
where: { sdsCategoryId: { not: null } },
|
||||
@@ -144,7 +144,12 @@ export class OriginGoodsService {
|
||||
}),
|
||||
this.prisma.goodTag.findMany({
|
||||
select: {
|
||||
good: { select: { originGoodId: true } },
|
||||
good: {
|
||||
select: {
|
||||
originGoodId: true,
|
||||
mergedOriginGoods: { select: { originGoodId: true } },
|
||||
},
|
||||
},
|
||||
tag: {
|
||||
select: {
|
||||
tagName: true,
|
||||
@@ -157,26 +162,57 @@ export class OriginGoodsService {
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Secondary-source references (good_origin_goods)
|
||||
this.prisma.goodOriginGood.groupBy({
|
||||
by: ['originGoodId'],
|
||||
_count: { _all: true },
|
||||
}),
|
||||
this.prisma.goodOriginGood.findMany({
|
||||
select: {
|
||||
originGoodId: true,
|
||||
good: { select: { country: { select: { countryName: true } } } },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const countMap = new Map<string, number>();
|
||||
configCounts.forEach((c) =>
|
||||
countMap.set(c.originGoodId.toString(), c._count._all),
|
||||
);
|
||||
|
||||
const countryMap = new Map<string, string[]>();
|
||||
goodsWithCountries.forEach((g) => {
|
||||
const key = g.originGoodId.toString();
|
||||
const name = g.country?.countryName;
|
||||
if (!name) return;
|
||||
const arr = countryMap.get(key);
|
||||
if (arr) arr.push(name);
|
||||
else countryMap.set(key, [name]);
|
||||
// Secondary (merged) references count towards configured status too.
|
||||
mergedCounts.forEach((c) => {
|
||||
const key = c.originGoodId.toString();
|
||||
countMap.set(key, (countMap.get(key) ?? 0) + c._count._all);
|
||||
});
|
||||
|
||||
const countryMap = new Map<string, string[]>();
|
||||
const addCountry = (key: string, name?: string | null) => {
|
||||
if (!name) return;
|
||||
const arr = countryMap.get(key);
|
||||
if (!arr?.includes(name)) {
|
||||
countryMap.set(key, [...(arr ?? []), name]);
|
||||
}
|
||||
};
|
||||
goodsWithCountries.forEach((g) =>
|
||||
addCountry(g.originGoodId.toString(), g.country?.countryName),
|
||||
);
|
||||
mergedWithCountries.forEach((m) =>
|
||||
addCountry(m.originGoodId.toString(), m.good.country?.countryName),
|
||||
);
|
||||
|
||||
const tagMap = new Map<string, { tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroupId: string | null; tagGroupName: string | null; sortOrder: number }[]>();
|
||||
const addTag = (
|
||||
key: string,
|
||||
tagInfo: { tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroupId: string | null; tagGroupName: string | null; sortOrder: number },
|
||||
) => {
|
||||
const arr = tagMap.get(key);
|
||||
if (arr) {
|
||||
if (!arr.some((t) => t.tagName === tagInfo.tagName)) arr.push(tagInfo);
|
||||
} else {
|
||||
tagMap.set(key, [tagInfo]);
|
||||
}
|
||||
};
|
||||
goodsWithTags.forEach((gt) => {
|
||||
const key = gt.good.originGoodId.toString();
|
||||
const tagInfo = {
|
||||
tagName: gt.tag.tagName,
|
||||
tagColor: gt.tag.tagColor,
|
||||
@@ -185,11 +221,10 @@ export class OriginGoodsService {
|
||||
tagGroupName: gt.tag.tagGroup?.groupName ?? null,
|
||||
sortOrder: gt.tag.sortOrder,
|
||||
};
|
||||
const arr = tagMap.get(key);
|
||||
if (arr) {
|
||||
if (!arr.some((t) => t.tagName === tagInfo.tagName)) arr.push(tagInfo);
|
||||
} else {
|
||||
tagMap.set(key, [tagInfo]);
|
||||
addTag(gt.good.originGoodId.toString(), tagInfo);
|
||||
// A good's tags also mark its secondary origin goods as configured.
|
||||
for (const m of gt.good.mergedOriginGoods) {
|
||||
addTag(m.originGoodId.toString(), tagInfo);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user