feat(pre-planting): 预种方案调整 — 18870 USDT/棵,10份合1树

=== 方案变更 ===
- 全树基础价: 17830 → 18870 USDT
- 每份价格:   3566 → 1887 USDT
- 合并阈值:   5 份 → 10 份

=== 后端改动 (planting-service) ===
1. pre-planting-right-amounts.ts:
   - PRE_PLANTING_PRICE_PER_PORTION: 3566 → 1887
   - PRE_PLANTING_PORTIONS_PER_TREE: 5 → 10
   - 10类权益金额按 floor(整棵树/10) 重算,余数归 HQ_BASE_FEE(319)
2. pre-planting-merge.aggregate.ts:
   - 合并校验从硬编码 5 改为引用 PRE_PLANTING_PORTIONS_PER_TREE 常量
3. purchase-pre-planting.dto.ts:
   - portionCount @Max(5) → @Max(10)
4. pre-planting-application.service.ts:
   - 加价补贴计算 /5 → /PRE_PLANTING_PORTIONS_PER_TREE
   - 错误文案引用常量,消除硬编码

=== 前端改动 (mobile-app) ===
1. pre_planting_purchase_page.dart: 默认价格、份数、协议文本(1/10、4%)
2. pre_planting_position_page.dart: _portionsPerTree 5→10
3. pre_planting_merge_detail_page.dart: 总价值计算和单份显示金额
4. tree_pricing_service.dart: fallback 默认值
5. pre_planting_service.dart: JSON 解析 fallback 默认值
6. 各文件注释同步更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-01 18:32:32 -08:00
parent 28cf0b7769
commit b3984c861c
15 changed files with 100 additions and 69 deletions

View File

