feat(product-family): link-level tags (origin_good_tags) with manual override + member detail enrichment
This commit is contained in:
@@ -1,8 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
|
||||
import { QueryOriginGoodDto } from './dto/query-origin-good.dto';
|
||||
|
||||
/** 链接标签(origin_good_tags 行,含人工/派生标记) */
|
||||
export interface OriginGoodTagItem {
|
||||
id: string;
|
||||
tagName: string;
|
||||
tagColor: string | null;
|
||||
tagFontColor: string | null;
|
||||
manual: boolean;
|
||||
}
|
||||
|
||||
export interface OriginGoodTagsResult {
|
||||
tagsManual: boolean;
|
||||
tags: OriginGoodTagItem[];
|
||||
}
|
||||
|
||||
export interface PaginatedOriginGoods {
|
||||
items: Array<{
|
||||
id: string;
|
||||
@@ -67,7 +86,94 @@ export interface OriginGoodsTreeResponse {
|
||||
|
||||
@Injectable()
|
||||
export class OriginGoodsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly familyRecompute: FamilyRecomputeService,
|
||||
) {}
|
||||
|
||||
/** 链接当前标签(含 manual 标记) */
|
||||
async getTags(id: bigint): Promise<OriginGoodTagsResult> {
|
||||
const og = await this.prisma.originGood.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
originGoodTags: {
|
||||
include: {
|
||||
tag: { select: { id: true, tagName: true, tagColor: true, tagFontColor: true } },
|
||||
},
|
||||
orderBy: { tagId: 'asc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!og) throw new NotFoundException(`Origin good ${id} not found`);
|
||||
return {
|
||||
tagsManual: og.tagsManual,
|
||||
tags: og.originGoodTags.map((r) => ({
|
||||
id: r.tag.id.toString(),
|
||||
tagName: r.tag.tagName,
|
||||
tagColor: r.tag.tagColor,
|
||||
tagFontColor: r.tag.tagFontColor,
|
||||
manual: r.manual,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 人工接管链接标签:全量替换为 manual 行(自动同步永不覆盖),
|
||||
* 并把有效标签镜像到该链接名下的商品。
|
||||
*/
|
||||
async updateTags(id: bigint, tagIds: number[]): Promise<OriginGoodTagsResult> {
|
||||
const og = await this.prisma.originGood.findUnique({
|
||||
where: { id },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!og) throw new NotFoundException(`Origin good ${id} not found`);
|
||||
const uniqueIds = [...new Set(tagIds.map((v) => BigInt(v)))].sort((a, b) =>
|
||||
Number(a - b),
|
||||
);
|
||||
if (uniqueIds.length) {
|
||||
const count = await this.prisma.tag.count({ where: { id: { in: uniqueIds } } });
|
||||
if (count !== uniqueIds.length) {
|
||||
throw new BadRequestException('存在无效标签');
|
||||
}
|
||||
}
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.originGoodTag.deleteMany({ where: { originGoodId: id } }),
|
||||
...(!uniqueIds.length
|
||||
? []
|
||||
: [
|
||||
this.prisma.originGoodTag.createMany({
|
||||
data: uniqueIds.map((tagId) => ({
|
||||
originGoodId: id,
|
||||
tagId,
|
||||
manual: true,
|
||||
})),
|
||||
}),
|
||||
]),
|
||||
this.prisma.originGood.update({ where: { id }, data: { tagsManual: true } }),
|
||||
]);
|
||||
await this.familyRecompute.mirrorLinkTagsToGoods(id);
|
||||
return this.getTags(id);
|
||||
}
|
||||
|
||||
/** 恢复自动:清掉全部标签行(含人工行),回到按链接名称派生 */
|
||||
async resetTags(id: bigint): Promise<OriginGoodTagsResult> {
|
||||
const og = await this.prisma.originGood.findUnique({
|
||||
where: { id },
|
||||
select: { id: true, familyId: true },
|
||||
});
|
||||
if (!og) throw new NotFoundException(`Origin good ${id} not found`);
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.originGoodTag.deleteMany({ where: { originGoodId: id } }),
|
||||
this.prisma.originGood.update({ where: { id }, data: { tagsManual: false } }),
|
||||
]);
|
||||
if (og.familyId) {
|
||||
// 族内链接:整族重派生 + 商品镜像
|
||||
await this.familyRecompute.syncFamilyTags(og.familyId);
|
||||
} else {
|
||||
await this.familyRecompute.refreshLinkTags(id);
|
||||
}
|
||||
return this.getTags(id);
|
||||
}
|
||||
|
||||
async findAll(query: QueryOriginGoodDto): Promise<PaginatedOriginGoods> {
|
||||
const { page, pageSize, keyword } = query;
|
||||
|
||||
Reference in New Issue
Block a user