fix(pre-planting): 合并详情显示实际份数和金额,不再硬编码

问题:合并详情页"合并份数"显示订单条数(7)而非实际份数(10),
"总价值"硬编码 订单数×1887,每笔订单金额也硬编码 1,887。

修复:
后端 getMergeDetail:
  - 新增 sourceOrders[] 含每笔订单的 portionCount + totalAmount
  - 新增 totalPortions(总份数)和 totalAmount(总金额)

前端 PrePlantingMerge model:
  - 新增 MergeSourceOrder 类
  - 新增 sourceOrders/totalPortions/totalAmount 字段

前端合并详情页:
  - "合并份数"用 totalPortions 替代 sourceOrderNos.length
  - "总价值"用 totalAmount 替代硬编码计算
  - 来源订单列表显示每笔实际金额和份数(多份时显示"N份"前缀)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-03 02:58:34 -08:00
parent a8e06e2eda
commit 532be9a561
3 changed files with 166 additions and 49 deletions

View File

@ -397,6 +397,9 @@ export class PrePlantingApplicationService {
): Promise<{ ): Promise<{
mergeNo: string; mergeNo: string;
sourceOrderNos: string[]; sourceOrderNos: string[];
sourceOrders: { orderNo: string; portionCount: number; totalAmount: number }[];
totalPortions: number;
totalAmount: number;
treeCount: number; treeCount: number;
contractStatus: string; contractStatus: string;
contractSignedAt: Date | null; contractSignedAt: Date | null;
@ -408,9 +411,28 @@ export class PrePlantingApplicationService {
if (!merge || merge.userId !== userId) { if (!merge || merge.userId !== userId) {
throw new NotFoundException(`合并记录不存在: ${mergeNo}`); throw new NotFoundException(`合并记录不存在: ${mergeNo}`);
} }
// 查询源订单的份数和金额
const orderRecords = await tx.prePlantingOrder.findMany({
where: { orderNo: { in: merge.sourceOrderNos } },
select: { orderNo: true, portionCount: true, totalAmount: true },
orderBy: { createdAt: 'asc' },
});
const sourceOrders = orderRecords.map((o) => ({
orderNo: o.orderNo,
portionCount: o.portionCount,
totalAmount: Number(o.totalAmount),
}));
const totalPortions = sourceOrders.reduce((sum, o) => sum + o.portionCount, 0);
const totalAmount = sourceOrders.reduce((sum, o) => sum + o.totalAmount, 0);
return { return {
mergeNo: merge.mergeNo, mergeNo: merge.mergeNo,
sourceOrderNos: merge.sourceOrderNos, sourceOrderNos: merge.sourceOrderNos,
sourceOrders,
totalPortions,
totalAmount,
treeCount: merge.treeCount, treeCount: merge.treeCount,
contractStatus: merge.contractStatus, contractStatus: merge.contractStatus,
contractSignedAt: merge.contractSignedAt, contractSignedAt: merge.contractSignedAt,

View File

@ -165,9 +165,33 @@ class PrePlantingOrder {
} }
/// 5 1 /// 5 1
///
class MergeSourceOrder {
final String orderNo;
final int portionCount;
final double totalAmount;
MergeSourceOrder({
required this.orderNo,
required this.portionCount,
required this.totalAmount,
});
factory MergeSourceOrder.fromJson(Map<String, dynamic> json) {
return MergeSourceOrder(
orderNo: json['orderNo'] ?? '',
portionCount: json['portionCount'] ?? 1,
totalAmount: (json['totalAmount'] ?? 0).toDouble(),
);
}
}
class PrePlantingMerge { class PrePlantingMerge {
final String mergeNo; final String mergeNo;
final List<String> sourceOrderNos; // 5 final List<String> sourceOrderNos; //
final List<MergeSourceOrder> sourceOrders; //
final int totalPortions; // 10
final double totalAmount; //
final int treeCount; // 1 final int treeCount; // 1
final PrePlantingContractStatus contractStatus; final PrePlantingContractStatus contractStatus;
final DateTime? contractSignedAt; final DateTime? contractSignedAt;
@ -179,6 +203,9 @@ class PrePlantingMerge {
PrePlantingMerge({ PrePlantingMerge({
required this.mergeNo, required this.mergeNo,
required this.sourceOrderNos, required this.sourceOrderNos,
this.sourceOrders = const [],
this.totalPortions = 0,
this.totalAmount = 0,
required this.treeCount, required this.treeCount,
required this.contractStatus, required this.contractStatus,
this.contractSignedAt, this.contractSignedAt,
@ -195,12 +222,19 @@ class PrePlantingMerge {
bool get isMiningEnabled => miningEnabledAt != null; bool get isMiningEnabled => miningEnabledAt != null;
factory PrePlantingMerge.fromJson(Map<String, dynamic> json) { factory PrePlantingMerge.fromJson(Map<String, dynamic> json) {
final sourceOrders = (json['sourceOrders'] as List<dynamic>?)
?.map((e) => MergeSourceOrder.fromJson(e as Map<String, dynamic>))
.toList() ??
[];
return PrePlantingMerge( return PrePlantingMerge(
mergeNo: json['mergeNo'] ?? '', mergeNo: json['mergeNo'] ?? '',
sourceOrderNos: (json['sourceOrderNos'] as List<dynamic>?) sourceOrderNos: (json['sourceOrderNos'] as List<dynamic>?)
?.map((e) => e.toString()) ?.map((e) => e.toString())
.toList() ?? .toList() ??
[], [],
sourceOrders: sourceOrders,
totalPortions: json['totalPortions'] ?? sourceOrders.fold(0, (sum, o) => sum + o.portionCount),
totalAmount: (json['totalAmount'] ?? sourceOrders.fold(0.0, (sum, o) => sum + o.totalAmount)).toDouble(),
treeCount: json['treeCount'] ?? 1, treeCount: json['treeCount'] ?? 1,
contractStatus: _parseContractStatus(json['contractStatus']), contractStatus: _parseContractStatus(json['contractStatus']),
contractSignedAt: json['contractSignedAt'] != null contractSignedAt: json['contractSignedAt'] != null

View File

@ -320,11 +320,11 @@ class _PrePlantingMergeDetailPageState
// //
_buildDetailRow('合并时间', _formatDateTime(merge.mergedAt)), _buildDetailRow('合并时间', _formatDateTime(merge.mergedAt)),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildDetailRow('合并份数', '${merge.sourceOrderNos.length} 份 → 1 棵树'), _buildDetailRow('合并份数', '${merge.totalPortions > 0 ? merge.totalPortions : merge.sourceOrderNos.length} 份 → 1 棵树'),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildDetailRow( _buildDetailRow(
'总价值', '总价值',
'${(merge.sourceOrderNos.length * 1887).toString()} 绿积分', '${merge.totalAmount > 0 ? _formatNumber(merge.totalAmount) : _formatNumber(merge.sourceOrderNos.length * 1887.0)} 绿积分',
), ),
if (merge.selectedProvince != null) ...[ if (merge.selectedProvince != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@ -458,7 +458,7 @@ class _PrePlantingMergeDetailPageState
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'来源订单(${merge.sourceOrderNos.length}', '来源订单(${merge.sourceOrderNos.length},共 ${merge.totalPortions > 0 ? merge.totalPortions : merge.sourceOrderNos.length}',
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontFamily: 'Inter', fontFamily: 'Inter',
@ -467,6 +467,60 @@ class _PrePlantingMergeDetailPageState
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// 使 sourceOrders sourceOrderNos
if (merge.sourceOrders.isNotEmpty)
...merge.sourceOrders.asMap().entries.map((entry) {
final index = entry.key;
final order = entry.value;
return Padding(
padding: EdgeInsets.only(
bottom: index < merge.sourceOrders.length - 1 ? 8 : 0,
),
child: Row(
children: [
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color:
const Color(0xFFD4AF37).withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFFD4AF37),
),
),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
order.orderNo,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
color: Color(0xFF5D4037),
),
overflow: TextOverflow.ellipsis,
),
),
Text(
'${order.portionCount > 1 ? "${order.portionCount}份 " : ""}${_formatNumber(order.totalAmount)} 绿积分',
style: const TextStyle(
fontSize: 13,
color: Color(0xFF745D43),
),
),
],
),
);
})
else
...merge.sourceOrderNos.asMap().entries.map((entry) { ...merge.sourceOrderNos.asMap().entries.map((entry) {
final index = entry.key; final index = entry.key;
final orderNo = entry.value; final orderNo = entry.value;
@ -507,13 +561,6 @@ class _PrePlantingMergeDetailPageState
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const Text(
'1,887 绿积分',
style: TextStyle(
fontSize: 13,
color: Color(0xFF745D43),
),
),
], ],
), ),
); );
@ -650,6 +697,20 @@ class _PrePlantingMergeDetailPageState
); );
} }
///
String _formatNumber(double value) {
if (value == value.truncateToDouble()) {
return value.toInt().toString().replaceAllMapped(
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
(m) => '${m[1]},',
);
}
return value.toStringAsFixed(2).replaceAllMapped(
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
(m) => '${m[1]},',
);
}
/// ///
String _formatDateTime(DateTime dt) { String _formatDateTime(DateTime dt) {
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-' return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-'