添加银行端后端接口

This commit is contained in:
2025-09-24 17:49:32 +08:00
parent b58ed724b0
commit 111ebaec84
95 changed files with 22115 additions and 4246 deletions

View 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();

View 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();

View 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
}
}

View 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
}

View 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
}

View 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
};

View 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
};

View File

@@ -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
};

View 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)
})

View 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();

View 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();

View 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();

View 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();

View File

@@ -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')
}
}

View File

@@ -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')
}
}

View 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');
}
};

View File

@@ -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');
}
};

View File

@@ -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');
}
};

View 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');
}
};

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -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;

View File

@@ -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;
}
}
/**

View File

@@ -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
};

View 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

View 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

View 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;

View 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;

View File

@@ -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;

View 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

View 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

View 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;

View 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;

View 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;

View 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

View 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

View 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;

View File

@@ -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'));
// 根路径

View 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
View 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();

View 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()

View 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()

View 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();

View 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()

View 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()

View 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();

View 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;

View 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();

View 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();

View 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();

View 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();