import { Type } from 'class-transformer'; import { IsArray, IsBoolean, IsIn, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString, Min, Max, MinLength, ValidateIf, } from 'class-validator'; export class UpdateStoreStatusDto { @IsString() @IsIn(['OPEN', 'PAUSED', 'CLOSED']) status: string; } export class CreateStoreDto { @IsString() @IsNotEmpty() partnerAccountId: string; @IsString() @IsNotEmpty() cityId: string; @IsString() @IsNotEmpty() name: string; @IsString() @IsNotEmpty() phone: string; @IsString() @IsNotEmpty() categoryId: string; @IsOptional() @IsString() province?: string; @IsOptional() @IsString() city?: string; @IsOptional() @IsString() district?: string; @IsString() @IsNotEmpty() address: string; @IsOptional() @IsString() intro?: string; @IsOptional() @IsString() coverUrl?: string; @IsOptional() @IsString() accountName?: string; @IsOptional() @IsString() bankAccountName?: string; @IsOptional() @IsString() bankAccountNo?: string; @IsOptional() @IsString() bankBranch?: string; @IsOptional() @IsArray() @IsString({ each: true }) envPhotoUrls?: string[]; @IsOptional() @IsString() contractUrl?: string; @IsOptional() @IsNumber() @Min(0) settlementRate?: number; } export class UpdateStoreDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsString() phone?: string; @IsOptional() @IsString() intro?: string; @IsOptional() @IsString() coverUrl?: string; @IsOptional() @IsString() address?: string; @IsOptional() @IsString() district?: string; @IsOptional() @IsNumber() @Min(0) settlementRate?: number; } export class CreateStoreAccountDto { @IsString() @IsNotEmpty() storeId: string; @IsString() @IsNotEmpty() phone: string; @IsString() @IsNotEmpty() name: string; } export class UpdateStoreAccountDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsString() phone?: string; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; } export class CreatePartnerDto { @IsString() @IsNotEmpty() cityId: string; @IsString() @IsNotEmpty() phone: string; @IsString() @IsNotEmpty() name: string; @IsString() @IsNotEmpty() companyName: string; @IsString() @IsNotEmpty() address: string; @IsOptional() @IsString() contactPhone?: string; @IsString() @IsIn(['CITY_WIDE', 'DISTRICT']) scopeType: string; @IsOptional() @IsArray() @IsString({ each: true }) districtCodes?: string[]; @IsOptional() @IsNumber() @Min(0) orderCommissionRate?: number; @IsOptional() @IsNumber() @Min(0) redeemCommissionRate?: number; @IsOptional() @IsIn(['ACTIVE', 'PAUSED']) bindingStatus?: string; @IsOptional() @IsString() managedWarehouseId?: string; @IsOptional() @IsString() contractNo?: string; @IsOptional() @IsString() bankAccountName?: string; @IsOptional() @IsString() bankAccountNo?: string; @IsOptional() @IsString() bankBranch?: string; @IsOptional() @IsNumber() weeklyStoreTarget?: number; } export class UpdatePartnerDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsString() phone?: string; @IsOptional() @IsString() companyName?: string; @IsOptional() @IsString() address?: string; @IsOptional() @IsString() contactPhone?: string; @IsOptional() @IsIn(['CITY_WIDE', 'DISTRICT']) scopeType?: string; @IsOptional() @IsArray() @IsString({ each: true }) districtCodes?: string[]; @IsOptional() @IsNumber() @Min(0) orderCommissionRate?: number; @IsOptional() @IsNumber() @Min(0) redeemCommissionRate?: number; @IsOptional() @IsIn(['ACTIVE', 'PAUSED']) bindingStatus?: string; @IsOptional() @ValidateIf((_, v) => v !== null) @IsString() managedWarehouseId?: string | null; @IsOptional() @IsString() contractNo?: string; @IsOptional() @IsString() bankAccountName?: string; @IsOptional() @IsString() bankAccountNo?: string; @IsOptional() @IsString() bankBranch?: string; @IsOptional() @IsNumber() weeklyStoreTarget?: number; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; } export class UpdatePartnerAccountDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsString() phone?: string; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; @IsOptional() @IsIn(['PARTNER', 'INTERNAL', 'PROMOTER']) staffRole?: string; @IsOptional() @IsArray() @IsString({ each: true }) permissions?: string[]; } export class CreatePartnerAccountDto { @IsString() @IsNotEmpty() parentAccountId: string; @IsString() @IsNotEmpty() phone: string; @IsString() @IsNotEmpty() name: string; @IsOptional() @IsIn(['PARTNER', 'INTERNAL', 'PROMOTER']) staffRole?: string; @IsOptional() @IsArray() @IsString({ each: true }) permissions?: string[]; } export class CreateCityDto { @IsString() @IsNotEmpty() code: string; @IsString() @IsNotEmpty() name: string; @IsString() @IsNotEmpty() province: string; @IsOptional() @IsIn(['PENDING', 'ACTIVE', 'PAUSED']) status?: string; } export class UpdateCityDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsString() province?: string; @IsOptional() @IsIn(['PENDING', 'ACTIVE', 'PAUSED']) status?: string; @IsOptional() localMinQty?: number; @IsOptional() crossMinQty?: number; @IsOptional() @IsNumber() @Min(0) @Max(1) maxPartnerCommissionRate?: number; } export class BindCityPartnerDto { @IsString() @IsNotEmpty() partnerId: string; @IsString() @IsIn(['CITY_WIDE', 'DISTRICT']) scopeType: 'CITY_WIDE' | 'DISTRICT'; @IsOptional() @IsArray() @IsString({ each: true }) districtCodes?: string[]; @IsOptional() @IsNumber() @Min(0) orderCommissionRate?: number; @IsOptional() @IsNumber() @Min(0) redeemCommissionRate?: number; @IsOptional() @IsIn(['ACTIVE', 'PAUSED']) status?: 'ACTIVE' | 'PAUSED'; } export class UpdateCityPartnerDto { @IsOptional() @IsIn(['CITY_WIDE', 'DISTRICT']) scopeType?: 'CITY_WIDE' | 'DISTRICT'; @IsOptional() @IsArray() @IsString({ each: true }) districtCodes?: string[]; @IsOptional() @IsNumber() @Min(0) orderCommissionRate?: number; @IsOptional() @IsNumber() @Min(0) redeemCommissionRate?: number; @IsOptional() @IsIn(['ACTIVE', 'PAUSED']) status?: 'ACTIVE' | 'PAUSED'; } export class CreateCityWarehouseDto { @IsString() @IsNotEmpty() name: string; @IsString() @IsNotEmpty() address: string; @IsString() @IsNotEmpty() contactName: string; @IsString() @IsNotEmpty() contactPhone: string; @IsString() @IsIn(['HQ', 'PARTNER']) managerType: 'HQ' | 'PARTNER'; @IsOptional() @IsString() partnerAccountId?: string; @IsOptional() @IsIn(['ACTIVE', 'PAUSED']) status?: 'ACTIVE' | 'PAUSED'; @IsOptional() @IsIn(['API_AUTO', 'MANUAL']) fulfillmentMode?: 'API_AUTO' | 'MANUAL'; @IsOptional() @IsString() fulfillmentProviderId?: string; @IsOptional() @IsString() manualCarrierLabel?: string; @IsOptional() @IsString() manualQueryUrlTemplate?: string; @IsOptional() @IsNumber() lng?: number; @IsOptional() @IsNumber() lat?: number; } export class UpdateCityWarehouseDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsString() address?: string; @IsOptional() @IsString() contactName?: string; @IsOptional() @IsString() contactPhone?: string; @IsOptional() @IsIn(['HQ', 'PARTNER']) managerType?: 'HQ' | 'PARTNER'; @IsOptional() @ValidateIf((_, v) => v !== null) @IsString() partnerAccountId?: string | null; @IsOptional() @IsIn(['ACTIVE', 'PAUSED']) status?: 'ACTIVE' | 'PAUSED'; @IsOptional() @IsIn(['API_AUTO', 'MANUAL']) fulfillmentMode?: 'API_AUTO' | 'MANUAL'; @IsOptional() @ValidateIf((_, v) => v !== null) @IsString() fulfillmentProviderId?: string | null; @IsOptional() @ValidateIf((_, v) => v !== null) @IsString() manualCarrierLabel?: string | null; @IsOptional() @ValidateIf((_, v) => v !== null) @IsString() manualQueryUrlTemplate?: string | null; @IsOptional() @ValidateIf((_, v) => v !== null) @IsNumber() lng?: number | null; @IsOptional() @ValidateIf((_, v) => v !== null) @IsNumber() lat?: number | null; } export class CreateFulfillmentProviderDto { @IsString() @IsNotEmpty() code: string; @IsString() @IsNotEmpty() name: string; @IsIn(['API', 'MANUAL']) type: 'API' | 'MANUAL'; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: 'ACTIVE' | 'DISABLED'; @IsOptional() @IsString() configJson?: string; @IsOptional() @IsString() capabilitiesJson?: string; @IsOptional() @IsObject() xiaofeixiaConfig?: { apiUrl?: string; mchId?: string; apiKey?: string; signType?: 'MD5' | 'HMAC-SHA256'; appId?: string; }; @IsOptional() @IsString() bankAccountName?: string | null; @IsOptional() @IsString() bankName?: string | null; @IsOptional() @IsString() bankBranch?: string | null; @IsOptional() @IsString() bankAccountNo?: string | null; @IsOptional() @IsIn(['PREPAID', 'MONTHLY_CREDIT']) settlementMethod?: 'PREPAID' | 'MONTHLY_CREDIT'; @IsOptional() @IsObject() pricingRules?: { baseBottles: number; baseFee: number; extraBottleFee: number; boxBottles?: number; boxFee?: number; } | null; } export class UpdateFulfillmentProviderDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsIn(['API', 'MANUAL']) type?: 'API' | 'MANUAL'; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: 'ACTIVE' | 'DISABLED'; @IsOptional() @IsString() configJson?: string; @IsOptional() @IsString() capabilitiesJson?: string; @IsOptional() @IsObject() xiaofeixiaConfig?: { apiUrl?: string; mchId?: string; apiKey?: string; signType?: 'MD5' | 'HMAC-SHA256'; appId?: string; }; @IsOptional() @IsString() bankAccountName?: string | null; @IsOptional() @IsString() bankName?: string | null; @IsOptional() @IsString() bankBranch?: string | null; @IsOptional() @IsString() bankAccountNo?: string | null; @IsOptional() @IsIn(['PREPAID', 'MONTHLY_CREDIT']) settlementMethod?: 'PREPAID' | 'MONTHLY_CREDIT'; @IsOptional() @IsObject() pricingRules?: { baseBottles: number; baseFee: number; extraBottleFee: number; boxBottles?: number; boxFee?: number; } | null; } export class RechargeFulfillmentProviderDto { @IsNumber() amount: number; @IsOptional() @IsString() remark?: string; } export class ManualShipOrderDto { @IsString() @IsNotEmpty() logisticsCompany: string; @IsString() @IsNotEmpty() trackingNo: string; @IsOptional() @IsString() manualQueryUrl?: string; } export class HqLogisticsShipDto extends ManualShipOrderDto {} export class CreateStoreMediaDto { @IsString() @IsNotEmpty() storeId: string; @IsString() @IsIn(['IMAGE', 'VIDEO']) mediaType: string; @IsString() @IsNotEmpty() url: string; @IsOptional() sortOrder?: number; } export class UpdateStoreMediaDto { @IsOptional() @IsString() url?: string; @IsOptional() @IsIn(['IMAGE', 'VIDEO']) mediaType?: string; @IsOptional() sortOrder?: number; } export class UpdateOrderStatusDto { @IsString() @IsIn([ 'PENDING_PAY', 'PENDING_SHIP', 'OUT_WAREHOUSE', 'SHIPPING', 'PENDING_RECEIVE', 'COMPLETED', 'CANCELLED', 'REFUNDING', 'REFUNDED', ]) status: string; } export class BatchDeleteOrdersDto { @IsArray() @IsString({ each: true }) ids: string[]; } export class BatchDeleteUsersDto { @IsArray() @IsString({ each: true }) ids: string[]; } export class BatchDeleteUsersConfirmDto extends BatchDeleteUsersDto { @IsBoolean() confirmRisk: boolean; } /** HQ 订单发货(目前仅小飞侠 XFX) */ export class AdminShipOrderDto { @IsIn(['XFX']) provider: string; /** 可选:指定/改派履约仓后再推仓配 */ @IsOptional() @IsString() warehouseId?: string; @IsOptional() @IsString() fromName?: string; @IsOptional() @IsString() fromMobile?: string; @IsOptional() @IsString() fromAddress?: string; @IsOptional() @IsString() fromAddressDetail?: string; @IsOptional() @IsNumber() fromLng?: number; @IsOptional() @IsNumber() fromLat?: number; @IsOptional() @Type(() => Number) @IsNumber() @Min(0.01) weight?: number; @IsOptional() @IsIn(['1', '2']) payMode?: string; @IsOptional() @IsString() remark?: string; } export class AdminRedeemDebugCreateTokenDto { @IsString() @IsNotEmpty() userId: string; @Type(() => Number) @IsNumber() @Min(0.01) amount: number; @IsOptional() @IsString() couponId?: string; @IsOptional() @IsString() storeId?: string; } export class AdminRedeemDebugStoreTokenDto { @IsString() @IsNotEmpty() storeId: string; @IsString() @IsNotEmpty() token: string; } export class AdminRedeemDebugPhoneStoreDto { @IsString() @IsNotEmpty() storeId: string; @IsString() @IsNotEmpty() phone: string; } export class AdminRedeemDebugPhoneBalanceDto extends AdminRedeemDebugPhoneStoreDto { @IsString() @IsNotEmpty() code: string; } export class AdminRedeemDebugPhonePrepareDto { @IsString() @IsNotEmpty() storeId: string; @IsString() @IsNotEmpty() sessionId: string; @Type(() => Number) @IsNumber() @Min(0.01) amount: number; } export class AdminRedeemDebugPhoneConfirmDto { @IsString() @IsNotEmpty() storeId: string; @IsString() @IsNotEmpty() sessionId: string; @IsString() @IsNotEmpty() code: string; } export class UpdateDeliveryDto { @IsOptional() @IsString() provider?: string; @IsOptional() @IsString() providerOrderNo?: string; @IsOptional() @IsString() trackingNo?: string; } export class CreateHqAccountDto { @IsIn(['phone', 'password']) credentialType: 'phone' | 'password'; @IsOptional() @IsString() phone?: string; @ValidateIf((o: CreateHqAccountDto) => o.credentialType === 'password') @IsString() @IsNotEmpty() loginName?: string; @ValidateIf((o: CreateHqAccountDto) => o.credentialType === 'password') @IsString() @MinLength(6) password?: string; @IsString() @IsNotEmpty() name: string; @IsOptional() @IsIn(['SUPER_ADMIN', 'OPS', 'FINANCE', 'CUSTOMER_SERVICE']) adminRole?: string; } export class UpdateHqAccountDto { @IsOptional() @IsString() loginName?: string; @IsOptional() @IsString() phone?: string; @IsOptional() @IsString() @MinLength(6) password?: string; @IsOptional() @IsString() name?: string; @IsOptional() @IsIn(['SUPER_ADMIN', 'OPS', 'FINANCE', 'CUSTOMER_SERVICE']) adminRole?: string; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; } export class SaveHqRolePermissionsDto { @IsArray() @IsString({ each: true }) permissionKeys: string[]; } export class SaveHqAccountPermissionsDto { @IsArray() @IsString({ each: true }) permissionKeys: string[]; } export class CreateProductDto { @IsString() @IsNotEmpty() skuCode: string; @IsString() @IsNotEmpty() barcode69: string; @IsString() @IsNotEmpty() name: string; @IsOptional() @IsString() subtitle?: string; @IsIn(['QINGXIANG', 'JIANGXIANG', 'NONGXIANG']) aromaType: string; @IsString() @IsNotEmpty() spec: string; @IsNumber() price: number; @IsOptional() @IsNumber() benefitAmount?: number; @IsOptional() @IsIn(['DRAFT', 'ON_SALE', 'OFF_SALE']) status?: string; @IsOptional() @IsNumber() sortOrder?: number; @IsOptional() @IsBoolean() allowOnSitePickup?: boolean; @IsOptional() @IsString() coverUrl?: string; @IsOptional() @IsArray() @IsString({ each: true }) carouselUrls?: string[]; @IsOptional() @IsArray() @IsString({ each: true }) detailImageUrls?: string[]; @IsOptional() @IsObject() detailContent?: Record; } export class UpdateProductDto { @IsOptional() @IsString() name?: string; @IsOptional() @IsString() subtitle?: string; @IsOptional() @IsString() spec?: string; @IsOptional() @IsNumber() price?: number; @IsOptional() @IsNumber() benefitAmount?: number; @IsOptional() @IsIn(['DRAFT', 'ON_SALE', 'OFF_SALE']) status?: string; @IsOptional() @IsNumber() sortOrder?: number; @IsOptional() @IsBoolean() allowOnSitePickup?: boolean; @IsOptional() @IsString() coverUrl?: string; @IsOptional() @IsArray() @IsString({ each: true }) carouselUrls?: string[]; @IsOptional() @IsArray() @IsString({ each: true }) detailImageUrls?: string[]; @IsOptional() @IsObject() detailContent?: Record; } class ProductDetailFeatureInputDto { @IsString() icon: string; @IsString() title: string; @IsString() desc: string; } export class CreateProductDetailTemplateDto { @IsString() @IsNotEmpty() code: string; @IsString() @IsNotEmpty() name: string; @IsOptional() @IsString() description?: string; @IsOptional() @IsIn(['QINGXIANG', 'JIANGXIANG', 'NONGXIANG']) aromaType?: string; @IsOptional() @IsString() storyTitle?: string; @IsOptional() @IsString() storyText?: string; @IsOptional() @IsArray() features?: ProductDetailFeatureInputDto[]; @IsOptional() @IsArray() @IsString({ each: true }) detailImageUrls?: string[]; @IsOptional() @IsNumber() suggestedDetailImageCount?: number; @IsOptional() @IsNumber() sortOrder?: number; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; } export class UpdateProductDetailTemplateDto { @IsOptional() @IsString() code?: string; @IsOptional() @IsString() name?: string; @IsOptional() @IsString() description?: string; @IsOptional() @IsIn(['QINGXIANG', 'JIANGXIANG', 'NONGXIANG', null]) aromaType?: string | null; @IsOptional() @IsString() storyTitle?: string; @IsOptional() @IsString() storyText?: string; @IsOptional() @IsArray() features?: ProductDetailFeatureInputDto[]; @IsOptional() @IsArray() @IsString({ each: true }) detailImageUrls?: string[]; @IsOptional() @IsNumber() suggestedDetailImageCount?: number; @IsOptional() @IsNumber() sortOrder?: number; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; } export class CreatePromoCodeDto { @IsString() @IsNotEmpty() name: string; @IsOptional() @IsString() code?: string; } export class UpdatePromoCodeStatusDto { @IsString() @IsIn(['ACTIVE', 'DISABLED']) status: 'ACTIVE' | 'DISABLED'; } export class AdminBenefitGrantDto { @IsString() @IsNotEmpty() phone: string; @Type(() => Number) @IsNumber() @Min(0.01) @Max(999999.99) amount: number; @IsOptional() @IsString() remark?: string; } export class CreateStoreCategoryDto { @IsString() @IsNotEmpty() code: string; @IsString() @IsNotEmpty() name: string; @IsOptional() @Type(() => Number) @IsNumber() sort?: number; /** 空/不传 = 一级分类;传入一级 id = 二级分类 */ @IsOptional() @IsString() parentId?: string | null; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; } export class UpdateStoreCategoryDto { @IsOptional() @IsString() @IsNotEmpty() code?: string; @IsOptional() @IsString() @IsNotEmpty() name?: string; @IsOptional() @Type(() => Number) @IsNumber() sort?: number; @IsOptional() @ValidateIf((_, v) => v !== null && v !== undefined) @IsString() parentId?: string | null; @IsOptional() @IsIn(['ACTIVE', 'DISABLED']) status?: string; }