From 20b8d4121298befb7a207cbd0f85cfbb90a70b1f Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 28 Feb 2026 07:48:47 -0800 Subject: [PATCH] =?UTF-8?q?feat(pre-planting):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=9C=81=E5=B8=82=E5=90=8D=E7=A7=B0=E5=AD=98=E5=82=A8=EF=BC=8C?= =?UTF-8?q?=E5=8F=82=E7=85=A7=E6=AD=A3=E5=B8=B8=E8=AE=A4=E7=A7=8D=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PurchasePrePlantingDto 添加可选字段 provinceName/cityName, 与 SelectProvinceCityDto 保持一致,解决 NestJS forbidNonWhitelisted 400 错误 - pre_planting_positions 表新增 province_name/city_name 列(迁移) - PrePlantingPosition aggregate 增加 provinceName/cityName 字段 - addPortions() 接受并存储省市名称 - getPosition() 返回 provinceName/cityName 供续购时显示 Co-Authored-By: Claude Sonnet 4.6 --- .../migration.sql | 3 +++ .../planting-service/prisma/schema.prisma | 2 ++ .../api/controllers/pre-planting.controller.ts | 2 ++ .../api/dto/request/purchase-pre-planting.dto.ts | 16 ++++++++++++++-- .../services/pre-planting-application.service.ts | 8 +++++++- .../pre-planting-position.aggregate.ts | 16 +++++++++++++++- .../pre-planting-position.repository.ts | 6 ++++++ 7 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 backend/services/planting-service/prisma/migrations/20260228000000_add_province_city_name_to_position/migration.sql diff --git a/backend/services/planting-service/prisma/migrations/20260228000000_add_province_city_name_to_position/migration.sql b/backend/services/planting-service/prisma/migrations/20260228000000_add_province_city_name_to_position/migration.sql new file mode 100644 index 00000000..3249f38d --- /dev/null +++ b/backend/services/planting-service/prisma/migrations/20260228000000_add_province_city_name_to_position/migration.sql @@ -0,0 +1,3 @@ +-- Add province_name and city_name to pre_planting_positions +ALTER TABLE "pre_planting_positions" ADD COLUMN IF NOT EXISTS "province_name" VARCHAR(50); +ALTER TABLE "pre_planting_positions" ADD COLUMN IF NOT EXISTS "city_name" VARCHAR(50); diff --git a/backend/services/planting-service/prisma/schema.prisma b/backend/services/planting-service/prisma/schema.prisma index 4d701959..af1d9163 100644 --- a/backend/services/planting-service/prisma/schema.prisma +++ b/backend/services/planting-service/prisma/schema.prisma @@ -453,7 +453,9 @@ model PrePlantingPosition { // 省市 (首次购买时选择,后续复用) provinceCode String? @map("province_code") @db.VarChar(10) + provinceName String? @map("province_name") @db.VarChar(50) cityCode String? @map("city_code") @db.VarChar(10) + cityName String? @map("city_name") @db.VarChar(50) // 首次购买时间 (1年冻结起点) firstPurchaseAt DateTime? @map("first_purchase_at") diff --git a/backend/services/planting-service/src/pre-planting/api/controllers/pre-planting.controller.ts b/backend/services/planting-service/src/pre-planting/api/controllers/pre-planting.controller.ts index e4afff0d..144faecd 100644 --- a/backend/services/planting-service/src/pre-planting/api/controllers/pre-planting.controller.ts +++ b/backend/services/planting-service/src/pre-planting/api/controllers/pre-planting.controller.ts @@ -49,6 +49,8 @@ export class PrePlantingController { dto.portionCount, dto.provinceCode, dto.cityCode, + dto.provinceName, + dto.cityName, ); } diff --git a/backend/services/planting-service/src/pre-planting/api/dto/request/purchase-pre-planting.dto.ts b/backend/services/planting-service/src/pre-planting/api/dto/request/purchase-pre-planting.dto.ts index 4ac9fae2..f796d19c 100644 --- a/backend/services/planting-service/src/pre-planting/api/dto/request/purchase-pre-planting.dto.ts +++ b/backend/services/planting-service/src/pre-planting/api/dto/request/purchase-pre-planting.dto.ts @@ -1,5 +1,5 @@ -import { IsInt, IsString, Min, Max, IsNotEmpty } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { IsInt, IsString, Min, Max, IsNotEmpty, IsOptional, MaxLength } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class PurchasePrePlantingDto { @ApiProperty({ description: '购买份数', example: 1, minimum: 1, maximum: 5 }) @@ -13,8 +13,20 @@ export class PurchasePrePlantingDto { @IsNotEmpty() provinceCode: string; + @ApiPropertyOptional({ description: '省名称', example: '广东省' }) + @IsOptional() + @IsString() + @MaxLength(50) + provinceName?: string; + @ApiProperty({ description: '市代码', example: '4401' }) @IsString() @IsNotEmpty() cityCode: string; + + @ApiPropertyOptional({ description: '市名称', example: '广州市' }) + @IsOptional() + @IsString() + @MaxLength(50) + cityName?: string; } diff --git a/backend/services/planting-service/src/pre-planting/application/services/pre-planting-application.service.ts b/backend/services/planting-service/src/pre-planting/application/services/pre-planting-application.service.ts index 47f3f0ea..e433c3de 100644 --- a/backend/services/planting-service/src/pre-planting/application/services/pre-planting-application.service.ts +++ b/backend/services/planting-service/src/pre-planting/application/services/pre-planting-application.service.ts @@ -45,6 +45,8 @@ export class PrePlantingApplicationService { portionCount: number, provinceCode: string, cityCode: string, + provinceName?: string, + cityName?: string, ): Promise<{ orderNo: string; merged: boolean; mergeNo?: string }> { this.logger.log( `[PRE-PLANTING] Purchase request: userId=${userId}, portions=${portionCount}, ` + @@ -98,7 +100,7 @@ export class PrePlantingApplicationService { } // 增加份数 - position.addPortions(portionCount, provinceCode, cityCode); + position.addPortions(portionCount, provinceCode, cityCode, provinceName, cityName); // 标记订单为已支付 order.markAsPaid(position.totalPortions, position.availablePortions); @@ -278,7 +280,9 @@ export class PrePlantingApplicationService { mergedPortions: number; totalTreesMerged: number; provinceCode: string | null; + provinceName: string | null; cityCode: string | null; + cityName: string | null; firstPurchaseAt: Date | null; } | null> { return this.prisma.$transaction(async (tx) => { @@ -290,7 +294,9 @@ export class PrePlantingApplicationService { mergedPortions: position.mergedPortions, totalTreesMerged: position.totalTreesMerged, provinceCode: position.provinceCode, + provinceName: position.provinceName, cityCode: position.cityCode, + cityName: position.cityName, firstPurchaseAt: position.firstPurchaseAt, }; }); diff --git a/backend/services/planting-service/src/pre-planting/domain/aggregates/pre-planting-position.aggregate.ts b/backend/services/planting-service/src/pre-planting/domain/aggregates/pre-planting-position.aggregate.ts index e74c2e4a..1ef29f46 100644 --- a/backend/services/planting-service/src/pre-planting/domain/aggregates/pre-planting-position.aggregate.ts +++ b/backend/services/planting-service/src/pre-planting/domain/aggregates/pre-planting-position.aggregate.ts @@ -9,7 +9,9 @@ export interface PrePlantingPositionData { mergedPortions: number; totalTreesMerged: number; provinceCode?: string | null; + provinceName?: string | null; cityCode?: string | null; + cityName?: string | null; firstPurchaseAt?: Date | null; createdAt?: Date; } @@ -23,7 +25,9 @@ export class PrePlantingPosition { private _mergedPortions: number; private _totalTreesMerged: number; private _provinceCode: string | null; + private _provinceName: string | null; private _cityCode: string | null; + private _cityName: string | null; private _firstPurchaseAt: Date | null; private readonly _createdAt: Date; @@ -36,7 +40,9 @@ export class PrePlantingPosition { this._mergedPortions = 0; this._totalTreesMerged = 0; this._provinceCode = null; + this._provinceName = null; this._cityCode = null; + this._cityName = null; this._firstPurchaseAt = null; this._createdAt = createdAt || new Date(); } @@ -53,7 +59,9 @@ export class PrePlantingPosition { position._mergedPortions = data.mergedPortions; position._totalTreesMerged = data.totalTreesMerged; position._provinceCode = data.provinceCode || null; + position._provinceName = data.provinceName || null; position._cityCode = data.cityCode || null; + position._cityName = data.cityName || null; position._firstPurchaseAt = data.firstPurchaseAt || null; return position; } @@ -61,7 +69,7 @@ export class PrePlantingPosition { /** * 增加购买份数 */ - addPortions(count: number, provinceCode: string, cityCode: string): void { + addPortions(count: number, provinceCode: string, cityCode: string, provinceName?: string, cityName?: string): void { this._totalPortions += count; this._availablePortions += count; @@ -70,6 +78,8 @@ export class PrePlantingPosition { this._firstPurchaseAt = new Date(); this._provinceCode = provinceCode; this._cityCode = cityCode; + this._provinceName = provinceName || null; + this._cityName = cityName || null; } } @@ -113,7 +123,9 @@ export class PrePlantingPosition { get mergedPortions(): number { return this._mergedPortions; } get totalTreesMerged(): number { return this._totalTreesMerged; } get provinceCode(): string | null { return this._provinceCode; } + get provinceName(): string | null { return this._provinceName; } get cityCode(): string | null { return this._cityCode; } + get cityName(): string | null { return this._cityName; } get firstPurchaseAt(): Date | null { return this._firstPurchaseAt; } get createdAt(): Date { return this._createdAt; } @@ -127,7 +139,9 @@ export class PrePlantingPosition { mergedPortions: this._mergedPortions, totalTreesMerged: this._totalTreesMerged, provinceCode: this._provinceCode, + provinceName: this._provinceName, cityCode: this._cityCode, + cityName: this._cityName, firstPurchaseAt: this._firstPurchaseAt, createdAt: this._createdAt, }; diff --git a/backend/services/planting-service/src/pre-planting/infrastructure/repositories/pre-planting-position.repository.ts b/backend/services/planting-service/src/pre-planting/infrastructure/repositories/pre-planting-position.repository.ts index dff29993..e2dd3cab 100644 --- a/backend/services/planting-service/src/pre-planting/infrastructure/repositories/pre-planting-position.repository.ts +++ b/backend/services/planting-service/src/pre-planting/infrastructure/repositories/pre-planting-position.repository.ts @@ -24,7 +24,9 @@ export class PrePlantingPositionRepository { mergedPortions: data.mergedPortions, totalTreesMerged: data.totalTreesMerged, provinceCode: data.provinceCode || null, + provinceName: data.provinceName || null, cityCode: data.cityCode || null, + cityName: data.cityName || null, firstPurchaseAt: data.firstPurchaseAt || null, }, }); @@ -38,7 +40,9 @@ export class PrePlantingPositionRepository { mergedPortions: data.mergedPortions, totalTreesMerged: data.totalTreesMerged, provinceCode: data.provinceCode || null, + provinceName: data.provinceName || null, cityCode: data.cityCode || null, + cityName: data.cityName || null, firstPurchaseAt: data.firstPurchaseAt || null, }, }); @@ -97,7 +101,9 @@ export class PrePlantingPositionRepository { mergedPortions: record.mergedPortions, totalTreesMerged: record.totalTreesMerged, provinceCode: record.provinceCode, + provinceName: record.provinceName, cityCode: record.cityCode, + cityName: record.cityName, firstPurchaseAt: record.firstPurchaseAt, createdAt: record.createdAt, };