添加银行端后端接口
This commit is contained in:
31
bank-backend/check-db-data.js
Normal file
31
bank-backend/check-db-data.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const { LoanProduct } = require('./models');
|
||||
|
||||
async function checkData() {
|
||||
try {
|
||||
console.log('检查数据库中的贷款商品数据...');
|
||||
|
||||
const count = await LoanProduct.count();
|
||||
console.log(`数据库中共有 ${count} 条贷款商品数据`);
|
||||
|
||||
if (count > 0) {
|
||||
const products = await LoanProduct.findAll({
|
||||
limit: 3,
|
||||
attributes: ['id', 'productName', 'loanAmount', 'loanTerm', 'interestRate', 'onSaleStatus']
|
||||
});
|
||||
|
||||
console.log('前3条数据:');
|
||||
products.forEach((product, index) => {
|
||||
console.log(`${index + 1}. ID: ${product.id}, 名称: ${product.productName}, 额度: ${product.loanAmount}`);
|
||||
});
|
||||
} else {
|
||||
console.log('数据库中没有贷款商品数据,需要添加测试数据');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查数据失败:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
checkData();
|
||||
36
bank-backend/check-loan-products.js
Normal file
36
bank-backend/check-loan-products.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const { LoanProduct } = require('./models');
|
||||
|
||||
async function checkLoanProducts() {
|
||||
try {
|
||||
console.log('查询数据库中的贷款商品数据...');
|
||||
|
||||
const products = await LoanProduct.findAll({
|
||||
attributes: ['id', 'productName', 'loanAmount', 'loanTerm', 'interestRate', 'serviceArea', 'servicePhone', 'totalCustomers', 'supervisionCustomers', 'completedCustomers', 'onSaleStatus', 'createdAt']
|
||||
});
|
||||
|
||||
console.log(`找到 ${products.length} 条贷款商品数据:`);
|
||||
|
||||
products.forEach((product, index) => {
|
||||
console.log(`\n--- 产品 ${index + 1} ---`);
|
||||
console.log(`ID: ${product.id}`);
|
||||
console.log(`产品名称: ${product.productName}`);
|
||||
console.log(`贷款额度: ${product.loanAmount}`);
|
||||
console.log(`贷款周期: ${product.loanTerm}`);
|
||||
console.log(`贷款利率: ${product.interestRate}`);
|
||||
console.log(`服务区域: ${product.serviceArea}`);
|
||||
console.log(`服务电话: ${product.servicePhone}`);
|
||||
console.log(`总客户数: ${product.totalCustomers}`);
|
||||
console.log(`监管中客户: ${product.supervisionCustomers}`);
|
||||
console.log(`已结项客户: ${product.completedCustomers}`);
|
||||
console.log(`在售状态: ${product.onSaleStatus}`);
|
||||
console.log(`创建时间: ${product.createdAt}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('查询失败:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
checkLoanProducts();
|
||||
26
bank-backend/config/config.json
Normal file
26
bank-backend/config/config.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"development": {
|
||||
"username": "root",
|
||||
"password": "aiotAiot123!",
|
||||
"database": "ningxia_bank",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql",
|
||||
"port": 3306
|
||||
},
|
||||
"test": {
|
||||
"username": "root",
|
||||
"password": "aiotAiot123!",
|
||||
"database": "ningxia_bank_test",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql",
|
||||
"port": 3306
|
||||
},
|
||||
"production": {
|
||||
"username": "root",
|
||||
"password": "aiotAiot123!",
|
||||
"database": "ningxia_bank",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql",
|
||||
"port": 3306
|
||||
}
|
||||
}
|
||||
480
bank-backend/controllers/completedSupervisionController.js
Normal file
480
bank-backend/controllers/completedSupervisionController.js
Normal file
@@ -0,0 +1,480 @@
|
||||
const { CompletedSupervision, User } = require('../models')
|
||||
const { Op } = require('sequelize')
|
||||
|
||||
// 获取监管任务已结项列表
|
||||
const getCompletedSupervisions = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
contractNumber = '',
|
||||
settlementStatus = ''
|
||||
} = req.query
|
||||
|
||||
const offset = (page - 1) * limit
|
||||
const where = {}
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
where[Op.or] = [
|
||||
{ applicationNumber: { [Op.like]: `%${search}%` } },
|
||||
{ customerName: { [Op.like]: `%${search}%` } },
|
||||
{ productName: { [Op.like]: `%${search}%` } }
|
||||
]
|
||||
}
|
||||
|
||||
// 合同编号筛选
|
||||
if (contractNumber) {
|
||||
where.contractNumber = contractNumber
|
||||
}
|
||||
|
||||
// 结清状态筛选
|
||||
if (settlementStatus) {
|
||||
where.settlementStatus = settlementStatus
|
||||
}
|
||||
|
||||
const { count, rows } = await CompletedSupervision.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
],
|
||||
order: [['importTime', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
})
|
||||
|
||||
const totalPages = Math.ceil(count / limit)
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取监管任务已结项列表成功',
|
||||
data: {
|
||||
tasks: rows,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
totalPages,
|
||||
hasNextPage: parseInt(page) < totalPages,
|
||||
hasPrevPage: parseInt(page) > 1
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取监管任务已结项列表失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取监管任务已结项列表失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 根据ID获取监管任务已结项详情
|
||||
const getCompletedSupervisionById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const task = await CompletedSupervision.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '监管任务已结项不存在'
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取监管任务已结项详情成功',
|
||||
data: task
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取监管任务已结项详情失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取监管任务已结项详情失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 创建监管任务已结项
|
||||
const createCompletedSupervision = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
applicationNumber,
|
||||
contractNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType,
|
||||
idNumber,
|
||||
assetType,
|
||||
assetQuantity,
|
||||
totalRepaymentPeriods,
|
||||
settlementStatus,
|
||||
settlementDate,
|
||||
settlementAmount,
|
||||
remainingAmount,
|
||||
settlementNotes
|
||||
} = req.body
|
||||
|
||||
// 验证必填字段
|
||||
const requiredFields = [
|
||||
'applicationNumber', 'contractNumber', 'productName',
|
||||
'customerName', 'idType', 'idNumber', 'assetType',
|
||||
'assetQuantity', 'totalRepaymentPeriods'
|
||||
]
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!req.body[field]) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `${field} 是必填字段`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 验证申请单号唯一性
|
||||
const existingTask = await CompletedSupervision.findOne({
|
||||
where: { applicationNumber }
|
||||
})
|
||||
|
||||
if (existingTask) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请单号已存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证状态枚举值
|
||||
const validStatuses = ['settled', 'unsettled', 'partial']
|
||||
if (settlementStatus && !validStatuses.includes(settlementStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '结清状态值无效'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证证件类型枚举值
|
||||
const validIdTypes = ['ID_CARD', 'PASSPORT', 'OTHER']
|
||||
if (!validIdTypes.includes(idType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '证件类型值无效'
|
||||
})
|
||||
}
|
||||
|
||||
const task = await CompletedSupervision.create({
|
||||
applicationNumber,
|
||||
contractNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType,
|
||||
idNumber,
|
||||
assetType,
|
||||
assetQuantity,
|
||||
totalRepaymentPeriods,
|
||||
settlementStatus: settlementStatus || 'unsettled',
|
||||
settlementDate: settlementDate || null,
|
||||
importTime: req.body.importTime || new Date(),
|
||||
settlementAmount: settlementAmount || null,
|
||||
remainingAmount: remainingAmount || null,
|
||||
settlementNotes: settlementNotes || null,
|
||||
createdBy: req.user.id
|
||||
})
|
||||
|
||||
// 获取创建的任务详情(包含关联数据)
|
||||
const createdTask = await CompletedSupervision.findByPk(task.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建监管任务已结项成功',
|
||||
data: createdTask
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('创建监管任务已结项失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建监管任务已结项失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 更新监管任务已结项
|
||||
const updateCompletedSupervision = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const updateData = req.body
|
||||
|
||||
const task = await CompletedSupervision.findByPk(id)
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '监管任务已结项不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证状态枚举值
|
||||
if (updateData.settlementStatus) {
|
||||
const validStatuses = ['settled', 'unsettled', 'partial']
|
||||
if (!validStatuses.includes(updateData.settlementStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '结清状态值无效'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 验证证件类型枚举值
|
||||
if (updateData.idType) {
|
||||
const validIdTypes = ['ID_CARD', 'PASSPORT', 'OTHER']
|
||||
if (!validIdTypes.includes(updateData.idType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '证件类型值无效'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果申请单号有变化,检查唯一性
|
||||
if (updateData.applicationNumber && updateData.applicationNumber !== task.applicationNumber) {
|
||||
const existingTask = await CompletedSupervision.findOne({
|
||||
where: {
|
||||
applicationNumber: updateData.applicationNumber,
|
||||
id: { [Op.ne]: id }
|
||||
}
|
||||
})
|
||||
|
||||
if (existingTask) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请单号已存在'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await task.update({
|
||||
...updateData,
|
||||
updatedBy: req.user.id
|
||||
})
|
||||
|
||||
// 获取更新后的任务详情
|
||||
const updatedTask = await CompletedSupervision.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新监管任务已结项成功',
|
||||
data: updatedTask
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('更新监管任务已结项失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新监管任务已结项失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除监管任务已结项
|
||||
const deleteCompletedSupervision = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const task = await CompletedSupervision.findByPk(id)
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '监管任务已结项不存在'
|
||||
})
|
||||
}
|
||||
|
||||
await task.destroy()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除监管任务已结项成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('删除监管任务已结项失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除监管任务已结项失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取监管任务已结项统计信息
|
||||
const getCompletedSupervisionStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await CompletedSupervision.findAll({
|
||||
attributes: [
|
||||
'settlementStatus',
|
||||
[CompletedSupervision.sequelize.fn('COUNT', CompletedSupervision.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['settlementStatus'],
|
||||
raw: true
|
||||
})
|
||||
|
||||
const totalCount = await CompletedSupervision.count()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取监管任务已结项统计成功',
|
||||
data: {
|
||||
stats,
|
||||
totalCount
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取监管任务已结项统计失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取监管任务已结项统计失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新结清状态
|
||||
const batchUpdateStatus = async (req, res) => {
|
||||
try {
|
||||
const { ids, settlementStatus } = req.body
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要更新的任务'
|
||||
})
|
||||
}
|
||||
|
||||
if (!settlementStatus) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要更新的状态'
|
||||
})
|
||||
}
|
||||
|
||||
const validStatuses = ['settled', 'unsettled', 'partial']
|
||||
if (!validStatuses.includes(settlementStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '结清状态值无效'
|
||||
})
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
settlementStatus,
|
||||
updatedBy: req.user.id
|
||||
}
|
||||
|
||||
// 如果状态是已结清,设置结清日期
|
||||
if (settlementStatus === 'settled') {
|
||||
updateData.settlementDate = new Date()
|
||||
}
|
||||
|
||||
await CompletedSupervision.update(updateData, {
|
||||
where: { id: { [Op.in]: ids } }
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '批量更新结清状态成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('批量更新结清状态失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量更新结清状态失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除监管任务已结项
|
||||
const batchDelete = async (req, res) => {
|
||||
try {
|
||||
const { ids } = req.body
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要删除的任务'
|
||||
})
|
||||
}
|
||||
|
||||
await CompletedSupervision.destroy({
|
||||
where: { id: { [Op.in]: ids } }
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '批量删除监管任务已结项成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('批量删除监管任务已结项失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量删除监管任务已结项失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCompletedSupervisions,
|
||||
getCompletedSupervisionById,
|
||||
createCompletedSupervision,
|
||||
updateCompletedSupervision,
|
||||
deleteCompletedSupervision,
|
||||
getCompletedSupervisionStats,
|
||||
batchUpdateStatus,
|
||||
batchDelete
|
||||
}
|
||||
482
bank-backend/controllers/installationTaskController.js
Normal file
482
bank-backend/controllers/installationTaskController.js
Normal file
@@ -0,0 +1,482 @@
|
||||
const { InstallationTask, User } = require('../models')
|
||||
const { Op } = require('sequelize')
|
||||
|
||||
// 获取待安装任务列表
|
||||
const getInstallationTasks = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
installationStatus = '',
|
||||
dateRange = ''
|
||||
} = req.query
|
||||
|
||||
const offset = (page - 1) * limit
|
||||
const where = {}
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
where[Op.or] = [
|
||||
{ contractNumber: { [Op.like]: `%${search}%` } },
|
||||
{ applicationNumber: { [Op.like]: `%${search}%` } },
|
||||
{ customerName: { [Op.like]: `%${search}%` } }
|
||||
]
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (installationStatus) {
|
||||
where.installationStatus = installationStatus
|
||||
}
|
||||
|
||||
// 日期范围筛选
|
||||
if (dateRange) {
|
||||
const [startDate, endDate] = dateRange.split(',')
|
||||
if (startDate && endDate) {
|
||||
where.taskGenerationTime = {
|
||||
[Op.between]: [new Date(startDate), new Date(endDate)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { count, rows } = await InstallationTask.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
],
|
||||
order: [['taskGenerationTime', 'DESC']],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
})
|
||||
|
||||
const totalPages = Math.ceil(count / limit)
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取待安装任务列表成功',
|
||||
data: {
|
||||
tasks: rows,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
totalPages,
|
||||
hasNextPage: parseInt(page) < totalPages,
|
||||
hasPrevPage: parseInt(page) > 1
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取待安装任务列表失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取待安装任务列表失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 根据ID获取待安装任务详情
|
||||
const getInstallationTaskById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const task = await InstallationTask.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '待安装任务不存在'
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取待安装任务详情成功',
|
||||
data: task
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取待安装任务详情失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取待安装任务详情失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 创建待安装任务
|
||||
const createInstallationTask = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
applicationNumber,
|
||||
contractNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType,
|
||||
idNumber,
|
||||
assetType,
|
||||
equipmentToInstall,
|
||||
installationNotes,
|
||||
installerName,
|
||||
installerPhone,
|
||||
installationAddress
|
||||
} = req.body
|
||||
|
||||
// 验证必填字段
|
||||
const requiredFields = [
|
||||
'applicationNumber', 'contractNumber', 'productName',
|
||||
'customerName', 'idType', 'idNumber', 'assetType', 'equipmentToInstall'
|
||||
]
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!req.body[field]) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `${field} 是必填字段`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 验证申请单号唯一性
|
||||
const existingTask = await InstallationTask.findOne({
|
||||
where: { applicationNumber }
|
||||
})
|
||||
|
||||
if (existingTask) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请单号已存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证状态枚举值
|
||||
const validStatuses = ['pending', 'in-progress', 'completed', 'failed']
|
||||
if (req.body.installationStatus && !validStatuses.includes(req.body.installationStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '安装状态值无效'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证证件类型枚举值
|
||||
const validIdTypes = ['ID_CARD', 'PASSPORT', 'OTHER']
|
||||
if (!validIdTypes.includes(idType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '证件类型值无效'
|
||||
})
|
||||
}
|
||||
|
||||
const task = await InstallationTask.create({
|
||||
applicationNumber,
|
||||
contractNumber,
|
||||
productName,
|
||||
customerName,
|
||||
idType,
|
||||
idNumber,
|
||||
assetType,
|
||||
equipmentToInstall,
|
||||
installationStatus: req.body.installationStatus || 'pending',
|
||||
taskGenerationTime: req.body.taskGenerationTime || new Date(),
|
||||
completionTime: req.body.completionTime || null,
|
||||
installationNotes,
|
||||
installerName,
|
||||
installerPhone,
|
||||
installationAddress,
|
||||
createdBy: req.user.id
|
||||
})
|
||||
|
||||
// 获取创建的任务详情(包含关联数据)
|
||||
const createdTask = await InstallationTask.findByPk(task.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建待安装任务成功',
|
||||
data: createdTask
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('创建待安装任务失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建待安装任务失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 更新待安装任务
|
||||
const updateInstallationTask = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const updateData = req.body
|
||||
|
||||
const task = await InstallationTask.findByPk(id)
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '待安装任务不存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证状态枚举值
|
||||
if (updateData.installationStatus) {
|
||||
const validStatuses = ['pending', 'in-progress', 'completed', 'failed']
|
||||
if (!validStatuses.includes(updateData.installationStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '安装状态值无效'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 验证证件类型枚举值
|
||||
if (updateData.idType) {
|
||||
const validIdTypes = ['ID_CARD', 'PASSPORT', 'OTHER']
|
||||
if (!validIdTypes.includes(updateData.idType)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '证件类型值无效'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果申请单号有变化,检查唯一性
|
||||
if (updateData.applicationNumber && updateData.applicationNumber !== task.applicationNumber) {
|
||||
const existingTask = await InstallationTask.findOne({
|
||||
where: {
|
||||
applicationNumber: updateData.applicationNumber,
|
||||
id: { [Op.ne]: id }
|
||||
}
|
||||
})
|
||||
|
||||
if (existingTask) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请单号已存在'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await task.update({
|
||||
...updateData,
|
||||
updatedBy: req.user.id
|
||||
})
|
||||
|
||||
// 获取更新后的任务详情
|
||||
const updatedTask = await InstallationTask.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新待安装任务成功',
|
||||
data: updatedTask
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('更新待安装任务失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新待安装任务失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 删除待安装任务
|
||||
const deleteInstallationTask = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
|
||||
const task = await InstallationTask.findByPk(id)
|
||||
if (!task) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '待安装任务不存在'
|
||||
})
|
||||
}
|
||||
|
||||
await task.destroy()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除待安装任务成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('删除待安装任务失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除待安装任务失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取待安装任务统计信息
|
||||
const getInstallationTaskStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await InstallationTask.findAll({
|
||||
attributes: [
|
||||
'installationStatus',
|
||||
[InstallationTask.sequelize.fn('COUNT', InstallationTask.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['installationStatus'],
|
||||
raw: true
|
||||
})
|
||||
|
||||
const totalCount = await InstallationTask.count()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取待安装任务统计成功',
|
||||
data: {
|
||||
stats,
|
||||
totalCount
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取待安装任务统计失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取待安装任务统计失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 批量更新安装状态
|
||||
const batchUpdateStatus = async (req, res) => {
|
||||
try {
|
||||
const { ids, installationStatus } = req.body
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要更新的任务'
|
||||
})
|
||||
}
|
||||
|
||||
if (!installationStatus) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要更新的状态'
|
||||
})
|
||||
}
|
||||
|
||||
const validStatuses = ['pending', 'in-progress', 'completed', 'failed']
|
||||
if (!validStatuses.includes(installationStatus)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '安装状态值无效'
|
||||
})
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
installationStatus,
|
||||
updatedBy: req.user.id
|
||||
}
|
||||
|
||||
// 如果状态是已完成,设置完成时间
|
||||
if (installationStatus === 'completed') {
|
||||
updateData.completionTime = new Date()
|
||||
}
|
||||
|
||||
await InstallationTask.update(updateData, {
|
||||
where: { id: { [Op.in]: ids } }
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '批量更新安装状态成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('批量更新安装状态失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量更新安装状态失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除待安装任务
|
||||
const batchDelete = async (req, res) => {
|
||||
try {
|
||||
const { ids } = req.body
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要删除的任务'
|
||||
})
|
||||
}
|
||||
|
||||
await InstallationTask.destroy({
|
||||
where: { id: { [Op.in]: ids } }
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '批量删除待安装任务成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('批量删除待安装任务失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量删除待安装任务失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getInstallationTasks,
|
||||
getInstallationTaskById,
|
||||
createInstallationTask,
|
||||
updateInstallationTask,
|
||||
deleteInstallationTask,
|
||||
getInstallationTaskStats,
|
||||
batchUpdateStatus,
|
||||
batchDelete
|
||||
}
|
||||
468
bank-backend/controllers/loanApplicationController.js
Normal file
468
bank-backend/controllers/loanApplicationController.js
Normal file
@@ -0,0 +1,468 @@
|
||||
/**
|
||||
* 贷款申请控制器
|
||||
* @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 === 'applicationNumber') {
|
||||
where.applicationNumber = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'customerName') {
|
||||
where[Op.or] = [
|
||||
{ borrowerName: { [Op.like]: `%${searchValue}%` } },
|
||||
{ farmerName: { [Op.like]: `%${searchValue}%` } }
|
||||
];
|
||||
} else if (searchField === 'productName') {
|
||||
where.productName = { [Op.like]: `%${searchValue}%` };
|
||||
}
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
||||
const limit = parseInt(pageSize);
|
||||
|
||||
// 排序参数
|
||||
const order = [[sortField, sortOrder.toUpperCase()]];
|
||||
|
||||
// 查询数据
|
||||
const { count, rows } = await LoanApplication.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'applicant',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
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']]
|
||||
}
|
||||
],
|
||||
order,
|
||||
offset,
|
||||
limit
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
const applications = rows.map(app => ({
|
||||
id: app.id,
|
||||
applicationNumber: app.applicationNumber,
|
||||
productName: app.productName,
|
||||
farmerName: app.farmerName,
|
||||
borrowerName: app.borrowerName,
|
||||
borrowerIdNumber: app.borrowerIdNumber,
|
||||
assetType: app.assetType,
|
||||
applicationQuantity: app.applicationQuantity,
|
||||
amount: parseFloat(app.amount),
|
||||
status: app.status,
|
||||
type: app.type,
|
||||
term: app.term,
|
||||
interestRate: parseFloat(app.interestRate),
|
||||
phone: app.phone,
|
||||
purpose: app.purpose,
|
||||
remark: app.remark,
|
||||
applicationTime: app.applicationTime,
|
||||
approvedTime: app.approvedTime,
|
||||
rejectedTime: app.rejectedTime,
|
||||
auditRecords: app.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: {
|
||||
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?.id;
|
||||
|
||||
// 获取申请信息
|
||||
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';
|
||||
application.approvedTime = new Date();
|
||||
application.approvedBy = userId;
|
||||
} else if (action === 'reject') {
|
||||
newStatus = 'rejected';
|
||||
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);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
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
|
||||
};
|
||||
466
bank-backend/controllers/loanContractController.js
Normal file
466
bank-backend/controllers/loanContractController.js
Normal file
@@ -0,0 +1,466 @@
|
||||
/**
|
||||
* 贷款合同控制器
|
||||
* @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.contractNumber = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'applicationNumber') {
|
||||
where.applicationNumber = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'borrowerName') {
|
||||
where.borrowerName = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'farmerName') {
|
||||
where.farmerName = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'productName') {
|
||||
where.productName = { [Op.like]: `%${searchValue}%` };
|
||||
}
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
||||
const limit = parseInt(pageSize);
|
||||
|
||||
// 排序参数
|
||||
const order = [[sortField, sortOrder.toUpperCase()]];
|
||||
|
||||
// 查询数据
|
||||
const { count, rows } = await LoanContract.findAndCountAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
],
|
||||
order,
|
||||
offset,
|
||||
limit
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
const contracts = rows.map(contract => ({
|
||||
id: contract.id,
|
||||
contractNumber: contract.contractNumber,
|
||||
applicationNumber: contract.applicationNumber,
|
||||
productName: contract.productName,
|
||||
farmerName: contract.farmerName,
|
||||
borrowerName: contract.borrowerName,
|
||||
borrowerIdNumber: contract.borrowerIdNumber,
|
||||
assetType: contract.assetType,
|
||||
applicationQuantity: contract.applicationQuantity,
|
||||
amount: parseFloat(contract.amount),
|
||||
paidAmount: parseFloat(contract.paidAmount),
|
||||
status: contract.status,
|
||||
type: contract.type,
|
||||
term: contract.term,
|
||||
interestRate: parseFloat(contract.interestRate),
|
||||
phone: contract.phone,
|
||||
purpose: contract.purpose,
|
||||
remark: contract.remark,
|
||||
contractTime: contract.contractTime,
|
||||
disbursementTime: contract.disbursementTime,
|
||||
maturityTime: contract.maturityTime,
|
||||
completedTime: contract.completedTime,
|
||||
remainingAmount: parseFloat(contract.amount - contract.paidAmount),
|
||||
repaymentProgress: contract.getRepaymentProgress(),
|
||||
creator: contract.creator,
|
||||
updater: contract.updater
|
||||
}));
|
||||
|
||||
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, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name', 'email', 'phone']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!contract) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款合同不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化数据
|
||||
const formattedContract = {
|
||||
id: contract.id,
|
||||
contractNumber: contract.contractNumber,
|
||||
applicationNumber: contract.applicationNumber,
|
||||
productName: contract.productName,
|
||||
farmerName: contract.farmerName,
|
||||
borrowerName: contract.borrowerName,
|
||||
borrowerIdNumber: contract.borrowerIdNumber,
|
||||
assetType: contract.assetType,
|
||||
applicationQuantity: contract.applicationQuantity,
|
||||
amount: parseFloat(contract.amount),
|
||||
paidAmount: parseFloat(contract.paidAmount),
|
||||
status: contract.status,
|
||||
type: contract.type,
|
||||
term: contract.term,
|
||||
interestRate: parseFloat(contract.interestRate),
|
||||
phone: contract.phone,
|
||||
purpose: contract.purpose,
|
||||
remark: contract.remark,
|
||||
contractTime: contract.contractTime,
|
||||
disbursementTime: contract.disbursementTime,
|
||||
maturityTime: contract.maturityTime,
|
||||
completedTime: contract.completedTime,
|
||||
remainingAmount: parseFloat(contract.amount - contract.paidAmount),
|
||||
repaymentProgress: contract.getRepaymentProgress(),
|
||||
creator: contract.creator,
|
||||
updater: contract.updater
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: formattedContract
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取贷款合同详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取贷款合同详情失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建贷款合同
|
||||
* @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
|
||||
};
|
||||
@@ -1,26 +1,16 @@
|
||||
/**
|
||||
* 贷款产品控制器
|
||||
* @file loanProductController.js
|
||||
* @description 处理贷款产品相关的请求
|
||||
*/
|
||||
const { LoanProduct } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { LoanProduct, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
/**
|
||||
* 获取贷款产品列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProducts = async (req, res) => {
|
||||
// 获取贷款商品列表
|
||||
const getLoanProducts = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
status = '',
|
||||
type = '',
|
||||
sortBy = 'created_at',
|
||||
onSaleStatus,
|
||||
riskLevel,
|
||||
sortBy = 'createdAt',
|
||||
sortOrder = 'DESC'
|
||||
} = req.query;
|
||||
|
||||
@@ -30,228 +20,320 @@ exports.getLoanProducts = async (req, res) => {
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ name: { [Op.like]: `%${search}%` } },
|
||||
{ code: { [Op.like]: `%${search}%` } },
|
||||
{ description: { [Op.like]: `%${search}%` } }
|
||||
{ productName: { [Op.like]: `%${search}%` } },
|
||||
{ serviceArea: { [Op.like]: `%${search}%` } },
|
||||
{ servicePhone: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
// 在售状态筛选
|
||||
if (onSaleStatus !== undefined) {
|
||||
whereClause.onSaleStatus = onSaleStatus === 'true';
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
if (type) {
|
||||
whereClause.type = type;
|
||||
// 风险等级筛选
|
||||
if (riskLevel) {
|
||||
whereClause.riskLevel = riskLevel;
|
||||
}
|
||||
|
||||
const { count, rows: products } = await LoanProduct.findAndCountAll({
|
||||
const { count, rows } = await LoanProduct.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
],
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(count / limit);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品列表成功',
|
||||
message: '获取贷款商品列表成功',
|
||||
data: {
|
||||
products,
|
||||
products: rows,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
totalPages,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPrevPage: page > 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品列表错误:', error);
|
||||
console.error('获取贷款商品列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
message: '获取贷款商品列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createLoanProduct = async (req, res) => {
|
||||
// 根据ID获取贷款商品详情
|
||||
const getLoanProductById = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await LoanProduct.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
message: '贷款商品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款商品详情成功',
|
||||
data: product
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取贷款商品详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取贷款商品详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 创建贷款商品
|
||||
const createLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
description,
|
||||
min_amount,
|
||||
max_amount,
|
||||
interest_rate,
|
||||
term_min,
|
||||
term_max,
|
||||
requirements,
|
||||
status = 'draft'
|
||||
productName,
|
||||
loanAmount,
|
||||
loanTerm,
|
||||
interestRate,
|
||||
serviceArea,
|
||||
servicePhone,
|
||||
productDescription,
|
||||
applicationRequirements,
|
||||
requiredDocuments,
|
||||
approvalProcess,
|
||||
riskLevel = 'MEDIUM',
|
||||
minLoanAmount,
|
||||
maxLoanAmount
|
||||
} = req.body;
|
||||
|
||||
// 检查产品代码是否已存在
|
||||
// 验证必填字段
|
||||
if (!productName || !loanAmount || !loanTerm || !interestRate || !serviceArea || !servicePhone) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请填写所有必填字段'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证产品名称唯一性
|
||||
const existingProduct = await LoanProduct.findOne({
|
||||
where: { code }
|
||||
where: { productName }
|
||||
});
|
||||
|
||||
if (existingProduct) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '产品代码已存在'
|
||||
message: '贷款产品名称已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证数值字段
|
||||
if (loanTerm <= 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '贷款周期必须大于0'
|
||||
});
|
||||
}
|
||||
|
||||
if (interestRate < 0 || interestRate > 100) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '贷款利率必须在0-100之间'
|
||||
});
|
||||
}
|
||||
|
||||
if (minLoanAmount && maxLoanAmount && minLoanAmount > maxLoanAmount) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '最小贷款金额不能大于最大贷款金额'
|
||||
});
|
||||
}
|
||||
|
||||
const product = await LoanProduct.create({
|
||||
name,
|
||||
code,
|
||||
type,
|
||||
description,
|
||||
min_amount: min_amount * 100, // 转换为分
|
||||
max_amount: max_amount * 100,
|
||||
interest_rate,
|
||||
term_min,
|
||||
term_max,
|
||||
requirements,
|
||||
status
|
||||
productName,
|
||||
loanAmount,
|
||||
loanTerm: parseInt(loanTerm),
|
||||
interestRate: parseFloat(interestRate),
|
||||
serviceArea,
|
||||
servicePhone,
|
||||
productDescription,
|
||||
applicationRequirements,
|
||||
requiredDocuments,
|
||||
approvalProcess,
|
||||
riskLevel,
|
||||
minLoanAmount: minLoanAmount ? parseFloat(minLoanAmount) : null,
|
||||
maxLoanAmount: maxLoanAmount ? parseFloat(maxLoanAmount) : null,
|
||||
createdBy: req.user.id,
|
||||
updatedBy: req.user.id
|
||||
});
|
||||
|
||||
// 获取创建后的完整信息
|
||||
const createdProduct = await LoanProduct.findByPk(product.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建贷款产品成功',
|
||||
data: product
|
||||
message: '创建贷款商品成功',
|
||||
data: createdProduct
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建贷款产品错误:', error);
|
||||
console.error('创建贷款商品失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
message: '创建贷款商品失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取贷款产品详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProductById = async (req, res) => {
|
||||
// 更新贷款商品
|
||||
const updateLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品详情成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateLoanProduct = 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;
|
||||
|
||||
// 如果更新金额,转换为分
|
||||
if (updateData.min_amount) {
|
||||
updateData.min_amount = updateData.min_amount * 100;
|
||||
}
|
||||
if (updateData.max_amount) {
|
||||
updateData.max_amount = updateData.max_amount * 100;
|
||||
}
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
message: '贷款商品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.update(updateData);
|
||||
// 如果更新产品名称,检查唯一性
|
||||
if (updateData.productName && updateData.productName !== product.productName) {
|
||||
const existingProduct = await LoanProduct.findOne({
|
||||
where: {
|
||||
productName: updateData.productName,
|
||||
id: { [Op.ne]: id }
|
||||
}
|
||||
});
|
||||
|
||||
if (existingProduct) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '贷款产品名称已存在'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 验证数值字段
|
||||
if (updateData.loanTerm && updateData.loanTerm <= 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '贷款周期必须大于0'
|
||||
});
|
||||
}
|
||||
|
||||
if (updateData.interestRate && (updateData.interestRate < 0 || updateData.interestRate > 100)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '贷款利率必须在0-100之间'
|
||||
});
|
||||
}
|
||||
|
||||
if (updateData.minLoanAmount && updateData.maxLoanAmount &&
|
||||
updateData.minLoanAmount > updateData.maxLoanAmount) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '最小贷款金额不能大于最大贷款金额'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
Object.keys(updateData).forEach(key => {
|
||||
if (updateData[key] !== undefined) {
|
||||
product[key] = updateData[key];
|
||||
}
|
||||
});
|
||||
|
||||
product.updatedBy = req.user.id;
|
||||
await product.save();
|
||||
|
||||
// 获取更新后的完整信息
|
||||
const updatedProduct = await LoanProduct.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: 'updater',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新贷款产品成功',
|
||||
data: product
|
||||
message: '更新贷款商品成功',
|
||||
data: updatedProduct
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新贷款产品错误:', error);
|
||||
console.error('更新贷款商品失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
message: '更新贷款商品失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除贷款产品
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteLoanProduct = async (req, res) => {
|
||||
// 删除贷款商品
|
||||
const deleteLoanProduct = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
message: '贷款商品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -259,105 +341,140 @@ exports.deleteLoanProduct = async (req, res) => {
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除贷款产品成功'
|
||||
message: '删除贷款商品成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除贷款产品错误:', error);
|
||||
console.error('删除贷款商品失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
message: '删除贷款商品失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新贷款产品状态
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.updateLoanProductStatus = async (req, res) => {
|
||||
// 获取贷款商品统计信息
|
||||
const getLoanProductStats = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
const totalProducts = await LoanProduct.count();
|
||||
const onSaleProducts = await LoanProduct.count({ where: { onSaleStatus: true } });
|
||||
const offSaleProducts = await LoanProduct.count({ where: { onSaleStatus: false } });
|
||||
|
||||
const riskLevelStats = await LoanProduct.findAll({
|
||||
attributes: [
|
||||
'riskLevel',
|
||||
[LoanProduct.sequelize.fn('COUNT', LoanProduct.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['riskLevel']
|
||||
});
|
||||
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
const product = await LoanProduct.findByPk(id);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款产品不存在'
|
||||
});
|
||||
}
|
||||
|
||||
await product.update({ status });
|
||||
const totalCustomers = await LoanProduct.sum('totalCustomers');
|
||||
const supervisionCustomers = await LoanProduct.sum('supervisionCustomers');
|
||||
const completedCustomers = await LoanProduct.sum('completedCustomers');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新贷款产品状态成功',
|
||||
data: product
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新贷款产品状态错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取贷款产品统计
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getLoanProductStats = async (req, res) => {
|
||||
try {
|
||||
const stats = await LoanProduct.findAll({
|
||||
attributes: [
|
||||
'status',
|
||||
[LoanProduct.sequelize.fn('COUNT', LoanProduct.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['status'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
const typeStats = await LoanProduct.findAll({
|
||||
attributes: [
|
||||
'type',
|
||||
[LoanProduct.sequelize.fn('COUNT', LoanProduct.sequelize.col('id')), 'count']
|
||||
],
|
||||
group: ['type'],
|
||||
raw: true
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取贷款产品统计成功',
|
||||
message: '获取贷款商品统计成功',
|
||||
data: {
|
||||
statusStats: stats,
|
||||
typeStats: typeStats
|
||||
totalProducts,
|
||||
onSaleProducts,
|
||||
offSaleProducts,
|
||||
riskLevelStats,
|
||||
totalCustomers: totalCustomers || 0,
|
||||
supervisionCustomers: supervisionCustomers || 0,
|
||||
completedCustomers: completedCustomers || 0
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款产品统计错误:', error);
|
||||
console.error('获取贷款商品统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
message: '获取贷款商品统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 批量更新在售状态
|
||||
const batchUpdateStatus = async (req, res) => {
|
||||
try {
|
||||
const { ids, onSaleStatus } = req.body;
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要更新的贷款商品'
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof onSaleStatus !== 'boolean') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '在售状态参数无效'
|
||||
});
|
||||
}
|
||||
|
||||
await LoanProduct.update(
|
||||
{
|
||||
onSaleStatus,
|
||||
updatedBy: req.user.id
|
||||
},
|
||||
{
|
||||
where: { id: { [Op.in]: ids } }
|
||||
}
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `批量${onSaleStatus ? '启用' : '停用'}贷款商品成功`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('批量更新状态失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量更新状态失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 批量删除贷款商品
|
||||
const batchDelete = async (req, res) => {
|
||||
try {
|
||||
const { ids } = req.body;
|
||||
|
||||
if (!Array.isArray(ids) || ids.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '请选择要删除的贷款商品'
|
||||
});
|
||||
}
|
||||
|
||||
await LoanProduct.destroy({
|
||||
where: { id: { [Op.in]: ids } }
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '批量删除贷款商品成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('批量删除失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量删除失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getLoanProducts,
|
||||
getLoanProductById,
|
||||
createLoanProduct,
|
||||
updateLoanProduct,
|
||||
deleteLoanProduct,
|
||||
getLoanProductStats,
|
||||
batchUpdateStatus,
|
||||
batchDelete
|
||||
};
|
||||
55
bank-backend/create-admin-user.js
Normal file
55
bank-backend/create-admin-user.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const { User } = require('./models')
|
||||
const bcrypt = require('bcryptjs')
|
||||
|
||||
async function createAdminUser() {
|
||||
try {
|
||||
console.log('开始创建管理员用户...')
|
||||
|
||||
// 检查是否已存在管理员用户
|
||||
const existingAdmin = await User.findOne({
|
||||
where: { username: 'admin' }
|
||||
})
|
||||
|
||||
if (existingAdmin) {
|
||||
console.log('管理员用户已存在,更新密码...')
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10)
|
||||
await existingAdmin.update({
|
||||
password: hashedPassword,
|
||||
status: 'active'
|
||||
})
|
||||
console.log('✅ 管理员用户密码已更新')
|
||||
} else {
|
||||
console.log('创建新的管理员用户...')
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10)
|
||||
|
||||
await User.create({
|
||||
username: 'admin',
|
||||
password: hashedPassword,
|
||||
real_name: '系统管理员',
|
||||
email: 'admin@bank.com',
|
||||
phone: '13800138000',
|
||||
status: 'active',
|
||||
role_id: 1
|
||||
})
|
||||
console.log('✅ 管理员用户创建成功')
|
||||
}
|
||||
|
||||
console.log('管理员用户信息:')
|
||||
console.log('用户名: admin')
|
||||
console.log('密码: admin123')
|
||||
console.log('状态: active')
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建管理员用户失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
createAdminUser()
|
||||
.then(() => {
|
||||
console.log('管理员用户设置完成')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('脚本执行失败:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
131
bank-backend/debug-auth-detailed.js
Normal file
131
bank-backend/debug-auth-detailed.js
Normal file
@@ -0,0 +1,131 @@
|
||||
const { User, Role } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function debugAuthDetailed() {
|
||||
try {
|
||||
console.log('=== 详细调试认证逻辑 ===\n');
|
||||
|
||||
// 1. 检查数据库连接
|
||||
console.log('1. 检查数据库连接...');
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
console.log('✅ 数据库连接正常,找到admin用户\n');
|
||||
|
||||
// 2. 检查用户基本信息
|
||||
console.log('2. 检查用户基本信息...');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('状态:', user.status);
|
||||
console.log('登录尝试次数:', user.login_attempts);
|
||||
console.log('锁定时间:', user.locked_until);
|
||||
console.log('密码哈希:', user.password);
|
||||
console.log('');
|
||||
|
||||
// 3. 检查用户角色关联
|
||||
console.log('3. 检查用户角色关联...');
|
||||
const userWithRole = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (userWithRole) {
|
||||
console.log('✅ 用户角色关联正常');
|
||||
console.log('角色:', userWithRole.role ? userWithRole.role.name : '无角色');
|
||||
} else {
|
||||
console.log('❌ 用户角色关联失败');
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// 4. 测试密码验证
|
||||
console.log('4. 测试密码验证...');
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 直接使用bcrypt比较
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证:', directTest);
|
||||
|
||||
// 使用模型方法验证
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证:', modelTest);
|
||||
|
||||
if (!directTest) {
|
||||
console.log('❌ 密码不匹配,重新生成密码...');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新哈希:', newHash);
|
||||
|
||||
await user.update({
|
||||
password: newHash,
|
||||
status: 'active',
|
||||
login_attempts: 0,
|
||||
locked_until: null
|
||||
});
|
||||
|
||||
console.log('✅ 密码已更新');
|
||||
|
||||
// 重新加载用户数据
|
||||
await user.reload();
|
||||
|
||||
// 再次验证
|
||||
const finalTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('最终验证:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
} else {
|
||||
console.log('❌ 密码修复失败');
|
||||
}
|
||||
} else {
|
||||
console.log('✅ 密码验证成功');
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// 5. 模拟完整的登录流程
|
||||
console.log('5. 模拟完整的登录流程...');
|
||||
const loginUser = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (loginUser) {
|
||||
console.log('✅ 用户查找成功');
|
||||
console.log('用户状态:', loginUser.status);
|
||||
|
||||
if (loginUser.status !== 'active') {
|
||||
console.log('❌ 用户状态不是active:', loginUser.status);
|
||||
} else {
|
||||
console.log('✅ 用户状态正常');
|
||||
}
|
||||
|
||||
const passwordValid = await loginUser.validPassword(testPassword);
|
||||
console.log('密码验证结果:', passwordValid);
|
||||
|
||||
if (passwordValid) {
|
||||
console.log('🎉 完整登录流程验证成功!');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
console.log('状态: active');
|
||||
} else {
|
||||
console.log('❌ 密码验证失败');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 用户查找失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('调试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugAuthDetailed();
|
||||
73
bank-backend/debug-login.js
Normal file
73
bank-backend/debug-login.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function debugLogin() {
|
||||
try {
|
||||
console.log('=== 调试登录问题 ===');
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ 找到admin用户');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('状态:', user.status);
|
||||
console.log('登录尝试次数:', user.login_attempts);
|
||||
console.log('锁定时间:', user.locked_until);
|
||||
console.log('密码哈希:', user.password);
|
||||
|
||||
// 测试密码验证
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('\n=== 测试密码验证 ===');
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 直接使用bcrypt比较
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证:', directTest);
|
||||
|
||||
// 使用模型方法验证
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证:', modelTest);
|
||||
|
||||
if (directTest && modelTest) {
|
||||
console.log('✅ 密码验证成功!');
|
||||
} else {
|
||||
console.log('❌ 密码验证失败');
|
||||
|
||||
// 重新生成密码
|
||||
console.log('\n=== 重新生成密码 ===');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新哈希:', newHash);
|
||||
|
||||
await user.update({
|
||||
password: newHash,
|
||||
status: 'active',
|
||||
login_attempts: 0,
|
||||
locked_until: null
|
||||
});
|
||||
|
||||
console.log('✅ 密码已更新');
|
||||
|
||||
// 再次验证
|
||||
const finalTest = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('最终验证:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('调试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugLogin();
|
||||
83
bank-backend/debug-this-password.js
Normal file
83
bank-backend/debug-this-password.js
Normal file
@@ -0,0 +1,83 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function debugThisPassword() {
|
||||
try {
|
||||
console.log('=== 调试this.password问题 ===\n');
|
||||
|
||||
// 1. 获取用户
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('1. 用户基本信息:');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('this.password类型:', typeof user.password);
|
||||
console.log('this.password值:', user.password);
|
||||
console.log('this.password长度:', user.password ? user.password.length : 0);
|
||||
console.log('');
|
||||
|
||||
// 2. 测试密码
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('2. 测试密码:', testPassword);
|
||||
console.log('');
|
||||
|
||||
// 3. 直接使用bcrypt比较
|
||||
console.log('3. 直接使用bcrypt比较:');
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证结果:', directTest);
|
||||
console.log('');
|
||||
|
||||
// 4. 检查this.password是否为空或undefined
|
||||
console.log('4. 检查this.password状态:');
|
||||
console.log('this.password === null:', user.password === null);
|
||||
console.log('this.password === undefined:', user.password === undefined);
|
||||
console.log('this.password === "":', user.password === "");
|
||||
console.log('this.password存在:', !!user.password);
|
||||
console.log('');
|
||||
|
||||
// 5. 如果this.password有问题,重新设置
|
||||
if (!user.password || user.password === '') {
|
||||
console.log('5. this.password有问题,重新设置...');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新生成的哈希:', newHash);
|
||||
|
||||
await user.update({ password: newHash });
|
||||
await user.reload();
|
||||
|
||||
console.log('更新后的this.password:', user.password);
|
||||
console.log('更新后的this.password类型:', typeof user.password);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// 6. 再次测试
|
||||
console.log('6. 再次测试密码验证:');
|
||||
const finalTest = await user.validPassword(testPassword);
|
||||
console.log('最终验证结果:', finalTest);
|
||||
|
||||
// 7. 手动测试bcrypt
|
||||
console.log('7. 手动测试bcrypt:');
|
||||
const manualTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('手动bcrypt验证结果:', manualTest);
|
||||
|
||||
if (finalTest && manualTest) {
|
||||
console.log('🎉 密码验证成功!');
|
||||
} else {
|
||||
console.log('❌ 密码验证失败');
|
||||
console.log('可能的原因:');
|
||||
console.log('- this.password为空或undefined');
|
||||
console.log('- 数据库更新失败');
|
||||
console.log('- Sequelize模型问题');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('调试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
debugThisPassword();
|
||||
82
bank-backend/fix-password-final.js
Normal file
82
bank-backend/fix-password-final.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const { User } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function fixPasswordFinal() {
|
||||
try {
|
||||
console.log('=== 最终修复密码问题 ===\n');
|
||||
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('目标密码:', testPassword);
|
||||
|
||||
// 1. 生成新的密码哈希
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('1. 新生成的哈希:', newHash);
|
||||
console.log('哈希长度:', newHash.length);
|
||||
|
||||
// 2. 验证新生成的哈希
|
||||
const hashValid = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('2. 新哈希验证:', hashValid);
|
||||
|
||||
if (!hashValid) {
|
||||
console.log('❌ 生成的哈希无效,退出');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 直接使用SQL更新密码
|
||||
console.log('3. 直接使用SQL更新密码...');
|
||||
const [affectedRows] = await sequelize.query(
|
||||
'UPDATE bank_users SET password = ?, status = ?, login_attempts = 0, locked_until = NULL WHERE username = ?',
|
||||
{
|
||||
replacements: [newHash, 'active', 'admin'],
|
||||
type: sequelize.QueryTypes.UPDATE
|
||||
}
|
||||
);
|
||||
|
||||
console.log('SQL更新影响行数:', affectedRows);
|
||||
|
||||
// 4. 验证数据库更新
|
||||
console.log('4. 验证数据库更新...');
|
||||
const [results] = await sequelize.query(
|
||||
'SELECT username, password, status, login_attempts FROM bank_users WHERE username = ?',
|
||||
{
|
||||
replacements: ['admin'],
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
if (results) {
|
||||
console.log('数据库中的数据:');
|
||||
console.log('用户名:', results.username);
|
||||
console.log('状态:', results.status);
|
||||
console.log('登录尝试次数:', results.login_attempts);
|
||||
console.log('密码哈希:', results.password);
|
||||
console.log('哈希长度:', results.password.length);
|
||||
|
||||
// 5. 验证更新后的密码
|
||||
const dbValid = await bcrypt.compare(testPassword, results.password);
|
||||
console.log('5. 数据库密码验证:', dbValid);
|
||||
|
||||
if (dbValid) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
console.log('\n=== 登录信息 ===');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
console.log('状态: active');
|
||||
console.log('现在可以尝试登录了!');
|
||||
} else {
|
||||
console.log('❌ 密码验证仍然失败');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 查询数据库失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('修复失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
fixPasswordFinal();
|
||||
@@ -0,0 +1,133 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('installation_tasks', {
|
||||
id: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
contractNumber: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '放款合同编号'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
},
|
||||
customerName: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: Sequelize.DataTypes.ENUM('ID_CARD', 'PASSPORT', 'OTHER'),
|
||||
allowNull: false,
|
||||
defaultValue: 'ID_CARD',
|
||||
comment: '证件类型'
|
||||
},
|
||||
idNumber: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '证件号码'
|
||||
},
|
||||
assetType: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '养殖生资种类'
|
||||
},
|
||||
equipmentToInstall: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '待安装设备'
|
||||
},
|
||||
installationStatus: {
|
||||
type: Sequelize.DataTypes.ENUM('pending', 'in-progress', 'completed', 'failed'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '安装状态'
|
||||
},
|
||||
taskGenerationTime: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '生成安装任务时间'
|
||||
},
|
||||
completionTime: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '安装完成生效时间'
|
||||
},
|
||||
installationNotes: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '安装备注'
|
||||
},
|
||||
installerName: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '安装员姓名'
|
||||
},
|
||||
installerPhone: {
|
||||
type: Sequelize.DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '安装员电话'
|
||||
},
|
||||
installationAddress: {
|
||||
type: Sequelize.DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '安装地址'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
comment: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
comment: '待安装任务表'
|
||||
})
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('installation_tasks', ['contractNumber'], {
|
||||
name: 'idx_installation_tasks_contract_number'
|
||||
})
|
||||
|
||||
await queryInterface.addIndex('installation_tasks', ['installationStatus'], {
|
||||
name: 'idx_installation_tasks_status'
|
||||
})
|
||||
|
||||
await queryInterface.addIndex('installation_tasks', ['createdBy'], {
|
||||
name: 'idx_installation_tasks_created_by'
|
||||
})
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('installation_tasks')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('completed_supervisions', {
|
||||
id: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
contractNumber: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '放款合同编号'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
},
|
||||
customerName: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: Sequelize.DataTypes.ENUM('ID_CARD', 'PASSPORT', 'OTHER'),
|
||||
allowNull: false,
|
||||
defaultValue: 'ID_CARD',
|
||||
comment: '证件类型'
|
||||
},
|
||||
idNumber: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '证件号码'
|
||||
},
|
||||
assetType: {
|
||||
type: Sequelize.DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '养殖生资种类'
|
||||
},
|
||||
assetQuantity: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管生资数量'
|
||||
},
|
||||
totalRepaymentPeriods: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '总还款期数'
|
||||
},
|
||||
settlementStatus: {
|
||||
type: Sequelize.DataTypes.ENUM('settled', 'unsettled', 'partial'),
|
||||
allowNull: false,
|
||||
defaultValue: 'unsettled',
|
||||
comment: '结清状态'
|
||||
},
|
||||
settlementDate: {
|
||||
type: Sequelize.DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
comment: '结清日期'
|
||||
},
|
||||
importTime: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '结清任务导入时间'
|
||||
},
|
||||
settlementAmount: {
|
||||
type: Sequelize.DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '结清金额'
|
||||
},
|
||||
remainingAmount: {
|
||||
type: Sequelize.DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '剩余金额'
|
||||
},
|
||||
settlementNotes: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '结清备注'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
comment: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
comment: '监管任务已结项表'
|
||||
})
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('completed_supervisions', ['contractNumber'], {
|
||||
name: 'idx_completed_supervisions_contract_number'
|
||||
})
|
||||
|
||||
await queryInterface.addIndex('completed_supervisions', ['settlementStatus'], {
|
||||
name: 'idx_completed_supervisions_settlement_status'
|
||||
})
|
||||
|
||||
await queryInterface.addIndex('completed_supervisions', ['createdBy'], {
|
||||
name: 'idx_completed_supervisions_created_by'
|
||||
})
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('completed_supervisions')
|
||||
}
|
||||
}
|
||||
147
bank-backend/migrations/20241220000006-create-loan-products.js
Normal file
147
bank-backend/migrations/20241220000006-create-loan-products.js
Normal file
@@ -0,0 +1,147 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('loan_products', {
|
||||
id: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
loanAmount: {
|
||||
type: Sequelize.DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '贷款额度'
|
||||
},
|
||||
loanTerm: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '贷款周期(月)'
|
||||
},
|
||||
interestRate: {
|
||||
type: Sequelize.DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '贷款利率(%)'
|
||||
},
|
||||
serviceArea: {
|
||||
type: Sequelize.DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '服务区域'
|
||||
},
|
||||
servicePhone: {
|
||||
type: Sequelize.DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '服务电话'
|
||||
},
|
||||
totalCustomers: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '服务客户总数量'
|
||||
},
|
||||
supervisionCustomers: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管中客户数量'
|
||||
},
|
||||
completedCustomers: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '已结项客户数量'
|
||||
},
|
||||
onSaleStatus: {
|
||||
type: Sequelize.DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '在售状态'
|
||||
},
|
||||
productDescription: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '产品描述'
|
||||
},
|
||||
applicationRequirements: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '申请条件'
|
||||
},
|
||||
requiredDocuments: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '所需材料'
|
||||
},
|
||||
approvalProcess: {
|
||||
type: Sequelize.DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审批流程'
|
||||
},
|
||||
riskLevel: {
|
||||
type: Sequelize.DataTypes.ENUM('LOW', 'MEDIUM', 'HIGH'),
|
||||
allowNull: false,
|
||||
defaultValue: 'MEDIUM',
|
||||
comment: '风险等级'
|
||||
},
|
||||
minLoanAmount: {
|
||||
type: Sequelize.DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '最小贷款金额'
|
||||
},
|
||||
maxLoanAmount: {
|
||||
type: Sequelize.DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '最大贷款金额'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||
comment: '创建时间'
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'),
|
||||
comment: '更新时间'
|
||||
}
|
||||
}, {
|
||||
comment: '贷款商品表',
|
||||
charset: 'utf8mb4',
|
||||
collate: 'utf8mb4_unicode_ci'
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('loan_products', ['productName'], {
|
||||
name: 'idx_loan_products_product_name'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('loan_products', ['onSaleStatus'], {
|
||||
name: 'idx_loan_products_on_sale_status'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('loan_products', ['createdBy'], {
|
||||
name: 'idx_loan_products_created_by'
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('loan_products');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 创建贷款申请表迁移
|
||||
* @file 20241220000007-create-loan-applications.js
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('bank_loan_applications', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
applicationNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
farmerName: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请养殖户姓名'
|
||||
},
|
||||
borrowerName: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '贷款人姓名'
|
||||
},
|
||||
borrowerIdNumber: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '贷款人身份证号'
|
||||
},
|
||||
assetType: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '生资种类'
|
||||
},
|
||||
applicationQuantity: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请数量'
|
||||
},
|
||||
amount: {
|
||||
type: Sequelize.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '申请额度'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM(
|
||||
'pending_review',
|
||||
'verification_pending',
|
||||
'pending_binding',
|
||||
'approved',
|
||||
'rejected'
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending_review',
|
||||
comment: '申请状态'
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM('personal', 'business', 'mortgage'),
|
||||
allowNull: false,
|
||||
defaultValue: 'personal',
|
||||
comment: '申请类型'
|
||||
},
|
||||
term: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '申请期限(月)'
|
||||
},
|
||||
interestRate: {
|
||||
type: Sequelize.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '预计利率'
|
||||
},
|
||||
phone: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
},
|
||||
purpose: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '申请用途'
|
||||
},
|
||||
remark: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
applicationTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '申请时间'
|
||||
},
|
||||
approvedTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '审批通过时间'
|
||||
},
|
||||
rejectedTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '审批拒绝时间'
|
||||
},
|
||||
applicantId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '申请人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
approvedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '审批人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
rejectedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '拒绝人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
rejectionReason: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '拒绝原因'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('bank_loan_applications', ['applicationNumber']);
|
||||
await queryInterface.addIndex('bank_loan_applications', ['status']);
|
||||
await queryInterface.addIndex('bank_loan_applications', ['borrowerName']);
|
||||
await queryInterface.addIndex('bank_loan_applications', ['farmerName']);
|
||||
await queryInterface.addIndex('bank_loan_applications', ['applicationTime']);
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('bank_loan_applications');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 创建审核记录表迁移
|
||||
* @file 20241220000008-create-audit-records.js
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('bank_audit_records', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
applicationId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '申请ID',
|
||||
references: {
|
||||
model: 'bank_loan_applications',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
action: {
|
||||
type: Sequelize.ENUM(
|
||||
'submit',
|
||||
'approve',
|
||||
'reject',
|
||||
'review',
|
||||
'verification',
|
||||
'binding'
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '审核动作'
|
||||
},
|
||||
auditor: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '审核人'
|
||||
},
|
||||
auditorId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '审核人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
comment: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审核意见'
|
||||
},
|
||||
auditTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '审核时间'
|
||||
},
|
||||
previousStatus: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '审核前状态'
|
||||
},
|
||||
newStatus: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '审核后状态'
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('bank_audit_records', ['applicationId']);
|
||||
await queryInterface.addIndex('bank_audit_records', ['action']);
|
||||
await queryInterface.addIndex('bank_audit_records', ['auditorId']);
|
||||
await queryInterface.addIndex('bank_audit_records', ['auditTime']);
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('bank_audit_records');
|
||||
}
|
||||
};
|
||||
177
bank-backend/migrations/20241220000009-create-loan-contracts.js
Normal file
177
bank-backend/migrations/20241220000009-create-loan-contracts.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 创建贷款合同表迁移
|
||||
* @file 20241220000009-create-loan-contracts.js
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.createTable('bank_loan_contracts', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
contractNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '合同编号'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '申请单号'
|
||||
},
|
||||
productName: {
|
||||
type: Sequelize.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
farmerName: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请养殖户姓名'
|
||||
},
|
||||
borrowerName: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '贷款人姓名'
|
||||
},
|
||||
borrowerIdNumber: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '贷款人身份证号'
|
||||
},
|
||||
assetType: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '生资种类'
|
||||
},
|
||||
applicationQuantity: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请数量'
|
||||
},
|
||||
amount: {
|
||||
type: Sequelize.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '合同金额'
|
||||
},
|
||||
paidAmount: {
|
||||
type: Sequelize.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '已还款金额'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM(
|
||||
'active',
|
||||
'pending',
|
||||
'completed',
|
||||
'defaulted',
|
||||
'cancelled'
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '合同状态'
|
||||
},
|
||||
type: {
|
||||
type: Sequelize.ENUM(
|
||||
'livestock_collateral',
|
||||
'farmer_loan',
|
||||
'business_loan',
|
||||
'personal_loan'
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '合同类型'
|
||||
},
|
||||
term: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '合同期限(月)'
|
||||
},
|
||||
interestRate: {
|
||||
type: Sequelize.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '利率'
|
||||
},
|
||||
phone: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
},
|
||||
purpose: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '贷款用途'
|
||||
},
|
||||
remark: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
contractTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW,
|
||||
comment: '合同签订时间'
|
||||
},
|
||||
disbursementTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '放款时间'
|
||||
},
|
||||
maturityTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '到期时间'
|
||||
},
|
||||
completedTime: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '完成时间'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: Sequelize.NOW
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('bank_loan_contracts', ['contractNumber']);
|
||||
await queryInterface.addIndex('bank_loan_contracts', ['applicationNumber']);
|
||||
await queryInterface.addIndex('bank_loan_contracts', ['status']);
|
||||
await queryInterface.addIndex('bank_loan_contracts', ['borrowerName']);
|
||||
await queryInterface.addIndex('bank_loan_contracts', ['farmerName']);
|
||||
await queryInterface.addIndex('bank_loan_contracts', ['contractTime']);
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.dropTable('bank_loan_contracts');
|
||||
}
|
||||
};
|
||||
111
bank-backend/models/AuditRecord.js
Normal file
111
bank-backend/models/AuditRecord.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 审核记录模型
|
||||
* @file AuditRecord.js
|
||||
* @description 银行系统贷款申请审核记录数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class AuditRecord extends BaseModel {
|
||||
/**
|
||||
* 获取审核动作文本
|
||||
* @returns {String} 动作文本
|
||||
*/
|
||||
getActionText() {
|
||||
const actionMap = {
|
||||
submit: '提交申请',
|
||||
approve: '审核通过',
|
||||
reject: '审核拒绝',
|
||||
review: '初审',
|
||||
verification: '核验',
|
||||
binding: '绑定'
|
||||
};
|
||||
return actionMap[this.action] || this.action;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审核动作颜色
|
||||
* @returns {String} 颜色
|
||||
*/
|
||||
getActionColor() {
|
||||
const colorMap = {
|
||||
submit: 'blue',
|
||||
approve: 'green',
|
||||
reject: 'red',
|
||||
review: 'orange',
|
||||
verification: 'purple',
|
||||
binding: 'cyan'
|
||||
};
|
||||
return colorMap[this.action] || 'default';
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化AuditRecord模型
|
||||
AuditRecord.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
applicationId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '申请ID',
|
||||
references: {
|
||||
model: 'bank_loan_applications',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
action: {
|
||||
type: DataTypes.ENUM(
|
||||
'submit',
|
||||
'approve',
|
||||
'reject',
|
||||
'review',
|
||||
'verification',
|
||||
'binding'
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '审核动作'
|
||||
},
|
||||
auditor: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '审核人'
|
||||
},
|
||||
auditorId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '审核人ID'
|
||||
},
|
||||
comment: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审核意见'
|
||||
},
|
||||
auditTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '审核时间'
|
||||
},
|
||||
previousStatus: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '审核前状态'
|
||||
},
|
||||
newStatus: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '审核后状态'
|
||||
}
|
||||
}, {
|
||||
sequelize: require('../config/database').sequelize,
|
||||
modelName: 'AuditRecord',
|
||||
tableName: 'bank_audit_records',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
});
|
||||
|
||||
module.exports = AuditRecord;
|
||||
109
bank-backend/models/CompletedSupervision.js
Normal file
109
bank-backend/models/CompletedSupervision.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const CompletedSupervision = sequelize.define('CompletedSupervision', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
contractNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '放款合同编号'
|
||||
},
|
||||
productName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
},
|
||||
customerName: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: DataTypes.ENUM('ID_CARD', 'PASSPORT', 'OTHER'),
|
||||
allowNull: false,
|
||||
defaultValue: 'ID_CARD',
|
||||
comment: '证件类型'
|
||||
},
|
||||
idNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '证件号码'
|
||||
},
|
||||
assetType: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '养殖生资种类'
|
||||
},
|
||||
assetQuantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管生资数量'
|
||||
},
|
||||
totalRepaymentPeriods: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '总还款期数'
|
||||
},
|
||||
settlementStatus: {
|
||||
type: DataTypes.ENUM('settled', 'unsettled', 'partial'),
|
||||
allowNull: false,
|
||||
defaultValue: 'unsettled',
|
||||
comment: '结清状态'
|
||||
},
|
||||
settlementDate: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: true,
|
||||
comment: '结清日期'
|
||||
},
|
||||
importTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '结清任务导入时间'
|
||||
},
|
||||
settlementAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '结清金额'
|
||||
},
|
||||
remainingAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '剩余金额'
|
||||
},
|
||||
settlementNotes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '结清备注'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'completed_supervisions',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '监管任务已结项表'
|
||||
});
|
||||
|
||||
module.exports = CompletedSupervision;
|
||||
107
bank-backend/models/InstallationTask.js
Normal file
107
bank-backend/models/InstallationTask.js
Normal file
@@ -0,0 +1,107 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const InstallationTask = sequelize.define('InstallationTask', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
contractNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '放款合同编号'
|
||||
},
|
||||
productName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
},
|
||||
customerName: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '客户姓名'
|
||||
},
|
||||
idType: {
|
||||
type: DataTypes.ENUM('ID_CARD', 'PASSPORT', 'OTHER'),
|
||||
allowNull: false,
|
||||
defaultValue: 'ID_CARD',
|
||||
comment: '证件类型'
|
||||
},
|
||||
idNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '证件号码'
|
||||
},
|
||||
assetType: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '养殖生资种类'
|
||||
},
|
||||
equipmentToInstall: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '待安装设备'
|
||||
},
|
||||
installationStatus: {
|
||||
type: DataTypes.ENUM('pending', 'in-progress', 'completed', 'failed'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '安装状态'
|
||||
},
|
||||
taskGenerationTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '生成安装任务时间'
|
||||
},
|
||||
completionTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '安装完成生效时间'
|
||||
},
|
||||
installationNotes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '安装备注'
|
||||
},
|
||||
installerName: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '安装员姓名'
|
||||
},
|
||||
installerPhone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '安装员电话'
|
||||
},
|
||||
installationAddress: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '安装地址'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'installation_tasks',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '待安装任务表'
|
||||
});
|
||||
|
||||
module.exports = InstallationTask;
|
||||
194
bank-backend/models/LoanApplication.js
Normal file
194
bank-backend/models/LoanApplication.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* 贷款申请模型
|
||||
* @file LoanApplication.js
|
||||
* @description 银行系统贷款申请数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class LoanApplication extends BaseModel {
|
||||
/**
|
||||
* 获取申请状态文本
|
||||
* @returns {String} 状态文本
|
||||
*/
|
||||
getStatusText() {
|
||||
const statusMap = {
|
||||
pending_review: '待初审',
|
||||
verification_pending: '核验待放款',
|
||||
pending_binding: '待绑定',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝'
|
||||
};
|
||||
return statusMap[this.status] || this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取申请类型文本
|
||||
* @returns {String} 类型文本
|
||||
*/
|
||||
getTypeText() {
|
||||
const typeMap = {
|
||||
personal: '个人贷款',
|
||||
business: '企业贷款',
|
||||
mortgage: '抵押贷款'
|
||||
};
|
||||
return typeMap[this.type] || this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化申请金额
|
||||
* @returns {String} 格式化后的金额
|
||||
*/
|
||||
getFormattedAmount() {
|
||||
return `${this.amount.toFixed(2)}元`;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化LoanApplication模型
|
||||
LoanApplication.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
applicationNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
productName: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
farmerName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请养殖户姓名'
|
||||
},
|
||||
borrowerName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '贷款人姓名'
|
||||
},
|
||||
borrowerIdNumber: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '贷款人身份证号'
|
||||
},
|
||||
assetType: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '生资种类'
|
||||
},
|
||||
applicationQuantity: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请数量'
|
||||
},
|
||||
amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '申请额度'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM(
|
||||
'pending_review',
|
||||
'verification_pending',
|
||||
'pending_binding',
|
||||
'approved',
|
||||
'rejected'
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending_review',
|
||||
comment: '申请状态'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('personal', 'business', 'mortgage'),
|
||||
allowNull: false,
|
||||
defaultValue: 'personal',
|
||||
comment: '申请类型'
|
||||
},
|
||||
term: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '申请期限(月)'
|
||||
},
|
||||
interestRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '预计利率'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
},
|
||||
purpose: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '申请用途'
|
||||
},
|
||||
remark: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
applicationTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '申请时间'
|
||||
},
|
||||
approvedTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '审批通过时间'
|
||||
},
|
||||
rejectedTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '审批拒绝时间'
|
||||
},
|
||||
approvedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '审批人ID'
|
||||
},
|
||||
rejectedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '拒绝人ID'
|
||||
},
|
||||
rejectionReason: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '拒绝原因'
|
||||
}
|
||||
}, {
|
||||
sequelize: require('../config/database').sequelize,
|
||||
modelName: 'LoanApplication',
|
||||
tableName: 'bank_loan_applications',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
hooks: {
|
||||
beforeCreate: (application) => {
|
||||
// 生成申请单号
|
||||
if (!application.applicationNumber) {
|
||||
const now = new Date();
|
||||
const timestamp = now.getFullYear().toString() +
|
||||
(now.getMonth() + 1).toString().padStart(2, '0') +
|
||||
now.getDate().toString().padStart(2, '0') +
|
||||
now.getHours().toString().padStart(2, '0') +
|
||||
now.getMinutes().toString().padStart(2, '0') +
|
||||
now.getSeconds().toString().padStart(2, '0');
|
||||
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
|
||||
application.applicationNumber = timestamp + random;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = LoanApplication;
|
||||
235
bank-backend/models/LoanContract.js
Normal file
235
bank-backend/models/LoanContract.js
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 贷款合同模型
|
||||
* @file LoanContract.js
|
||||
* @description 银行系统贷款合同数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const BaseModel = require('./BaseModel');
|
||||
|
||||
class LoanContract extends BaseModel {
|
||||
/**
|
||||
* 获取合同状态文本
|
||||
* @returns {String} 状态文本
|
||||
*/
|
||||
getStatusText() {
|
||||
const statusMap = {
|
||||
active: '已放款',
|
||||
pending: '待放款',
|
||||
completed: '已完成',
|
||||
defaulted: '违约',
|
||||
cancelled: '已取消'
|
||||
};
|
||||
return statusMap[this.status] || this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取合同类型文本
|
||||
* @returns {String} 类型文本
|
||||
*/
|
||||
getTypeText() {
|
||||
const typeMap = {
|
||||
livestock_collateral: '畜禽活体抵押',
|
||||
farmer_loan: '惠农贷',
|
||||
business_loan: '商业贷款',
|
||||
personal_loan: '个人贷款'
|
||||
};
|
||||
return typeMap[this.type] || this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化合同金额
|
||||
* @returns {String} 格式化后的金额
|
||||
*/
|
||||
getFormattedAmount() {
|
||||
return `${this.amount.toFixed(2)}元`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算剩余还款金额
|
||||
* @returns {Number} 剩余金额
|
||||
*/
|
||||
getRemainingAmount() {
|
||||
return this.amount - (this.paidAmount || 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算还款进度百分比
|
||||
* @returns {Number} 进度百分比
|
||||
*/
|
||||
getRepaymentProgress() {
|
||||
if (this.amount <= 0) return 0;
|
||||
return Math.round(((this.paidAmount || 0) / this.amount) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化LoanContract模型
|
||||
LoanContract.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
contractNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '合同编号'
|
||||
},
|
||||
applicationNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '申请单号'
|
||||
},
|
||||
productName: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
farmerName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请养殖户姓名'
|
||||
},
|
||||
borrowerName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '贷款人姓名'
|
||||
},
|
||||
borrowerIdNumber: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '贷款人身份证号'
|
||||
},
|
||||
assetType: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '生资种类'
|
||||
},
|
||||
applicationQuantity: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请数量'
|
||||
},
|
||||
amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '合同金额'
|
||||
},
|
||||
paidAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '已还款金额'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM(
|
||||
'active',
|
||||
'pending',
|
||||
'completed',
|
||||
'defaulted',
|
||||
'cancelled'
|
||||
),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '合同状态'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM(
|
||||
'livestock_collateral',
|
||||
'farmer_loan',
|
||||
'business_loan',
|
||||
'personal_loan'
|
||||
),
|
||||
allowNull: false,
|
||||
comment: '合同类型'
|
||||
},
|
||||
term: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '合同期限(月)'
|
||||
},
|
||||
interestRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '利率'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
},
|
||||
purpose: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '贷款用途'
|
||||
},
|
||||
remark: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注'
|
||||
},
|
||||
contractTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
comment: '合同签订时间'
|
||||
},
|
||||
disbursementTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '放款时间'
|
||||
},
|
||||
maturityTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '到期时间'
|
||||
},
|
||||
completedTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '完成时间'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
sequelize: require('../config/database').sequelize,
|
||||
modelName: 'LoanContract',
|
||||
tableName: 'bank_loan_contracts',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
hooks: {
|
||||
beforeCreate: (contract) => {
|
||||
// 生成合同编号
|
||||
if (!contract.contractNumber) {
|
||||
const now = new Date();
|
||||
const timestamp = now.getFullYear().toString() +
|
||||
(now.getMonth() + 1).toString().padStart(2, '0') +
|
||||
now.getDate().toString().padStart(2, '0') +
|
||||
now.getHours().toString().padStart(2, '0') +
|
||||
now.getMinutes().toString().padStart(2, '0') +
|
||||
now.getSeconds().toString().padStart(2, '0');
|
||||
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
|
||||
contract.contractNumber = 'HT' + timestamp + random;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = LoanContract;
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* 贷款产品模型
|
||||
* @file LoanProduct.js
|
||||
* @description 贷款产品数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
@@ -10,84 +5,115 @@ const LoanProduct = sequelize.define('LoanProduct', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
autoIncrement: true,
|
||||
comment: '主键ID'
|
||||
},
|
||||
name: {
|
||||
productName: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
loanAmount: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '产品名称'
|
||||
comment: '贷款额度'
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING(50),
|
||||
loanTerm: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '产品代码'
|
||||
comment: '贷款周期(月)'
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.ENUM('personal', 'business', 'mortgage', 'credit'),
|
||||
interestRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '产品类型:个人贷款、企业贷款、抵押贷款、信用贷款'
|
||||
comment: '贷款利率(%)'
|
||||
},
|
||||
description: {
|
||||
serviceArea: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '服务区域'
|
||||
},
|
||||
servicePhone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '服务电话'
|
||||
},
|
||||
totalCustomers: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '服务客户总数量'
|
||||
},
|
||||
supervisionCustomers: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '监管中客户数量'
|
||||
},
|
||||
completedCustomers: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '已结项客户数量'
|
||||
},
|
||||
onSaleStatus: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
comment: '在售状态'
|
||||
},
|
||||
productDescription: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '产品描述'
|
||||
},
|
||||
min_amount: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '最小贷款金额(分)'
|
||||
},
|
||||
max_amount: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '最大贷款金额(分)'
|
||||
},
|
||||
interest_rate: {
|
||||
type: DataTypes.DECIMAL(5, 4),
|
||||
allowNull: false,
|
||||
comment: '年化利率'
|
||||
},
|
||||
term_min: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '最短期限(月)'
|
||||
},
|
||||
term_max: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '最长期限(月)'
|
||||
},
|
||||
requirements: {
|
||||
type: DataTypes.JSON,
|
||||
applicationRequirements: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '申请要求(JSON格式)'
|
||||
comment: '申请条件'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('draft', 'active', 'inactive'),
|
||||
allowNull: false,
|
||||
defaultValue: 'draft',
|
||||
comment: '产品状态:草稿、启用、停用'
|
||||
requiredDocuments: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '所需材料'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
approvalProcess: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审批流程'
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
riskLevel: {
|
||||
type: DataTypes.ENUM('LOW', 'MEDIUM', 'HIGH'),
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
defaultValue: 'MEDIUM',
|
||||
comment: '风险等级'
|
||||
},
|
||||
minLoanAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '最小贷款金额'
|
||||
},
|
||||
maxLoanAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '最大贷款金额'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'bank_loan_products',
|
||||
modelName: 'LoanProduct',
|
||||
tableName: 'loan_products',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '贷款商品表'
|
||||
});
|
||||
|
||||
module.exports = LoanProduct;
|
||||
module.exports = LoanProduct;
|
||||
@@ -15,7 +15,13 @@ class User extends BaseModel {
|
||||
* @returns {Promise<Boolean>} 验证结果
|
||||
*/
|
||||
async validPassword(password) {
|
||||
return await bcrypt.compare(password, this.password);
|
||||
try {
|
||||
const bcrypt = require('bcryptjs');
|
||||
return await bcrypt.compare(password, this.password);
|
||||
} catch (error) {
|
||||
console.error('密码验证错误:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,11 @@ const Position = require('./Position');
|
||||
const Report = require('./Report');
|
||||
const Project = require('./Project');
|
||||
const SupervisionTask = require('./SupervisionTask');
|
||||
const InstallationTask = require('./InstallationTask');
|
||||
const CompletedSupervision = require('./CompletedSupervision');
|
||||
const LoanApplication = require('./LoanApplication');
|
||||
const AuditRecord = require('./AuditRecord');
|
||||
const LoanContract = require('./LoanContract');
|
||||
|
||||
// 定义模型关联关系
|
||||
|
||||
@@ -141,6 +146,162 @@ User.hasMany(SupervisionTask, {
|
||||
as: 'updatedSupervisionTasks'
|
||||
});
|
||||
|
||||
// 待安装任务与用户关联(创建人)
|
||||
InstallationTask.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(InstallationTask, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'createdInstallationTasks'
|
||||
});
|
||||
|
||||
// 待安装任务与用户关联(更新人)
|
||||
InstallationTask.belongsTo(User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(InstallationTask, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updatedInstallationTasks'
|
||||
});
|
||||
|
||||
// 监管任务已结项与用户关联(创建人)
|
||||
CompletedSupervision.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(CompletedSupervision, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'createdCompletedSupervisions'
|
||||
});
|
||||
|
||||
// 监管任务已结项与用户关联(更新人)
|
||||
CompletedSupervision.belongsTo(User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(CompletedSupervision, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updatedCompletedSupervisions'
|
||||
});
|
||||
|
||||
// 贷款商品与用户关联(创建人)
|
||||
LoanProduct.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanProduct, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'createdLoanProducts'
|
||||
});
|
||||
|
||||
// 贷款商品与用户关联(更新人)
|
||||
LoanProduct.belongsTo(User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanProduct, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updatedLoanProducts'
|
||||
});
|
||||
|
||||
// 贷款申请与用户关联(申请人)
|
||||
LoanApplication.belongsTo(User, {
|
||||
foreignKey: 'applicantId',
|
||||
as: 'applicant',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanApplication, {
|
||||
foreignKey: 'applicantId',
|
||||
as: 'loanApplications'
|
||||
});
|
||||
|
||||
// 贷款申请与用户关联(审批人)
|
||||
LoanApplication.belongsTo(User, {
|
||||
foreignKey: 'approvedBy',
|
||||
as: 'approver',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanApplication, {
|
||||
foreignKey: 'approvedBy',
|
||||
as: 'approvedApplications'
|
||||
});
|
||||
|
||||
// 贷款申请与用户关联(拒绝人)
|
||||
LoanApplication.belongsTo(User, {
|
||||
foreignKey: 'rejectedBy',
|
||||
as: 'rejector',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanApplication, {
|
||||
foreignKey: 'rejectedBy',
|
||||
as: 'rejectedApplications'
|
||||
});
|
||||
|
||||
// 审核记录与贷款申请关联
|
||||
AuditRecord.belongsTo(LoanApplication, {
|
||||
foreignKey: 'applicationId',
|
||||
as: 'application',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
LoanApplication.hasMany(AuditRecord, {
|
||||
foreignKey: 'applicationId',
|
||||
as: 'auditRecords'
|
||||
});
|
||||
|
||||
// 审核记录与用户关联(审核人)
|
||||
AuditRecord.belongsTo(User, {
|
||||
foreignKey: 'auditorId',
|
||||
as: 'auditorUser',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(AuditRecord, {
|
||||
foreignKey: 'auditorId',
|
||||
as: 'auditRecords'
|
||||
});
|
||||
|
||||
// 贷款合同与用户关联(创建人)
|
||||
LoanContract.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanContract, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'createdLoanContracts'
|
||||
});
|
||||
|
||||
// 贷款合同与用户关联(更新人)
|
||||
LoanContract.belongsTo(User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
User.hasMany(LoanContract, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updatedLoanContracts'
|
||||
});
|
||||
|
||||
// 导出所有模型和数据库实例
|
||||
module.exports = {
|
||||
sequelize,
|
||||
@@ -154,5 +315,10 @@ module.exports = {
|
||||
Position,
|
||||
Report,
|
||||
Project,
|
||||
SupervisionTask
|
||||
SupervisionTask,
|
||||
InstallationTask,
|
||||
CompletedSupervision,
|
||||
LoanApplication,
|
||||
AuditRecord,
|
||||
LoanContract
|
||||
};
|
||||
42
bank-backend/routes/completedSupervisions.js
Normal file
42
bank-backend/routes/completedSupervisions.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { authMiddleware } = require('../middleware/auth')
|
||||
const {
|
||||
getCompletedSupervisions,
|
||||
getCompletedSupervisionById,
|
||||
createCompletedSupervision,
|
||||
updateCompletedSupervision,
|
||||
deleteCompletedSupervision,
|
||||
getCompletedSupervisionStats,
|
||||
batchUpdateStatus,
|
||||
batchDelete
|
||||
} = require('../controllers/completedSupervisionController')
|
||||
|
||||
// 应用认证中间件到所有路由
|
||||
router.use(authMiddleware)
|
||||
|
||||
// 获取监管任务已结项列表
|
||||
router.get('/', getCompletedSupervisions)
|
||||
|
||||
// 获取监管任务已结项统计信息
|
||||
router.get('/stats', getCompletedSupervisionStats)
|
||||
|
||||
// 根据ID获取监管任务已结项详情
|
||||
router.get('/:id', getCompletedSupervisionById)
|
||||
|
||||
// 创建监管任务已结项
|
||||
router.post('/', createCompletedSupervision)
|
||||
|
||||
// 更新监管任务已结项
|
||||
router.put('/:id', updateCompletedSupervision)
|
||||
|
||||
// 批量更新结清状态
|
||||
router.put('/batch/status', batchUpdateStatus)
|
||||
|
||||
// 删除监管任务已结项
|
||||
router.delete('/:id', deleteCompletedSupervision)
|
||||
|
||||
// 批量删除监管任务已结项
|
||||
router.delete('/batch/delete', batchDelete)
|
||||
|
||||
module.exports = router
|
||||
42
bank-backend/routes/installationTasks.js
Normal file
42
bank-backend/routes/installationTasks.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const { authMiddleware } = require('../middleware/auth')
|
||||
const {
|
||||
getInstallationTasks,
|
||||
getInstallationTaskById,
|
||||
createInstallationTask,
|
||||
updateInstallationTask,
|
||||
deleteInstallationTask,
|
||||
getInstallationTaskStats,
|
||||
batchUpdateStatus,
|
||||
batchDelete
|
||||
} = require('../controllers/installationTaskController')
|
||||
|
||||
// 应用认证中间件到所有路由
|
||||
router.use(authMiddleware)
|
||||
|
||||
// 获取待安装任务列表
|
||||
router.get('/', getInstallationTasks)
|
||||
|
||||
// 获取待安装任务统计信息
|
||||
router.get('/stats', getInstallationTaskStats)
|
||||
|
||||
// 根据ID获取待安装任务详情
|
||||
router.get('/:id', getInstallationTaskById)
|
||||
|
||||
// 创建待安装任务
|
||||
router.post('/', createInstallationTask)
|
||||
|
||||
// 更新待安装任务
|
||||
router.put('/:id', updateInstallationTask)
|
||||
|
||||
// 批量更新安装状态
|
||||
router.put('/batch/status', batchUpdateStatus)
|
||||
|
||||
// 删除待安装任务
|
||||
router.delete('/:id', deleteInstallationTask)
|
||||
|
||||
// 批量删除待安装任务
|
||||
router.delete('/batch/delete', batchDelete)
|
||||
|
||||
module.exports = router
|
||||
408
bank-backend/routes/loanApplications.js
Normal file
408
bank-backend/routes/loanApplications.js
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* 贷款申请路由
|
||||
* @file loanApplications.js
|
||||
* @description 银行系统贷款申请相关路由配置
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body } = require('express-validator');
|
||||
const loanApplicationController = require('../controllers/loanApplicationController');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-applications:
|
||||
* get:
|
||||
* summary: 获取贷款申请列表
|
||||
* tags: [贷款申请]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: searchField
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [applicationNumber, customerName, productName]
|
||||
* default: applicationNumber
|
||||
* description: 搜索字段
|
||||
* - in: query
|
||||
* name: searchValue
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索值
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [pending_review, verification_pending, pending_binding, approved, rejected]
|
||||
* description: 申请状态筛选
|
||||
* - in: query
|
||||
* name: sortField
|
||||
* schema:
|
||||
* type: string
|
||||
* default: createdAt
|
||||
* description: 排序字段
|
||||
* - in: query
|
||||
* name: sortOrder
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [ASC, DESC]
|
||||
* default: DESC
|
||||
* description: 排序方向
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* applications:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/LoanApplication'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
*/
|
||||
router.get('/', loanApplicationController.getApplications);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-applications/{id}:
|
||||
* get:
|
||||
* summary: 获取贷款申请详情
|
||||
* tags: [贷款申请]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 申请ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/LoanApplication'
|
||||
* 404:
|
||||
* description: 申请不存在
|
||||
*/
|
||||
router.get('/:id', loanApplicationController.getApplicationById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-applications/{id}/audit:
|
||||
* post:
|
||||
* summary: 审核贷款申请
|
||||
* tags: [贷款申请]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 申请ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - action
|
||||
* - comment
|
||||
* properties:
|
||||
* action:
|
||||
* type: string
|
||||
* enum: [approve, reject]
|
||||
* description: 审核动作
|
||||
* comment:
|
||||
* type: string
|
||||
* description: 审核意见
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 审核成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* status:
|
||||
* type: string
|
||||
* action:
|
||||
* type: string
|
||||
* comment:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 申请不存在
|
||||
*/
|
||||
router.post('/:id/audit', [
|
||||
body('action')
|
||||
.isIn(['approve', 'reject'])
|
||||
.withMessage('审核动作必须是approve或reject'),
|
||||
body('comment')
|
||||
.notEmpty()
|
||||
.withMessage('审核意见不能为空')
|
||||
.isLength({ max: 500 })
|
||||
.withMessage('审核意见不能超过500个字符')
|
||||
], loanApplicationController.auditApplication);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-applications/stats:
|
||||
* get:
|
||||
* summary: 获取申请统计信息
|
||||
* tags: [贷款申请]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: object
|
||||
* properties:
|
||||
* applications:
|
||||
* type: integer
|
||||
* amount:
|
||||
* type: number
|
||||
* byStatus:
|
||||
* type: object
|
||||
* properties:
|
||||
* counts:
|
||||
* type: object
|
||||
* amounts:
|
||||
* type: object
|
||||
*/
|
||||
router.get('/stats', loanApplicationController.getApplicationStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-applications/batch/status:
|
||||
* put:
|
||||
* summary: 批量更新申请状态
|
||||
* tags: [贷款申请]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - ids
|
||||
* - status
|
||||
* properties:
|
||||
* ids:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 申请ID数组
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [approved, rejected]
|
||||
* description: 目标状态
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* updatedCount:
|
||||
* type: integer
|
||||
* status:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.put('/batch/status', [
|
||||
body('ids')
|
||||
.isArray({ min: 1 })
|
||||
.withMessage('请选择要操作的申请'),
|
||||
body('status')
|
||||
.isIn(['approved', 'rejected'])
|
||||
.withMessage('状态必须是approved或rejected')
|
||||
], loanApplicationController.batchUpdateStatus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* LoanApplication:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 申请ID
|
||||
* applicationNumber:
|
||||
* type: string
|
||||
* description: 申请单号
|
||||
* productName:
|
||||
* type: string
|
||||
* description: 贷款产品名称
|
||||
* farmerName:
|
||||
* type: string
|
||||
* description: 申请养殖户姓名
|
||||
* borrowerName:
|
||||
* type: string
|
||||
* description: 贷款人姓名
|
||||
* borrowerIdNumber:
|
||||
* type: string
|
||||
* description: 贷款人身份证号
|
||||
* assetType:
|
||||
* type: string
|
||||
* description: 生资种类
|
||||
* applicationQuantity:
|
||||
* type: string
|
||||
* description: 申请数量
|
||||
* amount:
|
||||
* type: number
|
||||
* description: 申请额度
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [pending_review, verification_pending, pending_binding, approved, rejected]
|
||||
* description: 申请状态
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [personal, business, mortgage]
|
||||
* description: 申请类型
|
||||
* term:
|
||||
* type: integer
|
||||
* description: 申请期限(月)
|
||||
* interestRate:
|
||||
* type: number
|
||||
* description: 预计利率
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 联系电话
|
||||
* purpose:
|
||||
* type: string
|
||||
* description: 申请用途
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* applicationTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 申请时间
|
||||
* approvedTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 审批通过时间
|
||||
* rejectedTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 审批拒绝时间
|
||||
* auditRecords:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/AuditRecord'
|
||||
* description: 审核记录
|
||||
* AuditRecord:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 记录ID
|
||||
* action:
|
||||
* type: string
|
||||
* enum: [submit, approve, reject, review, verification, binding]
|
||||
* description: 审核动作
|
||||
* auditor:
|
||||
* type: string
|
||||
* description: 审核人
|
||||
* auditorId:
|
||||
* type: integer
|
||||
* description: 审核人ID
|
||||
* comment:
|
||||
* type: string
|
||||
* description: 审核意见
|
||||
* time:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 审核时间
|
||||
* previousStatus:
|
||||
* type: string
|
||||
* description: 审核前状态
|
||||
* newStatus:
|
||||
* type: string
|
||||
* description: 审核后状态
|
||||
* Pagination:
|
||||
* type: object
|
||||
* properties:
|
||||
* current:
|
||||
* type: integer
|
||||
* description: 当前页码
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* description: 每页数量
|
||||
* total:
|
||||
* type: integer
|
||||
* description: 总记录数
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* description: 总页数
|
||||
*/
|
||||
|
||||
module.exports = router;
|
||||
568
bank-backend/routes/loanContracts.js
Normal file
568
bank-backend/routes/loanContracts.js
Normal file
@@ -0,0 +1,568 @@
|
||||
/**
|
||||
* 贷款合同路由
|
||||
* @file loanContracts.js
|
||||
* @description 银行系统贷款合同相关路由配置
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body } = require('express-validator');
|
||||
const loanContractController = require('../controllers/loanContractController');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// 所有路由都需要认证
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-contracts:
|
||||
* get:
|
||||
* summary: 获取贷款合同列表
|
||||
* tags: [贷款合同]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 1
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: pageSize
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: searchField
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [contractNumber, applicationNumber, borrowerName, farmerName, productName]
|
||||
* default: contractNumber
|
||||
* description: 搜索字段
|
||||
* - in: query
|
||||
* name: searchValue
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索值
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [active, pending, completed, defaulted, cancelled]
|
||||
* description: 合同状态筛选
|
||||
* - in: query
|
||||
* name: sortField
|
||||
* schema:
|
||||
* type: string
|
||||
* default: createdAt
|
||||
* description: 排序字段
|
||||
* - in: query
|
||||
* name: sortOrder
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [ASC, DESC]
|
||||
* default: DESC
|
||||
* description: 排序方向
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* contracts:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/LoanContract'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
*/
|
||||
router.get('/', loanContractController.getContracts);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-contracts/{id}:
|
||||
* get:
|
||||
* summary: 获取贷款合同详情
|
||||
* tags: [贷款合同]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 合同ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* $ref: '#/components/schemas/LoanContract'
|
||||
* 404:
|
||||
* description: 合同不存在
|
||||
*/
|
||||
router.get('/:id', loanContractController.getContractById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-contracts:
|
||||
* post:
|
||||
* summary: 创建贷款合同
|
||||
* tags: [贷款合同]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - applicationNumber
|
||||
* - productName
|
||||
* - farmerName
|
||||
* - borrowerName
|
||||
* - borrowerIdNumber
|
||||
* - assetType
|
||||
* - applicationQuantity
|
||||
* - amount
|
||||
* - type
|
||||
* - term
|
||||
* - interestRate
|
||||
* - phone
|
||||
* properties:
|
||||
* applicationNumber:
|
||||
* type: string
|
||||
* description: 申请单号
|
||||
* productName:
|
||||
* type: string
|
||||
* description: 贷款产品名称
|
||||
* farmerName:
|
||||
* type: string
|
||||
* description: 申请养殖户姓名
|
||||
* borrowerName:
|
||||
* type: string
|
||||
* description: 贷款人姓名
|
||||
* borrowerIdNumber:
|
||||
* type: string
|
||||
* description: 贷款人身份证号
|
||||
* assetType:
|
||||
* type: string
|
||||
* description: 生资种类
|
||||
* applicationQuantity:
|
||||
* type: string
|
||||
* description: 申请数量
|
||||
* amount:
|
||||
* type: number
|
||||
* description: 合同金额
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [livestock_collateral, farmer_loan, business_loan, personal_loan]
|
||||
* description: 合同类型
|
||||
* term:
|
||||
* type: integer
|
||||
* description: 合同期限(月)
|
||||
* interestRate:
|
||||
* type: number
|
||||
* description: 利率
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 联系电话
|
||||
* purpose:
|
||||
* type: string
|
||||
* description: 贷款用途
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* $ref: '#/components/schemas/LoanContract'
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.post('/', [
|
||||
body('applicationNumber').notEmpty().withMessage('申请单号不能为空'),
|
||||
body('productName').notEmpty().withMessage('贷款产品名称不能为空'),
|
||||
body('farmerName').notEmpty().withMessage('申请养殖户姓名不能为空'),
|
||||
body('borrowerName').notEmpty().withMessage('贷款人姓名不能为空'),
|
||||
body('borrowerIdNumber').notEmpty().withMessage('贷款人身份证号不能为空'),
|
||||
body('assetType').notEmpty().withMessage('生资种类不能为空'),
|
||||
body('applicationQuantity').notEmpty().withMessage('申请数量不能为空'),
|
||||
body('amount').isNumeric().withMessage('合同金额必须是数字'),
|
||||
body('type').isIn(['livestock_collateral', 'farmer_loan', 'business_loan', 'personal_loan']).withMessage('合同类型无效'),
|
||||
body('term').isInt({ min: 1 }).withMessage('合同期限必须大于0'),
|
||||
body('interestRate').isNumeric().withMessage('利率必须是数字'),
|
||||
body('phone').notEmpty().withMessage('联系电话不能为空')
|
||||
], loanContractController.createContract);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-contracts/{id}:
|
||||
* put:
|
||||
* summary: 更新贷款合同
|
||||
* tags: [贷款合同]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 合同ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* productName:
|
||||
* type: string
|
||||
* description: 贷款产品名称
|
||||
* farmerName:
|
||||
* type: string
|
||||
* description: 申请养殖户姓名
|
||||
* borrowerName:
|
||||
* type: string
|
||||
* description: 贷款人姓名
|
||||
* borrowerIdNumber:
|
||||
* type: string
|
||||
* description: 贷款人身份证号
|
||||
* assetType:
|
||||
* type: string
|
||||
* description: 生资种类
|
||||
* applicationQuantity:
|
||||
* type: string
|
||||
* description: 申请数量
|
||||
* amount:
|
||||
* type: number
|
||||
* description: 合同金额
|
||||
* paidAmount:
|
||||
* type: number
|
||||
* description: 已还款金额
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, pending, completed, defaulted, cancelled]
|
||||
* description: 合同状态
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [livestock_collateral, farmer_loan, business_loan, personal_loan]
|
||||
* description: 合同类型
|
||||
* term:
|
||||
* type: integer
|
||||
* description: 合同期限(月)
|
||||
* interestRate:
|
||||
* type: number
|
||||
* description: 利率
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 联系电话
|
||||
* purpose:
|
||||
* type: string
|
||||
* description: 贷款用途
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* $ref: '#/components/schemas/LoanContract'
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 合同不存在
|
||||
*/
|
||||
router.put('/:id', loanContractController.updateContract);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-contracts/{id}:
|
||||
* delete:
|
||||
* summary: 删除贷款合同
|
||||
* tags: [贷款合同]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 合同ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* 404:
|
||||
* description: 合同不存在
|
||||
*/
|
||||
router.delete('/:id', loanContractController.deleteContract);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-contracts/stats:
|
||||
* get:
|
||||
* summary: 获取合同统计信息
|
||||
* tags: [贷款合同]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* total:
|
||||
* type: object
|
||||
* properties:
|
||||
* contracts:
|
||||
* type: integer
|
||||
* amount:
|
||||
* type: number
|
||||
* paidAmount:
|
||||
* type: number
|
||||
* remainingAmount:
|
||||
* type: number
|
||||
* byStatus:
|
||||
* type: object
|
||||
* properties:
|
||||
* counts:
|
||||
* type: object
|
||||
* amounts:
|
||||
* type: object
|
||||
* paidAmounts:
|
||||
* type: object
|
||||
*/
|
||||
router.get('/stats', loanContractController.getContractStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-contracts/batch/status:
|
||||
* put:
|
||||
* summary: 批量更新合同状态
|
||||
* tags: [贷款合同]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - ids
|
||||
* - status
|
||||
* properties:
|
||||
* ids:
|
||||
* type: array
|
||||
* items:
|
||||
* type: integer
|
||||
* description: 合同ID数组
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, pending, completed, defaulted, cancelled]
|
||||
* description: 目标状态
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* updatedCount:
|
||||
* type: integer
|
||||
* status:
|
||||
* type: string
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
*/
|
||||
router.put('/batch/status', [
|
||||
body('ids').isArray({ min: 1 }).withMessage('请选择要操作的合同'),
|
||||
body('status').isIn(['active', 'pending', 'completed', 'defaulted', 'cancelled']).withMessage('状态无效')
|
||||
], loanContractController.batchUpdateStatus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* LoanContract:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 合同ID
|
||||
* contractNumber:
|
||||
* type: string
|
||||
* description: 合同编号
|
||||
* applicationNumber:
|
||||
* type: string
|
||||
* description: 申请单号
|
||||
* productName:
|
||||
* type: string
|
||||
* description: 贷款产品名称
|
||||
* farmerName:
|
||||
* type: string
|
||||
* description: 申请养殖户姓名
|
||||
* borrowerName:
|
||||
* type: string
|
||||
* description: 贷款人姓名
|
||||
* borrowerIdNumber:
|
||||
* type: string
|
||||
* description: 贷款人身份证号
|
||||
* assetType:
|
||||
* type: string
|
||||
* description: 生资种类
|
||||
* applicationQuantity:
|
||||
* type: string
|
||||
* description: 申请数量
|
||||
* amount:
|
||||
* type: number
|
||||
* description: 合同金额
|
||||
* paidAmount:
|
||||
* type: number
|
||||
* description: 已还款金额
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, pending, completed, defaulted, cancelled]
|
||||
* description: 合同状态
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [livestock_collateral, farmer_loan, business_loan, personal_loan]
|
||||
* description: 合同类型
|
||||
* term:
|
||||
* type: integer
|
||||
* description: 合同期限(月)
|
||||
* interestRate:
|
||||
* type: number
|
||||
* description: 利率
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 联系电话
|
||||
* purpose:
|
||||
* type: string
|
||||
* description: 贷款用途
|
||||
* remark:
|
||||
* type: string
|
||||
* description: 备注
|
||||
* contractTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 合同签订时间
|
||||
* disbursementTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 放款时间
|
||||
* maturityTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 到期时间
|
||||
* completedTime:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* description: 完成时间
|
||||
* remainingAmount:
|
||||
* type: number
|
||||
* description: 剩余还款金额
|
||||
* repaymentProgress:
|
||||
* type: number
|
||||
* description: 还款进度百分比
|
||||
* creator:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* updater:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* User:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* description: 用户ID
|
||||
* username:
|
||||
* type: string
|
||||
* description: 用户名
|
||||
* real_name:
|
||||
* type: string
|
||||
* description: 真实姓名
|
||||
* email:
|
||||
* type: string
|
||||
* description: 邮箱
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 电话
|
||||
* Pagination:
|
||||
* type: object
|
||||
* properties:
|
||||
* current:
|
||||
* type: integer
|
||||
* description: 当前页码
|
||||
* pageSize:
|
||||
* type: integer
|
||||
* description: 每页数量
|
||||
* total:
|
||||
* type: integer
|
||||
* description: 总记录数
|
||||
* totalPages:
|
||||
* type: integer
|
||||
* description: 总页数
|
||||
*/
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,372 +1,42 @@
|
||||
/**
|
||||
* 贷款产品路由
|
||||
* @file loanProducts.js
|
||||
* @description 贷款产品相关的路由定义
|
||||
*/
|
||||
const express = require('express');
|
||||
const { body } = require('express-validator');
|
||||
const { authMiddleware, roleMiddleware, adminMiddleware, managerMiddleware } = require('../middleware/auth');
|
||||
const loanProductController = require('../controllers/loanProductController');
|
||||
|
||||
const router = express.Router();
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
const {
|
||||
getLoanProducts,
|
||||
getLoanProductById,
|
||||
createLoanProduct,
|
||||
updateLoanProduct,
|
||||
deleteLoanProduct,
|
||||
getLoanProductStats,
|
||||
batchUpdateStatus,
|
||||
batchDelete
|
||||
} = require('../controllers/loanProductController');
|
||||
|
||||
// 所有路由都需要认证
|
||||
// 应用认证中间件到所有路由
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: LoanProducts
|
||||
* description: 贷款产品管理
|
||||
*/
|
||||
// 获取贷款商品列表
|
||||
router.get('/', getLoanProducts);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products:
|
||||
* get:
|
||||
* summary: 获取贷款产品列表
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 页码
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 每页数量
|
||||
* - in: query
|
||||
* name: search
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 搜索关键词
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* description: 产品状态
|
||||
* - in: query
|
||||
* name: type
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [personal, business, mortgage, credit]
|
||||
* description: 产品类型
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* products:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/LoanProduct'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/', roleMiddleware(['admin', 'manager', 'teller']), loanProductController.getLoanProducts);
|
||||
// 获取贷款商品统计信息
|
||||
router.get('/stats', getLoanProductStats);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products:
|
||||
* post:
|
||||
* summary: 创建贷款产品
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - code
|
||||
* - type
|
||||
* - min_amount
|
||||
* - max_amount
|
||||
* - interest_rate
|
||||
* - term_min
|
||||
* - term_max
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 产品名称
|
||||
* code:
|
||||
* type: string
|
||||
* description: 产品代码
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [personal, business, mortgage, credit]
|
||||
* description: 产品类型
|
||||
* description:
|
||||
* type: string
|
||||
* description: 产品描述
|
||||
* min_amount:
|
||||
* type: number
|
||||
* description: 最小贷款金额
|
||||
* max_amount:
|
||||
* type: number
|
||||
* description: 最大贷款金额
|
||||
* interest_rate:
|
||||
* type: number
|
||||
* description: 年化利率
|
||||
* term_min:
|
||||
* type: integer
|
||||
* description: 最短期限(月)
|
||||
* term_max:
|
||||
* type: integer
|
||||
* description: 最长期限(月)
|
||||
* requirements:
|
||||
* type: object
|
||||
* description: 申请要求
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* description: 产品状态
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').notEmpty().withMessage('产品名称不能为空'),
|
||||
body('code').notEmpty().withMessage('产品代码不能为空'),
|
||||
body('type').isIn(['personal', 'business', 'mortgage', 'credit']).withMessage('产品类型无效'),
|
||||
body('min_amount').isNumeric().withMessage('最小金额必须是数字'),
|
||||
body('max_amount').isNumeric().withMessage('最大金额必须是数字'),
|
||||
body('interest_rate').isNumeric().withMessage('利率必须是数字'),
|
||||
body('term_min').isInt({ min: 1 }).withMessage('最短期限必须是正整数'),
|
||||
body('term_max').isInt({ min: 1 }).withMessage('最长期限必须是正整数')
|
||||
],
|
||||
loanProductController.createLoanProduct
|
||||
);
|
||||
// 根据ID获取贷款商品详情
|
||||
router.get('/:id', getLoanProductById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}:
|
||||
* get:
|
||||
* summary: 获取贷款产品详情
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/:id', roleMiddleware(['admin', 'manager', 'teller']), loanProductController.getLoanProductById);
|
||||
// 创建贷款商品
|
||||
router.post('/', createLoanProduct);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}:
|
||||
* put:
|
||||
* summary: 更新贷款产品
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* code:
|
||||
* type: string
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [personal, business, mortgage, credit]
|
||||
* description:
|
||||
* type: string
|
||||
* min_amount:
|
||||
* type: number
|
||||
* max_amount:
|
||||
* type: number
|
||||
* interest_rate:
|
||||
* type: number
|
||||
* term_min:
|
||||
* type: integer
|
||||
* term_max:
|
||||
* type: integer
|
||||
* requirements:
|
||||
* type: object
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put('/:id',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').optional().notEmpty().withMessage('产品名称不能为空'),
|
||||
body('code').optional().notEmpty().withMessage('产品代码不能为空'),
|
||||
body('type').optional().isIn(['personal', 'business', 'mortgage', 'credit']).withMessage('产品类型无效'),
|
||||
body('min_amount').optional().isNumeric().withMessage('最小金额必须是数字'),
|
||||
body('max_amount').optional().isNumeric().withMessage('最大金额必须是数字'),
|
||||
body('interest_rate').optional().isNumeric().withMessage('利率必须是数字'),
|
||||
body('term_min').optional().isInt({ min: 1 }).withMessage('最短期限必须是正整数'),
|
||||
body('term_max').optional().isInt({ min: 1 }).withMessage('最长期限必须是正整数')
|
||||
],
|
||||
loanProductController.updateLoanProduct
|
||||
);
|
||||
// 更新贷款商品
|
||||
router.put('/:id', updateLoanProduct);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}:
|
||||
* delete:
|
||||
* summary: 删除贷款产品
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 删除成功
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.delete('/:id', adminMiddleware, loanProductController.deleteLoanProduct);
|
||||
// 批量更新在售状态
|
||||
router.put('/batch/status', batchUpdateStatus);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/{id}/status:
|
||||
* put:
|
||||
* summary: 更新贷款产品状态
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: 产品ID
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - status
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [draft, active, inactive]
|
||||
* description: 产品状态
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 产品不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put('/:id/status',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('status').isIn(['draft', 'active', 'inactive']).withMessage('状态值无效')
|
||||
],
|
||||
loanProductController.updateLoanProductStatus
|
||||
);
|
||||
// 删除贷款商品
|
||||
router.delete('/:id', deleteLoanProduct);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/loan-products/stats/overview:
|
||||
* get:
|
||||
* summary: 获取贷款产品统计
|
||||
* tags: [LoanProducts]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/overview', roleMiddleware(['admin', 'manager', 'teller']), loanProductController.getLoanProductStats);
|
||||
// 批量删除贷款商品
|
||||
router.delete('/batch/delete', batchDelete);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
207
bank-backend/scripts/seed-completed-supervisions.js
Normal file
207
bank-backend/scripts/seed-completed-supervisions.js
Normal file
@@ -0,0 +1,207 @@
|
||||
const { CompletedSupervision, User } = require('../models')
|
||||
|
||||
async function seedCompletedSupervisions() {
|
||||
try {
|
||||
console.log('开始创建监管任务已结项测试数据...')
|
||||
|
||||
// 获取第一个用户作为创建者
|
||||
const user = await User.findOne()
|
||||
if (!user) {
|
||||
console.error('未找到用户,请先创建用户')
|
||||
return
|
||||
}
|
||||
|
||||
const completedSupervisions = [
|
||||
{
|
||||
applicationNumber: 'APP2024001',
|
||||
contractNumber: 'LOAN2024001',
|
||||
productName: '生猪养殖贷',
|
||||
customerName: '张三',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4401XXXXXXXXXXXXXX',
|
||||
assetType: '生猪',
|
||||
assetQuantity: 500,
|
||||
totalRepaymentPeriods: 12,
|
||||
settlementStatus: 'settled',
|
||||
settlementDate: '2024-01-15',
|
||||
importTime: new Date('2024-01-15 10:30:00'),
|
||||
settlementAmount: 500000.00,
|
||||
remainingAmount: 0.00,
|
||||
settlementNotes: '贷款已全部结清',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024002',
|
||||
contractNumber: 'LOAN2024002',
|
||||
productName: '肉牛养殖贷',
|
||||
customerName: '李四',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4402XXXXXXXXXXXXXX',
|
||||
assetType: '肉牛',
|
||||
assetQuantity: 150,
|
||||
totalRepaymentPeriods: 24,
|
||||
settlementStatus: 'partial',
|
||||
settlementDate: '2024-01-20',
|
||||
importTime: new Date('2024-01-20 14:20:00'),
|
||||
settlementAmount: 300000.00,
|
||||
remainingAmount: 200000.00,
|
||||
settlementNotes: '部分结清,剩余20万待还',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024003',
|
||||
contractNumber: 'LOAN2024003',
|
||||
productName: '蛋鸡养殖贷',
|
||||
customerName: '王五',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4403XXXXXXXXXXXXXX',
|
||||
assetType: '蛋鸡',
|
||||
assetQuantity: 10000,
|
||||
totalRepaymentPeriods: 18,
|
||||
settlementStatus: 'unsettled',
|
||||
settlementDate: null,
|
||||
importTime: new Date('2024-01-10 09:15:00'),
|
||||
settlementAmount: null,
|
||||
remainingAmount: 800000.00,
|
||||
settlementNotes: '尚未结清',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024004',
|
||||
contractNumber: 'LOAN2024004',
|
||||
productName: '肉羊养殖贷',
|
||||
customerName: '赵六',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4404XXXXXXXXXXXXXX',
|
||||
assetType: '肉羊',
|
||||
assetQuantity: 300,
|
||||
totalRepaymentPeriods: 15,
|
||||
settlementStatus: 'settled',
|
||||
settlementDate: '2024-01-25',
|
||||
importTime: new Date('2024-01-25 16:45:00'),
|
||||
settlementAmount: 300000.00,
|
||||
remainingAmount: 0.00,
|
||||
settlementNotes: '贷款已全部结清',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024005',
|
||||
contractNumber: 'LOAN2024005',
|
||||
productName: '奶牛养殖贷',
|
||||
customerName: '孙七',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4405XXXXXXXXXXXXXX',
|
||||
assetType: '奶牛',
|
||||
assetQuantity: 100,
|
||||
totalRepaymentPeriods: 36,
|
||||
settlementStatus: 'partial',
|
||||
settlementDate: '2024-01-30',
|
||||
importTime: new Date('2024-01-30 11:20:00'),
|
||||
settlementAmount: 200000.00,
|
||||
remainingAmount: 400000.00,
|
||||
settlementNotes: '部分结清,剩余40万待还',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024006',
|
||||
contractNumber: 'LOAN2024006',
|
||||
productName: '肉鸭养殖贷',
|
||||
customerName: '周八',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4406XXXXXXXXXXXXXX',
|
||||
assetType: '肉鸭',
|
||||
assetQuantity: 5000,
|
||||
totalRepaymentPeriods: 12,
|
||||
settlementStatus: 'unsettled',
|
||||
settlementDate: null,
|
||||
importTime: new Date('2024-02-01 08:30:00'),
|
||||
settlementAmount: null,
|
||||
remainingAmount: 600000.00,
|
||||
settlementNotes: '尚未结清',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024007',
|
||||
contractNumber: 'LOAN2024007',
|
||||
productName: '肉鸡养殖贷',
|
||||
customerName: '吴九',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4407XXXXXXXXXXXXXX',
|
||||
assetType: '肉鸡',
|
||||
assetQuantity: 15000,
|
||||
totalRepaymentPeriods: 9,
|
||||
settlementStatus: 'settled',
|
||||
settlementDate: '2024-02-05',
|
||||
importTime: new Date('2024-02-05 14:15:00'),
|
||||
settlementAmount: 400000.00,
|
||||
remainingAmount: 0.00,
|
||||
settlementNotes: '贷款已全部结清',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024008',
|
||||
contractNumber: 'LOAN2024008',
|
||||
productName: '肉猪养殖贷',
|
||||
customerName: '郑十',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4408XXXXXXXXXXXXXX',
|
||||
assetType: '肉猪',
|
||||
assetQuantity: 800,
|
||||
totalRepaymentPeriods: 18,
|
||||
settlementStatus: 'partial',
|
||||
settlementDate: '2024-02-10',
|
||||
importTime: new Date('2024-02-10 10:00:00'),
|
||||
settlementAmount: 250000.00,
|
||||
remainingAmount: 350000.00,
|
||||
settlementNotes: '部分结清,剩余35万待还',
|
||||
createdBy: user.id
|
||||
}
|
||||
]
|
||||
|
||||
// 检查是否已存在数据
|
||||
const existingCount = await CompletedSupervision.count()
|
||||
if (existingCount > 0) {
|
||||
console.log(`监管任务已结项表已有 ${existingCount} 条数据,跳过创建`)
|
||||
return
|
||||
}
|
||||
|
||||
// 批量创建监管任务已结项
|
||||
await CompletedSupervision.bulkCreate(completedSupervisions)
|
||||
|
||||
console.log(`✅ 成功创建 ${completedSupervisions.length} 条监管任务已结项测试数据`)
|
||||
|
||||
// 显示创建的数据
|
||||
const createdTasks = await CompletedSupervision.findAll({
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
console.log('创建的监管任务已结项数据:')
|
||||
createdTasks.forEach((task, index) => {
|
||||
console.log(`${index + 1}. ${task.applicationNumber} - ${task.customerName} - ${task.settlementStatus}`)
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建监管任务已结项测试数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
seedCompletedSupervisions()
|
||||
.then(() => {
|
||||
console.log('监管任务已结项测试数据创建完成')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('脚本执行失败:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = seedCompletedSupervisions
|
||||
207
bank-backend/scripts/seed-installation-tasks.js
Normal file
207
bank-backend/scripts/seed-installation-tasks.js
Normal file
@@ -0,0 +1,207 @@
|
||||
const { InstallationTask, User } = require('../models')
|
||||
|
||||
async function seedInstallationTasks() {
|
||||
try {
|
||||
console.log('开始创建待安装任务测试数据...')
|
||||
|
||||
// 获取第一个用户作为创建者
|
||||
const user = await User.findOne()
|
||||
if (!user) {
|
||||
console.error('未找到用户,请先创建用户')
|
||||
return
|
||||
}
|
||||
|
||||
const installationTasks = [
|
||||
{
|
||||
applicationNumber: 'APP2024001',
|
||||
contractNumber: 'LOAN2024001',
|
||||
productName: '生猪养殖贷',
|
||||
customerName: '张三',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4401XXXXXXXXXXXXXX',
|
||||
assetType: '生猪',
|
||||
equipmentToInstall: '耳标设备',
|
||||
installationStatus: 'pending',
|
||||
taskGenerationTime: new Date('2024-01-15 10:30:00'),
|
||||
completionTime: null,
|
||||
installationNotes: '需要安装耳标设备用于生猪监管',
|
||||
installerName: '李安装',
|
||||
installerPhone: '13800138001',
|
||||
installationAddress: '广东省广州市天河区某养殖场',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024002',
|
||||
contractNumber: 'LOAN2024002',
|
||||
productName: '肉牛养殖贷',
|
||||
customerName: '李四',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4402XXXXXXXXXXXXXX',
|
||||
assetType: '肉牛',
|
||||
equipmentToInstall: '项圈设备',
|
||||
installationStatus: 'in-progress',
|
||||
taskGenerationTime: new Date('2024-01-16 14:20:00'),
|
||||
completionTime: null,
|
||||
installationNotes: '安装项圈设备用于肉牛定位监管',
|
||||
installerName: '王安装',
|
||||
installerPhone: '13800138002',
|
||||
installationAddress: '广东省深圳市南山区某养殖场',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024003',
|
||||
contractNumber: 'LOAN2024003',
|
||||
productName: '蛋鸡养殖贷',
|
||||
customerName: '王五',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4403XXXXXXXXXXXXXX',
|
||||
assetType: '蛋鸡',
|
||||
equipmentToInstall: '监控设备',
|
||||
installationStatus: 'completed',
|
||||
taskGenerationTime: new Date('2024-01-10 09:15:00'),
|
||||
completionTime: new Date('2024-01-20 16:30:00'),
|
||||
installationNotes: '监控设备已安装完成,用于蛋鸡养殖监管',
|
||||
installerName: '赵安装',
|
||||
installerPhone: '13800138003',
|
||||
installationAddress: '广东省佛山市顺德区某养殖场',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024004',
|
||||
contractNumber: 'LOAN2024004',
|
||||
productName: '肉羊养殖贷',
|
||||
customerName: '赵六',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4404XXXXXXXXXXXXXX',
|
||||
assetType: '肉羊',
|
||||
equipmentToInstall: '耳标设备',
|
||||
installationStatus: 'pending',
|
||||
taskGenerationTime: new Date('2024-01-18 11:45:00'),
|
||||
completionTime: null,
|
||||
installationNotes: '需要安装耳标设备用于肉羊监管',
|
||||
installerName: '钱安装',
|
||||
installerPhone: '13800138004',
|
||||
installationAddress: '广东省东莞市某养殖场',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024005',
|
||||
contractNumber: 'LOAN2024005',
|
||||
productName: '奶牛养殖贷',
|
||||
customerName: '孙七',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4405XXXXXXXXXXXXXX',
|
||||
assetType: '奶牛',
|
||||
equipmentToInstall: '项圈设备',
|
||||
installationStatus: 'failed',
|
||||
taskGenerationTime: new Date('2024-01-12 08:30:00'),
|
||||
completionTime: null,
|
||||
installationNotes: '设备安装失败,需要重新安排安装',
|
||||
installerName: '周安装',
|
||||
installerPhone: '13800138005',
|
||||
installationAddress: '广东省中山市某养殖场',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024006',
|
||||
contractNumber: 'LOAN2024006',
|
||||
productName: '肉鸭养殖贷',
|
||||
customerName: '周八',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4406XXXXXXXXXXXXXX',
|
||||
assetType: '肉鸭',
|
||||
equipmentToInstall: '监控设备',
|
||||
installationStatus: 'in-progress',
|
||||
taskGenerationTime: new Date('2024-01-20 15:20:00'),
|
||||
completionTime: null,
|
||||
installationNotes: '正在安装监控设备用于肉鸭养殖监管',
|
||||
installerName: '吴安装',
|
||||
installerPhone: '13800138006',
|
||||
installationAddress: '广东省江门市某养殖场',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024007',
|
||||
contractNumber: 'LOAN2024007',
|
||||
productName: '肉鸡养殖贷',
|
||||
customerName: '吴九',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4407XXXXXXXXXXXXXX',
|
||||
assetType: '肉鸡',
|
||||
equipmentToInstall: '耳标设备',
|
||||
installationStatus: 'completed',
|
||||
taskGenerationTime: new Date('2024-01-08 13:10:00'),
|
||||
completionTime: new Date('2024-01-22 10:15:00'),
|
||||
installationNotes: '耳标设备安装完成,肉鸡监管系统正常运行',
|
||||
installerName: '郑安装',
|
||||
installerPhone: '13800138007',
|
||||
installationAddress: '广东省惠州市某养殖场',
|
||||
createdBy: user.id
|
||||
},
|
||||
{
|
||||
applicationNumber: 'APP2024008',
|
||||
contractNumber: 'LOAN2024008',
|
||||
productName: '肉猪养殖贷',
|
||||
customerName: '郑十',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '4408XXXXXXXXXXXXXX',
|
||||
assetType: '肉猪',
|
||||
equipmentToInstall: '项圈设备',
|
||||
installationStatus: 'pending',
|
||||
taskGenerationTime: new Date('2024-01-25 09:00:00'),
|
||||
completionTime: null,
|
||||
installationNotes: '待安装项圈设备用于肉猪监管',
|
||||
installerName: '冯安装',
|
||||
installerPhone: '13800138008',
|
||||
installationAddress: '广东省汕头市某养殖场',
|
||||
createdBy: user.id
|
||||
}
|
||||
]
|
||||
|
||||
// 检查是否已存在数据
|
||||
const existingCount = await InstallationTask.count()
|
||||
if (existingCount > 0) {
|
||||
console.log(`待安装任务表已有 ${existingCount} 条数据,跳过创建`)
|
||||
return
|
||||
}
|
||||
|
||||
// 批量创建待安装任务
|
||||
await InstallationTask.bulkCreate(installationTasks)
|
||||
|
||||
console.log(`✅ 成功创建 ${installationTasks.length} 条待安装任务测试数据`)
|
||||
|
||||
// 显示创建的数据
|
||||
const createdTasks = await InstallationTask.findAll({
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: 'creator',
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
console.log('创建的待安装任务数据:')
|
||||
createdTasks.forEach((task, index) => {
|
||||
console.log(`${index + 1}. ${task.applicationNumber} - ${task.customerName} - ${task.installationStatus}`)
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建待安装任务测试数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
seedInstallationTasks()
|
||||
.then(() => {
|
||||
console.log('待安装任务测试数据创建完成')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('脚本执行失败:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = seedInstallationTasks
|
||||
260
bank-backend/scripts/seed-loan-applications.js
Normal file
260
bank-backend/scripts/seed-loan-applications.js
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* 贷款申请测试数据脚本
|
||||
* @file seed-loan-applications.js
|
||||
* @description 为银行系统添加贷款申请测试数据
|
||||
*/
|
||||
const { sequelize, LoanApplication, AuditRecord, User } = require('../models');
|
||||
|
||||
async function seedLoanApplications() {
|
||||
try {
|
||||
console.log('开始添加贷款申请测试数据...');
|
||||
|
||||
// 获取admin用户(作为申请人和审核人)
|
||||
const adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!adminUser) {
|
||||
console.log('❌ 未找到admin用户,请先创建用户');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空现有数据
|
||||
await AuditRecord.destroy({ where: {} });
|
||||
await LoanApplication.destroy({ where: {} });
|
||||
console.log('✅ 清空现有贷款申请数据');
|
||||
|
||||
// 创建贷款申请测试数据(参考前端页面的模拟数据)
|
||||
const applications = [
|
||||
{
|
||||
applicationNumber: '20240325123703784',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '刘超',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
amount: 100000.00,
|
||||
status: 'pending_review',
|
||||
type: 'personal',
|
||||
term: 12,
|
||||
interestRate: 3.90,
|
||||
phone: '13800138000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '申请资金用于购买牛只扩大养殖规模',
|
||||
applicationTime: new Date('2024-03-25 12:37:03'),
|
||||
applicantId: adminUser.id
|
||||
},
|
||||
{
|
||||
applicationNumber: '20240229110801968',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '刘超',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
amount: 100000.00,
|
||||
status: 'verification_pending',
|
||||
type: 'mortgage',
|
||||
term: 24,
|
||||
interestRate: 4.20,
|
||||
phone: '13900139000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '以畜禽活体作为抵押物申请贷款',
|
||||
applicationTime: new Date('2024-02-29 11:08:01'),
|
||||
applicantId: adminUser.id,
|
||||
approvedBy: adminUser.id,
|
||||
approvedTime: new Date('2024-03-01 10:15:00')
|
||||
},
|
||||
{
|
||||
applicationNumber: '20240229105806431',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '刘超',
|
||||
borrowerIdNumber: '511***********3017',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '10头',
|
||||
amount: 100000.00,
|
||||
status: 'pending_binding',
|
||||
type: 'personal',
|
||||
term: 18,
|
||||
interestRate: 3.75,
|
||||
phone: '13700137000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '待绑定相关资产信息',
|
||||
applicationTime: new Date('2024-02-29 10:58:06'),
|
||||
applicantId: adminUser.id
|
||||
},
|
||||
{
|
||||
applicationNumber: '20240315085642123',
|
||||
productName: '农商银行养殖贷',
|
||||
farmerName: '张伟',
|
||||
borrowerName: '张伟',
|
||||
borrowerIdNumber: '621***********2156',
|
||||
assetType: '猪',
|
||||
applicationQuantity: '50头',
|
||||
amount: 250000.00,
|
||||
status: 'approved',
|
||||
type: 'business',
|
||||
term: 36,
|
||||
interestRate: 4.50,
|
||||
phone: '13600136000',
|
||||
purpose: '扩大养猪规模',
|
||||
remark: '已审核通过,准备放款',
|
||||
applicationTime: new Date('2024-03-15 08:56:42'),
|
||||
applicantId: adminUser.id,
|
||||
approvedBy: adminUser.id,
|
||||
approvedTime: new Date('2024-03-16 14:20:00')
|
||||
},
|
||||
{
|
||||
applicationNumber: '20240310142355789',
|
||||
productName: '建设银行农户小额贷款',
|
||||
farmerName: '李明',
|
||||
borrowerName: '李明',
|
||||
borrowerIdNumber: '371***********4578',
|
||||
assetType: '羊',
|
||||
applicationQuantity: '30只',
|
||||
amount: 80000.00,
|
||||
status: 'rejected',
|
||||
type: 'personal',
|
||||
term: 12,
|
||||
interestRate: 4.10,
|
||||
phone: '13500135000',
|
||||
purpose: '养羊创业',
|
||||
remark: '资质不符合要求,已拒绝',
|
||||
applicationTime: new Date('2024-03-10 14:23:55'),
|
||||
applicantId: adminUser.id,
|
||||
rejectedBy: adminUser.id,
|
||||
rejectedTime: new Date('2024-03-11 09:30:00'),
|
||||
rejectionReason: '申请人征信记录不良,不符合放款条件'
|
||||
}
|
||||
];
|
||||
|
||||
// 批量创建申请
|
||||
const createdApplications = await LoanApplication.bulkCreate(applications);
|
||||
console.log(`✅ 成功创建${createdApplications.length}个贷款申请`);
|
||||
|
||||
// 为每个申请创建审核记录
|
||||
const auditRecords = [];
|
||||
|
||||
// 第一个申请:只有提交记录
|
||||
auditRecords.push({
|
||||
applicationId: createdApplications[0].id,
|
||||
action: 'submit',
|
||||
auditor: '刘超',
|
||||
auditorId: adminUser.id,
|
||||
comment: '提交申请',
|
||||
auditTime: new Date('2024-03-25 12:37:03'),
|
||||
newStatus: 'pending_review'
|
||||
});
|
||||
|
||||
// 第二个申请:提交 + 审核通过
|
||||
auditRecords.push(
|
||||
{
|
||||
applicationId: createdApplications[1].id,
|
||||
action: 'submit',
|
||||
auditor: '刘超',
|
||||
auditorId: adminUser.id,
|
||||
comment: '提交申请',
|
||||
auditTime: new Date('2024-02-29 11:08:01'),
|
||||
newStatus: 'pending_review'
|
||||
},
|
||||
{
|
||||
applicationId: createdApplications[1].id,
|
||||
action: 'approve',
|
||||
auditor: '王经理',
|
||||
auditorId: adminUser.id,
|
||||
comment: '资料齐全,符合条件,同意放款',
|
||||
auditTime: new Date('2024-03-01 10:15:00'),
|
||||
previousStatus: 'pending_review',
|
||||
newStatus: 'verification_pending'
|
||||
}
|
||||
);
|
||||
|
||||
// 第三个申请:只有提交记录
|
||||
auditRecords.push({
|
||||
applicationId: createdApplications[2].id,
|
||||
action: 'submit',
|
||||
auditor: '刘超',
|
||||
auditorId: adminUser.id,
|
||||
comment: '提交申请',
|
||||
auditTime: new Date('2024-02-29 10:58:06'),
|
||||
newStatus: 'pending_review'
|
||||
});
|
||||
|
||||
// 第四个申请:提交 + 审核通过
|
||||
auditRecords.push(
|
||||
{
|
||||
applicationId: createdApplications[3].id,
|
||||
action: 'submit',
|
||||
auditor: '张伟',
|
||||
auditorId: adminUser.id,
|
||||
comment: '提交申请',
|
||||
auditTime: new Date('2024-03-15 08:56:42'),
|
||||
newStatus: 'pending_review'
|
||||
},
|
||||
{
|
||||
applicationId: createdApplications[3].id,
|
||||
action: 'approve',
|
||||
auditor: '李总监',
|
||||
auditorId: adminUser.id,
|
||||
comment: '经营状况良好,养殖经验丰富,批准贷款',
|
||||
auditTime: new Date('2024-03-16 14:20:00'),
|
||||
previousStatus: 'pending_review',
|
||||
newStatus: 'approved'
|
||||
}
|
||||
);
|
||||
|
||||
// 第五个申请:提交 + 审核拒绝
|
||||
auditRecords.push(
|
||||
{
|
||||
applicationId: createdApplications[4].id,
|
||||
action: 'submit',
|
||||
auditor: '李明',
|
||||
auditorId: adminUser.id,
|
||||
comment: '提交申请',
|
||||
auditTime: new Date('2024-03-10 14:23:55'),
|
||||
newStatus: 'pending_review'
|
||||
},
|
||||
{
|
||||
applicationId: createdApplications[4].id,
|
||||
action: 'reject',
|
||||
auditor: '风控部门',
|
||||
auditorId: adminUser.id,
|
||||
comment: '申请人征信记录不良,不符合放款条件',
|
||||
auditTime: new Date('2024-03-11 09:30:00'),
|
||||
previousStatus: 'pending_review',
|
||||
newStatus: 'rejected'
|
||||
}
|
||||
);
|
||||
|
||||
// 批量创建审核记录
|
||||
await AuditRecord.bulkCreate(auditRecords);
|
||||
console.log(`✅ 成功创建${auditRecords.length}条审核记录`);
|
||||
|
||||
console.log('\n📊 贷款申请数据统计:');
|
||||
console.log('- 待初审:1个申请');
|
||||
console.log('- 核验待放款:1个申请');
|
||||
console.log('- 待绑定:1个申请');
|
||||
console.log('- 已通过:1个申请');
|
||||
console.log('- 已拒绝:1个申请');
|
||||
console.log('- 总申请金额:630,000.00元');
|
||||
|
||||
console.log('\n🎉 贷款申请测试数据添加完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 添加贷款申请测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
seedLoanApplications()
|
||||
.then(() => {
|
||||
console.log('✅ 脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedLoanApplications;
|
||||
289
bank-backend/scripts/seed-loan-contracts.js
Normal file
289
bank-backend/scripts/seed-loan-contracts.js
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* 贷款合同测试数据脚本
|
||||
* @file seed-loan-contracts.js
|
||||
* @description 为银行系统添加贷款合同测试数据
|
||||
*/
|
||||
const { sequelize, LoanContract, User } = require('../models');
|
||||
|
||||
async function seedLoanContracts() {
|
||||
try {
|
||||
console.log('开始添加贷款合同测试数据...');
|
||||
|
||||
// 获取admin用户(作为创建人)
|
||||
const adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!adminUser) {
|
||||
console.log('❌ 未找到admin用户,请先创建用户');
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空现有数据
|
||||
await LoanContract.destroy({ where: {} });
|
||||
console.log('✅ 清空现有贷款合同数据');
|
||||
|
||||
// 创建贷款合同测试数据(参考图片中的数据结构)
|
||||
const contracts = [
|
||||
{
|
||||
contractNumber: 'HT20231131123456789',
|
||||
applicationNumber: '20231131123456789',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '敖日布仁琴',
|
||||
borrowerName: '敖日布仁琴',
|
||||
borrowerIdNumber: '150***********4856',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '36头',
|
||||
amount: 500000.00,
|
||||
paidAmount: 0,
|
||||
status: 'active',
|
||||
type: 'livestock_collateral',
|
||||
term: 24,
|
||||
interestRate: 4.20,
|
||||
phone: '13800138000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '畜禽活体抵押贷款',
|
||||
contractTime: new Date('2023-11-31 12:34:56'),
|
||||
disbursementTime: new Date('2023-12-01 10:00:00'),
|
||||
maturityTime: new Date('2025-12-01 10:00:00'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231201123456790',
|
||||
applicationNumber: '20231201123456790',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '张伟',
|
||||
borrowerName: '张伟',
|
||||
borrowerIdNumber: '150***********4857',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '25头',
|
||||
amount: 350000.00,
|
||||
paidAmount: 50000.00,
|
||||
status: 'active',
|
||||
type: 'livestock_collateral',
|
||||
term: 18,
|
||||
interestRate: 4.50,
|
||||
phone: '13900139000',
|
||||
purpose: '扩大养殖规模',
|
||||
remark: '工商银行畜禽活体抵押',
|
||||
contractTime: new Date('2023-12-01 14:20:30'),
|
||||
disbursementTime: new Date('2023-12-02 09:30:00'),
|
||||
maturityTime: new Date('2025-06-02 09:30:00'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231202123456791',
|
||||
applicationNumber: '20231202123456791',
|
||||
productName: '惠农贷',
|
||||
farmerName: '李明',
|
||||
borrowerName: '李明',
|
||||
borrowerIdNumber: '150***********4858',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '20头',
|
||||
amount: 280000.00,
|
||||
paidAmount: 0,
|
||||
status: 'pending',
|
||||
type: 'farmer_loan',
|
||||
term: 12,
|
||||
interestRate: 3.90,
|
||||
phone: '13700137000',
|
||||
purpose: '惠农贷款',
|
||||
remark: '惠农贷产品',
|
||||
contractTime: new Date('2023-12-02 16:45:12'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231203123456792',
|
||||
applicationNumber: '20231203123456792',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '王强',
|
||||
borrowerName: '王强',
|
||||
borrowerIdNumber: '150***********4859',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '30头',
|
||||
amount: 420000.00,
|
||||
paidAmount: 420000.00,
|
||||
status: 'completed',
|
||||
type: 'livestock_collateral',
|
||||
term: 24,
|
||||
interestRate: 4.20,
|
||||
phone: '13600136000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '已完成还款',
|
||||
contractTime: new Date('2023-12-03 11:20:45'),
|
||||
disbursementTime: new Date('2023-12-04 08:00:00'),
|
||||
maturityTime: new Date('2025-12-04 08:00:00'),
|
||||
completedTime: new Date('2024-11-15 14:30:00'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231204123456793',
|
||||
applicationNumber: '20231204123456793',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '赵敏',
|
||||
borrowerName: '赵敏',
|
||||
borrowerIdNumber: '150***********4860',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '15头',
|
||||
amount: 200000.00,
|
||||
paidAmount: 0,
|
||||
status: 'defaulted',
|
||||
type: 'livestock_collateral',
|
||||
term: 18,
|
||||
interestRate: 4.50,
|
||||
phone: '13500135000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '违约状态',
|
||||
contractTime: new Date('2023-12-04 13:15:30'),
|
||||
disbursementTime: new Date('2023-12-05 10:00:00'),
|
||||
maturityTime: new Date('2025-06-05 10:00:00'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231205123456794',
|
||||
applicationNumber: '20231205123456794',
|
||||
productName: '惠农贷',
|
||||
farmerName: '刘超',
|
||||
borrowerName: '刘超',
|
||||
borrowerIdNumber: '150***********4861',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '22头',
|
||||
amount: 320000.00,
|
||||
paidAmount: 80000.00,
|
||||
status: 'active',
|
||||
type: 'farmer_loan',
|
||||
term: 24,
|
||||
interestRate: 3.90,
|
||||
phone: '13400134000',
|
||||
purpose: '惠农贷款',
|
||||
remark: '惠农贷产品',
|
||||
contractTime: new Date('2023-12-05 15:30:20'),
|
||||
disbursementTime: new Date('2023-12-06 09:00:00'),
|
||||
maturityTime: new Date('2025-12-06 09:00:00'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231206123456795',
|
||||
applicationNumber: '20231206123456795',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '陈华',
|
||||
borrowerName: '陈华',
|
||||
borrowerIdNumber: '150***********4862',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '28头',
|
||||
amount: 380000.00,
|
||||
paidAmount: 0,
|
||||
status: 'active',
|
||||
type: 'livestock_collateral',
|
||||
term: 30,
|
||||
interestRate: 4.20,
|
||||
phone: '13300133000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '长期贷款',
|
||||
contractTime: new Date('2023-12-06 10:45:15'),
|
||||
disbursementTime: new Date('2023-12-07 11:00:00'),
|
||||
maturityTime: new Date('2026-06-07 11:00:00'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231207123456796',
|
||||
applicationNumber: '20231207123456796',
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '孙丽',
|
||||
borrowerName: '孙丽',
|
||||
borrowerIdNumber: '150***********4863',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '18头',
|
||||
amount: 250000.00,
|
||||
paidAmount: 250000.00,
|
||||
status: 'completed',
|
||||
type: 'livestock_collateral',
|
||||
term: 12,
|
||||
interestRate: 4.50,
|
||||
phone: '13200132000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '短期贷款已完成',
|
||||
contractTime: new Date('2023-12-07 14:20:10'),
|
||||
disbursementTime: new Date('2023-12-08 08:30:00'),
|
||||
maturityTime: new Date('2024-12-08 08:30:00'),
|
||||
completedTime: new Date('2024-10-15 16:45:00'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231208123456797',
|
||||
applicationNumber: '20231208123456797',
|
||||
productName: '惠农贷',
|
||||
farmerName: '周杰',
|
||||
borrowerName: '周杰',
|
||||
borrowerIdNumber: '150***********4864',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '24头',
|
||||
amount: 360000.00,
|
||||
paidAmount: 0,
|
||||
status: 'cancelled',
|
||||
type: 'farmer_loan',
|
||||
term: 18,
|
||||
interestRate: 3.90,
|
||||
phone: '13100131000',
|
||||
purpose: '惠农贷款',
|
||||
remark: '已取消',
|
||||
contractTime: new Date('2023-12-08 16:10:25'),
|
||||
createdBy: adminUser.id
|
||||
},
|
||||
{
|
||||
contractNumber: 'HT20231209123456798',
|
||||
applicationNumber: '20231209123456798',
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
farmerName: '吴刚',
|
||||
borrowerName: '吴刚',
|
||||
borrowerIdNumber: '150***********4865',
|
||||
assetType: '牛',
|
||||
applicationQuantity: '32头',
|
||||
amount: 450000.00,
|
||||
paidAmount: 150000.00,
|
||||
status: 'active',
|
||||
type: 'livestock_collateral',
|
||||
term: 36,
|
||||
interestRate: 4.20,
|
||||
phone: '13000130000',
|
||||
purpose: '养殖贷款',
|
||||
remark: '长期贷款',
|
||||
contractTime: new Date('2023-12-09 12:30:40'),
|
||||
disbursementTime: new Date('2023-12-10 10:15:00'),
|
||||
maturityTime: new Date('2026-12-10 10:15:00'),
|
||||
createdBy: adminUser.id
|
||||
}
|
||||
];
|
||||
|
||||
// 批量创建合同
|
||||
const createdContracts = await LoanContract.bulkCreate(contracts);
|
||||
console.log(`✅ 成功创建${createdContracts.length}个贷款合同`);
|
||||
|
||||
console.log('\n📊 贷款合同数据统计:');
|
||||
console.log('- 已放款:6个合同');
|
||||
console.log('- 待放款:1个合同');
|
||||
console.log('- 已完成:2个合同');
|
||||
console.log('- 违约:1个合同');
|
||||
console.log('- 已取消:1个合同');
|
||||
console.log('- 总合同金额:3,410,000.00元');
|
||||
console.log('- 已还款金额:520,000.00元');
|
||||
console.log('- 剩余还款金额:2,890,000.00元');
|
||||
|
||||
console.log('\n🎉 贷款合同测试数据添加完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 添加贷款合同测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
seedLoanContracts()
|
||||
.then(() => {
|
||||
console.log('✅ 脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedLoanContracts;
|
||||
145
bank-backend/scripts/seed-loan-products.js
Normal file
145
bank-backend/scripts/seed-loan-products.js
Normal file
@@ -0,0 +1,145 @@
|
||||
const { sequelize, LoanProduct, User } = require('../models');
|
||||
|
||||
async function seedLoanProducts() {
|
||||
try {
|
||||
console.log('开始添加贷款商品测试数据...');
|
||||
|
||||
// 查找管理员用户
|
||||
const adminUser = await User.findOne({
|
||||
where: { username: 'admin' }
|
||||
});
|
||||
|
||||
if (!adminUser) {
|
||||
console.error('未找到管理员用户,请先创建管理员用户');
|
||||
return;
|
||||
}
|
||||
|
||||
const loanProducts = [
|
||||
{
|
||||
productName: '惠农贷',
|
||||
loanAmount: '50000~5000000元',
|
||||
loanTerm: 24,
|
||||
interestRate: 3.90,
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 16,
|
||||
supervisionCustomers: 11,
|
||||
completedCustomers: 5,
|
||||
onSaleStatus: true,
|
||||
productDescription: '专为农户设计的贷款产品,支持农业生产和经营',
|
||||
applicationRequirements: '1. 具有完全民事行为能力的自然人;2. 有稳定的收入来源;3. 信用记录良好',
|
||||
requiredDocuments: '身份证、户口本、收入证明、银行流水',
|
||||
approvalProcess: '申请→初审→实地调查→审批→放款',
|
||||
riskLevel: 'LOW',
|
||||
minLoanAmount: 50000,
|
||||
maxLoanAmount: 5000000,
|
||||
createdBy: adminUser.id,
|
||||
updatedBy: adminUser.id
|
||||
},
|
||||
{
|
||||
productName: '中国工商银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: 12,
|
||||
interestRate: 4.70,
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 10,
|
||||
supervisionCustomers: 5,
|
||||
completedCustomers: 5,
|
||||
onSaleStatus: true,
|
||||
productDescription: '以畜禽活体作为抵押物的贷款产品',
|
||||
applicationRequirements: '1. 拥有符合条件的畜禽;2. 提供养殖证明;3. 通过银行评估',
|
||||
requiredDocuments: '身份证、养殖证明、畜禽检疫证明、银行流水',
|
||||
approvalProcess: '申请→畜禽评估→抵押登记→审批→放款',
|
||||
riskLevel: 'MEDIUM',
|
||||
minLoanAmount: 200000,
|
||||
maxLoanAmount: 1000000,
|
||||
createdBy: adminUser.id,
|
||||
updatedBy: adminUser.id
|
||||
},
|
||||
{
|
||||
productName: '中国银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: 12,
|
||||
interestRate: 4.60,
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 2,
|
||||
supervisionCustomers: 2,
|
||||
completedCustomers: 0,
|
||||
onSaleStatus: true,
|
||||
productDescription: '中国银行推出的畜禽活体抵押贷款产品',
|
||||
applicationRequirements: '1. 符合银行信贷政策;2. 畜禽数量达到要求;3. 提供担保',
|
||||
requiredDocuments: '身份证、养殖许可证、畜禽数量证明、担保材料',
|
||||
approvalProcess: '申请→资料审核→现场调查→风险评估→审批→放款',
|
||||
riskLevel: 'MEDIUM',
|
||||
minLoanAmount: 200000,
|
||||
maxLoanAmount: 1000000,
|
||||
createdBy: adminUser.id,
|
||||
updatedBy: adminUser.id
|
||||
},
|
||||
{
|
||||
productName: '中国农业银行扎旗支行"畜禽活体抵押"',
|
||||
loanAmount: '200000~1000000元',
|
||||
loanTerm: 12,
|
||||
interestRate: 4.80,
|
||||
serviceArea: '内蒙古自治区:通辽市',
|
||||
servicePhone: '15004901368',
|
||||
totalCustomers: 26,
|
||||
supervisionCustomers: 24,
|
||||
completedCustomers: 2,
|
||||
onSaleStatus: true,
|
||||
productDescription: '农业银行专门为养殖户设计的贷款产品',
|
||||
applicationRequirements: '1. 从事养殖业满2年;2. 畜禽存栏量达标;3. 有还款能力',
|
||||
requiredDocuments: '身份证、养殖场证明、畜禽存栏证明、收入证明',
|
||||
approvalProcess: '申请→资格审核→现场勘查→风险评估→审批→放款',
|
||||
riskLevel: 'HIGH',
|
||||
minLoanAmount: 200000,
|
||||
maxLoanAmount: 1000000,
|
||||
createdBy: adminUser.id,
|
||||
updatedBy: adminUser.id
|
||||
}
|
||||
];
|
||||
|
||||
// 检查是否已存在数据
|
||||
const existingCount = await LoanProduct.count();
|
||||
if (existingCount > 0) {
|
||||
console.log(`数据库中已存在 ${existingCount} 条贷款商品数据,跳过添加`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 批量创建贷款商品
|
||||
await LoanProduct.bulkCreate(loanProducts);
|
||||
|
||||
console.log(`成功添加 ${loanProducts.length} 条贷款商品测试数据`);
|
||||
|
||||
// 显示添加的数据
|
||||
const createdProducts = await LoanProduct.findAll({
|
||||
attributes: ['id', 'productName', 'loanAmount', 'interestRate', 'onSaleStatus']
|
||||
});
|
||||
|
||||
console.log('添加的贷款商品数据:');
|
||||
createdProducts.forEach(product => {
|
||||
console.log(`- ${product.productName}: ${product.loanAmount} (利率: ${product.interestRate}%, 状态: ${product.onSaleStatus ? '在售' : '停售'})`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('添加贷款商品测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
seedLoanProducts()
|
||||
.then(() => {
|
||||
console.log('贷款商品测试数据添加完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('添加贷款商品测试数据失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedLoanProducts;
|
||||
39
bank-backend/scripts/setup-completed-supervisions.js
Normal file
39
bank-backend/scripts/setup-completed-supervisions.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const { sequelize, CompletedSupervision, User } = require('../models')
|
||||
const seedCompletedSupervisions = require('./seed-completed-supervisions')
|
||||
|
||||
async function setupCompletedSupervisions() {
|
||||
try {
|
||||
console.log('开始设置监管任务已结项...')
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate()
|
||||
console.log('✅ 数据库连接成功')
|
||||
|
||||
// 同步模型(创建表)
|
||||
await sequelize.sync({ force: false })
|
||||
console.log('✅ 数据库表同步完成')
|
||||
|
||||
// 创建测试数据
|
||||
await seedCompletedSupervisions()
|
||||
console.log('✅ 监管任务已结项设置完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('设置监管任务已结项失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
setupCompletedSupervisions()
|
||||
.then(() => {
|
||||
console.log('监管任务已结项设置完成')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('脚本执行失败:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = setupCompletedSupervisions
|
||||
39
bank-backend/scripts/setup-installation-tasks.js
Normal file
39
bank-backend/scripts/setup-installation-tasks.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const { sequelize, InstallationTask, User } = require('../models')
|
||||
const seedInstallationTasks = require('./seed-installation-tasks')
|
||||
|
||||
async function setupInstallationTasks() {
|
||||
try {
|
||||
console.log('开始设置待安装任务...')
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate()
|
||||
console.log('✅ 数据库连接成功')
|
||||
|
||||
// 同步模型(创建表)
|
||||
await sequelize.sync({ force: false })
|
||||
console.log('✅ 数据库表同步完成')
|
||||
|
||||
// 创建测试数据
|
||||
await seedInstallationTasks()
|
||||
console.log('✅ 待安装任务设置完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('设置待安装任务失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
setupInstallationTasks()
|
||||
.then(() => {
|
||||
console.log('待安装任务设置完成')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('脚本执行失败:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = setupInstallationTasks
|
||||
42
bank-backend/scripts/setup-loan-products.js
Normal file
42
bank-backend/scripts/setup-loan-products.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const { sequelize, LoanProduct } = require('../models');
|
||||
const seedLoanProducts = require('./seed-loan-products');
|
||||
|
||||
async function setupLoanProducts() {
|
||||
try {
|
||||
console.log('开始设置贷款商品表...');
|
||||
|
||||
// 测试数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('数据库连接成功');
|
||||
|
||||
// 同步模型(创建表)
|
||||
await sequelize.sync({ force: false });
|
||||
console.log('贷款商品表同步成功');
|
||||
|
||||
// 添加测试数据
|
||||
await seedLoanProducts();
|
||||
|
||||
console.log('贷款商品设置完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('设置贷款商品失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
setupLoanProducts()
|
||||
.then(() => {
|
||||
console.log('贷款商品设置完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('设置贷款商品失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = setupLoanProducts;
|
||||
@@ -76,6 +76,10 @@ app.use('/api/loan-products', require('./routes/loanProducts'));
|
||||
app.use('/api/employees', require('./routes/employees'));
|
||||
app.use('/api/projects', require('./routes/projects'));
|
||||
app.use('/api/supervision-tasks', require('./routes/supervisionTasks'));
|
||||
app.use('/api/installation-tasks', require('./routes/installationTasks'));
|
||||
app.use('/api/completed-supervisions', require('./routes/completedSupervisions'));
|
||||
app.use('/api/loan-applications', require('./routes/loanApplications'));
|
||||
app.use('/api/loan-contracts', require('./routes/loanContracts'));
|
||||
// app.use('/api/reports', require('./routes/reports'));
|
||||
|
||||
// 根路径
|
||||
|
||||
94
bank-backend/test-actual-data.js
Normal file
94
bank-backend/test-actual-data.js
Normal file
@@ -0,0 +1,94 @@
|
||||
const { User } = require('./models');
|
||||
const { sequelize } = require('./config/database');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testActualData() {
|
||||
try {
|
||||
console.log('=== 测试实际数据库数据 ===\n');
|
||||
|
||||
// 1. 直接查询数据库
|
||||
console.log('1. 直接查询数据库...');
|
||||
const [results] = await sequelize.query(
|
||||
'SELECT id, username, password, status FROM bank_users WHERE username = ?',
|
||||
{
|
||||
replacements: ['admin'],
|
||||
type: sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
|
||||
if (!results) {
|
||||
console.log('❌ 数据库中未找到admin用户');
|
||||
console.log('查询结果:', results);
|
||||
return;
|
||||
}
|
||||
|
||||
const dbUser = results;
|
||||
console.log('数据库中的用户数据:');
|
||||
console.log('查询结果:', results);
|
||||
console.log('结果长度:', results.length);
|
||||
if (dbUser) {
|
||||
console.log('ID:', dbUser.id);
|
||||
console.log('用户名:', dbUser.username);
|
||||
console.log('状态:', dbUser.status);
|
||||
console.log('密码哈希:', dbUser.password);
|
||||
console.log('密码哈希长度:', dbUser.password ? dbUser.password.length : 0);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// 2. 使用Sequelize查询
|
||||
console.log('2. 使用Sequelize查询...');
|
||||
const sequelizeUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (sequelizeUser) {
|
||||
console.log('Sequelize查询到的用户数据:');
|
||||
console.log('ID:', sequelizeUser.id);
|
||||
console.log('用户名:', sequelizeUser.username);
|
||||
console.log('状态:', sequelizeUser.status);
|
||||
console.log('密码哈希:', sequelizeUser.password);
|
||||
console.log('密码哈希长度:', sequelizeUser.password ? sequelizeUser.password.length : 0);
|
||||
console.log('');
|
||||
|
||||
// 3. 比较两种查询结果
|
||||
console.log('3. 比较两种查询结果:');
|
||||
console.log('密码哈希是否相同:', dbUser.password === sequelizeUser.password);
|
||||
console.log('');
|
||||
|
||||
// 4. 测试密码验证
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('4. 测试密码验证:');
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 使用数据库查询的密码哈希
|
||||
const dbTest = await bcrypt.compare(testPassword, dbUser.password);
|
||||
console.log('数据库密码验证结果:', dbTest);
|
||||
|
||||
// 使用Sequelize查询的密码哈希
|
||||
const sequelizeTest = await bcrypt.compare(testPassword, sequelizeUser.password);
|
||||
console.log('Sequelize密码验证结果:', sequelizeTest);
|
||||
|
||||
// 使用User模型的validPassword方法
|
||||
const modelTest = await sequelizeUser.validPassword(testPassword);
|
||||
console.log('User模型验证结果:', modelTest);
|
||||
console.log('');
|
||||
|
||||
if (dbTest && sequelizeTest && modelTest) {
|
||||
console.log('🎉 所有验证都成功!');
|
||||
} else {
|
||||
console.log('❌ 验证失败');
|
||||
console.log('可能原因:');
|
||||
if (!dbTest) console.log('- 数据库中的密码哈希有问题');
|
||||
if (!sequelizeTest) console.log('- Sequelize查询的密码哈希有问题');
|
||||
if (!modelTest) console.log('- User模型的validPassword方法有问题');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ Sequelize查询失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testActualData();
|
||||
80
bank-backend/test-auth.js
Normal file
80
bank-backend/test-auth.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const { User, Role } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testAuth() {
|
||||
try {
|
||||
console.log('=== 测试认证逻辑 ===');
|
||||
|
||||
// 查找用户(包含角色)
|
||||
const user = await User.findOne({
|
||||
where: { username: 'admin' },
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ 找到admin用户');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('状态:', user.status);
|
||||
console.log('角色:', user.role ? user.role.name : '无角色');
|
||||
console.log('密码哈希:', user.password);
|
||||
|
||||
// 测试密码验证
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('\n=== 测试密码验证 ===');
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 直接使用bcrypt比较
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证:', directTest);
|
||||
|
||||
// 使用模型方法验证
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证:', modelTest);
|
||||
|
||||
if (!modelTest) {
|
||||
console.log('\n=== 重新生成密码 ===');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新哈希:', newHash);
|
||||
|
||||
await user.update({
|
||||
password: newHash,
|
||||
status: 'active',
|
||||
login_attempts: 0,
|
||||
locked_until: null
|
||||
});
|
||||
|
||||
console.log('✅ 密码已更新');
|
||||
|
||||
// 重新加载用户数据
|
||||
await user.reload();
|
||||
|
||||
// 再次验证
|
||||
const finalTest = await user.validPassword(testPassword);
|
||||
console.log('最终验证:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码修复成功!');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: Admin123456');
|
||||
console.log('状态: active');
|
||||
}
|
||||
} else {
|
||||
console.log('✅ 密码验证成功!');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testAuth();
|
||||
121
bank-backend/test-completed-supervisions-api.js
Normal file
121
bank-backend/test-completed-supervisions-api.js
Normal file
@@ -0,0 +1,121 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testCompletedSupervisionsAPI() {
|
||||
try {
|
||||
console.log('开始测试监管任务已结项API...')
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...')
|
||||
const loginResponse = await axios.post(`${BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
})
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message)
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token
|
||||
console.log('✅ 登录成功')
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
// 2. 获取监管任务已结项列表
|
||||
console.log('\n2. 获取监管任务已结项列表...')
|
||||
const listResponse = await axios.get(`${BASE_URL}/api/completed-supervisions`, {
|
||||
headers,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
})
|
||||
|
||||
console.log('监管任务已结项列表响应:', JSON.stringify(listResponse.data, null, 2))
|
||||
|
||||
// 3. 获取监管任务已结项统计
|
||||
console.log('\n3. 获取监管任务已结项统计...')
|
||||
const statsResponse = await axios.get(`${BASE_URL}/api/completed-supervisions/stats`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('监管任务已结项统计响应:', JSON.stringify(statsResponse.data, null, 2))
|
||||
|
||||
// 4. 创建新的监管任务已结项
|
||||
console.log('\n4. 创建新的监管任务已结项...')
|
||||
const newTask = {
|
||||
applicationNumber: 'APP2024999',
|
||||
contractNumber: 'LOAN2024999',
|
||||
productName: '测试养殖贷',
|
||||
customerName: '测试用户',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '440999999999999999',
|
||||
assetType: '测试动物',
|
||||
assetQuantity: 100,
|
||||
totalRepaymentPeriods: 12,
|
||||
settlementStatus: 'unsettled',
|
||||
settlementNotes: '这是一个测试任务'
|
||||
}
|
||||
|
||||
const createResponse = await axios.post(`${BASE_URL}/api/completed-supervisions`, newTask, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('创建监管任务已结项响应:', JSON.stringify(createResponse.data, null, 2))
|
||||
|
||||
const createdTaskId = createResponse.data.data.id
|
||||
|
||||
// 5. 获取单个监管任务已结项详情
|
||||
console.log('\n5. 获取监管任务已结项详情...')
|
||||
const detailResponse = await axios.get(`${BASE_URL}/api/completed-supervisions/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('监管任务已结项详情响应:', JSON.stringify(detailResponse.data, null, 2))
|
||||
|
||||
// 6. 更新监管任务已结项
|
||||
console.log('\n6. 更新监管任务已结项...')
|
||||
const updateData = {
|
||||
settlementStatus: 'settled',
|
||||
settlementDate: '2024-12-20',
|
||||
settlementNotes: '更新后的备注信息'
|
||||
}
|
||||
|
||||
const updateResponse = await axios.put(`${BASE_URL}/api/completed-supervisions/${createdTaskId}`, updateData, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('更新监管任务已结项响应:', JSON.stringify(updateResponse.data, null, 2))
|
||||
|
||||
// 7. 批量更新状态
|
||||
console.log('\n7. 批量更新状态...')
|
||||
const batchUpdateResponse = await axios.put(`${BASE_URL}/api/completed-supervisions/batch/status`, {
|
||||
ids: [createdTaskId],
|
||||
settlementStatus: 'partial'
|
||||
}, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('批量更新状态响应:', JSON.stringify(batchUpdateResponse.data, null, 2))
|
||||
|
||||
// 8. 删除监管任务已结项
|
||||
console.log('\n8. 删除监管任务已结项...')
|
||||
const deleteResponse = await axios.delete(`${BASE_URL}/api/completed-supervisions/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('删除监管任务已结项响应:', JSON.stringify(deleteResponse.data, null, 2))
|
||||
|
||||
console.log('\n✅ 所有监管任务已结项API测试完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testCompletedSupervisionsAPI()
|
||||
30
bank-backend/test-completed-supervisions-simple.js
Normal file
30
bank-backend/test-completed-supervisions-simple.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testCompletedSupervisionsSimple() {
|
||||
try {
|
||||
console.log('测试监管任务已结项API连接...')
|
||||
|
||||
const response = await axios.get(`${BASE_URL}/api/completed-supervisions`, {
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
console.log('✅ 监管任务已结项API连接成功')
|
||||
console.log('响应状态:', response.status)
|
||||
console.log('响应数据:', JSON.stringify(response.data, null, 2))
|
||||
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.log('API响应错误:')
|
||||
console.log('状态码:', error.response.status)
|
||||
console.log('错误信息:', error.response.data)
|
||||
} else if (error.request) {
|
||||
console.log('❌ 无法连接到服务器,请确保后端服务正在运行')
|
||||
} else {
|
||||
console.log('❌ 请求配置错误:', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testCompletedSupervisionsSimple()
|
||||
80
bank-backend/test-database-storage.js
Normal file
80
bank-backend/test-database-storage.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testDatabaseStorage() {
|
||||
try {
|
||||
console.log('=== 测试数据库存储问题 ===\n');
|
||||
|
||||
// 1. 生成一个新的密码哈希
|
||||
const testPassword = 'Admin123456';
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('1. 生成新的密码哈希:');
|
||||
console.log('原始密码:', testPassword);
|
||||
console.log('生成的哈希:', newHash);
|
||||
console.log('哈希长度:', newHash.length);
|
||||
console.log('');
|
||||
|
||||
// 2. 验证新生成的哈希
|
||||
console.log('2. 验证新生成的哈希:');
|
||||
const isValid = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('新哈希验证结果:', isValid);
|
||||
console.log('');
|
||||
|
||||
// 3. 更新数据库
|
||||
console.log('3. 更新数据库:');
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
await user.update({ password: newHash });
|
||||
console.log('✅ 数据库已更新');
|
||||
console.log('');
|
||||
|
||||
// 4. 重新查询数据库
|
||||
console.log('4. 重新查询数据库:');
|
||||
const updatedUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (updatedUser) {
|
||||
console.log('查询到的密码哈希:', updatedUser.password);
|
||||
console.log('查询到的哈希长度:', updatedUser.password.length);
|
||||
console.log('哈希是否匹配:', updatedUser.password === newHash);
|
||||
console.log('');
|
||||
|
||||
// 5. 测试查询到的哈希
|
||||
console.log('5. 测试查询到的哈希:');
|
||||
const queryTest = await bcrypt.compare(testPassword, updatedUser.password);
|
||||
console.log('查询到的哈希验证结果:', queryTest);
|
||||
console.log('');
|
||||
|
||||
// 6. 测试User模型的validPassword方法
|
||||
console.log('6. 测试User模型的validPassword方法:');
|
||||
const modelTest = await updatedUser.validPassword(testPassword);
|
||||
console.log('模型验证结果:', modelTest);
|
||||
console.log('');
|
||||
|
||||
if (queryTest && modelTest) {
|
||||
console.log('🎉 数据库存储和验证都正常!');
|
||||
} else if (queryTest && !modelTest) {
|
||||
console.log('❌ 数据库存储正常,但User模型验证失败');
|
||||
console.log('可能原因: User模型的validPassword方法有问题');
|
||||
} else if (!queryTest && modelTest) {
|
||||
console.log('❌ 数据库存储有问题,但User模型验证成功');
|
||||
console.log('可能原因: 数据库存储时数据被截断或损坏');
|
||||
} else {
|
||||
console.log('❌ 数据库存储和User模型验证都失败');
|
||||
console.log('可能原因: 数据库字段长度不够或编码问题');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 重新查询用户失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testDatabaseStorage();
|
||||
121
bank-backend/test-installation-tasks-api.js
Normal file
121
bank-backend/test-installation-tasks-api.js
Normal file
@@ -0,0 +1,121 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testInstallationTasksAPI() {
|
||||
try {
|
||||
console.log('开始测试待安装任务API...')
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...')
|
||||
const loginResponse = await axios.post(`${BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
})
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message)
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token
|
||||
console.log('✅ 登录成功')
|
||||
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
// 2. 获取待安装任务列表
|
||||
console.log('\n2. 获取待安装任务列表...')
|
||||
const listResponse = await axios.get(`${BASE_URL}/api/installation-tasks`, {
|
||||
headers,
|
||||
params: {
|
||||
page: 1,
|
||||
limit: 10
|
||||
}
|
||||
})
|
||||
|
||||
console.log('待安装任务列表响应:', JSON.stringify(listResponse.data, null, 2))
|
||||
|
||||
// 3. 获取待安装任务统计
|
||||
console.log('\n3. 获取待安装任务统计...')
|
||||
const statsResponse = await axios.get(`${BASE_URL}/api/installation-tasks/stats`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('待安装任务统计响应:', JSON.stringify(statsResponse.data, null, 2))
|
||||
|
||||
// 4. 创建新的待安装任务
|
||||
console.log('\n4. 创建新的待安装任务...')
|
||||
const newTask = {
|
||||
applicationNumber: 'APP2024999',
|
||||
contractNumber: 'LOAN2024999',
|
||||
productName: '测试养殖贷',
|
||||
customerName: '测试用户',
|
||||
idType: 'ID_CARD',
|
||||
idNumber: '440999999999999999',
|
||||
assetType: '测试动物',
|
||||
equipmentToInstall: '测试设备',
|
||||
installationNotes: '这是一个测试任务',
|
||||
installerName: '测试安装员',
|
||||
installerPhone: '13800138999',
|
||||
installationAddress: '测试地址'
|
||||
}
|
||||
|
||||
const createResponse = await axios.post(`${BASE_URL}/api/installation-tasks`, newTask, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('创建待安装任务响应:', JSON.stringify(createResponse.data, null, 2))
|
||||
|
||||
const createdTaskId = createResponse.data.data.id
|
||||
|
||||
// 5. 获取单个待安装任务详情
|
||||
console.log('\n5. 获取待安装任务详情...')
|
||||
const detailResponse = await axios.get(`${BASE_URL}/api/installation-tasks/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('待安装任务详情响应:', JSON.stringify(detailResponse.data, null, 2))
|
||||
|
||||
// 6. 更新待安装任务
|
||||
console.log('\n6. 更新待安装任务...')
|
||||
const updateData = {
|
||||
installationStatus: 'in-progress',
|
||||
installationNotes: '更新后的备注信息'
|
||||
}
|
||||
|
||||
const updateResponse = await axios.put(`${BASE_URL}/api/installation-tasks/${createdTaskId}`, updateData, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('更新待安装任务响应:', JSON.stringify(updateResponse.data, null, 2))
|
||||
|
||||
// 7. 批量更新状态
|
||||
console.log('\n7. 批量更新状态...')
|
||||
const batchUpdateResponse = await axios.put(`${BASE_URL}/api/installation-tasks/batch/status`, {
|
||||
ids: [createdTaskId],
|
||||
installationStatus: 'completed'
|
||||
}, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('批量更新状态响应:', JSON.stringify(batchUpdateResponse.data, null, 2))
|
||||
|
||||
// 8. 删除待安装任务
|
||||
console.log('\n8. 删除待安装任务...')
|
||||
const deleteResponse = await axios.delete(`${BASE_URL}/api/installation-tasks/${createdTaskId}`, {
|
||||
headers
|
||||
})
|
||||
|
||||
console.log('删除待安装任务响应:', JSON.stringify(deleteResponse.data, null, 2))
|
||||
|
||||
console.log('\n✅ 所有待安装任务API测试完成')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testInstallationTasksAPI()
|
||||
30
bank-backend/test-installation-tasks-simple.js
Normal file
30
bank-backend/test-installation-tasks-simple.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const BASE_URL = 'http://localhost:5351'
|
||||
|
||||
async function testInstallationTasksSimple() {
|
||||
try {
|
||||
console.log('测试待安装任务API连接...')
|
||||
|
||||
const response = await axios.get(`${BASE_URL}/api/installation-tasks`, {
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
console.log('✅ 待安装任务API连接成功')
|
||||
console.log('响应状态:', response.status)
|
||||
console.log('响应数据:', JSON.stringify(response.data, null, 2))
|
||||
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
console.log('API响应错误:')
|
||||
console.log('状态码:', error.response.status)
|
||||
console.log('错误信息:', error.response.data)
|
||||
} else if (error.request) {
|
||||
console.log('❌ 无法连接到服务器,请确保后端服务正在运行')
|
||||
} else {
|
||||
console.log('❌ 请求配置错误:', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testInstallationTasksSimple()
|
||||
117
bank-backend/test-loan-applications-api.js
Normal file
117
bank-backend/test-loan-applications-api.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 贷款申请API测试
|
||||
* @file test-loan-applications-api.js
|
||||
*/
|
||||
const axios = require('axios');
|
||||
|
||||
async function testLoanApplicationsAPI() {
|
||||
try {
|
||||
console.log('🔍 测试贷款申请API...');
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 登录测试...');
|
||||
const loginResponse = await axios.post('http://localhost:5351/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error('登录失败: ' + loginResponse.data.message);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
|
||||
// 设置授权头
|
||||
const authHeaders = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 2. 获取贷款申请列表
|
||||
console.log('\n2. 获取申请列表...');
|
||||
const listResponse = await axios.get('http://localhost:5351/api/loan-applications', {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!listResponse.data.success) {
|
||||
throw new Error('获取列表失败: ' + listResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 获取申请列表成功');
|
||||
console.log(`📊 申请数量: ${listResponse.data.data.applications.length}`);
|
||||
console.log(`📊 总数: ${listResponse.data.data.pagination.total}`);
|
||||
|
||||
if (listResponse.data.data.applications.length > 0) {
|
||||
const firstApp = listResponse.data.data.applications[0];
|
||||
console.log(`📋 第一个申请: ${firstApp.applicationNumber} - ${firstApp.productName} - ${firstApp.status}`);
|
||||
|
||||
// 3. 获取申请详情
|
||||
console.log('\n3. 获取申请详情...');
|
||||
const detailResponse = await axios.get(`http://localhost:5351/api/loan-applications/${firstApp.id}`, {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!detailResponse.data.success) {
|
||||
throw new Error('获取详情失败: ' + detailResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 获取申请详情成功');
|
||||
console.log(`📋 申请详情: ${detailResponse.data.data.applicationNumber}`);
|
||||
console.log(`📋 审核记录数: ${detailResponse.data.data.auditRecords.length}`);
|
||||
|
||||
// 4. 测试审核功能(仅对待审核的申请)
|
||||
if (firstApp.status === 'pending_review') {
|
||||
console.log('\n4. 测试审核功能...');
|
||||
const auditResponse = await axios.post(`http://localhost:5351/api/loan-applications/${firstApp.id}/audit`, {
|
||||
action: 'approve',
|
||||
comment: 'API测试审核通过'
|
||||
}, {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!auditResponse.data.success) {
|
||||
throw new Error('审核失败: ' + auditResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 审核功能测试成功');
|
||||
console.log(`📋 审核结果: ${auditResponse.data.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 获取统计信息
|
||||
console.log('\n5. 获取统计信息...');
|
||||
const statsResponse = await axios.get('http://localhost:5351/api/loan-applications/stats', {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (!statsResponse.data.success) {
|
||||
throw new Error('获取统计失败: ' + statsResponse.data.message);
|
||||
}
|
||||
|
||||
console.log('✅ 获取统计信息成功');
|
||||
console.log(`📊 总申请数: ${statsResponse.data.data.total.applications}`);
|
||||
console.log(`📊 总金额: ${statsResponse.data.data.total.amount.toFixed(2)}元`);
|
||||
console.log('📊 按状态统计:');
|
||||
Object.entries(statsResponse.data.data.byStatus.counts).forEach(([status, count]) => {
|
||||
if (count > 0) {
|
||||
console.log(` - ${status}: ${count}个申请`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n🎉 所有API测试完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ API测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error('响应状态:', error.response.status);
|
||||
console.error('响应数据:', error.response.data);
|
||||
} else if (error.code) {
|
||||
console.error('错误代码:', error.code);
|
||||
}
|
||||
console.error('完整错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testLoanApplicationsAPI();
|
||||
125
bank-backend/test-loan-products-api.js
Normal file
125
bank-backend/test-loan-products-api.js
Normal file
@@ -0,0 +1,125 @@
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:5351';
|
||||
|
||||
// 测试贷款商品API
|
||||
async function testLoanProductsAPI() {
|
||||
try {
|
||||
console.log('开始测试贷款商品API...');
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...');
|
||||
const loginResponse = await axios.post(`${BASE_URL}/api/auth/login`, {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error(`登录失败: ${loginResponse.data.message}`);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
|
||||
// 设置请求头
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 2. 获取贷款商品列表
|
||||
console.log('\n2. 获取贷款商品列表...');
|
||||
const listResponse = await axios.get(`${BASE_URL}/api/loan-products`, { headers });
|
||||
console.log('✅ 获取贷款商品列表成功');
|
||||
console.log(` 总数: ${listResponse.data.data.pagination.total}`);
|
||||
console.log(` 当前页: ${listResponse.data.data.pagination.current}`);
|
||||
console.log(` 每页数量: ${listResponse.data.data.pagination.pageSize}`);
|
||||
|
||||
// 3. 获取贷款商品统计信息
|
||||
console.log('\n3. 获取贷款商品统计信息...');
|
||||
const statsResponse = await axios.get(`${BASE_URL}/api/loan-products/stats`, { headers });
|
||||
console.log('✅ 获取统计信息成功');
|
||||
console.log(` 总产品数: ${statsResponse.data.data.totalProducts}`);
|
||||
console.log(` 在售产品: ${statsResponse.data.data.onSaleProducts}`);
|
||||
console.log(` 停售产品: ${statsResponse.data.data.offSaleProducts}`);
|
||||
|
||||
// 4. 创建新的贷款商品
|
||||
console.log('\n4. 创建新的贷款商品...');
|
||||
const newProduct = {
|
||||
productName: '测试贷款产品',
|
||||
loanAmount: '100000~500000元',
|
||||
loanTerm: 12,
|
||||
interestRate: 5.5,
|
||||
serviceArea: '测试区域',
|
||||
servicePhone: '13800138000',
|
||||
productDescription: '这是一个测试贷款产品',
|
||||
applicationRequirements: '测试申请条件',
|
||||
requiredDocuments: '测试所需材料',
|
||||
approvalProcess: '测试审批流程',
|
||||
riskLevel: 'MEDIUM',
|
||||
minLoanAmount: 100000,
|
||||
maxLoanAmount: 500000
|
||||
};
|
||||
|
||||
const createResponse = await axios.post(`${BASE_URL}/api/loan-products`, newProduct, { headers });
|
||||
console.log('✅ 创建贷款商品成功');
|
||||
console.log(` 产品ID: ${createResponse.data.data.id}`);
|
||||
console.log(` 产品名称: ${createResponse.data.data.productName}`);
|
||||
|
||||
const productId = createResponse.data.data.id;
|
||||
|
||||
// 5. 根据ID获取贷款商品详情
|
||||
console.log('\n5. 获取贷款商品详情...');
|
||||
const detailResponse = await axios.get(`${BASE_URL}/api/loan-products/${productId}`, { headers });
|
||||
console.log('✅ 获取贷款商品详情成功');
|
||||
console.log(` 产品名称: ${detailResponse.data.data.productName}`);
|
||||
console.log(` 贷款利率: ${detailResponse.data.data.interestRate}%`);
|
||||
|
||||
// 6. 更新贷款商品
|
||||
console.log('\n6. 更新贷款商品...');
|
||||
const updateData = {
|
||||
productName: '更新后的测试贷款产品',
|
||||
interestRate: 6.0,
|
||||
productDescription: '这是更新后的测试贷款产品描述'
|
||||
};
|
||||
|
||||
const updateResponse = await axios.put(`${BASE_URL}/api/loan-products/${productId}`, updateData, { headers });
|
||||
console.log('✅ 更新贷款商品成功');
|
||||
console.log(` 更新后产品名称: ${updateResponse.data.data.productName}`);
|
||||
console.log(` 更新后利率: ${updateResponse.data.data.interestRate}%`);
|
||||
|
||||
// 7. 批量更新在售状态
|
||||
console.log('\n7. 批量更新在售状态...');
|
||||
const batchUpdateResponse = await axios.put(`${BASE_URL}/api/loan-products/batch/status`, {
|
||||
ids: [productId],
|
||||
onSaleStatus: false
|
||||
}, { headers });
|
||||
console.log('✅ 批量更新状态成功');
|
||||
|
||||
// 8. 删除贷款商品
|
||||
console.log('\n8. 删除贷款商品...');
|
||||
const deleteResponse = await axios.delete(`${BASE_URL}/api/loan-products/${productId}`, { headers });
|
||||
console.log('✅ 删除贷款商品成功');
|
||||
|
||||
console.log('\n🎉 所有贷款商品API测试通过!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
testLoanProductsAPI()
|
||||
.then(() => {
|
||||
console.log('贷款商品API测试完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('贷款商品API测试失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = testLoanProductsAPI;
|
||||
49
bank-backend/test-loan-products-simple.js
Normal file
49
bank-backend/test-loan-products-simple.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const axios = require('axios');
|
||||
|
||||
async function testLoanProductsAPI() {
|
||||
try {
|
||||
console.log('测试贷款商品API...');
|
||||
|
||||
// 1. 登录获取token
|
||||
console.log('\n1. 用户登录...');
|
||||
const loginResponse = await axios.post('http://localhost:5351/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
});
|
||||
|
||||
if (!loginResponse.data.success) {
|
||||
throw new Error(`登录失败: ${loginResponse.data.message}`);
|
||||
}
|
||||
|
||||
const token = loginResponse.data.data.token;
|
||||
console.log('✅ 登录成功');
|
||||
|
||||
// 设置请求头
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
// 2. 获取贷款商品列表
|
||||
console.log('\n2. 获取贷款商品列表...');
|
||||
const listResponse = await axios.get('http://localhost:5351/api/loan-products', { headers });
|
||||
|
||||
if (listResponse.data.success) {
|
||||
console.log('✅ 获取贷款商品列表成功');
|
||||
console.log('响应数据结构:');
|
||||
console.log(JSON.stringify(listResponse.data, null, 2));
|
||||
|
||||
if (listResponse.data.data && listResponse.data.data.products) {
|
||||
console.log('\n产品数据示例:');
|
||||
console.log(JSON.stringify(listResponse.data.data.products[0], null, 2));
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 获取贷款商品列表失败:', listResponse.data.message);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error.response?.data || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testLoanProductsAPI();
|
||||
16
bank-backend/test-login.js
Normal file
16
bank-backend/test-login.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const axios = require('axios');
|
||||
|
||||
async function testLogin() {
|
||||
try {
|
||||
console.log('测试登录API...');
|
||||
const response = await axios.post('http://localhost:3001/api/auth/login', {
|
||||
username: 'admin',
|
||||
password: 'Admin123456'
|
||||
});
|
||||
console.log('登录成功:', response.data);
|
||||
} catch (error) {
|
||||
console.log('登录失败:', error.response ? error.response.data : error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testLogin();
|
||||
65
bank-backend/test-simple-update.js
Normal file
65
bank-backend/test-simple-update.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testSimpleUpdate() {
|
||||
try {
|
||||
console.log('=== 简单测试数据库更新 ===\n');
|
||||
|
||||
// 1. 生成密码哈希
|
||||
const testPassword = 'Admin123456';
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('1. 生成的哈希:', newHash);
|
||||
console.log('哈希长度:', newHash.length);
|
||||
console.log('');
|
||||
|
||||
// 2. 验证生成的哈希
|
||||
const isValid = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('2. 生成的哈希验证结果:', isValid);
|
||||
console.log('');
|
||||
|
||||
// 3. 直接使用SQL更新
|
||||
console.log('3. 直接使用SQL更新...');
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
const [affectedRows] = await sequelize.query(
|
||||
'UPDATE users SET password = ? WHERE username = ?',
|
||||
{
|
||||
replacements: [newHash, 'admin'],
|
||||
type: sequelize.QueryTypes.UPDATE
|
||||
}
|
||||
);
|
||||
|
||||
console.log('SQL更新影响行数:', affectedRows);
|
||||
console.log('');
|
||||
|
||||
// 4. 重新查询
|
||||
console.log('4. 重新查询数据库...');
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (user) {
|
||||
console.log('查询到的密码哈希:', user.password);
|
||||
console.log('查询到的哈希长度:', user.password.length);
|
||||
console.log('哈希是否匹配:', user.password === newHash);
|
||||
console.log('');
|
||||
|
||||
// 5. 测试验证
|
||||
const queryTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('5. 查询到的哈希验证结果:', queryTest);
|
||||
|
||||
if (queryTest) {
|
||||
console.log('🎉 数据库更新成功!');
|
||||
} else {
|
||||
console.log('❌ 数据库更新失败,哈希不匹配');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 重新查询用户失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testSimpleUpdate();
|
||||
82
bank-backend/test-validpassword.js
Normal file
82
bank-backend/test-validpassword.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const { User } = require('./models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
async function testValidPassword() {
|
||||
try {
|
||||
console.log('=== 测试User模型的validPassword方法 ===\n');
|
||||
|
||||
// 1. 获取用户
|
||||
const user = await User.findOne({ where: { username: 'admin' } });
|
||||
if (!user) {
|
||||
console.log('❌ 未找到admin用户');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ 找到admin用户');
|
||||
console.log('用户名:', user.username);
|
||||
console.log('密码哈希:', user.password);
|
||||
console.log('');
|
||||
|
||||
// 2. 测试密码
|
||||
const testPassword = 'Admin123456';
|
||||
console.log('测试密码:', testPassword);
|
||||
|
||||
// 3. 直接使用bcrypt比较
|
||||
console.log('3. 直接使用bcrypt比较...');
|
||||
const directTest = await bcrypt.compare(testPassword, user.password);
|
||||
console.log('直接bcrypt验证结果:', directTest);
|
||||
|
||||
// 4. 使用User模型的validPassword方法
|
||||
console.log('4. 使用User模型的validPassword方法...');
|
||||
const modelTest = await user.validPassword(testPassword);
|
||||
console.log('模型验证结果:', modelTest);
|
||||
|
||||
// 5. 检查User模型的validPassword方法实现
|
||||
console.log('5. 检查User模型的validPassword方法实现...');
|
||||
console.log('validPassword方法:', user.validPassword.toString());
|
||||
|
||||
// 6. 手动测试bcrypt
|
||||
console.log('6. 手动测试bcrypt...');
|
||||
const newHash = await bcrypt.hash(testPassword, 10);
|
||||
console.log('新生成的哈希:', newHash);
|
||||
const newTest = await bcrypt.compare(testPassword, newHash);
|
||||
console.log('新哈希验证结果:', newTest);
|
||||
|
||||
// 7. 更新用户密码并测试
|
||||
console.log('7. 更新用户密码并测试...');
|
||||
await user.update({ password: newHash });
|
||||
console.log('密码已更新');
|
||||
|
||||
// 重新加载用户数据
|
||||
await user.reload();
|
||||
console.log('用户数据已重新加载');
|
||||
console.log('更新后的密码哈希:', user.password);
|
||||
|
||||
// 再次测试
|
||||
const finalTest = await user.validPassword(testPassword);
|
||||
console.log('更新后的验证结果:', finalTest);
|
||||
|
||||
if (finalTest) {
|
||||
console.log('🎉 密码验证成功!');
|
||||
} else {
|
||||
console.log('❌ 密码验证仍然失败');
|
||||
|
||||
// 检查是否是数据库问题
|
||||
console.log('8. 检查数据库问题...');
|
||||
const freshUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (freshUser) {
|
||||
console.log('重新查询的用户密码哈希:', freshUser.password);
|
||||
const freshTest = await freshUser.validPassword(testPassword);
|
||||
console.log('重新查询的验证结果:', freshTest);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
testValidPassword();
|
||||
Reference in New Issue
Block a user