fix(goods): 商品展示名统一「品名 SKU」——剥 ASCII 型号限定词、纯款号补描述
左栏商品名显示成光款号(如 PLTK016)的根因是双层解析不一致:api 写路径按 首括号对解析产出「童装…(DTG180) PLTK016」,admin 展示解析按末括号取品名 误显示成 PLTK016。 - admin parseLinkName 对齐 api 首括号对解析 + 无 SKU 段中文品名守卫, cleanLinkName 剥离 ASCII 型号限定词(中文括号内容保留) - api 新增 cleanGoodDisplayName/describePureSkuGoodName,接入 create/update/ batchCreate(batchCreate 补上缺失的 normalize);纯款号按 SDS 分类名补描述 - 新增幂等存量修复脚本 fix:good-names(默认 dry-run,--apply 写库) - 已执行:V2 栈 34 条限定词名、V1 栈 213 条原始链接名规范化,复跑幂等 0 Tests: api 22 suites/199 passed, admin 27 passed, tsc clean
This commit is contained in:
@@ -3,7 +3,12 @@ import {
|
||||
BadRequestException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { GoodsService } from './goods.service';
|
||||
import {
|
||||
cleanGoodDisplayName,
|
||||
describePureSkuGoodName,
|
||||
GoodsService,
|
||||
isPureSkuGoodName,
|
||||
} from './goods.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { SyncService } from '../sync/sync.service';
|
||||
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
|
||||
@@ -312,6 +317,223 @@ describe('GoodsService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('pure sku good name(纯款号补描述)', () => {
|
||||
const pskuCode = `PSKU${stamp}`;
|
||||
const pskuDesc = `童装纯色测试插肩T恤${stamp}`;
|
||||
let pskuOriginGoodId: bigint;
|
||||
let bareOriginGoodId: bigint;
|
||||
|
||||
beforeAll(async () => {
|
||||
await prisma.category.create({
|
||||
data: {
|
||||
categoryName: `${pskuCode} ${pskuDesc}`,
|
||||
sdsCategoryId: `psku-cat-${stamp}`,
|
||||
},
|
||||
});
|
||||
const pskuOg = await prisma.originGood.create({
|
||||
data: {
|
||||
sdsGoodId: `sds-psku-${stamp}`,
|
||||
goodName: pskuCode,
|
||||
sdsCategoryId: `psku-cat-${stamp}`,
|
||||
},
|
||||
});
|
||||
pskuOriginGoodId = pskuOg.id;
|
||||
const bareOg = await prisma.originGood.create({
|
||||
data: {
|
||||
sdsGoodId: `sds-psku-bare-${stamp}`,
|
||||
goodName: `PSKB${stamp}`,
|
||||
},
|
||||
});
|
||||
bareOriginGoodId = bareOg.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.good.deleteMany({
|
||||
where: { OR: [{ goodName: { contains: pskuCode } }, { goodName: { contains: `PSKB${stamp}` } }] },
|
||||
});
|
||||
await prisma.originGood.deleteMany({
|
||||
where: { id: { in: [pskuOriginGoodId, bareOriginGoodId] } },
|
||||
});
|
||||
await prisma.category.deleteMany({
|
||||
where: { sdsCategoryId: `psku-cat-${stamp}` },
|
||||
});
|
||||
});
|
||||
|
||||
it('isPureSkuGoodName:仅纯字母数字为真', () => {
|
||||
expect(isPureSkuGoodName('PLTK016')).toBe(true);
|
||||
expect(isPureSkuGoodName(' pltk016 ')).toBe(true);
|
||||
expect(isPureSkuGoodName('PSKU1')).toBe(true);
|
||||
expect(isPureSkuGoodName('童装纯色插肩短袖T恤 PLTK016')).toBe(false);
|
||||
expect(isPureSkuGoodName('德国(不包邮)T恤-DG001-烫画')).toBe(false);
|
||||
expect(isPureSkuGoodName('230g水洗T恤 DETM002')).toBe(false);
|
||||
expect(isPureSkuGoodName('P')).toBe(false);
|
||||
expect(isPureSkuGoodName('')).toBe(false);
|
||||
});
|
||||
|
||||
it('cleanGoodDisplayName:品名内的 ASCII 型号限定词剥离,中文括号内容保留', () => {
|
||||
expect(
|
||||
cleanGoodDisplayName('波兰(包邮)童装纯色插肩短袖T恤(DTG180)-PLTK016-双面印花'),
|
||||
).toBe('童装纯色插肩短袖T恤 PLTK016');
|
||||
expect(cleanGoodDisplayName('童装纯色插肩短袖T恤(DTG180) PLTK016')).toBe(
|
||||
'童装纯色插肩短袖T恤 PLTK016',
|
||||
);
|
||||
expect(cleanGoodDisplayName('180G纯棉T恤 (JSA002) DG001')).toBe('180G纯棉T恤 DG001');
|
||||
expect(cleanGoodDisplayName('牛奶丝T恤(女款) DG601')).toBe('牛奶丝T恤(女款) DG601');
|
||||
expect(cleanGoodDisplayName('230g水洗T恤 DETM002')).toBe('230g水洗T恤 DETM002');
|
||||
});
|
||||
|
||||
it('describePureSkuGoodName:标准分类名剥款号补描述', () => {
|
||||
expect(
|
||||
describePureSkuGoodName('PLTK016', 'PLTK016 童装纯色插肩短袖T恤'),
|
||||
).toBe('童装纯色插肩短袖T恤 PLTK016');
|
||||
});
|
||||
|
||||
it('describePureSkuGoodName:分类名无款号前缀时用全名', () => {
|
||||
expect(
|
||||
describePureSkuGoodName('PLTK016', '童装纯色插肩短袖T恤'),
|
||||
).toBe('童装纯色插肩短袖T恤 PLTK016');
|
||||
});
|
||||
|
||||
it('describePureSkuGoodName:分类名即款号/空分类名/非纯款号名原样返回', () => {
|
||||
expect(describePureSkuGoodName('PLTK016', 'PLTK016')).toBe('PLTK016');
|
||||
expect(describePureSkuGoodName('PLTK016', null)).toBe('PLTK016');
|
||||
expect(describePureSkuGoodName('PLTK016', undefined)).toBe('PLTK016');
|
||||
expect(
|
||||
describePureSkuGoodName('童装T恤 PLTK016', 'PLTK016 童装T恤'),
|
||||
).toBe('童装T恤 PLTK016');
|
||||
expect(describePureSkuGoodName('230g水洗T恤 DETM002', 'PLTK016 童装T恤')).toBe(
|
||||
'230g水洗T恤 DETM002',
|
||||
);
|
||||
});
|
||||
|
||||
it('describePureSkuGoodName:分类名中的 ASCII 型号限定词一并剥离', () => {
|
||||
expect(
|
||||
describePureSkuGoodName('PLTK016', 'PLTK016 童装纯色插肩短袖T恤(DTG180)'),
|
||||
).toBe('童装纯色插肩短袖T恤 PLTK016');
|
||||
});
|
||||
|
||||
it('create:纯款号名按主链接 SDS 分类名补描述', async () => {
|
||||
const created = await service.create({
|
||||
goodName: pskuCode,
|
||||
originGoodId: Number(pskuOriginGoodId),
|
||||
countryId: Number(countryId),
|
||||
categoryId: Number(categoryId),
|
||||
});
|
||||
try {
|
||||
expect(created.goodName).toBe(`${pskuDesc} ${pskuCode}`);
|
||||
} finally {
|
||||
await prisma.good.delete({ where: { id: BigInt(created.id) } });
|
||||
}
|
||||
});
|
||||
|
||||
it('create:主链接无 sdsCategoryId 映射时纯款号原样保留', async () => {
|
||||
const created = await service.create({
|
||||
goodName: `PSKB${stamp}`,
|
||||
originGoodId: Number(bareOriginGoodId),
|
||||
countryId: Number(countryId),
|
||||
categoryId: Number(categoryId),
|
||||
});
|
||||
try {
|
||||
expect(created.goodName).toBe(`PSKB${stamp}`);
|
||||
} finally {
|
||||
await prisma.good.delete({ where: { id: BigInt(created.id) } });
|
||||
}
|
||||
});
|
||||
|
||||
it('update:改成纯款号名按商品主链接分类名补描述', async () => {
|
||||
const created = await service.create({
|
||||
goodName: `Goods Test ${stamp} psku-rename`,
|
||||
originGoodId: Number(pskuOriginGoodId),
|
||||
countryId: Number(countryId),
|
||||
categoryId: Number(categoryId),
|
||||
});
|
||||
try {
|
||||
const updated = await service.update(BigInt(created.id), {
|
||||
goodName: pskuCode,
|
||||
});
|
||||
expect(updated.goodName).toBe(`${pskuDesc} ${pskuCode}`);
|
||||
} finally {
|
||||
await prisma.good.delete({ where: { id: BigInt(created.id) } });
|
||||
}
|
||||
});
|
||||
|
||||
it('batchCreate:纯款号链接名补描述、完整链接名规范化', async () => {
|
||||
const created = await service.batchCreate({
|
||||
countryId: Number(countryId),
|
||||
categoryId: Number(categoryId),
|
||||
items: [{ originGoodId: Number(pskuOriginGoodId) }],
|
||||
});
|
||||
try {
|
||||
expect(created[0]?.goodName).toBe(`${pskuDesc} ${pskuCode}`);
|
||||
} finally {
|
||||
await prisma.good.deleteMany({
|
||||
where: { id: { in: created.map((g) => BigInt(g.id)) } },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('backfillPureSkuGoodNames:dry-run 只列不改,apply 改名且幂等', async () => {
|
||||
// 绕过写路径直写库,模拟存量纯款号名
|
||||
const row = await prisma.good.create({
|
||||
data: {
|
||||
goodName: pskuCode,
|
||||
originGoodId: pskuOriginGoodId,
|
||||
countryId,
|
||||
categoryId,
|
||||
},
|
||||
});
|
||||
try {
|
||||
// dry-run / apply 均用 goodId 限定在本夹具,避免测试副作用波及共享库真实数据
|
||||
const dry = await service.backfillPureSkuGoodNames({ dryRun: true, goodId: row.id });
|
||||
expect(dry.renames).toEqual([
|
||||
{ id: row.id.toString(), from: pskuCode, to: `${pskuDesc} ${pskuCode}` },
|
||||
]);
|
||||
expect(
|
||||
(await prisma.good.findUniqueOrThrow({ where: { id: row.id } })).goodName,
|
||||
).toBe(pskuCode);
|
||||
|
||||
const applied = await service.backfillPureSkuGoodNames({ dryRun: false, goodId: row.id });
|
||||
expect(applied.renamed).toBe(1);
|
||||
expect(
|
||||
(await prisma.good.findUniqueOrThrow({ where: { id: row.id } })).goodName,
|
||||
).toBe(`${pskuDesc} ${pskuCode}`);
|
||||
|
||||
// 幂等:已改名的行不再出现在后续清单
|
||||
const again = await service.backfillPureSkuGoodNames({ dryRun: true, goodId: row.id });
|
||||
expect(again.renames).toEqual([]);
|
||||
} finally {
|
||||
await prisma.good.delete({ where: { id: row.id } });
|
||||
}
|
||||
});
|
||||
|
||||
it('backfillPureSkuGoodNames:剥离「品名(型号限定词) SKU」存量名中的限定词', async () => {
|
||||
const row = await prisma.good.create({
|
||||
data: {
|
||||
goodName: `${pskuDesc}(DTG180) ${pskuCode}`,
|
||||
originGoodId: pskuOriginGoodId,
|
||||
countryId,
|
||||
categoryId,
|
||||
},
|
||||
});
|
||||
try {
|
||||
const dry = await service.backfillPureSkuGoodNames({ dryRun: true, goodId: row.id });
|
||||
expect(dry.renames).toEqual([
|
||||
{
|
||||
id: row.id.toString(),
|
||||
from: `${pskuDesc}(DTG180) ${pskuCode}`,
|
||||
to: `${pskuDesc} ${pskuCode}`,
|
||||
},
|
||||
]);
|
||||
await service.backfillPureSkuGoodNames({ dryRun: false, goodId: row.id });
|
||||
expect(
|
||||
(await prisma.good.findUniqueOrThrow({ where: { id: row.id } })).goodName,
|
||||
).toBe(`${pskuDesc} ${pskuCode}`);
|
||||
} finally {
|
||||
await prisma.good.delete({ where: { id: row.id } });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('merged origin goods', () => {
|
||||
it('creates a good with merged origin goods and reads them back', async () => {
|
||||
const created = await service.create({
|
||||
|
||||
Reference in New Issue
Block a user