feat(product-family): auto-derive logistics/craft tags from family members

This commit is contained in:
yeuimu
2026-08-28 14:57:25 +08:00
parent a13931c84c
commit eedfbb344e
6 changed files with 340 additions and 13 deletions
+2 -1
View File
@@ -33,7 +33,7 @@ export interface GoodRelations {
}>;
_count?: { variants: number };
} | null;
goodTags?: { tag: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null } }[];
goodTags?: { tag: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null; tagGroupId: bigint | null } }[];
mergedOriginGoods?: Array<{
originGood: {
id: bigint;
@@ -175,6 +175,7 @@ export class GoodDto {
tagName: gt.tag.tagName,
tagColor: gt.tag.tagColor,
tagFontColor: gt.tag.tagFontColor,
tagGroupId: gt.tag.tagGroupId?.toString() ?? null,
}))
: [],
mergedOriginGoods: (rel.mergedOriginGoods ?? []).map((m) => ({
+63 -7
View File
@@ -128,6 +128,7 @@ export class GoodsService {
where: { id: BigInt(dto.originGoodId) },
select: { familyId: true },
});
const tagIds = await this.stripAutoGroupTags(dto.tagIds ?? [], primary?.familyId ?? null);
const created = await tx.good.create({
data: {
goodName: dto.goodName,
@@ -140,9 +141,9 @@ export class GoodsService {
goodPriority: dto.goodPriority ?? 0,
},
});
if (dto.tagIds && dto.tagIds.length > 0) {
if (tagIds.length > 0) {
await tx.goodTag.createMany({
data: dto.tagIds.map((tagId) => ({
data: tagIds.map((tagId) => ({
goodId: created.id,
tagId: BigInt(tagId),
})),
@@ -170,6 +171,10 @@ export class GoodsService {
mergedOriginGoods: result.mergedOriginGoods,
});
});
if (result.originGood?.family?.familyId) {
// 建商品后立即同步族派生标签(物流/工艺/位置组)
await this.familyRecompute.syncFamilyTags(BigInt(result.originGood.family.familyId));
}
if (
result.originGood?.source === 'SDS' &&
result.originGood.sdsGoodId &&
@@ -353,12 +358,30 @@ export class GoodsService {
}
}
// 有族商品的自动组(物流/工艺/位置)标签为族派生数据,不接受手动写入
let familyIdForTags: bigint | null;
if (dto.familyId !== undefined) {
familyIdForTags = dto.familyId === null ? null : BigInt(dto.familyId);
} else {
familyIdForTags =
(
await this.prisma.good.findUnique({
where: { id },
select: { familyId: true },
})
)?.familyId ?? null;
}
const updateTagIds =
dto.tagIds !== undefined
? await this.stripAutoGroupTags(dto.tagIds, familyIdForTags)
: undefined;
const result = await this.prisma.$transaction(async (tx) => {
if (dto.tagIds !== undefined) {
if (updateTagIds !== undefined) {
await tx.goodTag.deleteMany({ where: { goodId: id } });
if (dto.tagIds.length > 0) {
if (updateTagIds.length > 0) {
await tx.goodTag.createMany({
data: dto.tagIds.map((tagId) => ({
data: updateTagIds.map((tagId) => ({
goodId: id,
tagId: BigInt(tagId),
})),
@@ -391,6 +414,10 @@ export class GoodsService {
mergedOriginGoods: updated.mergedOriginGoods,
});
});
if (familyIdForTags) {
// 更新后重同步族派生标签(人工编辑不会破坏派生集合)
await this.familyRecompute.syncFamilyTags(familyIdForTags);
}
if (
result.originGood?.source === 'SDS' &&
result.originGood.sdsGoodId &&
@@ -592,8 +619,37 @@ export class GoodsService {
if (!c) throw new BadRequestException(`Category ${id} not found`);
}
private async ensureTag(id: number) {
const t = await this.prisma.tag.findUnique({ where: { id: BigInt(id) } });
/** 有族商品:剔除自动组(物流/工艺/位置)标签——它们由族同步管理 */
private async stripAutoGroupTags(
tagIds: number[],
familyId: bigint | null,
): Promise<number[]> {
if (!familyId || !tagIds.length) return tagIds;
const autoGroups = await this.prisma.tagGroup.findMany({
where: {
OR: [
{ groupName: { contains: '物流' } },
{ groupName: { contains: '工艺' } },
{ groupName: { contains: '位置' } },
],
},
select: { id: true },
});
if (!autoGroups.length) return tagIds;
const autoIds = new Set(autoGroups.map((g) => g.id.toString()));
const tags = await this.prisma.tag.findMany({
where: { id: { in: tagIds.map((id) => BigInt(id)) } },
select: { id: true, tagGroupId: true },
});
const blocked = new Set(
tags
.filter((t) => t.tagGroupId && autoIds.has(t.tagGroupId.toString()))
.map((t) => t.id.toString()),
);
return blocked.size ? tagIds.filter((id) => !blocked.has(String(id))) : tagIds;
}
private async ensureTag(id: number) { const t = await this.prisma.tag.findUnique({ where: { id: BigInt(id) } });
if (!t) throw new BadRequestException(`Tag ${id} not found`);
}