Files
nxxmdata/bank-backend/controllers/loanApplicationController.js

449 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 贷款申请控制器
* @file loanApplicationController.js
* @description 银行系统贷款申请相关API控制器
*/
const { LoanApplication, AuditRecord, User } = require('../models');
const { Op } = require('sequelize');
const { validationResult } = require('express-validator');
/**
* 获取贷款申请列表
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
const getApplications = async (req, res) => {
try {
const {
page = 1,
pageSize = 10,
searchField = 'applicationNumber',
searchValue = '',
status = '',
sortField = 'createdAt',
sortOrder = 'DESC'
} = req.query;
// 构建查询条件
const where = {};
// 搜索条件
if (searchValue) {
if (searchField === 'customerName') {
where.customer_name = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerPhone') {
where.customer_phone = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'customerIdCard') {
where.customer_id_card = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'applicationNumber') {
// 数据库中实际没有applicationNumber字段使用id作为替代
where.id = { [Op.like]: `%${searchValue}%` };
} else if (searchField === 'productName') {
// 产品名称搜索暂时不处理,因为数据库中没有这个字段
// 可以在前端过滤
}
}
// 状态筛选
if (status) {
where.status = status;
}
// 分页参数
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
// 排序参数 - 映射字段名
const fieldMapping = {
'createdAt': 'created_at',
'updatedAt': 'updated_at',
'applicationDate': 'application_date',
'loanAmount': 'loan_amount',
'loanTerm': 'loan_term',
'interestRate': 'interest_rate'
};
const dbSortField = fieldMapping[sortField] || sortField;
const order = [[dbSortField, sortOrder.toUpperCase()]];
// 查询数据
const { count, rows } = await LoanApplication.findAndCountAll({
where,
order,
offset,
limit
});
// 格式化数据 - 匹配前端期望的字段
const applications = rows.map(app => ({
id: app.id,
applicationNumber: `APP-${String(app.id).padStart(6, '0')}`, // 生成申请单号
productName: '养殖贷款', // 默认产品名称
farmerName: app.customer_name || '未知养殖户',
borrowerName: app.customer_name || '未知借款人',
borrowerIdNumber: app.customer_id_card || '',
assetType: '养殖设备', // 默认生资种类
applicationQuantity: '1', // 默认申请数量
amount: parseFloat(app.loan_amount),
status: app.status,
type: 'personal', // 默认类型
term: app.loan_term,
interestRate: parseFloat(app.interest_rate),
phone: app.customer_phone || '',
purpose: '养殖经营', // 默认用途
remark: '', // 默认备注
applicationTime: app.application_date,
approvedTime: null,
rejectedTime: null,
created_at: app.created_at,
updated_at: app.updated_at
}));
res.json({
success: true,
data: {
applications,
pagination: {
current: parseInt(page),
pageSize: parseInt(pageSize),
total: count,
totalPages: Math.ceil(count / parseInt(pageSize))
}
}
});
} catch (error) {
console.error('获取贷款申请列表失败:', error);
res.status(500).json({
success: false,
message: '获取贷款申请列表失败'
});
}
};
/**
* 获取贷款申请详情
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
const getApplicationById = async (req, res) => {
try {
const { id } = req.params;
const application = await LoanApplication.findByPk(id, {
include: [
{
model: User,
as: 'applicant',
attributes: ['id', 'username', 'real_name', 'email', 'phone']
},
{
model: User,
as: 'approver',
attributes: ['id', 'username', 'real_name']
},
{
model: User,
as: 'rejector',
attributes: ['id', 'username', 'real_name']
},
{
model: AuditRecord,
as: 'auditRecords',
include: [
{
model: User,
as: 'auditorUser',
attributes: ['id', 'username', 'real_name']
}
],
order: [['auditTime', 'DESC']]
}
]
});
if (!application) {
return res.status(404).json({
success: false,
message: '贷款申请不存在'
});
}
// 格式化数据
const formattedApplication = {
id: application.id,
applicationNumber: application.applicationNumber,
productName: application.productName,
farmerName: application.farmerName,
borrowerName: application.borrowerName,
borrowerIdNumber: application.borrowerIdNumber,
assetType: application.assetType,
applicationQuantity: application.applicationQuantity,
amount: parseFloat(application.amount),
status: application.status,
type: application.type,
term: application.term,
interestRate: parseFloat(application.interestRate),
phone: application.phone,
purpose: application.purpose,
remark: application.remark,
applicationTime: application.applicationTime,
approvedTime: application.approvedTime,
rejectedTime: application.rejectedTime,
applicant: application.applicant,
approver: application.approver,
rejector: application.rejector,
auditRecords: application.auditRecords.map(record => ({
id: record.id,
action: record.action,
auditor: record.auditorUser?.real_name || record.auditor,
auditorId: record.auditorId,
comment: record.comment,
time: record.auditTime,
previousStatus: record.previousStatus,
newStatus: record.newStatus
}))
};
res.json({
success: true,
data: formattedApplication
});
} catch (error) {
console.error('获取贷款申请详情失败:', error);
res.status(500).json({
success: false,
message: '获取贷款申请详情失败'
});
}
};
/**
* 审核贷款申请
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
const auditApplication = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: '请求参数错误',
errors: errors.array()
});
}
const { id } = req.params;
const { action, comment } = req.body;
const userId = req.user?.userId;
// 获取申请信息
const application = await LoanApplication.findByPk(id);
if (!application) {
return res.status(404).json({
success: false,
message: '贷款申请不存在'
});
}
// 检查申请状态
if (application.status === 'approved' || application.status === 'rejected') {
return res.status(400).json({
success: false,
message: '该申请已完成审核,无法重复操作'
});
}
const previousStatus = application.status;
let newStatus = application.status;
// 根据审核动作更新状态
if (action === 'approve') {
newStatus = 'approved';
// 注意LoanApplication模型中没有这些字段暂时注释掉
// application.approvedTime = new Date();
// application.approvedBy = userId;
} else if (action === 'reject') {
newStatus = 'rejected';
// 注意LoanApplication模型中没有这些字段暂时注释掉
// application.rejectedTime = new Date();
// application.rejectedBy = userId;
// application.rejectionReason = comment;
}
// 更新申请状态
application.status = newStatus;
await application.save();
// 创建审核记录
await AuditRecord.create({
applicationId: id,
action,
auditor: req.user?.real_name || req.user?.username || '系统',
auditorId: userId,
comment,
auditTime: new Date(),
previousStatus,
newStatus
});
res.json({
success: true,
message: action === 'approve' ? '审核通过' : '审核拒绝',
data: {
id: application.id,
status: newStatus,
action,
comment
}
});
} catch (error) {
console.error('审核贷款申请失败:', error);
console.error('错误详情:', error.message);
console.error('错误堆栈:', error.stack);
res.status(500).json({
success: false,
message: '审核贷款申请失败',
error: error.message
});
}
};
/**
* 获取申请统计信息
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
const getApplicationStats = async (req, res) => {
try {
const stats = await LoanApplication.findAll({
attributes: [
'status',
[LoanApplication.sequelize.fn('COUNT', '*'), 'count'],
[LoanApplication.sequelize.fn('SUM', LoanApplication.sequelize.col('amount')), 'totalAmount']
],
group: ['status'],
raw: true
});
const totalApplications = await LoanApplication.count();
const totalAmount = await LoanApplication.sum('amount') || 0;
const statusStats = {
pending_review: 0,
verification_pending: 0,
pending_binding: 0,
approved: 0,
rejected: 0
};
const amountStats = {
pending_review: 0,
verification_pending: 0,
pending_binding: 0,
approved: 0,
rejected: 0
};
stats.forEach(stat => {
statusStats[stat.status] = parseInt(stat.count);
amountStats[stat.status] = parseFloat(stat.totalAmount) || 0;
});
res.json({
success: true,
data: {
total: {
applications: totalApplications,
amount: parseFloat(totalAmount)
},
byStatus: {
counts: statusStats,
amounts: amountStats
}
}
});
} catch (error) {
console.error('获取申请统计失败:', error);
res.status(500).json({
success: false,
message: '获取申请统计失败'
});
}
};
/**
* 批量更新申请状态
* @param {Object} req - 请求对象
* @param {Object} res - 响应对象
*/
const batchUpdateStatus = async (req, res) => {
try {
const { ids, status } = req.body;
const userId = req.user?.id;
if (!ids || !Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({
success: false,
message: '请选择要操作的申请'
});
}
if (!status) {
return res.status(400).json({
success: false,
message: '请指定目标状态'
});
}
// 更新申请状态
const [updatedCount] = await LoanApplication.update(
{
status,
...(status === 'approved' && { approvedTime: new Date(), approvedBy: userId }),
...(status === 'rejected' && { rejectedTime: new Date(), rejectedBy: userId })
},
{
where: {
id: { [Op.in]: ids }
}
}
);
// 为每个申请创建审核记录
for (const id of ids) {
await AuditRecord.create({
applicationId: id,
action: status === 'approved' ? 'approve' : 'reject',
auditor: req.user?.real_name || req.user?.username || '系统',
auditorId: userId,
comment: `批量${status === 'approved' ? '通过' : '拒绝'}`,
auditTime: new Date(),
newStatus: status
});
}
res.json({
success: true,
message: `成功更新${updatedCount}个申请的状态`,
data: {
updatedCount,
status
}
});
} catch (error) {
console.error('批量更新申请状态失败:', error);
res.status(500).json({
success: false,
message: '批量更新申请状态失败'
});
}
};
module.exports = {
getApplications,
getApplicationById,
auditApplication,
getApplicationStats,
batchUpdateStatus
};