feat(api): sync hooks for parsing, auto-attach and family recompute
This commit is contained in:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user