feat(public): resolve goods by secondary sds id and merge variants
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||||
import { PublicService } from './public.service';
|
import { PublicService } from './public.service';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
@@ -11,9 +11,9 @@ describe('PublicService', () => {
|
|||||||
let categoryId: bigint;
|
let categoryId: bigint;
|
||||||
let childCategoryId: bigint;
|
let childCategoryId: bigint;
|
||||||
let otherCategoryId: bigint;
|
let otherCategoryId: bigint;
|
||||||
let tagId: bigint;
|
let tagId: bigint;
|
||||||
let filterGroupIds: bigint[] = [];
|
let filterGroupIds: bigint[] = [];
|
||||||
let filterTagIds: bigint[] = [];
|
let filterTagIds: bigint[] = [];
|
||||||
let originGoodId: bigint;
|
let originGoodId: bigint;
|
||||||
let goodIds: bigint[] = [];
|
let goodIds: bigint[] = [];
|
||||||
|
|
||||||
@@ -98,51 +98,51 @@ describe('PublicService', () => {
|
|||||||
goodPriority: 1,
|
goodPriority: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
goodIds = [g1.id, g2.id, g3.id];
|
goodIds = [g1.id, g2.id, g3.id];
|
||||||
|
|
||||||
const craftGroup = await prisma.tagGroup.create({
|
const craftGroup = await prisma.tagGroup.create({
|
||||||
data: { groupName: `Pub Craft ${stamp}`, sortOrder: 100 },
|
data: { groupName: `Pub Craft ${stamp}`, sortOrder: 100 },
|
||||||
});
|
});
|
||||||
const materialGroup = await prisma.tagGroup.create({
|
const materialGroup = await prisma.tagGroup.create({
|
||||||
data: { groupName: `Pub Material ${stamp}`, sortOrder: 101 },
|
data: { groupName: `Pub Material ${stamp}`, sortOrder: 101 },
|
||||||
});
|
});
|
||||||
filterGroupIds = [craftGroup.id, materialGroup.id];
|
filterGroupIds = [craftGroup.id, materialGroup.id];
|
||||||
const craftA = await prisma.tag.create({
|
const craftA = await prisma.tag.create({
|
||||||
data: { tagName: `Pub Craft A ${stamp}`, tagGroupId: craftGroup.id },
|
data: { tagName: `Pub Craft A ${stamp}`, tagGroupId: craftGroup.id },
|
||||||
});
|
});
|
||||||
const craftB = await prisma.tag.create({
|
const craftB = await prisma.tag.create({
|
||||||
data: { tagName: `Pub Craft B ${stamp}`, tagGroupId: craftGroup.id },
|
data: { tagName: `Pub Craft B ${stamp}`, tagGroupId: craftGroup.id },
|
||||||
});
|
});
|
||||||
const cotton = await prisma.tag.create({
|
const cotton = await prisma.tag.create({
|
||||||
data: { tagName: `Pub Cotton ${stamp}`, tagGroupId: materialGroup.id },
|
data: { tagName: `Pub Cotton ${stamp}`, tagGroupId: materialGroup.id },
|
||||||
});
|
});
|
||||||
filterTagIds = [craftA.id, craftB.id, cotton.id];
|
filterTagIds = [craftA.id, craftB.id, cotton.id];
|
||||||
await prisma.goodTag.createMany({
|
await prisma.goodTag.createMany({
|
||||||
data: [
|
data: [
|
||||||
{ goodId: g1.id, tagId: craftA.id },
|
{ goodId: g1.id, tagId: craftA.id },
|
||||||
{ goodId: g1.id, tagId: cotton.id },
|
{ goodId: g1.id, tagId: cotton.id },
|
||||||
{ goodId: g2.id, tagId: craftB.id },
|
{ goodId: g2.id, tagId: craftB.id },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.originGoodDetail.create({
|
await prisma.originGoodDetail.create({
|
||||||
data: {
|
data: {
|
||||||
originGoodId,
|
originGoodId,
|
||||||
productCode: 'OZ10827003',
|
productCode: 'OZ10827003',
|
||||||
productionProcess: '白墨烫画',
|
productionProcess: '白墨烫画',
|
||||||
sizeChart: { columns: [], rows: [{ sizeId: 'size_0', sizeName: 'S', measurements: [] }] },
|
sizeChart: { columns: [], rows: [{ sizeId: 'size_0', sizeName: 'S', measurements: [] }] },
|
||||||
packageSpecs: { rows: [{ sizeId: 'size_0', sizeName: 'S' }] },
|
packageSpecs: { rows: [{ sizeId: 'size_0', sizeName: 'S' }] },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await prisma.originGoodVariant.create({
|
await prisma.originGoodVariant.create({
|
||||||
data: {
|
data: {
|
||||||
originGoodId,
|
originGoodId,
|
||||||
sdsVariantId: `pub-variant-${stamp}`,
|
sdsVariantId: `pub-variant-${stamp}`,
|
||||||
sku: `OZ${stamp}`,
|
sku: `OZ${stamp}`,
|
||||||
sizeName: 'S',
|
sizeName: 'S',
|
||||||
price: 38,
|
price: 38,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Seed a good in `otherCategory` so the "onlyHaveGoods" filter
|
// Seed a good in `otherCategory` so the "onlyHaveGoods" filter
|
||||||
// returns more than one category.
|
// returns more than one category.
|
||||||
@@ -179,9 +179,9 @@ describe('PublicService', () => {
|
|||||||
await prisma.position.deleteMany({
|
await prisma.position.deleteMany({
|
||||||
where: { countryId },
|
where: { countryId },
|
||||||
});
|
});
|
||||||
await prisma.tag.delete({ where: { id: tagId } });
|
await prisma.tag.delete({ where: { id: tagId } });
|
||||||
await prisma.tag.deleteMany({ where: { id: { in: filterTagIds } } });
|
await prisma.tag.deleteMany({ where: { id: { in: filterTagIds } } });
|
||||||
await prisma.tagGroup.deleteMany({ where: { id: { in: filterGroupIds } } });
|
await prisma.tagGroup.deleteMany({ where: { id: { in: filterGroupIds } } });
|
||||||
await prisma.originGood.delete({ where: { id: originGoodId } });
|
await prisma.originGood.delete({ where: { id: originGoodId } });
|
||||||
// Delete children before parent (FK self-relation is RESTRICT).
|
// Delete children before parent (FK self-relation is RESTRICT).
|
||||||
await prisma.category.delete({ where: { id: childCategoryId } });
|
await prisma.category.delete({ where: { id: childCategoryId } });
|
||||||
@@ -220,139 +220,139 @@ describe('PublicService', () => {
|
|||||||
const filtered = await service.getGoods({
|
const filtered = await service.getGoods({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
countryId: countryId.toString(),
|
countryId: countryId.toString(),
|
||||||
categoryId: categoryId.toString(), // includes child
|
categoryId: categoryId.toString(), // includes child
|
||||||
keyword: `Pub `,
|
keyword: `Pub `,
|
||||||
});
|
});
|
||||||
expect(filtered.total).toBeGreaterThanOrEqual(4); // High, Mid, NoPos, ChildGood
|
expect(filtered.total).toBeGreaterThanOrEqual(4); // High, Mid, NoPos, ChildGood
|
||||||
expect(filtered.items.every((g) => g.country.id === countryId.toString())).toBe(true);
|
expect(filtered.items.every((g) => g.country.id === countryId.toString())).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sorts by priority DESC, position.indexVal ASC, createdAt DESC', async () => {
|
it('sorts by priority DESC, position.indexVal ASC, createdAt DESC', async () => {
|
||||||
const result = await service.getGoods({
|
const result = await service.getGoods({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
countryId: countryId.toString(),
|
countryId: countryId.toString(),
|
||||||
keyword: `Pub `,
|
keyword: `Pub `,
|
||||||
});
|
});
|
||||||
const priorities = result.items.map((g) => g.goodPriority);
|
const priorities = result.items.map((g) => g.goodPriority);
|
||||||
// First verify primary descending priority.
|
// First verify primary descending priority.
|
||||||
const sorted = [...priorities].sort((a, b) => b - a);
|
const sorted = [...priorities].sort((a, b) => b - a);
|
||||||
expect(priorities).toEqual(sorted);
|
expect(priorities).toEqual(sorted);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses OR within one tag group and AND across tag groups', async () => {
|
|
||||||
const sameGroup = await service.getGoods({
|
|
||||||
page: 1,
|
|
||||||
pageSize: 50,
|
|
||||||
countryId: countryId.toString(),
|
|
||||||
keyword: `Pub `,
|
|
||||||
tags: [
|
|
||||||
{
|
|
||||||
tagGroupId: filterGroupIds[0].toString(),
|
|
||||||
tagIds: filterTagIds.slice(0, 2).map(String),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
expect(sameGroup.items.map((item) => item.goodName)).toEqual(
|
|
||||||
expect.arrayContaining([`Pub High ${stamp}`, `Pub Mid ${stamp}`]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const acrossGroups = await service.getGoods({
|
|
||||||
page: 1,
|
|
||||||
pageSize: 50,
|
|
||||||
countryId: countryId.toString(),
|
|
||||||
keyword: `Pub `,
|
|
||||||
tags: [
|
|
||||||
{
|
|
||||||
tagGroupId: filterGroupIds[0].toString(),
|
|
||||||
tagIds: filterTagIds.slice(0, 2).map(String),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tagGroupId: filterGroupIds[1].toString(),
|
|
||||||
tagIds: [filterTagIds[2].toString()],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
expect(acrossGroups.items.map((item) => item.goodName)).toContain(`Pub High ${stamp}`);
|
|
||||||
expect(acrossGroups.items.map((item) => item.goodName)).not.toContain(`Pub Mid ${stamp}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('rejects a tag paired with the wrong tag group', async () => {
|
|
||||||
await expect(
|
|
||||||
service.getGoods({
|
|
||||||
page: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
tags: [
|
|
||||||
{
|
|
||||||
tagGroupId: filterGroupIds[1].toString(),
|
|
||||||
tagIds: [filterTagIds[0].toString()],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the SDS product id as the public product id', async () => {
|
it('uses OR within one tag group and AND across tag groups', async () => {
|
||||||
|
const sameGroup = await service.getGoods({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
countryId: countryId.toString(),
|
||||||
|
keyword: `Pub `,
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
tagGroupId: filterGroupIds[0].toString(),
|
||||||
|
tagIds: filterTagIds.slice(0, 2).map(String),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(sameGroup.items.map((item) => item.goodName)).toEqual(
|
||||||
|
expect.arrayContaining([`Pub High ${stamp}`, `Pub Mid ${stamp}`]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const acrossGroups = await service.getGoods({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
countryId: countryId.toString(),
|
||||||
|
keyword: `Pub `,
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
tagGroupId: filterGroupIds[0].toString(),
|
||||||
|
tagIds: filterTagIds.slice(0, 2).map(String),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tagGroupId: filterGroupIds[1].toString(),
|
||||||
|
tagIds: [filterTagIds[2].toString()],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(acrossGroups.items.map((item) => item.goodName)).toContain(`Pub High ${stamp}`);
|
||||||
|
expect(acrossGroups.items.map((item) => item.goodName)).not.toContain(`Pub Mid ${stamp}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a tag paired with the wrong tag group', async () => {
|
||||||
|
await expect(
|
||||||
|
service.getGoods({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
tagGroupId: filterGroupIds[1].toString(),
|
||||||
|
tagIds: [filterTagIds[0].toString()],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the SDS product id as the public product id', async () => {
|
||||||
const result = await service.getGoods({
|
const result = await service.getGoods({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
countryId: countryId.toString(),
|
countryId: countryId.toString(),
|
||||||
keyword: `Pub High ${stamp}`,
|
keyword: `Pub High ${stamp}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.items).toHaveLength(1);
|
expect(result.items).toHaveLength(1);
|
||||||
expect(result.items[0].goodId).toBe(`pub-sds-${stamp}`);
|
expect(result.items[0].goodId).toBe(`pub-sds-${stamp}`);
|
||||||
expect(result.items[0].goodId).not.toBe(goodIds[0].toString());
|
expect(result.items[0].goodId).not.toBe(goodIds[0].toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns custom goods through the same public product contract', async () => {
|
it('returns custom goods through the same public product contract', async () => {
|
||||||
const customPublicId = `custom-public-${stamp}`;
|
const customPublicId = `custom-public-${stamp}`;
|
||||||
const origin = await prisma.originGood.create({
|
const origin = await prisma.originGood.create({
|
||||||
data: {
|
data: {
|
||||||
source: 'CUSTOM',
|
source: 'CUSTOM',
|
||||||
sdsGoodId: customPublicId,
|
sdsGoodId: customPublicId,
|
||||||
goodName: `Pub Custom ${stamp}`,
|
goodName: `Pub Custom ${stamp}`,
|
||||||
goodPrice: 42,
|
goodPrice: 42,
|
||||||
detail: { create: { productCode: `CUSTOM-${stamp}` } },
|
detail: { create: { productCode: `CUSTOM-${stamp}` } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const good = await prisma.good.create({
|
const good = await prisma.good.create({
|
||||||
data: {
|
data: {
|
||||||
originGoodId: origin.id,
|
originGoodId: origin.id,
|
||||||
countryId,
|
countryId,
|
||||||
categoryId,
|
categoryId,
|
||||||
goodName: `Pub Custom ${stamp}`,
|
goodName: `Pub Custom ${stamp}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const detail = await service.getGood(customPublicId);
|
const detail = await service.getGood(customPublicId);
|
||||||
expect(detail.goodId).toBe(customPublicId);
|
expect(detail.goodId).toBe(customPublicId);
|
||||||
expect(detail.goodName).toBe(`Pub Custom ${stamp}`);
|
expect(detail.goodName).toBe(`Pub Custom ${stamp}`);
|
||||||
expect(detail.productCode).toBe(`CUSTOM-${stamp}`);
|
expect(detail.productCode).toBe(`CUSTOM-${stamp}`);
|
||||||
} finally {
|
} finally {
|
||||||
await prisma.good.delete({ where: { id: good.id } });
|
await prisma.good.delete({ where: { id: good.id } });
|
||||||
await prisma.originGood.delete({ where: { id: origin.id } });
|
await prisma.originGood.delete({ where: { id: origin.id } });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getGood returns detail and 404 for unknown id', async () => {
|
it('getGood returns detail and 404 for unknown id', async () => {
|
||||||
const first = await service.getGoods({
|
const first = await service.getGoods({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 1,
|
pageSize: 1,
|
||||||
countryId: countryId.toString(),
|
countryId: countryId.toString(),
|
||||||
keyword: `Pub `,
|
keyword: `Pub `,
|
||||||
});
|
});
|
||||||
expect(first.items.length).toBe(1);
|
expect(first.items.length).toBe(1);
|
||||||
const detail = await service.getGood(`pub-sds-${stamp}`);
|
const detail = await service.getGood(`pub-sds-${stamp}`);
|
||||||
expect(detail.goodId).toBe(first.items[0].goodId);
|
expect(detail.goodId).toBe(first.items[0].goodId);
|
||||||
expect(detail.productCode).toBe('OZ10827003');
|
expect(detail.productCode).toBe('OZ10827003');
|
||||||
expect(detail.details.productionProcess).toBe('白墨烫画');
|
expect(detail.details.productionProcess).toBe('白墨烫画');
|
||||||
expect((detail.sizeChart?.rows as unknown[])).toHaveLength(1);
|
expect((detail.sizeChart?.rows as unknown[])).toHaveLength(1);
|
||||||
expect((detail.packageSpecs?.rows as unknown[])).toHaveLength(1);
|
expect((detail.packageSpecs?.rows as unknown[])).toHaveLength(1);
|
||||||
expect(detail.variants).toHaveLength(1);
|
expect(detail.variants).toHaveLength(1);
|
||||||
|
|
||||||
await expect(service.getGood('99999999')).rejects.toBeInstanceOf(
|
await expect(service.getGood('99999999')).rejects.toBeInstanceOf(
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -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