公开契约族化(前端只需知道款号/族): - GET /public/goods 一族一条(goodId=族ID,price=族起价,分页作用于分组后); 无族商品(自定义)不进任何公开端点(列表/首页/分类树/标签统计) - GET /public/goods/:goodId 仅认族 ID;公共字段取代表 Good,变体=全体成员并集, 尺码表/包装规格=族物化并集;旧 SDS 链接 ID 寻址 404 - priceMatrix 严格五维:尺码×颜色×印花数量×工艺×物流;维度来源改为链接级 标签(人工接管按人工标签),弃用原始 craftLabel;CUSTOM 成员尊重显式标签 - 名称派生补裸「单面/双面」写法(直喷双面→双面印花+直喷,18 条存量链接修复) - family_price_overrides 加 print_count 列(五键唯一),PUT/DELETE/校验五键化 - admin 编辑弹窗矩阵消费适配(SKU 列直读 printCount,成员格子三维匹配, 改价 payload 带 printCount) - 存量 339 族已全量重算;api 162/162、admin 22/22、双端构建绿
281 lines
6.3 KiB
TypeScript
281 lines
6.3 KiB
TypeScript
import { ApiProperty, PartialType } from '@nestjs/swagger';
|
||
import { Type } from 'class-transformer';
|
||
import {
|
||
IsArray,
|
||
IsBoolean,
|
||
IsNotEmpty,
|
||
IsNumber,
|
||
IsNumberString,
|
||
IsObject,
|
||
IsOptional,
|
||
IsString,
|
||
MaxLength,
|
||
Min,
|
||
ValidateNested,
|
||
} from 'class-validator';
|
||
|
||
export class CreateProductFamilyDto {
|
||
@ApiProperty({ example: '180g纯棉T恤(成人款)' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
@MaxLength(200)
|
||
familyName!: string;
|
||
|
||
@ApiProperty({ required: false, example: 'DG001' })
|
||
@IsOptional()
|
||
@IsString()
|
||
@MaxLength(100)
|
||
familyCode?: string;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
familyImage?: string | null;
|
||
|
||
@ApiProperty({ required: false, example: '1' })
|
||
@IsOptional()
|
||
@IsNumberString()
|
||
countryId?: string;
|
||
|
||
@ApiProperty({ required: false, example: '2' })
|
||
@IsOptional()
|
||
@IsNumberString()
|
||
categoryId?: string;
|
||
|
||
@ApiProperty({
|
||
required: false,
|
||
description: '主链接 origin_good_id(canonical 详情与跳转兜底)',
|
||
example: '123',
|
||
})
|
||
@IsOptional()
|
||
@IsNumberString()
|
||
primaryOriginGoodId?: string;
|
||
|
||
@ApiProperty({ required: false, type: [String], example: ['123', '124'] })
|
||
@IsOptional()
|
||
@IsArray()
|
||
@IsNumberString({}, { each: true })
|
||
originGoodIds?: string[];
|
||
}
|
||
|
||
export class PatchProductFamilyDto extends PartialType(CreateProductFamilyDto) {
|
||
@ApiProperty({ required: false, description: 'false = 人工锁定,重算只置 stale' })
|
||
@IsOptional()
|
||
@IsBoolean()
|
||
autoManaged?: boolean;
|
||
}
|
||
|
||
export class QueryProductFamilyDto {
|
||
@ApiProperty({ required: false, description: '匹配 familyName / familyCode' })
|
||
@IsOptional()
|
||
@IsString()
|
||
keyword?: string;
|
||
|
||
@ApiProperty({ required: false, default: 1 })
|
||
@IsOptional()
|
||
@Type(() => Number)
|
||
@IsNumber()
|
||
@Min(1)
|
||
page?: number;
|
||
|
||
@ApiProperty({ required: false, default: 20 })
|
||
@IsOptional()
|
||
@Type(() => Number)
|
||
@IsNumber()
|
||
@Min(1)
|
||
pageSize?: number;
|
||
}
|
||
|
||
export class AutoGroupDto {
|
||
@ApiProperty({ required: false, default: false, description: 'true = 实际建族' })
|
||
@IsOptional()
|
||
@IsBoolean()
|
||
apply?: boolean;
|
||
}
|
||
|
||
export class UpdateFamilyMembersDto {
|
||
@ApiProperty({ required: false, type: [String] })
|
||
@IsOptional()
|
||
@IsArray()
|
||
@IsNumberString({}, { each: true })
|
||
addOriginGoodIds?: string[];
|
||
|
||
@ApiProperty({ required: false, type: [String] })
|
||
@IsOptional()
|
||
@IsArray()
|
||
@IsNumberString({}, { each: true })
|
||
removeOriginGoodIds?: string[];
|
||
}
|
||
|
||
export class CustomMemberVariantDto {
|
||
@ApiProperty()
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
sku!: string;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
sizeId?: string | null;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
sizeName?: string | null;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
colorId?: string | null;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
colorName?: string | null;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
colorHex?: string | null;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
imageUrl?: string | null;
|
||
|
||
@ApiProperty({ example: '28.00' })
|
||
@IsNumber()
|
||
@Min(0.01)
|
||
price!: number;
|
||
}
|
||
|
||
export class CreateCustomMemberDto {
|
||
@ApiProperty({ example: '美国(包邮)180g纯棉T恤-DG001-双面印花(自建)' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
@MaxLength(300)
|
||
goodName!: string;
|
||
|
||
@ApiProperty({ required: false, nullable: true })
|
||
@IsOptional()
|
||
@IsString()
|
||
goodImage?: string | null;
|
||
|
||
@ApiProperty({ description: '物流归因(价格矩阵维度,必填)', example: '包邮' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
@MaxLength(100)
|
||
logisticsLabel!: string;
|
||
|
||
@ApiProperty({ description: '工艺/印花数量归因(价格矩阵维度,必填)', example: '双面印花' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
@MaxLength(100)
|
||
craftLabel!: string;
|
||
|
||
@ApiProperty({ required: false })
|
||
@IsOptional()
|
||
@IsString()
|
||
skuCode?: string;
|
||
|
||
@ApiProperty({ required: false })
|
||
@IsOptional()
|
||
@IsString()
|
||
warehouseLabel?: string;
|
||
|
||
@ApiProperty({ type: [CustomMemberVariantDto], minItems: 1 })
|
||
@IsArray()
|
||
@ValidateNested({ each: true })
|
||
@Type(() => CustomMemberVariantDto)
|
||
variants!: CustomMemberVariantDto[];
|
||
|
||
@ApiProperty({
|
||
required: false,
|
||
type: Object,
|
||
description: '可选人工详情(尺码表/包装规则参与并集)',
|
||
})
|
||
@IsOptional()
|
||
@IsObject()
|
||
detail?: { sizeChart?: Record<string, unknown>; packageSpecs?: Record<string, unknown> };
|
||
}
|
||
|
||
export class PriceOverrideItemDto {
|
||
@ApiProperty({ example: 'size_S' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
sizeId!: string;
|
||
|
||
@ApiProperty({ example: 'color_blk' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
colorId!: string;
|
||
|
||
@ApiProperty({ example: '单面印花', description: '印花数量维:单面印花 / 双面印花' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
printCount!: string;
|
||
|
||
@ApiProperty({ example: '烫画', description: '工艺维:烫画 / 直喷 / 不打印' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
craft!: string;
|
||
|
||
@ApiProperty({ example: '包邮' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
logistics!: string;
|
||
|
||
@ApiProperty({ example: '23.00' })
|
||
@IsNumber()
|
||
@Min(0.01)
|
||
price!: number;
|
||
|
||
@ApiProperty({ required: false, description: '改价原因(审计)' })
|
||
@IsOptional()
|
||
@IsString()
|
||
note?: string;
|
||
}
|
||
|
||
export class PutPriceOverridesDto {
|
||
@ApiProperty({ type: [PriceOverrideItemDto] })
|
||
@IsArray()
|
||
@ValidateNested({ each: true })
|
||
@Type(() => PriceOverrideItemDto)
|
||
items!: PriceOverrideItemDto[];
|
||
}
|
||
|
||
export class PriceOverrideCellDto {
|
||
@ApiProperty({ example: 'size_S' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
sizeId!: string;
|
||
|
||
@ApiProperty({ example: 'color_blk' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
colorId!: string;
|
||
|
||
@ApiProperty({ example: '单面印花', description: '印花数量维:单面印花 / 双面印花' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
printCount!: string;
|
||
|
||
@ApiProperty({ example: '烫画', description: '工艺维:烫画 / 直喷 / 不打印' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
craft!: string;
|
||
|
||
@ApiProperty({ example: '包邮' })
|
||
@IsString()
|
||
@IsNotEmpty()
|
||
logistics!: string;
|
||
}
|
||
|
||
export class DeletePriceOverridesDto {
|
||
@ApiProperty({ type: [PriceOverrideCellDto] })
|
||
@IsArray()
|
||
@ValidateNested({ each: true })
|
||
@Type(() => PriceOverrideCellDto)
|
||
cells!: PriceOverrideCellDto[];
|
||
}
|