Files
inkreach-official-website/apps/api/src/product-families/dto/product-family.dto.ts
T
yeuimu 8a052773cd feat(public): family-first contract — goodId=familyId, strict 5-dim price matrix
公开契约族化(前端只需知道款号/族):
- 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、双端构建绿
2026-08-28 18:43:30 +08:00

281 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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_idcanonical 详情与跳转兜底)',
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[];
}