refactor(api): parsing out of runtime — pure-mirror sync, explicit organize, auto aggregate recompute
解析去运行时化(三层架构,plans/refactor/organize-script-refactor.md): - 同步 = 纯镜像:upsertOriginGood 不再写解析列、不再自动挂族;详情同步仍触发重算 - 整理 = 显式人工动作(OrganizeService):解析列回填 → 派生标签(人工接管永不 覆盖)→ auto-group 建族 → 全量重算;入口 CLI(pnpm --filter @inkreach/api organize)+ POST /product-families/organize + 后台「整理」按钮 - 重算 = 纯结构化聚合:不再按名称重派生标签(防上游改名倒灌,回归测试覆盖); 矩阵维度只认标签/CUSTOM 显式标签,未整理成员不进矩阵;工艺=不打印时 印花数量以单面占位(纯结构化规则);「恢复自动」走整理的单链接派生 - 派生默认补齐(脚本层假设):名称无单/双面且工艺非不打印 → 印花数量单面印花 - goods 服务建品/更新后仅镜像标签+重算(不派生);含商品名入库规范化 (normalizeGoodName,管理员输入边界质检) - organize.service.spec 由 tag-sync spec 迁移 + 防倒灌回归;sync/recompute/ public/families spec 全部适配;API 173/173,admin typecheck+22/22
This commit is contained in:
@@ -3,16 +3,20 @@ import { Prisma } from '@prisma/client';
|
||||
import { SyncService } from './sync.service';
|
||||
import { SdsClientService } from './sds-client.service';
|
||||
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
|
||||
import { ProductFamiliesService } from '../product-families/product-families.service';
|
||||
import { OrganizeService } from '../product-families/organize.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
/**
|
||||
* 同步钩子集成测试:upsertOriginGood 的解析列写入、新链接自动挂族、
|
||||
* 同步钩子集成测试(解析去运行时化后):
|
||||
* upsertOriginGood 纯镜像(不写解析列)、新链接不自动挂族(归族走整理)、
|
||||
* persistProductDetail 后的族重算入队。
|
||||
*/
|
||||
describe('SyncService family hooks', () => {
|
||||
let service: SyncService;
|
||||
let prisma: PrismaService;
|
||||
let recompute: FamilyRecomputeService;
|
||||
let organize: OrganizeService;
|
||||
const stamp = Date.now();
|
||||
const createdOriginGoodIds: bigint[] = [];
|
||||
const createdFamilyIds: bigint[] = [];
|
||||
@@ -29,12 +33,15 @@ describe('SyncService family hooks', () => {
|
||||
useValue: {},
|
||||
},
|
||||
FamilyRecomputeService,
|
||||
ProductFamiliesService,
|
||||
OrganizeService,
|
||||
PrismaService,
|
||||
],
|
||||
}).compile();
|
||||
service = moduleRef.get(SyncService);
|
||||
prisma = moduleRef.get(PrismaService);
|
||||
recompute = moduleRef.get(FamilyRecomputeService);
|
||||
organize = moduleRef.get(OrganizeService);
|
||||
await prisma.onModuleInit();
|
||||
});
|
||||
|
||||
@@ -44,7 +51,7 @@ describe('SyncService family hooks', () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it('upsertOriginGood:写入四个解析列(create 与 update 全量覆盖)', async () => {
|
||||
it('upsertOriginGood:纯镜像——只存原文,不写解析列', async () => {
|
||||
const sdsId = `hook-${stamp}-parse`;
|
||||
const result1 = await (service as any).upsertOriginGood(
|
||||
sdsProduct(sdsId, '美国(包邮)240g涤纶休闲短裤-DG206-单面印花-美西洛杉矶一仓'),
|
||||
@@ -53,22 +60,24 @@ describe('SyncService family hooks', () => {
|
||||
expect(result1).toBe('inserted');
|
||||
const og = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: sdsId } });
|
||||
createdOriginGoodIds.push(og.id);
|
||||
expect(og.skuCode).toBe('DG206');
|
||||
expect(og.logisticsLabel).toBe('包邮');
|
||||
expect(og.craftLabel).toBe('单面印花');
|
||||
expect(og.warehouseLabel).toBe('美西洛杉矶一仓');
|
||||
expect(og.goodName).toBe('美国(包邮)240g涤纶休闲短裤-DG206-单面印花-美西洛杉矶一仓');
|
||||
expect(og.skuCode).toBeNull();
|
||||
expect(og.logisticsLabel).toBeNull();
|
||||
expect(og.craftLabel).toBeNull();
|
||||
expect(og.warehouseLabel).toBeNull();
|
||||
|
||||
// 更新为不带仓库的名称 → 解析列全量覆盖(warehouseLabel 置空)
|
||||
// 更新名称 → 镜像原文变化;解析列保持为空(由整理回填)
|
||||
await (service as any).upsertOriginGood(
|
||||
sdsProduct(sdsId, '美国(不包邮)240g涤纶休闲短裤-DG206-单面印花'),
|
||||
`cat-${stamp}-parse`,
|
||||
);
|
||||
const og2 = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: sdsId } });
|
||||
expect(og2.logisticsLabel).toBe('不包邮');
|
||||
expect(og2.warehouseLabel).toBeNull();
|
||||
expect(og2.goodName).toBe('美国(不包邮)240g涤纶休闲短裤-DG206-单面印花');
|
||||
expect(og2.skuCode).toBeNull();
|
||||
expect(og2.logisticsLabel).toBeNull();
|
||||
});
|
||||
|
||||
it('新链接自动挂族:按 SDS 分类唯一命中族则挂载并触发重算', async () => {
|
||||
it('新链接不自动挂族(归族由整理显式完成);族矩阵保持不变', async () => {
|
||||
const sdsCat = `cat-hook-${stamp}`;
|
||||
const cat = await prisma.category.create({
|
||||
data: { categoryName: `挂族测试分类-${stamp}`, sdsCategoryId: sdsCat },
|
||||
@@ -78,8 +87,6 @@ describe('SyncService family hooks', () => {
|
||||
sdsGoodId: `hook-${stamp}-seed`,
|
||||
goodName: `自动挂${stamp}(包邮)卫衣-ZZA${stamp}-单面印花`,
|
||||
sdsCategoryId: sdsCat,
|
||||
craftLabel: '单面印花',
|
||||
logisticsLabel: '包邮',
|
||||
},
|
||||
});
|
||||
createdOriginGoodIds.push(seed.id);
|
||||
@@ -106,11 +113,12 @@ describe('SyncService family hooks', () => {
|
||||
where: { id: seed.id },
|
||||
data: { familyId: family.id },
|
||||
});
|
||||
await organize.deriveTagsForOg(seed.id);
|
||||
await recompute.recomputeFamily(family.id);
|
||||
const before = await prisma.productFamily.findUniqueOrThrow({ where: { id: family.id } });
|
||||
expect((before.priceMatrix as any).rows).toHaveLength(1);
|
||||
|
||||
// 同分类新链接(不同工艺)→ 自动挂进唯一族
|
||||
// 同分类新链接(不同工艺)→ 同步后保持无族,等整理/管理员归族
|
||||
const newSdsId = `hook-${stamp}-new`;
|
||||
await (service as any).upsertOriginGood(
|
||||
sdsProduct(newSdsId, `自动挂${stamp}(不包邮)卫衣-ZZA${stamp}-双面印花-某仓`),
|
||||
@@ -118,10 +126,12 @@ describe('SyncService family hooks', () => {
|
||||
);
|
||||
const newOg = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: newSdsId } });
|
||||
createdOriginGoodIds.push(newOg.id);
|
||||
expect(newOg.familyId).toBe(family.id);
|
||||
expect(newOg.familyId).toBeNull();
|
||||
|
||||
// 入队的重算已执行(等待异步完成)
|
||||
// 族矩阵不因新链接同步而变化
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
const after = await prisma.productFamily.findUniqueOrThrow({ where: { id: family.id } });
|
||||
expect((after.priceMatrix as any).rows).toHaveLength(1);
|
||||
await prisma.category.delete({ where: { id: cat.id } }).catch(() => undefined);
|
||||
});
|
||||
|
||||
|
||||
+766
-826
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user