feat(public): resolve goods by secondary sds id and merge variants
This commit is contained in:
@@ -379,4 +379,39 @@ describe('PublicService', () => {
|
|||||||
const sortOrders = groups.map((g) => g.sortOrder);
|
const sortOrders = groups.map((g) => g.sortOrder);
|
||||||
expect([...sortOrders].sort((a, b) => a - b)).toEqual(sortOrders);
|
expect([...sortOrders].sort((a, b) => a - b)).toEqual(sortOrders);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('merged secondary origin goods', () => {
|
||||||
|
it('resolves a good by secondary sdsGoodId with merged variants', async () => {
|
||||||
|
const secondary = await prisma.originGood.create({
|
||||||
|
data: { sdsGoodId: `pub-secondary-${stamp}`, goodName: `Pub Secondary ${stamp}` },
|
||||||
|
});
|
||||||
|
const secVariant = await prisma.originGoodVariant.create({
|
||||||
|
data: {
|
||||||
|
originGoodId: secondary.id,
|
||||||
|
sdsVariantId: `pub-var-sec-${stamp}`,
|
||||||
|
sku: `PUB-SEC-${stamp}`,
|
||||||
|
colorId: 'black',
|
||||||
|
colorName: '黑色',
|
||||||
|
imageUrl: 'http://img/black-sec',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Attach as secondary source of the highest-priority fixture good.
|
||||||
|
await prisma.goodOriginGood.create({
|
||||||
|
data: { goodId: goodIds[0], originGoodId: secondary.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const detail = await service.getGood(`pub-secondary-${stamp}`);
|
||||||
|
expect(detail.goodId).toBe(`pub-sds-${stamp}`); // 对外 goodId 仍是主源
|
||||||
|
expect(detail.variants.length).toBeGreaterThanOrEqual(2);
|
||||||
|
const black = detail.mediaByColor.find((g) => g.colorName === '黑色');
|
||||||
|
expect(black).toBeTruthy();
|
||||||
|
expect(black!.images).toContain('http://img/black-sec');
|
||||||
|
} finally {
|
||||||
|
await prisma.goodOriginGood.deleteMany({ where: { originGoodId: secondary.id } });
|
||||||
|
await prisma.originGoodVariant.delete({ where: { id: secVariant.id } }).catch(() => undefined);
|
||||||
|
await prisma.originGood.delete({ where: { id: secondary.id } }).catch(() => undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ const PUBLIC_GOOD_INCLUDE = {
|
|||||||
variants: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
variants: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mergedOriginGoods: {
|
||||||
|
orderBy: { createdAt: 'asc' as const },
|
||||||
|
include: {
|
||||||
|
originGood: {
|
||||||
|
include: {
|
||||||
|
variants: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
goodTags: { include: { tag: { include: { tagGroup: true } } } },
|
goodTags: { include: { tag: { include: { tagGroup: true } } } },
|
||||||
} satisfies Prisma.GoodInclude;
|
} satisfies Prisma.GoodInclude;
|
||||||
|
|
||||||
@@ -198,7 +208,17 @@ export class PublicService {
|
|||||||
|
|
||||||
async getGood(goodId: string): Promise<PublicGoodDetailDto> {
|
async getGood(goodId: string): Promise<PublicGoodDetailDto> {
|
||||||
const good = await this.prisma.good.findFirst({
|
const good = await this.prisma.good.findFirst({
|
||||||
where: { originGood: { sdsGoodId: goodId, delisted: false } },
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ originGood: { sdsGoodId: goodId, delisted: false } },
|
||||||
|
// Merged secondary sources also resolve to the same good.
|
||||||
|
{
|
||||||
|
mergedOriginGoods: {
|
||||||
|
some: { originGood: { sdsGoodId: goodId, delisted: false } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
include: PUBLIC_GOOD_INCLUDE,
|
include: PUBLIC_GOOD_INCLUDE,
|
||||||
orderBy: [{ goodPriority: 'desc' }, { id: 'asc' }],
|
orderBy: [{ goodPriority: 'desc' }, { id: 'asc' }],
|
||||||
});
|
});
|
||||||
@@ -277,7 +297,7 @@ export class PublicService {
|
|||||||
* design-layer素材图 and the product-level blank garment photo are excluded
|
* design-layer素材图 and the product-level blank garment photo are excluded
|
||||||
* because they are not per-color gallery photos. */
|
* because they are not per-color gallery photos. */
|
||||||
private groupImagesByColor(
|
private groupImagesByColor(
|
||||||
variants: PublicGoodRow['originGood']['variants'],
|
variants: Array<PublicGoodRow['originGood']['variants'][number]>,
|
||||||
): Array<{ colorId: string | null; colorName: string | null; colorHex: string | null; images: string[] }> {
|
): Array<{ colorId: string | null; colorName: string | null; colorHex: string | null; images: string[] }> {
|
||||||
const groups = new Map<string, {
|
const groups = new Map<string, {
|
||||||
colorId: string | null;
|
colorId: string | null;
|
||||||
@@ -335,6 +355,11 @@ export class PublicService {
|
|||||||
private toPublicGoodDetail(good: PublicGoodRow): PublicGoodDetailDto {
|
private toPublicGoodDetail(good: PublicGoodRow): PublicGoodDetailDto {
|
||||||
const base = this.toPublicGood(good);
|
const base = this.toPublicGood(good);
|
||||||
const detail = good.originGood.detail;
|
const detail = good.originGood.detail;
|
||||||
|
// Merge primary and secondary origin good variants (dedup identical URLs).
|
||||||
|
const allVariants = [
|
||||||
|
...good.originGood.variants,
|
||||||
|
...good.mergedOriginGoods.flatMap((m) => m.originGood.variants),
|
||||||
|
];
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
productCode: detail?.productCode ?? null,
|
productCode: detail?.productCode ?? null,
|
||||||
@@ -354,11 +379,11 @@ export class PublicService {
|
|||||||
pictureRequest: detail?.pictureRequest ?? null,
|
pictureRequest: detail?.pictureRequest ?? null,
|
||||||
},
|
},
|
||||||
media: (detail?.media as Record<string, unknown> | null) ?? null,
|
media: (detail?.media as Record<string, unknown> | null) ?? null,
|
||||||
mediaByColor: this.groupImagesByColor(good.originGood.variants),
|
mediaByColor: this.groupImagesByColor(allVariants),
|
||||||
options: (detail?.options as Record<string, unknown> | null) ?? null,
|
options: (detail?.options as Record<string, unknown> | null) ?? null,
|
||||||
sizeChart: (detail?.sizeChart as Record<string, unknown> | null) ?? null,
|
sizeChart: (detail?.sizeChart as Record<string, unknown> | null) ?? null,
|
||||||
packageSpecs: (detail?.packageSpecs as Record<string, unknown> | null) ?? null,
|
packageSpecs: (detail?.packageSpecs as Record<string, unknown> | null) ?? null,
|
||||||
variants: good.originGood.variants.map((variant) => ({
|
variants: allVariants.map((variant) => ({
|
||||||
id: variant.sdsVariantId,
|
id: variant.sdsVariantId,
|
||||||
sku: variant.sku,
|
sku: variant.sku,
|
||||||
sizeId: variant.sizeId,
|
sizeId: variant.sizeId,
|
||||||
|
|||||||
Reference in New Issue
Block a user