fix(pre-planting): 签署合同前检查实名认证 + 修正合同金额

- getMergeContractPdf: KYC 为 null 时返回 400,不允许查看合同
- getMergeContractPdf: 从源订单汇总实际绿积分金额,CNY = 绿积分 × 1.1
- Flutter: KYC 错误时显示专用提示 + "去完成实名认证" 按钮

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-28 10:59:26 -08:00
parent b1e5e6b29f
commit cd73b2dec4
2 changed files with 53 additions and 22 deletions

View File

@ -406,10 +406,19 @@ export class PrePlantingApplicationService {
return m;
});
// 获取用户 KYC 信息
const kycInfo = await this.identityServiceClient.getUserKycInfo(
merge.accountSequence,
);
// 获取用户 KYC 信息 - 未完成实名认证则不允许查看合同
const kycInfo = await this.identityServiceClient.getUserKycInfo(merge.accountSequence);
if (!kycInfo || !kycInfo.realName) {
throw new BadRequestException('请先完成实名认证方可查看和签署合同');
}
// 从源订单汇总实际支付的绿积分金额
const sourceOrders = await this.prisma.prePlantingOrder.findMany({
where: { orderNo: { in: merge.sourceOrderNos } },
select: { totalAmount: true },
});
const greenPointsAmount = sourceOrders.reduce((sum, o) => sum + o.totalAmount, 0);
const totalAmount = Math.round(greenPointsAmount * 1.1 * 100) / 100; // 含税 CNY
// 生成北京时间日期
const now = new Date(Date.now() + 8 * 60 * 60 * 1000);
@ -417,10 +426,12 @@ export class PrePlantingApplicationService {
return this.pdfGeneratorService.generateContractPdf({
contractNo: mergeNo,
userRealName: kycInfo?.realName || '未认证',
userIdCard: kycInfo?.idCardNumber || '',
userPhone: kycInfo?.phoneNumber || '',
userRealName: kycInfo.realName,
userIdCard: kycInfo.idCardNumber || '',
userPhone: kycInfo.phoneNumber || '',
treeCount: merge.treeCount,
totalAmount,
greenPointsAmount,
signingDate,
});
}

View File

@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
import 'package:path_provider/path_provider.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../core/services/pre_planting_service.dart';
import '../../../../routes/route_paths.dart';
import '../../../contract_signing/presentation/widgets/signature_pad.dart';
///
@ -38,6 +39,7 @@ class _PrePlantingMergeSigningPageState
bool _isSubmitting = false;
bool _showSignaturePad = false;
String? _errorMessage;
bool _isKycRequired = false;
int _downloadProgress = 0;
int _downloadRetryCount = 0;
@ -90,10 +92,13 @@ class _PrePlantingMergeSigningPageState
} catch (e) {
debugPrint('[MergeSigningPage] PDF加载失败: $e');
if (mounted) {
final msg = e.toString();
final isKyc = msg.contains('实名认证') || msg.contains('400');
setState(() {
_isPdfLoading = false;
_downloadRetryCount++;
_errorMessage = '加载合同失败,请点击重试';
_isKycRequired = isKyc;
_errorMessage = isKyc ? '请先完成实名认证方可查看和签署合同' : '加载合同失败,请点击重试';
if (!isKyc) _downloadRetryCount++;
});
}
}
@ -306,8 +311,11 @@ class _PrePlantingMergeSigningPageState
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.cloud_download_outlined,
color: Color(0xFFD4AF37), size: 56),
Icon(
_isKycRequired ? Icons.person_off_outlined : Icons.cloud_download_outlined,
color: _isKycRequired ? Colors.red : const Color(0xFFD4AF37),
size: 56,
),
const SizedBox(height: 16),
Text(
_errorMessage!,
@ -315,17 +323,29 @@ class _PrePlantingMergeSigningPageState
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _loadPdf,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD4AF37),
padding: const EdgeInsets.symmetric(
horizontal: 32, vertical: 12)),
icon: const Icon(Icons.refresh, color: Colors.white),
label: const Text('重试',
style: TextStyle(color: Colors.white, fontSize: 16)),
),
if (_downloadRetryCount > 0)
if (_isKycRequired)
ElevatedButton.icon(
onPressed: () => context.push(RoutePaths.kycEntry),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: const EdgeInsets.symmetric(
horizontal: 32, vertical: 12)),
icon: const Icon(Icons.verified_user, color: Colors.white),
label: const Text('去完成实名认证',
style: TextStyle(color: Colors.white, fontSize: 16)),
)
else
ElevatedButton.icon(
onPressed: _loadPdf,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD4AF37),
padding: const EdgeInsets.symmetric(
horizontal: 32, vertical: 12)),
icon: const Icon(Icons.refresh, color: Colors.white),
label: const Text('重试',
style: TextStyle(color: Colors.white, fontSize: 16)),
),
if (!_isKycRequired && _downloadRetryCount > 0)
Padding(
padding: const EdgeInsets.only(top: 12),
child: Text(