@ -18,10 +18,10 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
* SelectProvinceCityDto
*/
export class PurchasePrePlantingDto {
@ApiProperty({ description: '购买份数', example: 1, minimum: 1, maximum: 5 })
@ApiProperty({ description: '购买份数', example: 1, minimum: 1, maximum: 10 })
@IsInt()
@Min(1)
@Max(5)
@Max(10)
portionCount: number;
@ApiProperty({ description: '省代码(行政区划代码前两位)', example: '44' })

View File

@ -62,8 +62,8 @@ export class PrePlantingApplicationService {
// 获取当前定价配置(含总部运营成本压力涨价)
const pricingConfig = await this.treePricingClient.getTreePricingConfig();
// 预种每份加价 = Math.floor(每棵树加价 / 5),涨价部分全额归总部 (S0000000001)
const portionSupplement = Math.floor(pricingConfig.currentSupplement / 5);
// 预种每份加价 = Math.floor(每棵树加价 / 份数),涨价部分全额归总部 (S0000000001)
const portionSupplement = Math.floor(pricingConfig.currentSupplement / PRE_PLANTING_PORTIONS_PER_TREE);
const orderNo = this.generateOrderNo();
const totalAmount = portionCount * (PRE_PLANTING_PRICE_PER_PORTION + portionSupplement);
@ -564,7 +564,7 @@ export class PrePlantingApplicationService {
throw new BadRequestException('预种功能已关闭,您当前份额已满,无法继续购买');
}
if (portionCount > maxAdditional) {
throw new BadRequestException(`当前只可再购买 ${maxAdditional} 份以凑满5`);
throw new BadRequestException(`当前只可再购买 ${maxAdditional} 份以凑满 ${PRE_PLANTING_PORTIONS_PER_TREE} `);
}
}
@ -574,7 +574,7 @@ export class PrePlantingApplicationService {
accountSequence: string,
position: import('../../domain/aggregates/pre-planting-position.aggregate').PrePlantingPosition,
) {
// 获取 5 笔待合并的已支付订单
// 获取待合并的已支付订单(数量由 PRE_PLANTING_PORTIONS_PER_TREE 决定)
const paidOrders = await this.orderRepo.findPaidOrdersByUserId(
tx,
userId,
@ -582,7 +582,7 @@ export class PrePlantingApplicationService {
);
if (paidOrders.length < PRE_PLANTING_PORTIONS_PER_TREE) {
throw new Error('不足 5 笔已支付订单进行合并');
throw new Error(`不足 ${PRE_PLANTING_PORTIONS_PER_TREE} 笔已支付订单进行合并`);
}
const sourceOrders = paidOrders.slice(0, PRE_PLANTING_PORTIONS_PER_TREE);
@ -605,7 +605,7 @@ export class PrePlantingApplicationService {
);
await this.mergeRepo.save(tx, merge);
// 标记 5 笔订单为已合并
// 标记订单为已合并
for (const order of sourceOrders) {
order.markAsMerged(merge.id!);
await this.orderRepo.save(tx, order);

View File

@ -1,4 +1,5 @@
import { PrePlantingContractStatus } from '../value-objects/pre-planting-contract-status.enum';
import { PRE_PLANTING_PORTIONS_PER_TREE } from '../value-objects/pre-planting-right-amounts';
import { DomainEvent } from '../../../domain/events/domain-event.interface';
import { PrePlantingMergedEvent } from '../events/pre-planting-merged.event';
import { PrePlantingContractSignedEvent } from '../events/pre-planting-contract-signed.event';
@ -69,8 +70,8 @@ export class PrePlantingMerge {
cityCode: string,
totalTreesMergedAfter: number,
): PrePlantingMerge {
if (sourceOrderNos.length !== 5) {
throw new Error(`合并需要 5 个订单,收到 ${sourceOrderNos.length}`);
if (sourceOrderNos.length !== PRE_PLANTING_PORTIONS_PER_TREE) {
throw new Error(`合并需要 ${PRE_PLANTING_PORTIONS_PER_TREE} 个订单,收到 ${sourceOrderNos.length}`);
}
const merge = new PrePlantingMerge(

View File

@ -91,7 +91,7 @@ export class PrePlantingPosition {
}
/**
* 5
* PRE_PLANTING_PORTIONS_PER_TREE
*/
performMerge(): void {
if (!this.canMerge()) {

View File

@ -1,26 +1,53 @@
/**
* 1/5
* 1/10
*
* reward-service RIGHT_AMOUNTS15831
* 9 = floor( / 5) HQ_BASE_FEE
* === ===
* [2026-03-01]
* - 15831 18870 USDT
* - 5 10
* - 3566 1887 USDT
*
* === ===
* reward-service RIGHT_AMOUNTS 10
* 9 = floor( / 10) HEADQUARTERS_BASE_FEE
*
* === & ===
* floor(/10) 10 (HQ)
* COST_FEE 2880 288 2880 0
* OPERATION_FEE 2100 210 2100 0
* RWAD_POOL_INJECTION 5760 576 5760 0
* SHARE_RIGHT 3600 360 3600 0
* PROVINCE_AREA_RIGHT 108 10 100 8
* PROVINCE_TEAM_RIGHT 144 14 140 4
* CITY_AREA_RIGHT 252 25 250 2
* CITY_TEAM_RIGHT 288 28 280 8
* COMMUNITY_RIGHT 576 57 570 6
*
* 9 15708 1568 15680 28
* HQ_BASE_FEE = 1887 - 1568 = 319 28
*
* 18870/10 1887 18870 0
*/
export const PRE_PLANTING_RIGHT_AMOUNTS = {
COST_FEE: 576, // floor(2880/5)
OPERATION_FEE: 420, // floor(2100/5)
HEADQUARTERS_BASE_FEE: 427, // 3566 - 3139 = 427吸收全部余额
RWAD_POOL_INJECTION: 1152, // floor(5760/5)
SHARE_RIGHT: 720, // floor(3600/5)
PROVINCE_AREA_RIGHT: 21, // floor(108/5)
PROVINCE_TEAM_RIGHT: 28, // floor(144/5)
CITY_AREA_RIGHT: 50, // floor(252/5)
CITY_TEAM_RIGHT: 57, // floor(288/5)
COMMUNITY_RIGHT: 115, // floor(576/5)
COST_FEE: 288, // floor(2880/10)
OPERATION_FEE: 210, // floor(2100/10)
HEADQUARTERS_BASE_FEE: 319, // 1887 - 1568 = 319(吸收全部余额)
RWAD_POOL_INJECTION: 576, // floor(5760/10)
SHARE_RIGHT: 360, // floor(3600/10)
PROVINCE_AREA_RIGHT: 10, // floor(108/10)
PROVINCE_TEAM_RIGHT: 14, // floor(144/10)
CITY_AREA_RIGHT: 25, // floor(252/10)
CITY_TEAM_RIGHT: 28, // floor(288/10)
COMMUNITY_RIGHT: 57, // floor(576/10)
} as const;
// 合计 = 576 + 420 + 427 + 1152 + 720 + 21 + 28 + 50 + 57 + 115 = 3566
// 合计 = 288 + 210 + 319 + 576 + 360 + 10 + 14 + 25 + 28 + 57 = 1887
export const PRE_PLANTING_PRICE_PER_PORTION = 3566;
export const PRE_PLANTING_PORTIONS_PER_TREE = 5;
/** 每份价格USDT全树 18870 / 10 = 1887 */
export const PRE_PLANTING_PRICE_PER_PORTION = 1887;
/** 合并阈值:购满此数量自动合并为 1 棵树 */
export const PRE_PLANTING_PORTIONS_PER_TREE = 10;
/**
*

View File

@ -22,10 +22,11 @@ import { PrePlantingReferralClient } from './infrastructure/external/pre-plantin
import { PrePlantingAuthorizationClient } from './infrastructure/external/pre-planting-authorization.client';
/**
* (3566 / )
* ( / )
*
* === ===
* 3566 USDT/ 11/551
* [2026-03-01] 1887 USDT/ × 10 = 18870 USDT/ 3566 × 5 = 17830
* 1887 USDT/ 11/10101
*
*
* === ===

View File

@ -98,7 +98,7 @@ class ApiEndpoints {
static const String pendingActionsComplete = '/user/pending-actions'; // POST /:id/complete
// [2026-02-17] (-> Planting Service / PrePlantingModule)
// 3566 USDT/ 5 1
// 1887 USDT/ 10 1
// /planting/*
static const String prePlanting = '/pre-planting';
static const String prePlantingConfig = '$prePlanting/config'; //

View File

@ -9,7 +9,7 @@ import '../services/authorization_service.dart';
import '../services/deposit_service.dart';
import '../services/wallet_service.dart';
import '../services/planting_service.dart';
// [2026-02-17] 3566 USDT/
// [2026-02-17] 1887 USDT/
import '../services/pre_planting_service.dart';
// [2026-02-19]
import '../services/transfer_service.dart';
@ -102,7 +102,7 @@ final plantingServiceProvider = Provider<PlantingService>((ref) {
});
// [2026-02-17] Pre-Planting Service Provider ( planting-service / PrePlantingModule)
// 3566 USDT/ 5 1 PlantingService
// 1887 USDT/ 10 1 PlantingService
final prePlantingServiceProvider = Provider<PrePlantingService>((ref) {
final apiClient = ref.watch(apiClientProvider);
return PrePlantingService(apiClient: apiClient);

View File

@ -7,8 +7,9 @@ import '../network/api_client.dart';
// [2026-02-17] API
// ============================================
//
// 3566 / Flutter API
// 3566 USDT/ 5 1
// / Flutter API
// [2026-03-01] 1887 USDT/ × 10 = 18870 USDT/
// 1887 USDT/ 10 1
//
// === API ===
// planting-service PrePlantingModule
@ -22,8 +23,8 @@ import '../network/api_client.dart';
// - POST /pre-planting/sign-contract
//
// === PlantingService ===
// PlantingService 15831 USDT/
// PrePlantingService 3566 USDT/
// PlantingService 18870 USDT/
// PrePlantingService 1887 USDT/
// API
///
@ -117,7 +118,7 @@ class PrePlantingPosition {
class PrePlantingOrder {
final String orderNo;
final int portionCount; // 1
final double pricePerPortion; // 3566 USDT
final double pricePerPortion; // 1887 USDT
final double totalAmount; //
final PrePlantingOrderStatus status;
final String? mergedToMergeId; // MergeId
@ -141,8 +142,8 @@ class PrePlantingOrder {
return PrePlantingOrder(
orderNo: json['orderNo'] ?? '',
portionCount: json['portionCount'] ?? 1,
pricePerPortion: (json['pricePerPortion'] ?? 3566).toDouble(),
totalAmount: (json['totalAmount'] ?? 3566).toDouble(),
pricePerPortion: (json['pricePerPortion'] ?? 1887).toDouble(),
totalAmount: (json['totalAmount'] ?? 1887).toDouble(),
status: _parseStatus(json['status']),
mergedToMergeId: json['mergedToMergeId']?.toString(),
paidAt: json['paidAt'] != null ? DateTime.parse(json['paidAt']) : null,
@ -248,8 +249,8 @@ class CreatePrePlantingOrderResponse {
return CreatePrePlantingOrderResponse(
orderNo: json['orderNo'] ?? '',
portionCount: json['portionCount'] ?? 1,
totalAmount: (json['totalAmount'] ?? 3566).toDouble(),
pricePerPortion: (json['pricePerPortion'] ?? 3566).toDouble(),
totalAmount: (json['totalAmount'] ?? 1887).toDouble(),
pricePerPortion: (json['pricePerPortion'] ?? 1887).toDouble(),
merged: json['merged'] == true,
mergeNo: json['mergeNo']?.toString(),
);

View File

@ -5,12 +5,12 @@ import '../network/api_client.dart';
///
///
/// - = basePrice + currentSupplement
/// - = totalPrice / 5
/// - = totalPrice / 10
class TreePricingConfig {
/// 15831
/// 18870
final int basePrice;
/// 3566
/// 1887
final int basePortionPrice;
///
@ -48,11 +48,11 @@ class TreePricingConfig {
factory TreePricingConfig.fromJson(Map<String, dynamic> json) {
return TreePricingConfig(
basePrice: json['basePrice'] ?? 15831,
basePortionPrice: json['basePortionPrice'] ?? 3566,
basePrice: json['basePrice'] ?? 18870,
basePortionPrice: json['basePortionPrice'] ?? 1887,
currentSupplement: json['currentSupplement'] ?? 0,
totalPrice: json['totalPrice'] ?? 15831,
totalPortionPrice: json['totalPortionPrice'] ?? 3566,
totalPrice: json['totalPrice'] ?? 18870,
totalPortionPrice: json['totalPortionPrice'] ?? 1887,
autoIncreaseEnabled: json['autoIncreaseEnabled'] ?? false,
autoIncreaseAmount: json['autoIncreaseAmount'] ?? 0,
autoIncreaseIntervalDays: json['autoIncreaseIntervalDays'] ?? 0,
@ -65,11 +65,11 @@ class TreePricingConfig {
/// supplement=0
factory TreePricingConfig.defaultConfig() {
return TreePricingConfig(
basePrice: 15831,
basePortionPrice: 3566,
basePrice: 18870,
basePortionPrice: 1887,
currentSupplement: 0,
totalPrice: 15831,
totalPortionPrice: 3566,
totalPrice: 18870,
totalPortionPrice: 1887,
autoIncreaseEnabled: false,
autoIncreaseAmount: 0,
autoIncreaseIntervalDays: 0,

View File

@ -324,7 +324,7 @@ class _PrePlantingMergeDetailPageState
const SizedBox(height: 8),
_buildDetailRow(
'总价值',
'${(merge.sourceOrderNos.length * 3566).toString()} 绿积分',
'${(merge.sourceOrderNos.length * 1887).toString()} 绿积分',
),
if (merge.selectedProvince != null) ...[
const SizedBox(height: 8),
@ -508,7 +508,7 @@ class _PrePlantingMergeDetailPageState
),
),
const Text(
'3,171 绿积分',
'1,887 绿积分',
style: TextStyle(
fontSize: 13,
color: Color(0xFF745D43),

View File

@ -11,7 +11,7 @@ import '../../../../routes/route_paths.dart';
//
//
// -
// - N/5
// - N/10
// -
// - /
//
@ -40,7 +40,7 @@ class _PrePlantingPositionPageState
extends ConsumerState<PrePlantingPositionPage>
with SingleTickerProviderStateMixin {
// === ===
static const int _portionsPerTree = 5;
static const int _portionsPerTree = 10;
// === ===

View File

@ -12,8 +12,9 @@ import '../../../../routes/route_paths.dart';
// ============================================
//
// /
// - 3566 USDT/
// - 5 1
// [2026-03-01] 1887 USDT/ × 10 = 18870 USDT/
// - 1887 USDT/
// - 10 1
// -
// -
//
@ -24,12 +25,12 @@ import '../../../../routes/route_paths.dart';
// 4.
//
// === ===
// planting_quantity_page.dart 15831 USDT/
// 3566 USDT/
// planting_quantity_page.dart 18870 USDT/
// 1887 USDT/
///
///
/// 3566 USDT/ 5 1
/// 1887 USDT/ 10 1
///
class PrePlantingPurchasePage extends ConsumerStatefulWidget {
const PrePlantingPurchasePage({super.key});
@ -44,10 +45,10 @@ class _PrePlantingPurchasePageState
// === ===
/// USDT- admin-service
double _pricePerPortion = 3566.0;
double _pricePerPortion = 1887.0;
///
static const int _portionsPerTree = 5;
static const int _portionsPerTree = 10;
///
TreePricingConfig? _pricingConfig;
@ -328,8 +329,8 @@ class _PrePlantingPurchasePageState
static const String _defaultAgreementText =
'预种协议\n\n'
'1. 用户成功购买预种计划后,自动获得分享权。\n'
'2. 每一份预种计划对应享有1棵猫山王榴莲树40%产果分红权的1/5份额,'
'即单份预种计划可享受该果树8%的产果分红权。';
'2. 每一份预种计划对应享有1棵猫山王榴莲树40%产果分红权的1/10份额,'
'即单份预种计划可享受该果树4%的产果分红权。';
///
Future<void> _confirmPurchase() async {
@ -870,7 +871,7 @@ class _PrePlantingPurchasePageState
);
}
/// N/5
/// N/10
Widget _buildMergeProgressCard() {
final available = _position!.availablePortions;
final treesMerged = _position!.totalTreesMerged;
@ -1385,7 +1386,7 @@ class _PrePlantingPurchasePageState
const SizedBox(width: 8),
Expanded(
child: Text(
'预计涨价: ${_pricingConfig!.nextAutoIncreaseAt!.month}${_pricingConfig!.nextAutoIncreaseAt!.day}日后每份 +${(_pricingConfig!.autoIncreaseAmount / 5).floor()} 绿积分',
'预计涨价: ${_pricingConfig!.nextAutoIncreaseAt!.month}${_pricingConfig!.nextAutoIncreaseAt!.day}日后每份 +${(_pricingConfig!.autoIncreaseAmount / _portionsPerTree).floor()} 绿积分',
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',

View File

@ -2056,7 +2056,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
/// [2026-02-17] +
///
///
/// - 3566 USDT/
/// - 1887 USDT/
/// -
Widget _buildPrePlantingButtons() {
return Row(

View File

@ -44,7 +44,7 @@ import '../features/kyc/presentation/pages/change_phone_page.dart';
import '../features/contract_signing/presentation/pages/contract_signing_page.dart';
import '../features/contract_signing/presentation/pages/pending_contracts_page.dart';
import '../features/pending_actions/presentation/pages/pending_actions_page.dart';
// [2026-02-17] 3566 USDT/ 5 1
// [2026-02-17] 1887 USDT/ 10 1
import '../features/pre_planting/presentation/pages/pre_planting_purchase_page.dart';
import '../features/pre_planting/presentation/pages/pre_planting_position_page.dart';
import '../features/pre_planting/presentation/pages/pre_planting_merge_detail_page.dart';