463 lines
12 KiB
JavaScript
463 lines
12 KiB
JavaScript
/**
|
||
* 贷款合同控制器
|
||
* @file loanContractController.js
|
||
* @description 银行系统贷款合同相关API控制器
|
||
*/
|
||
const { LoanContract, User } = require('../models');
|
||
const { Op } = require('sequelize');
|
||
const { validationResult } = require('express-validator');
|
||
|
||
/**
|
||
* 获取贷款合同列表
|
||
* @param {Object} req - 请求对象
|
||
* @param {Object} res - 响应对象
|
||
*/
|
||
const getContracts = async (req, res) => {
|
||
try {
|
||
const {
|
||
page = 1,
|
||
pageSize = 10,
|
||
searchField = 'contractNumber',
|
||
searchValue = '',
|
||
status = '',
|
||
sortField = 'createdAt',
|
||
sortOrder = 'DESC'
|
||
} = req.query;
|
||
|
||
// 构建查询条件
|
||
const where = {};
|
||
|
||
// 搜索条件
|
||
if (searchValue) {
|
||
if (searchField === 'contractNumber') {
|
||
where.contract_number = { [Op.like]: `%${searchValue}%` };
|
||
} else 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 === 'borrowerName') {
|
||
where.customer_name = { [Op.like]: `%${searchValue}%` };
|
||
} else if (searchField === 'farmerName') {
|
||
where.customer_name = { [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',
|
||
'contractDate': 'contract_date',
|
||
'loanAmount': 'loan_amount',
|
||
'loanTerm': 'loan_term',
|
||
'interestRate': 'interest_rate'
|
||
};
|
||
const dbSortField = fieldMapping[sortField] || sortField;
|
||
const order = [[dbSortField, sortOrder.toUpperCase()]];
|
||
|
||
// 查询数据
|
||
const { count, rows } = await LoanContract.findAndCountAll({
|
||
where,
|
||
order,
|
||
offset,
|
||
limit
|
||
});
|
||
|
||
// 格式化数据 - 匹配前端期望的字段
|
||
const contracts = rows.map(contract => ({
|
||
id: contract.id,
|
||
contractNumber: contract.contract_number,
|
||
applicationNumber: `APP-${String(contract.id).padStart(6, '0')}`, // 生成申请单号
|
||
productName: '养殖贷款', // 默认产品名称
|
||
farmerName: contract.customer_name || '未知养殖户',
|
||
borrowerName: contract.customer_name || '未知借款人',
|
||
borrowerIdNumber: contract.customer_id_card || '',
|
||
assetType: '养殖设备', // 默认生资种类
|
||
applicationQuantity: '1', // 默认申请数量
|
||
amount: parseFloat(contract.loan_amount),
|
||
paidAmount: 0, // 默认已还款金额
|
||
status: contract.status,
|
||
type: 'livestock_collateral', // 默认类型
|
||
term: contract.loan_term,
|
||
interestRate: parseFloat(contract.interest_rate),
|
||
phone: contract.customer_phone || '',
|
||
purpose: '养殖经营', // 默认用途
|
||
remark: '', // 默认备注
|
||
contractTime: contract.contract_date,
|
||
disbursementTime: null,
|
||
maturityTime: null,
|
||
completedTime: null,
|
||
remainingAmount: parseFloat(contract.loan_amount), // 剩余金额等于贷款金额
|
||
repaymentProgress: 0, // 默认还款进度
|
||
created_at: contract.created_at,
|
||
updated_at: contract.updated_at
|
||
}));
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
contracts,
|
||
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 getContractById = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
|
||
const contract = await LoanContract.findByPk(id);
|
||
|
||
if (!contract) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
message: '贷款合同不存在'
|
||
});
|
||
}
|
||
|
||
// 格式化数据 - 使用数据库实际字段名
|
||
const formattedContract = {
|
||
id: contract.id,
|
||
contractNumber: contract.contract_number,
|
||
applicationNumber: contract.application_number || '',
|
||
productName: contract.product_name || '',
|
||
farmerName: contract.farmer_name || contract.customer_name,
|
||
borrowerName: contract.borrower_name || contract.customer_name,
|
||
borrowerIdNumber: contract.borrower_id_number || contract.customer_id_card,
|
||
assetType: contract.asset_type || '',
|
||
applicationQuantity: contract.application_quantity || '',
|
||
amount: parseFloat(contract.loan_amount),
|
||
paidAmount: parseFloat(contract.paid_amount || 0),
|
||
status: contract.status,
|
||
type: contract.type || 'personal',
|
||
term: contract.loan_term,
|
||
interestRate: parseFloat(contract.interest_rate),
|
||
phone: contract.customer_phone,
|
||
purpose: contract.purpose || '',
|
||
remark: contract.remark || '',
|
||
contractTime: contract.contract_date,
|
||
disbursementTime: contract.disbursement_time,
|
||
maturityTime: contract.maturity_time,
|
||
completedTime: contract.completed_time,
|
||
remainingAmount: parseFloat(contract.loan_amount - (contract.paid_amount || 0)),
|
||
repaymentProgress: contract.getRepaymentProgress ? contract.getRepaymentProgress() : 0
|
||
};
|
||
|
||
res.json({
|
||
success: true,
|
||
data: formattedContract
|
||
});
|
||
} catch (error) {
|
||
console.error('获取贷款合同详情失败:', error);
|
||
console.error('错误详情:', {
|
||
message: error.message,
|
||
stack: error.stack,
|
||
name: error.name
|
||
});
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '获取贷款合同详情失败',
|
||
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 创建贷款合同
|
||
* @param {Object} req - 请求对象
|
||
* @param {Object} res - 响应对象
|
||
*/
|
||
const createContract = async (req, res) => {
|
||
try {
|
||
const errors = validationResult(req);
|
||
if (!errors.isEmpty()) {
|
||
return res.status(400).json({
|
||
success: false,
|
||
message: '请求参数错误',
|
||
errors: errors.array()
|
||
});
|
||
}
|
||
|
||
const contractData = {
|
||
...req.body,
|
||
createdBy: req.user?.id
|
||
};
|
||
|
||
const contract = await LoanContract.create(contractData);
|
||
|
||
res.status(201).json({
|
||
success: true,
|
||
message: '贷款合同创建成功',
|
||
data: contract
|
||
});
|
||
} catch (error) {
|
||
console.error('创建贷款合同失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '创建贷款合同失败'
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 更新贷款合同
|
||
* @param {Object} req - 请求对象
|
||
* @param {Object} res - 响应对象
|
||
*/
|
||
const updateContract = 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 updateData = {
|
||
...req.body,
|
||
updatedBy: req.user?.id
|
||
};
|
||
|
||
const [updatedCount] = await LoanContract.update(updateData, {
|
||
where: { id }
|
||
});
|
||
|
||
if (updatedCount === 0) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
message: '贷款合同不存在'
|
||
});
|
||
}
|
||
|
||
const updatedContract = await LoanContract.findByPk(id);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '贷款合同更新成功',
|
||
data: updatedContract
|
||
});
|
||
} catch (error) {
|
||
console.error('更新贷款合同失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '更新贷款合同失败'
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 删除贷款合同
|
||
* @param {Object} req - 请求对象
|
||
* @param {Object} res - 响应对象
|
||
*/
|
||
const deleteContract = async (req, res) => {
|
||
try {
|
||
const { id } = req.params;
|
||
|
||
const deletedCount = await LoanContract.destroy({
|
||
where: { id }
|
||
});
|
||
|
||
if (deletedCount === 0) {
|
||
return res.status(404).json({
|
||
success: false,
|
||
message: '贷款合同不存在'
|
||
});
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '贷款合同删除成功'
|
||
});
|
||
} catch (error) {
|
||
console.error('删除贷款合同失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '删除贷款合同失败'
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 获取合同统计信息
|
||
* @param {Object} req - 请求对象
|
||
* @param {Object} res - 响应对象
|
||
*/
|
||
const getContractStats = async (req, res) => {
|
||
try {
|
||
const stats = await LoanContract.findAll({
|
||
attributes: [
|
||
'status',
|
||
[LoanContract.sequelize.fn('COUNT', '*'), 'count'],
|
||
[LoanContract.sequelize.fn('SUM', LoanContract.sequelize.col('amount')), 'totalAmount'],
|
||
[LoanContract.sequelize.fn('SUM', LoanContract.sequelize.col('paidAmount')), 'totalPaidAmount']
|
||
],
|
||
group: ['status'],
|
||
raw: true
|
||
});
|
||
|
||
const totalContracts = await LoanContract.count();
|
||
const totalAmount = await LoanContract.sum('amount') || 0;
|
||
const totalPaidAmount = await LoanContract.sum('paidAmount') || 0;
|
||
|
||
const statusStats = {
|
||
active: 0,
|
||
pending: 0,
|
||
completed: 0,
|
||
defaulted: 0,
|
||
cancelled: 0
|
||
};
|
||
|
||
const amountStats = {
|
||
active: 0,
|
||
pending: 0,
|
||
completed: 0,
|
||
defaulted: 0,
|
||
cancelled: 0
|
||
};
|
||
|
||
const paidAmountStats = {
|
||
active: 0,
|
||
pending: 0,
|
||
completed: 0,
|
||
defaulted: 0,
|
||
cancelled: 0
|
||
};
|
||
|
||
stats.forEach(stat => {
|
||
statusStats[stat.status] = parseInt(stat.count);
|
||
amountStats[stat.status] = parseFloat(stat.totalAmount) || 0;
|
||
paidAmountStats[stat.status] = parseFloat(stat.totalPaidAmount) || 0;
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
total: {
|
||
contracts: totalContracts,
|
||
amount: parseFloat(totalAmount),
|
||
paidAmount: parseFloat(totalPaidAmount),
|
||
remainingAmount: parseFloat(totalAmount - totalPaidAmount)
|
||
},
|
||
byStatus: {
|
||
counts: statusStats,
|
||
amounts: amountStats,
|
||
paidAmounts: paidAmountStats
|
||
}
|
||
}
|
||
});
|
||
} 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 updateData = {
|
||
status,
|
||
updatedBy: userId
|
||
};
|
||
|
||
// 根据状态设置相应的时间字段
|
||
if (status === 'active') {
|
||
updateData.disbursementTime = new Date();
|
||
} else if (status === 'completed') {
|
||
updateData.completedTime = new Date();
|
||
}
|
||
|
||
const [updatedCount] = await LoanContract.update(updateData, {
|
||
where: {
|
||
id: { [Op.in]: ids }
|
||
}
|
||
});
|
||
|
||
res.json({
|
||
success: true,
|
||
message: `成功更新${updatedCount}个合同的状态`,
|
||
data: {
|
||
updatedCount,
|
||
status
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('批量更新合同状态失败:', error);
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '批量更新合同状态失败'
|
||
});
|
||
}
|
||
};
|
||
|
||
module.exports = {
|
||
getContracts,
|
||
getContractById,
|
||
createContract,
|
||
updateContract,
|
||
deleteContract,
|
||
getContractStats,
|
||
batchUpdateStatus
|
||
};
|