feat(api): sync hooks for parsing, auto-attach and family recompute

This commit is contained in:
yeuimu
2026-08-28 12:33:33 +08:00
parent f68898dea4
commit d20a933e34
3 changed files with 240 additions and 2 deletions
+64 -1
View File
@@ -14,6 +14,8 @@ import {
SdsProductDetail,
} from './sds-client.service';
import { normalizeProductDetail } from './sds-product-detail.mapper';
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
import { originGroupKey, parseOriginName } from '../product-families/origin-name.parser';
export interface CategorySyncResult {
inserted: number;
@@ -75,6 +77,7 @@ export class SyncService {
constructor(
private readonly prisma: PrismaService,
private readonly sds: SdsClientService,
private readonly familyRecompute: FamilyRecomputeService,
) {}
/**
@@ -656,6 +659,54 @@ export class SyncService {
},
});
});
// 族成员的详情/变体变化 → 异步重算该族(进程内去重)
await this.maybeEnqueueFamilyRecompute(originGoodId);
}
/** 详情同步后的族重算钩子:链接有族归属才入队 */
private async maybeEnqueueFamilyRecompute(originGoodId: bigint): Promise<void> {
const og = await this.prisma.originGood.findUnique({
where: { id: originGoodId },
select: { familyId: true },
});
if (og?.familyId) this.familyRecompute.enqueue(og.familyId);
}
/**
* 新链接自动挂族:3 段分组键恰好命中唯一族才挂载(多族/零族留给管理员裁决)。
* 锁定族(autoManaged=false)不吸收新成员,只置 stale 提示。
*/
private async tryAutoAttachToFamily(originGoodId: bigint, goodName: string): Promise<void> {
const key = originGroupKey(goodName);
if (!key) return;
const candidates = await this.prisma.originGood.findMany({
where: { familyId: { not: null }, goodName: { startsWith: key } },
select: { familyId: true, goodName: true },
});
const familyIds = new Set(
candidates
.filter((c) => originGroupKey(c.goodName) === key && c.familyId !== null)
.map((c) => c.familyId!.toString()),
);
if (familyIds.size !== 1) return;
const familyId = BigInt([...familyIds][0]);
const family = await this.prisma.productFamily.findUnique({
where: { id: familyId },
select: { autoManaged: true },
});
if (!family) return;
if (family.autoManaged) {
await this.prisma.originGood.update({
where: { id: originGoodId },
data: { familyId },
});
this.familyRecompute.enqueue(familyId);
} else {
await this.prisma.productFamily.update({
where: { id: familyId },
data: { stale: true },
});
}
}
/**
@@ -715,23 +766,35 @@ export class SyncService {
goodPrice = new Prisma.Decimal(n);
}
}
// 链接名结构化解析列(镜像纯度:全量覆盖,含空值)
const parsed = parseOriginName(goodName);
const parsedData = {
skuCode: parsed.skuCode,
logisticsLabel: parsed.logisticsLabel,
craftLabel: parsed.craftLabel,
warehouseLabel: parsed.warehouseLabel,
};
const data: Prisma.OriginGoodUncheckedUpdateInput = {
sdsCategoryId,
goodName,
goodImage,
goodPrice,
...parsedData,
};
if (!existing) {
await this.prisma.originGood.create({
const created = await this.prisma.originGood.create({
data: {
sdsGoodId,
sdsCategoryId,
goodName,
goodImage,
goodPrice,
...parsedData,
},
select: { id: true },
});
await this.tryAutoAttachToFamily(created.id, goodName);
return 'inserted';
}
await this.prisma.originGood.update({