feat(pre-planting): 支持省市名称存储,参照正常认种处理方式
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
5aa17b05c5
commit
20b8d41212
|
|
@ -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);
|
||||||
|
|
@ -453,7 +453,9 @@ model PrePlantingPosition {
|
||||||
|
|
||||||
// 省市 (首次购买时选择,后续复用)
|
// 省市 (首次购买时选择,后续复用)
|
||||||
provinceCode String? @map("province_code") @db.VarChar(10)
|
provinceCode String? @map("province_code") @db.VarChar(10)
|
||||||
|
provinceName String? @map("province_name") @db.VarChar(50)
|
||||||
cityCode String? @map("city_code") @db.VarChar(10)
|
cityCode String? @map("city_code") @db.VarChar(10)
|
||||||
|
cityName String? @map("city_name") @db.VarChar(50)
|
||||||
|
|
||||||
// 首次购买时间 (1年冻结起点)
|
// 首次购买时间 (1年冻结起点)
|
||||||
firstPurchaseAt DateTime? @map("first_purchase_at")
|
firstPurchaseAt DateTime? @map("first_purchase_at")
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ export class PrePlantingController {
|
||||||
dto.portionCount,
|
dto.portionCount,
|
||||||
dto.provinceCode,
|
dto.provinceCode,
|
||||||
dto.cityCode,
|
dto.cityCode,
|
||||||
|
dto.provinceName,
|
||||||
|
dto.cityName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { IsInt, IsString, Min, Max, IsNotEmpty } from 'class-validator';
|
import { IsInt, IsString, Min, Max, IsNotEmpty, IsOptional, MaxLength } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class PurchasePrePlantingDto {
|
export class PurchasePrePlantingDto {
|
||||||
@ApiProperty({ description: '购买份数', example: 1, minimum: 1, maximum: 5 })
|
@ApiProperty({ description: '购买份数', example: 1, minimum: 1, maximum: 5 })
|
||||||
|
|
@ -13,8 +13,20 @@ export class PurchasePrePlantingDto {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
provinceCode: string;
|
provinceCode: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: '省名称', example: '广东省' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
provinceName?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '市代码', example: '4401' })
|
@ApiProperty({ description: '市代码', example: '4401' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
cityCode: string;
|
cityCode: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: '市名称', example: '广州市' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(50)
|
||||||
|
cityName?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ export class PrePlantingApplicationService {
|
||||||
portionCount: number,
|
portionCount: number,
|
||||||
provinceCode: string,
|
provinceCode: string,
|
||||||
cityCode: string,
|
cityCode: string,
|
||||||
|
provinceName?: string,
|
||||||
|
cityName?: string,
|
||||||
): Promise<{ orderNo: string; merged: boolean; mergeNo?: string }> {
|
): Promise<{ orderNo: string; merged: boolean; mergeNo?: string }> {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[PRE-PLANTING] Purchase request: userId=${userId}, portions=${portionCount}, ` +
|
`[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);
|
order.markAsPaid(position.totalPortions, position.availablePortions);
|
||||||
|
|
@ -278,7 +280,9 @@ export class PrePlantingApplicationService {
|
||||||
mergedPortions: number;
|
mergedPortions: number;
|
||||||
totalTreesMerged: number;
|
totalTreesMerged: number;
|
||||||
provinceCode: string | null;
|
provinceCode: string | null;
|
||||||
|
provinceName: string | null;
|
||||||
cityCode: string | null;
|
cityCode: string | null;
|
||||||
|
cityName: string | null;
|
||||||
firstPurchaseAt: Date | null;
|
firstPurchaseAt: Date | null;
|
||||||
} | null> {
|
} | null> {
|
||||||
return this.prisma.$transaction(async (tx) => {
|
return this.prisma.$transaction(async (tx) => {
|
||||||
|
|
@ -290,7 +294,9 @@ export class PrePlantingApplicationService {
|
||||||
mergedPortions: position.mergedPortions,
|
mergedPortions: position.mergedPortions,
|
||||||
totalTreesMerged: position.totalTreesMerged,
|
totalTreesMerged: position.totalTreesMerged,
|
||||||
provinceCode: position.provinceCode,
|
provinceCode: position.provinceCode,
|
||||||
|
provinceName: position.provinceName,
|
||||||
cityCode: position.cityCode,
|
cityCode: position.cityCode,
|
||||||
|
cityName: position.cityName,
|
||||||
firstPurchaseAt: position.firstPurchaseAt,
|
firstPurchaseAt: position.firstPurchaseAt,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ export interface PrePlantingPositionData {
|
||||||
mergedPortions: number;
|
mergedPortions: number;
|
||||||
totalTreesMerged: number;
|
totalTreesMerged: number;
|
||||||
provinceCode?: string | null;
|
provinceCode?: string | null;
|
||||||
|
provinceName?: string | null;
|
||||||
cityCode?: string | null;
|
cityCode?: string | null;
|
||||||
|
cityName?: string | null;
|
||||||
firstPurchaseAt?: Date | null;
|
firstPurchaseAt?: Date | null;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +25,9 @@ export class PrePlantingPosition {
|
||||||
private _mergedPortions: number;
|
private _mergedPortions: number;
|
||||||
private _totalTreesMerged: number;
|
private _totalTreesMerged: number;
|
||||||
private _provinceCode: string | null;
|
private _provinceCode: string | null;
|
||||||
|
private _provinceName: string | null;
|
||||||
private _cityCode: string | null;
|
private _cityCode: string | null;
|
||||||
|
private _cityName: string | null;
|
||||||
private _firstPurchaseAt: Date | null;
|
private _firstPurchaseAt: Date | null;
|
||||||
private readonly _createdAt: Date;
|
private readonly _createdAt: Date;
|
||||||
|
|
||||||
|
|
@ -36,7 +40,9 @@ export class PrePlantingPosition {
|
||||||
this._mergedPortions = 0;
|
this._mergedPortions = 0;
|
||||||
this._totalTreesMerged = 0;
|
this._totalTreesMerged = 0;
|
||||||
this._provinceCode = null;
|
this._provinceCode = null;
|
||||||
|
this._provinceName = null;
|
||||||
this._cityCode = null;
|
this._cityCode = null;
|
||||||
|
this._cityName = null;
|
||||||
this._firstPurchaseAt = null;
|
this._firstPurchaseAt = null;
|
||||||
this._createdAt = createdAt || new Date();
|
this._createdAt = createdAt || new Date();
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +59,9 @@ export class PrePlantingPosition {
|
||||||
position._mergedPortions = data.mergedPortions;
|
position._mergedPortions = data.mergedPortions;
|
||||||
position._totalTreesMerged = data.totalTreesMerged;
|
position._totalTreesMerged = data.totalTreesMerged;
|
||||||
position._provinceCode = data.provinceCode || null;
|
position._provinceCode = data.provinceCode || null;
|
||||||
|
position._provinceName = data.provinceName || null;
|
||||||
position._cityCode = data.cityCode || null;
|
position._cityCode = data.cityCode || null;
|
||||||
|
position._cityName = data.cityName || null;
|
||||||
position._firstPurchaseAt = data.firstPurchaseAt || null;
|
position._firstPurchaseAt = data.firstPurchaseAt || null;
|
||||||
return position;
|
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._totalPortions += count;
|
||||||
this._availablePortions += count;
|
this._availablePortions += count;
|
||||||
|
|
||||||
|
|
@ -70,6 +78,8 @@ export class PrePlantingPosition {
|
||||||
this._firstPurchaseAt = new Date();
|
this._firstPurchaseAt = new Date();
|
||||||
this._provinceCode = provinceCode;
|
this._provinceCode = provinceCode;
|
||||||
this._cityCode = cityCode;
|
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 mergedPortions(): number { return this._mergedPortions; }
|
||||||
get totalTreesMerged(): number { return this._totalTreesMerged; }
|
get totalTreesMerged(): number { return this._totalTreesMerged; }
|
||||||
get provinceCode(): string | null { return this._provinceCode; }
|
get provinceCode(): string | null { return this._provinceCode; }
|
||||||
|
get provinceName(): string | null { return this._provinceName; }
|
||||||
get cityCode(): string | null { return this._cityCode; }
|
get cityCode(): string | null { return this._cityCode; }
|
||||||
|
get cityName(): string | null { return this._cityName; }
|
||||||
get firstPurchaseAt(): Date | null { return this._firstPurchaseAt; }
|
get firstPurchaseAt(): Date | null { return this._firstPurchaseAt; }
|
||||||
get createdAt(): Date { return this._createdAt; }
|
get createdAt(): Date { return this._createdAt; }
|
||||||
|
|
||||||
|
|
@ -127,7 +139,9 @@ export class PrePlantingPosition {
|
||||||
mergedPortions: this._mergedPortions,
|
mergedPortions: this._mergedPortions,
|
||||||
totalTreesMerged: this._totalTreesMerged,
|
totalTreesMerged: this._totalTreesMerged,
|
||||||
provinceCode: this._provinceCode,
|
provinceCode: this._provinceCode,
|
||||||
|
provinceName: this._provinceName,
|
||||||
cityCode: this._cityCode,
|
cityCode: this._cityCode,
|
||||||
|
cityName: this._cityName,
|
||||||
firstPurchaseAt: this._firstPurchaseAt,
|
firstPurchaseAt: this._firstPurchaseAt,
|
||||||
createdAt: this._createdAt,
|
createdAt: this._createdAt,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,9 @@ export class PrePlantingPositionRepository {
|
||||||
mergedPortions: data.mergedPortions,
|
mergedPortions: data.mergedPortions,
|
||||||
totalTreesMerged: data.totalTreesMerged,
|
totalTreesMerged: data.totalTreesMerged,
|
||||||
provinceCode: data.provinceCode || null,
|
provinceCode: data.provinceCode || null,
|
||||||
|
provinceName: data.provinceName || null,
|
||||||
cityCode: data.cityCode || null,
|
cityCode: data.cityCode || null,
|
||||||
|
cityName: data.cityName || null,
|
||||||
firstPurchaseAt: data.firstPurchaseAt || null,
|
firstPurchaseAt: data.firstPurchaseAt || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -38,7 +40,9 @@ export class PrePlantingPositionRepository {
|
||||||
mergedPortions: data.mergedPortions,
|
mergedPortions: data.mergedPortions,
|
||||||
totalTreesMerged: data.totalTreesMerged,
|
totalTreesMerged: data.totalTreesMerged,
|
||||||
provinceCode: data.provinceCode || null,
|
provinceCode: data.provinceCode || null,
|
||||||
|
provinceName: data.provinceName || null,
|
||||||
cityCode: data.cityCode || null,
|
cityCode: data.cityCode || null,
|
||||||
|
cityName: data.cityName || null,
|
||||||
firstPurchaseAt: data.firstPurchaseAt || null,
|
firstPurchaseAt: data.firstPurchaseAt || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -97,7 +101,9 @@ export class PrePlantingPositionRepository {
|
||||||
mergedPortions: record.mergedPortions,
|
mergedPortions: record.mergedPortions,
|
||||||
totalTreesMerged: record.totalTreesMerged,
|
totalTreesMerged: record.totalTreesMerged,
|
||||||
provinceCode: record.provinceCode,
|
provinceCode: record.provinceCode,
|
||||||
|
provinceName: record.provinceName,
|
||||||
cityCode: record.cityCode,
|
cityCode: record.cityCode,
|
||||||
|
cityName: record.cityName,
|
||||||
firstPurchaseAt: record.firstPurchaseAt,
|
firstPurchaseAt: record.firstPurchaseAt,
|
||||||
createdAt: record.createdAt,
|
createdAt: record.createdAt,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue