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:
parent
a8e06e2eda
commit
532be9a561
|
|
@ -397,6 +397,9 @@ export class PrePlantingApplicationService {
|
|||
): Promise<{
|
||||
mergeNo: string;
|
||||
sourceOrderNos: string[];
|
||||
sourceOrders: { orderNo: string; portionCount: number; totalAmount: number }[];
|
||||
totalPortions: number;
|
||||
totalAmount: number;
|
||||
treeCount: number;
|
||||
contractStatus: string;
|
||||
contractSignedAt: Date | null;
|
||||
|
|
@ -408,9 +411,28 @@ export class PrePlantingApplicationService {
|
|||
if (!merge || merge.userId !== userId) {
|
||||
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 {
|
||||
mergeNo: merge.mergeNo,
|
||||
sourceOrderNos: merge.sourceOrderNos,
|
||||
sourceOrders,
|
||||
totalPortions,
|
||||
totalAmount,
|
||||
treeCount: merge.treeCount,
|
||||
contractStatus: merge.contractStatus,
|
||||
contractSignedAt: merge.contractSignedAt,
|
||||
|
|
|
|||
|
|
@ -165,9 +165,33 @@ class PrePlantingOrder {
|
|||
}
|
||||
|
||||
/// 预种合并记录(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 {
|
||||
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 PrePlantingContractStatus contractStatus;
|
||||
final DateTime? contractSignedAt;
|
||||
|
|
@ -179,6 +203,9 @@ class PrePlantingMerge {
|
|||
PrePlantingMerge({
|
||||
required this.mergeNo,
|
||||
required this.sourceOrderNos,
|
||||
this.sourceOrders = const [],
|
||||
this.totalPortions = 0,
|
||||
this.totalAmount = 0,
|
||||
required this.treeCount,
|
||||
required this.contractStatus,
|
||||
this.contractSignedAt,
|
||||
|
|
@ -195,12 +222,19 @@ class PrePlantingMerge {
|
|||
bool get isMiningEnabled => miningEnabledAt != null;
|
||||
|
||||
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(
|
||||
mergeNo: json['mergeNo'] ?? '',
|
||||
sourceOrderNos: (json['sourceOrderNos'] as List<dynamic>?)
|
||||
?.map((e) => e.toString())
|
||||
.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,
|
||||
contractStatus: _parseContractStatus(json['contractStatus']),
|
||||
contractSignedAt: json['contractSignedAt'] != null
|
||||
|
|
|
|||
|
|
@ -320,11 +320,11 @@ class _PrePlantingMergeDetailPageState
|
|||
// 信息行
|
||||
_buildDetailRow('合并时间', _formatDateTime(merge.mergedAt)),
|
||||
const SizedBox(height: 8),
|
||||
_buildDetailRow('合并份数', '${merge.sourceOrderNos.length} 份 → 1 棵树'),
|
||||
_buildDetailRow('合并份数', '${merge.totalPortions > 0 ? merge.totalPortions : merge.sourceOrderNos.length} 份 → 1 棵树'),
|
||||
const SizedBox(height: 8),
|
||||
_buildDetailRow(
|
||||
'总价值',
|
||||
'${(merge.sourceOrderNos.length * 1887).toString()} 绿积分',
|
||||
'${merge.totalAmount > 0 ? _formatNumber(merge.totalAmount) : _formatNumber(merge.sourceOrderNos.length * 1887.0)} 绿积分',
|
||||
),
|
||||
if (merge.selectedProvince != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
|
|
@ -458,7 +458,7 @@ class _PrePlantingMergeDetailPageState
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'来源订单(${merge.sourceOrderNos.length} 笔)',
|
||||
'来源订单(${merge.sourceOrderNos.length} 笔,共 ${merge.totalPortions > 0 ? merge.totalPortions : merge.sourceOrderNos.length} 份)',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Inter',
|
||||
|
|
@ -467,57 +467,104 @@ class _PrePlantingMergeDetailPageState
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...merge.sourceOrderNos.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final orderNo = entry.value;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: index < merge.sourceOrderNos.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),
|
||||
// 优先使用 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(
|
||||
orderNo,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
color: Color(0xFF5D4037),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.orderNo,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'1,887 绿积分',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF745D43),
|
||||
Text(
|
||||
'${order.portionCount > 1 ? "${order.portionCount}份 " : ""}${_formatNumber(order.totalAmount)} 绿积分',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF745D43),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
})
|
||||
else
|
||||
...merge.sourceOrderNos.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final orderNo = entry.value;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: index < merge.sourceOrderNos.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(
|
||||
orderNo,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
@ -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) {
|
||||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-'
|
||||
|
|
|
|||
Loading…
Reference in New Issue