feat(api): category-based family grouping, tree family info and custom goods family attribution
This commit is contained in:
@@ -211,6 +211,31 @@ export class CreateCustomGoodDto extends OmitType(CreateGoodDto, [
|
||||
@IsNumberString()
|
||||
goodPrice?: string | null;
|
||||
|
||||
@ApiProperty({ required: false, description: '物流归因(入族后参与价格矩阵)', example: '包邮' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
logisticsLabel?: string;
|
||||
|
||||
@ApiProperty({ required: false, description: '工艺/印花数量归因', example: '双面印花' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
craftLabel?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
skuCode?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
warehouseLabel?: string;
|
||||
|
||||
@ApiProperty({ required: false, description: '挂入的产品族 id(缺省为独立商品)', example: '12' })
|
||||
@IsOptional()
|
||||
@IsNumberString()
|
||||
familyId?: string;
|
||||
|
||||
@ApiProperty({ required: false, type: CustomGoodDetailDto })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GoodsController } from './goods.controller';
|
||||
import { GoodsService } from './goods.service';
|
||||
import { SyncModule } from '../sync/sync.module';
|
||||
|
||||
@Module({
|
||||
imports: [SyncModule],
|
||||
import { GoodsService } from './goods.service';
|
||||
import { SyncModule } from '../sync/sync.module';
|
||||
import { ProductFamiliesModule } from '../product-families/product-families.module';
|
||||
|
||||
@Module({
|
||||
imports: [SyncModule, ProductFamiliesModule],
|
||||
controllers: [GoodsController],
|
||||
providers: [GoodsService],
|
||||
exports: [GoodsService],
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { GoodsService } from './goods.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { SyncService } from '../sync/sync.service';
|
||||
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
|
||||
|
||||
describe('GoodsService', () => {
|
||||
let service: GoodsService;
|
||||
@@ -30,6 +31,10 @@ describe('GoodsService', () => {
|
||||
provide: SyncService,
|
||||
useValue: { queueProductDetailSync: jest.fn() },
|
||||
},
|
||||
{
|
||||
provide: FamilyRecomputeService,
|
||||
useValue: { enqueue: jest.fn() },
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
service = moduleRef.get(GoodsService);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { BatchCreateGoodDto } from './dto/batch-create-good.dto';
|
||||
import { BatchPriorityDto } from './dto/batch-priority.dto';
|
||||
import { GoodDetailDto, GoodDto, PaginatedGoods } from './dto/good.dto';
|
||||
import { SyncService } from '../sync/sync.service';
|
||||
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
|
||||
import { randomUUID } from 'crypto';
|
||||
import {
|
||||
CreateCustomGoodDto,
|
||||
@@ -52,6 +53,7 @@ export class GoodsService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly syncService: SyncService,
|
||||
private readonly familyRecompute: FamilyRecomputeService,
|
||||
) {}
|
||||
|
||||
async findAll(query: QueryGoodDto): Promise<PaginatedGoods> {
|
||||
@@ -176,6 +178,14 @@ export class GoodsService {
|
||||
await this.ensureCategory(dto.categoryId);
|
||||
if (dto.positionId !== undefined) await this.ensurePosition(dto.positionId);
|
||||
for (const tagId of dto.tagIds ?? []) await this.ensureTag(tagId);
|
||||
let family: { id: bigint } | null = null;
|
||||
if (dto.familyId !== undefined) {
|
||||
family = await this.prisma.productFamily.findUnique({
|
||||
where: { id: BigInt(dto.familyId) },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!family) throw new NotFoundException(`product family ${dto.familyId} not found`);
|
||||
}
|
||||
|
||||
const goodId = await this.prisma.$transaction(async (tx) => {
|
||||
const originGood = await tx.originGood.create({
|
||||
@@ -185,6 +195,15 @@ export class GoodsService {
|
||||
goodName: dto.goodName,
|
||||
goodImage: dto.goodImage ?? null,
|
||||
goodPrice: this.decimal(dto.goodPrice),
|
||||
...(family ? { familyId: family.id } : {}),
|
||||
...(dto.logisticsLabel !== undefined || dto.craftLabel !== undefined
|
||||
? {
|
||||
logisticsLabel: dto.logisticsLabel ?? null,
|
||||
craftLabel: dto.craftLabel ?? null,
|
||||
skuCode: dto.skuCode ?? null,
|
||||
warehouseLabel: dto.warehouseLabel ?? null,
|
||||
}
|
||||
: {}),
|
||||
detail: {
|
||||
create: this.customDetailData(
|
||||
dto.detail ?? {},
|
||||
@@ -217,6 +236,7 @@ export class GoodsService {
|
||||
}
|
||||
return good.id;
|
||||
});
|
||||
if (family) this.familyRecompute.enqueue(family.id);
|
||||
return this.findOne(goodId);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user