添加银行政府后端接口
This commit is contained in:
104
bank-backend/add-test-data.js
Normal file
104
bank-backend/add-test-data.js
Normal file
@@ -0,0 +1,104 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function addTestData() {
|
||||
try {
|
||||
console.log('开始添加测试数据...');
|
||||
|
||||
// 添加supervision_tasks测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO supervision_tasks (
|
||||
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
|
||||
assetType, assetQuantity, supervisionStatus, importTime, startTime, endTime,
|
||||
loanAmount, interestRate, loanTerm, supervisorName, supervisorPhone, farmAddress,
|
||||
remarks, createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('APP001', 'CON001', '养殖贷款', '张三', 'id_card', '110101199001010001',
|
||||
'cattle', 10, 'pending', NOW(), '2024-01-01', '2024-12-31',
|
||||
50000.00, 0.05, 12, '李监督员', '13800138000', '北京市朝阳区农场',
|
||||
'测试监管任务', 2, 2, NOW(), NOW()),
|
||||
('APP002', 'CON002', '种植贷款', '李四', 'id_card', '110101199002020002',
|
||||
'sheep', 20, 'supervising', NOW(), '2024-02-01', '2024-11-30',
|
||||
30000.00, 0.06, 10, '王监督员', '13900139000', '北京市海淀区农场',
|
||||
'测试监管任务2', 2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加supervision_tasks测试数据成功');
|
||||
|
||||
// 添加projects测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO projects (
|
||||
name, status, farmName, supervisionObject, supervisionQuantity, supervisionPeriod,
|
||||
supervisionAmount, startTime, endTime, earTag, collar, host, loanOfficer,
|
||||
description, createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('养殖项目1', 'supervision', '张三农场', 'cattle', 50, '12个月',
|
||||
100000.00, '2024-01-01', '2024-12-31', 30, 20, 10, '李贷款员',
|
||||
'测试养殖项目', 2, 2, NOW(), NOW()),
|
||||
('种植项目1', 'completed', '李四农场', 'sheep', 100, '10个月',
|
||||
80000.00, '2024-02-01', '2024-11-30', 60, 40, 20, '王贷款员',
|
||||
'测试种植项目', 2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加projects测试数据成功');
|
||||
|
||||
// 添加installation_tasks测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO installation_tasks (
|
||||
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
|
||||
assetType, equipmentToInstall, installationStatus, taskGenerationTime, completionTime,
|
||||
installationNotes, installerName, installerPhone, installationAddress,
|
||||
createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('APP001', 'CON001', '养殖设备', '张三', 'ID_CARD', '110101199001010001',
|
||||
'cattle', '耳标设备', 'pending', NOW(), NULL,
|
||||
'测试安装任务', '安装员1', '13900139000', '北京市朝阳区农场',
|
||||
2, 2, NOW(), NOW()),
|
||||
('APP002', 'CON002', '种植设备', '李四', 'ID_CARD', '110101199002020002',
|
||||
'sheep', '项圈设备', 'completed', NOW(), NOW(),
|
||||
'测试安装任务2', '安装员2', '14000140000', '北京市海淀区农场',
|
||||
2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加installation_tasks测试数据成功');
|
||||
|
||||
// 添加completed_supervisions测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO completed_supervisions (
|
||||
applicationNumber, contractNumber, customerName, supervisionPeriod, totalAmount,
|
||||
paidAmount, remainingAmount, settlementNotes, createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('APP001', 'CON001', '张三', '12个月', 50000.00,
|
||||
30000.00, 20000.00, '已结清部分', 2, 2, NOW(), NOW()),
|
||||
('APP002', 'CON002', '李四', '10个月', 30000.00,
|
||||
30000.00, 0.00, '已完全结清', 2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加completed_supervisions测试数据成功');
|
||||
|
||||
// 添加loan_applications测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO loan_applications (
|
||||
customer_name, customer_phone, customer_id_card, loan_amount, loan_term,
|
||||
interest_rate, application_date, status
|
||||
) VALUES
|
||||
('张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'pending'),
|
||||
('李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'approved')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加loan_applications测试数据成功');
|
||||
|
||||
// 添加loan_contracts测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO loan_contracts (
|
||||
contract_number, customer_name, customer_phone, customer_id_card, loan_amount,
|
||||
loan_term, interest_rate, contract_date, status
|
||||
) VALUES
|
||||
('CON001', '张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'active'),
|
||||
('CON002', '李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'completed')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加loan_contracts测试数据成功');
|
||||
|
||||
console.log('\n✅ 所有测试数据添加完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 添加测试数据失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
addTestData();
|
||||
32
bank-backend/check-data.js
Normal file
32
bank-backend/check-data.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function checkData() {
|
||||
try {
|
||||
console.log('检查各表数据统计...\n');
|
||||
|
||||
const tables = [
|
||||
'supervision_tasks',
|
||||
'projects',
|
||||
'installation_tasks',
|
||||
'completed_supervisions',
|
||||
'loan_applications',
|
||||
'loan_contracts'
|
||||
];
|
||||
|
||||
for (const table of tables) {
|
||||
const result = await sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM ${table}`,
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
console.log(`${table}: ${result[0].count} 条记录`);
|
||||
}
|
||||
|
||||
console.log('\n检查完成!');
|
||||
} catch (error) {
|
||||
console.error('错误:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkData();
|
||||
@@ -3,9 +3,9 @@
|
||||
"username": "root",
|
||||
"password": "aiotAiot123!",
|
||||
"database": "ningxia_bank",
|
||||
"host": "127.0.0.1",
|
||||
"host": "129.211.213.226",
|
||||
"dialect": "mysql",
|
||||
"port": 3306
|
||||
"port": 9527
|
||||
},
|
||||
"test": {
|
||||
"username": "root",
|
||||
|
||||
@@ -3,10 +3,10 @@ const { Sequelize } = require('sequelize');
|
||||
// 从环境变量获取数据库配置
|
||||
const dialect = process.env.DB_DIALECT || 'mysql';
|
||||
const config = {
|
||||
logging: false,
|
||||
logging: console.log, // 启用SQL日志
|
||||
define: {
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
underscored: false,
|
||||
freezeTableName: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,77 +1,73 @@
|
||||
/**
|
||||
* 员工控制器
|
||||
* @file employeeController.js
|
||||
* @description 处理员工相关的请求
|
||||
*/
|
||||
const { Employee, Department, Position } = require('../models');
|
||||
const { Employee, User } = require('../models');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { Op } = require('sequelize');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
/**
|
||||
* 获取员工列表
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployees = async (req, res) => {
|
||||
const getEmployees = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
search = '',
|
||||
department = '',
|
||||
position = '',
|
||||
pageSize = 10,
|
||||
searchField = 'name',
|
||||
searchValue = '',
|
||||
status = '',
|
||||
sortBy = 'created_at',
|
||||
sortOrder = 'DESC'
|
||||
isLoanSpecialist = ''
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
const whereClause = {};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ name: { [Op.like]: `%${search}%` } },
|
||||
{ employee_id: { [Op.like]: `%${search}%` } },
|
||||
{ phone: { [Op.like]: `%${search}%` } },
|
||||
{ email: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
// 构建查询条件
|
||||
const where = {};
|
||||
|
||||
if (searchValue) {
|
||||
if (searchField === 'name') {
|
||||
where.name = { [require('sequelize').Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'phone') {
|
||||
where.phone = { [require('sequelize').Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'employeeNumber') {
|
||||
where.employeeNumber = { [require('sequelize').Op.like]: `%${searchValue}%` };
|
||||
}
|
||||
}
|
||||
|
||||
// 部门筛选
|
||||
if (department) {
|
||||
whereClause.department_id = department;
|
||||
}
|
||||
|
||||
// 职位筛选
|
||||
if (position) {
|
||||
whereClause.position_id = position;
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
whereClause.status = status;
|
||||
where.status = status;
|
||||
}
|
||||
|
||||
const { count, rows: employees } = await Employee.findAndCountAll({
|
||||
where: whereClause,
|
||||
include: [
|
||||
{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['id', 'name', 'level']
|
||||
}
|
||||
],
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
if (isLoanSpecialist !== '') {
|
||||
where.isLoanSpecialist = isLoanSpecialist === 'true';
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
const offset = (parseInt(page) - 1) * parseInt(pageSize);
|
||||
const limit = parseInt(pageSize);
|
||||
|
||||
// 查询数据
|
||||
const { count, rows } = await Employee.findAndCountAll({
|
||||
where,
|
||||
limit,
|
||||
offset,
|
||||
order: [['createdAt', 'DESC']],
|
||||
attributes: {
|
||||
exclude: ['password'] // 不返回密码
|
||||
}
|
||||
});
|
||||
|
||||
// 格式化数据
|
||||
const employees = rows.map(employee => ({
|
||||
id: employee.id,
|
||||
employeeNumber: employee.employeeNumber,
|
||||
name: employee.name,
|
||||
phone: employee.phone,
|
||||
email: employee.email,
|
||||
isLoanSpecialist: employee.isLoanSpecialist,
|
||||
department: employee.department,
|
||||
position: employee.position,
|
||||
status: employee.status,
|
||||
lastLogin: employee.lastLogin,
|
||||
createdAt: employee.createdAt,
|
||||
updatedAt: employee.updatedAt
|
||||
}));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工列表成功',
|
||||
@@ -79,113 +75,32 @@ exports.getEmployees = async (req, res) => {
|
||||
employees,
|
||||
pagination: {
|
||||
current: parseInt(page),
|
||||
pageSize: parseInt(limit),
|
||||
pageSize: parseInt(pageSize),
|
||||
total: count,
|
||||
pages: Math.ceil(count / limit)
|
||||
pages: Math.ceil(count / parseInt(pageSize))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工列表错误:', error);
|
||||
console.error('获取员工列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.createEmployee = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
employee_id,
|
||||
department_id,
|
||||
position_id,
|
||||
phone,
|
||||
email,
|
||||
hire_date,
|
||||
salary,
|
||||
status = 'active'
|
||||
} = req.body;
|
||||
|
||||
// 检查员工编号是否已存在
|
||||
const existingEmployee = await Employee.findOne({
|
||||
where: { employee_id }
|
||||
});
|
||||
|
||||
if (existingEmployee) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '员工编号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
const employee = await Employee.create({
|
||||
name,
|
||||
employee_id,
|
||||
department_id,
|
||||
position_id,
|
||||
phone,
|
||||
email,
|
||||
hire_date,
|
||||
salary: salary * 100, // 转换为分
|
||||
status
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建员工成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建员工错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
message: '获取员工列表失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取员工详情
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.getEmployeeById = async (req, res) => {
|
||||
const getEmployeeById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const employee = await Employee.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['id', 'name', 'description']
|
||||
},
|
||||
{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['id', 'name', 'level', 'description']
|
||||
}
|
||||
]
|
||||
attributes: {
|
||||
exclude: ['password'] // 不返回密码
|
||||
}
|
||||
});
|
||||
|
||||
if (!employee) {
|
||||
@@ -200,43 +115,128 @@ exports.getEmployeeById = async (req, res) => {
|
||||
message: '获取员工详情成功',
|
||||
data: employee
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工详情错误:', error);
|
||||
console.error('获取员工详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
message: '获取员工详情失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
* 创建员工
|
||||
*/
|
||||
exports.updateEmployee = async (req, res) => {
|
||||
const createEmployee = async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '输入数据验证失败',
|
||||
message: '请求参数错误',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
employeeNumber,
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
password,
|
||||
isLoanSpecialist,
|
||||
department,
|
||||
position
|
||||
} = req.body;
|
||||
|
||||
// 检查员工编号是否已存在
|
||||
const existingEmployee = await Employee.findOne({
|
||||
where: { employeeNumber }
|
||||
});
|
||||
|
||||
if (existingEmployee) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '员工编号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在
|
||||
const existingPhone = await Employee.findOne({
|
||||
where: { phone }
|
||||
});
|
||||
|
||||
if (existingPhone) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '手机号已存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建员工
|
||||
const employee = await Employee.create({
|
||||
employeeNumber,
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
password: password || '123456', // 默认密码
|
||||
isLoanSpecialist: isLoanSpecialist || false,
|
||||
department,
|
||||
position,
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '创建员工成功',
|
||||
data: {
|
||||
id: employee.id,
|
||||
employeeNumber: employee.employeeNumber,
|
||||
name: employee.name,
|
||||
phone: employee.phone,
|
||||
email: employee.email,
|
||||
isLoanSpecialist: employee.isLoanSpecialist,
|
||||
department: employee.department,
|
||||
position: employee.position,
|
||||
status: employee.status,
|
||||
createdAt: employee.createdAt
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建员工失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建员工失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新员工信息
|
||||
*/
|
||||
const updateEmployee = 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.salary) {
|
||||
updateData.salary = updateData.salary * 100;
|
||||
}
|
||||
const {
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
isLoanSpecialist,
|
||||
department,
|
||||
position,
|
||||
status
|
||||
} = req.body;
|
||||
|
||||
const employee = await Employee.findByPk(id);
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
@@ -244,35 +244,101 @@ exports.updateEmployee = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
await employee.update(updateData);
|
||||
// 检查手机号是否被其他员工使用
|
||||
if (phone && phone !== employee.phone) {
|
||||
const existingPhone = await Employee.findOne({
|
||||
where: {
|
||||
phone,
|
||||
id: { [require('sequelize').Op.ne]: id }
|
||||
}
|
||||
});
|
||||
|
||||
if (existingPhone) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '手机号已被其他员工使用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新员工信息
|
||||
await employee.update({
|
||||
name,
|
||||
phone,
|
||||
email,
|
||||
isLoanSpecialist,
|
||||
department,
|
||||
position,
|
||||
status
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '更新员工成功',
|
||||
data: employee
|
||||
message: '更新员工信息成功',
|
||||
data: {
|
||||
id: employee.id,
|
||||
employeeNumber: employee.employeeNumber,
|
||||
name: employee.name,
|
||||
phone: employee.phone,
|
||||
email: employee.email,
|
||||
isLoanSpecialist: employee.isLoanSpecialist,
|
||||
department: employee.department,
|
||||
position: employee.position,
|
||||
status: employee.status,
|
||||
updatedAt: employee.updatedAt
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新员工错误:', error);
|
||||
console.error('更新员工信息失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
message: '更新员工信息失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重设密码
|
||||
*/
|
||||
const resetPassword = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { newPassword } = req.body;
|
||||
|
||||
const employee = await Employee.findByPk(id);
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '员工不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
await employee.update({
|
||||
password: newPassword || '123456' // 默认密码
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '重设密码成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('重设密码失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '重设密码失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除员工
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
*/
|
||||
exports.deleteEmployee = async (req, res) => {
|
||||
const deleteEmployee = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const employee = await Employee.findByPk(id);
|
||||
|
||||
if (!employee) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
@@ -280,87 +346,105 @@ exports.deleteEmployee = async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// 软删除
|
||||
await employee.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '删除员工成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除员工错误:', error);
|
||||
console.error('删除员工失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
message: '删除员工失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取员工统计
|
||||
* @param {Object} req 请求对象
|
||||
* @param {Object} res 响应对象
|
||||
* 批量更新员工状态
|
||||
*/
|
||||
exports.getEmployeeStats = async (req, res) => {
|
||||
const batchUpdateStatus = async (req, res) => {
|
||||
try {
|
||||
const { ids, status } = req.body;
|
||||
|
||||
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: '请选择要更新的状态'
|
||||
});
|
||||
}
|
||||
|
||||
await Employee.update(
|
||||
{
|
||||
status
|
||||
},
|
||||
{
|
||||
where: {
|
||||
id: { [require('sequelize').Op.in]: ids }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '批量更新状态成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('批量更新状态失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量更新状态失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取员工统计信息
|
||||
*/
|
||||
const getEmployeeStats = async (req, res) => {
|
||||
try {
|
||||
const totalEmployees = await Employee.count();
|
||||
const activeEmployees = await Employee.count({ where: { status: 'active' } });
|
||||
const inactiveEmployees = await Employee.count({ where: { status: 'inactive' } });
|
||||
|
||||
const departmentStats = await Employee.findAll({
|
||||
attributes: [
|
||||
'department_id',
|
||||
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
|
||||
],
|
||||
include: [{
|
||||
model: Department,
|
||||
as: 'department',
|
||||
attributes: ['name']
|
||||
}],
|
||||
group: ['department_id', 'department.id'],
|
||||
raw: false
|
||||
});
|
||||
|
||||
const positionStats = await Employee.findAll({
|
||||
attributes: [
|
||||
'position_id',
|
||||
[Employee.sequelize.fn('COUNT', Employee.sequelize.col('id')), 'count']
|
||||
],
|
||||
include: [{
|
||||
model: Position,
|
||||
as: 'position',
|
||||
attributes: ['name', 'level']
|
||||
}],
|
||||
group: ['position_id', 'position.id'],
|
||||
raw: false
|
||||
});
|
||||
const lockedEmployees = await Employee.count({ where: { status: 'locked' } });
|
||||
const loanSpecialists = await Employee.count({ where: { isLoanSpecialist: true } });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '获取员工统计成功',
|
||||
data: {
|
||||
total: totalEmployees,
|
||||
active: activeEmployees,
|
||||
inactive: inactiveEmployees,
|
||||
departmentStats: departmentStats.map(item => ({
|
||||
department: item.department.name,
|
||||
count: parseInt(item.dataValues.count)
|
||||
})),
|
||||
positionStats: positionStats.map(item => ({
|
||||
position: item.position.name,
|
||||
level: item.position.level,
|
||||
count: parseInt(item.dataValues.count)
|
||||
}))
|
||||
totalEmployees,
|
||||
activeEmployees,
|
||||
inactiveEmployees,
|
||||
lockedEmployees,
|
||||
loanSpecialists
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取员工统计错误:', error);
|
||||
console.error('获取员工统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器内部错误',
|
||||
error: error.message
|
||||
message: '获取员工统计失败'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getEmployees,
|
||||
getEmployeeById,
|
||||
createEmployee,
|
||||
updateEmployee,
|
||||
resetPassword,
|
||||
deleteEmployee,
|
||||
batchUpdateStatus,
|
||||
getEmployeeStats
|
||||
};
|
||||
@@ -29,15 +29,12 @@ const getApplications = async (req, res) => {
|
||||
|
||||
// 搜索条件
|
||||
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 (searchField === 'customerName') {
|
||||
where.customer_name = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'customerPhone') {
|
||||
where.customer_phone = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'customerIdCard') {
|
||||
where.customer_id_card = { [Op.like]: `%${searchValue}%` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,35 +53,6 @@ const getApplications = async (req, res) => {
|
||||
// 查询数据
|
||||
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
|
||||
@@ -93,34 +61,16 @@ const getApplications = async (req, res) => {
|
||||
// 格式化数据
|
||||
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),
|
||||
customer_name: app.customer_name,
|
||||
customer_phone: app.customer_phone,
|
||||
customer_id_card: app.customer_id_card,
|
||||
loan_amount: parseFloat(app.loan_amount),
|
||||
loan_term: app.loan_term,
|
||||
interest_rate: parseFloat(app.interest_rate),
|
||||
application_date: app.application_date,
|
||||
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
|
||||
}))
|
||||
created_at: app.created_at,
|
||||
updated_at: app.updated_at
|
||||
}));
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -30,15 +30,13 @@ const getContracts = async (req, res) => {
|
||||
// 搜索条件
|
||||
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}%` };
|
||||
where.contract_number = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'customerName') {
|
||||
where.customer_name = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'customerPhone') {
|
||||
where.customer_phone = { [Op.like]: `%${searchValue}%` };
|
||||
} else if (searchField === 'customerIdCard') {
|
||||
where.customer_id_card = { [Op.like]: `%${searchValue}%` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,18 +55,6 @@ const getContracts = async (req, res) => {
|
||||
// 查询数据
|
||||
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
|
||||
@@ -77,31 +63,17 @@ const getContracts = async (req, res) => {
|
||||
// 格式化数据
|
||||
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),
|
||||
contract_number: contract.contract_number,
|
||||
customer_name: contract.customer_name,
|
||||
customer_phone: contract.customer_phone,
|
||||
customer_id_card: contract.customer_id_card,
|
||||
loan_amount: parseFloat(contract.loan_amount),
|
||||
loan_term: contract.loan_term,
|
||||
interest_rate: parseFloat(contract.interest_rate),
|
||||
contract_date: contract.contract_date,
|
||||
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
|
||||
created_at: contract.created_at,
|
||||
updated_at: contract.updated_at
|
||||
}));
|
||||
|
||||
res.json({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { LoanProduct, User } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
// 获取贷款商品列表
|
||||
const getLoanProducts = async (req, res) => {
|
||||
@@ -17,23 +18,37 @@ const getLoanProducts = async (req, res) => {
|
||||
const offset = (page - 1) * limit;
|
||||
const whereClause = {};
|
||||
|
||||
// 字段映射 - 将模型属性名映射到数据库字段名
|
||||
const fieldMapping = {
|
||||
'createdAt': 'created_at',
|
||||
'updatedAt': 'updated_at',
|
||||
'productName': 'product_name',
|
||||
'loanAmount': 'loan_amount',
|
||||
'loanTerm': 'loan_term',
|
||||
'interestRate': 'interest_rate',
|
||||
'serviceArea': 'service_area',
|
||||
'servicePhone': 'service_phone',
|
||||
'onSaleStatus': 'on_sale_status',
|
||||
'riskLevel': 'risk_level'
|
||||
};
|
||||
|
||||
// 搜索条件
|
||||
if (search) {
|
||||
whereClause[Op.or] = [
|
||||
{ productName: { [Op.like]: `%${search}%` } },
|
||||
{ serviceArea: { [Op.like]: `%${search}%` } },
|
||||
{ servicePhone: { [Op.like]: `%${search}%` } }
|
||||
{ product_name: { [Op.like]: `%${search}%` } },
|
||||
{ service_area: { [Op.like]: `%${search}%` } },
|
||||
{ service_phone: { [Op.like]: `%${search}%` } }
|
||||
];
|
||||
}
|
||||
|
||||
// 在售状态筛选
|
||||
if (onSaleStatus !== undefined) {
|
||||
whereClause.onSaleStatus = onSaleStatus === 'true';
|
||||
whereClause.on_sale_status = onSaleStatus === 'true';
|
||||
}
|
||||
|
||||
// 风险等级筛选
|
||||
if (riskLevel) {
|
||||
whereClause.riskLevel = riskLevel;
|
||||
whereClause.risk_level = riskLevel;
|
||||
}
|
||||
|
||||
const { count, rows } = await LoanProduct.findAndCountAll({
|
||||
@@ -50,7 +65,7 @@ const getLoanProducts = async (req, res) => {
|
||||
attributes: ['id', 'username', 'real_name']
|
||||
}
|
||||
],
|
||||
order: [[sortBy, sortOrder.toUpperCase()]],
|
||||
order: [[fieldMapping[sortBy] || sortBy, sortOrder.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset)
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ async function createAdminUser() {
|
||||
real_name: '系统管理员',
|
||||
email: 'admin@bank.com',
|
||||
phone: '13800138000',
|
||||
id_card: '110101199001010001',
|
||||
status: 'active',
|
||||
role_id: 1
|
||||
})
|
||||
|
||||
43
bank-backend/create-audit-records-table.js
Normal file
43
bank-backend/create-audit-records-table.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function createAuditRecordsTable() {
|
||||
try {
|
||||
console.log('创建bank_audit_records表...');
|
||||
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS bank_audit_records (
|
||||
id INT(11) NOT NULL AUTO_INCREMENT,
|
||||
applicationId INT(11) NOT NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
auditor VARCHAR(100) NOT NULL,
|
||||
auditorId INT(11) NOT NULL,
|
||||
comment TEXT,
|
||||
auditTime DATETIME NOT NULL,
|
||||
previousStatus VARCHAR(50),
|
||||
newStatus VARCHAR(50),
|
||||
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_application_id (applicationId),
|
||||
KEY idx_auditor_id (auditorId),
|
||||
KEY idx_audit_time (auditTime)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
|
||||
console.log('✅ bank_audit_records表创建成功');
|
||||
|
||||
// 标记迁移为完成
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000008-create-audit-records.js')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
|
||||
console.log('✅ 迁移记录已标记为完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建表失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
createAuditRecordsTable();
|
||||
39
bank-backend/create-missing-tables.sql
Normal file
39
bank-backend/create-missing-tables.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
-- 创建缺失的表
|
||||
|
||||
-- 创建贷款申请表
|
||||
CREATE TABLE IF NOT EXISTS loan_applications (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
|
||||
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
|
||||
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
|
||||
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
|
||||
loan_term INT NOT NULL COMMENT '贷款期限(月)',
|
||||
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%)',
|
||||
application_date DATE NOT NULL COMMENT '申请日期',
|
||||
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending' COMMENT '申请状态',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建贷款合同表
|
||||
CREATE TABLE IF NOT EXISTS loan_contracts (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
contract_number VARCHAR(50) NOT NULL UNIQUE COMMENT '合同编号',
|
||||
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
|
||||
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
|
||||
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
|
||||
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
|
||||
loan_term INT NOT NULL COMMENT '贷款期限(月)',
|
||||
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%)',
|
||||
contract_date DATE NOT NULL COMMENT '合同签订日期',
|
||||
status ENUM('active', 'completed', 'defaulted', 'cancelled') DEFAULT 'active' COMMENT '合同状态',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 标记迁移为已完成
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000006-create-loan-products.js');
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000007-create-loan-applications.js');
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000008-create-audit-records.js');
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000009-create-loan-contracts.js');
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000010-create-employees.js');
|
||||
68
bank-backend/create-tables.js
Normal file
68
bank-backend/create-tables.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function createMissingTables() {
|
||||
try {
|
||||
console.log('开始创建缺失的表...');
|
||||
|
||||
// 创建loan_applications表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS loan_applications (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
|
||||
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
|
||||
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
|
||||
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
|
||||
loan_term INT NOT NULL COMMENT '贷款期限(月)',
|
||||
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%)',
|
||||
application_date DATE NOT NULL COMMENT '申请日期',
|
||||
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending' COMMENT '申请状态',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 创建loan_applications表成功');
|
||||
|
||||
// 创建loan_contracts表
|
||||
await sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS loan_contracts (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
contract_number VARCHAR(50) NOT NULL UNIQUE COMMENT '合同编号',
|
||||
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
|
||||
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
|
||||
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
|
||||
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
|
||||
loan_term INT NOT NULL COMMENT '贷款期限(月)',
|
||||
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%)',
|
||||
contract_date DATE NOT NULL COMMENT '合同签订日期',
|
||||
status ENUM('active', 'completed', 'defaulted', 'cancelled') DEFAULT 'active' COMMENT '合同状态',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 创建loan_contracts表成功');
|
||||
|
||||
// 标记迁移为已完成
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO SequelizeMeta (name) VALUES
|
||||
('20241220000006-create-loan-products.js'),
|
||||
('20241220000007-create-loan-applications.js'),
|
||||
('20241220000008-create-audit-records.js'),
|
||||
('20241220000009-create-loan-contracts.js'),
|
||||
('20241220000010-create-employees.js')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 标记迁移为已完成');
|
||||
|
||||
// 显示所有表
|
||||
const tables = await sequelize.query('SHOW TABLES', { type: sequelize.QueryTypes.SELECT });
|
||||
console.log('\n当前数据库表:');
|
||||
tables.forEach(table => console.log('-', Object.values(table)[0]));
|
||||
|
||||
console.log('\n✅ 所有表创建完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 创建表失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
createMissingTables();
|
||||
@@ -3,11 +3,11 @@ PORT=5351
|
||||
NODE_ENV=development
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=bank_management
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_NAME=ningxia_bank
|
||||
DB_USER=root
|
||||
DB_PASSWORD=your_password
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
DB_DIALECT=mysql
|
||||
|
||||
# JWT配置
|
||||
|
||||
52
bank-backend/fix-supervision-tasks.js
Normal file
52
bank-backend/fix-supervision-tasks.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function fixSupervisionTasks() {
|
||||
try {
|
||||
console.log('开始修复supervision_tasks表...');
|
||||
|
||||
// 检查并添加created_by字段
|
||||
const createdByExists = await sequelize.query(
|
||||
'SHOW COLUMNS FROM supervision_tasks LIKE "created_by"',
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (createdByExists.length === 0) {
|
||||
await sequelize.query(
|
||||
'ALTER TABLE supervision_tasks ADD COLUMN created_by INT(11) AFTER remarks',
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log('✅ 添加created_by字段成功');
|
||||
} else {
|
||||
console.log('✅ created_by字段已存在');
|
||||
}
|
||||
|
||||
// 检查并添加updated_by字段
|
||||
const updatedByExists = await sequelize.query(
|
||||
'SHOW COLUMNS FROM supervision_tasks LIKE "updated_by"',
|
||||
{ type: sequelize.QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (updatedByExists.length === 0) {
|
||||
await sequelize.query(
|
||||
'ALTER TABLE supervision_tasks ADD COLUMN updated_by INT(11) AFTER created_by',
|
||||
{ type: sequelize.QueryTypes.RAW }
|
||||
);
|
||||
console.log('✅ 添加updated_by字段成功');
|
||||
} else {
|
||||
console.log('✅ updated_by字段已存在');
|
||||
}
|
||||
|
||||
// 显示表结构
|
||||
const columns = await sequelize.query('DESCRIBE supervision_tasks', { type: sequelize.QueryTypes.SELECT });
|
||||
console.log('\nsupervision_tasks表结构:');
|
||||
columns.forEach(col => console.log('-', col.Field, col.Type));
|
||||
|
||||
console.log('\n✅ supervision_tasks表修复完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 修复失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
fixSupervisionTasks();
|
||||
75
bank-backend/fix-test-data.js
Normal file
75
bank-backend/fix-test-data.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function fixTestData() {
|
||||
try {
|
||||
console.log('开始修复测试数据...');
|
||||
|
||||
// 添加installation_tasks测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO installation_tasks (
|
||||
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
|
||||
assetType, equipmentToInstall, installationStatus, taskGenerationTime, completionTime,
|
||||
installationNotes, installerName, installerPhone, installationAddress,
|
||||
createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('APP001', 'CON001', '养殖设备', '张三', 'ID_CARD', '110101199001010001',
|
||||
'cattle', '耳标设备', 'pending', NOW(), NULL,
|
||||
'测试安装任务', '安装员1', '13900139000', '北京市朝阳区农场',
|
||||
2, 2, NOW(), NOW()),
|
||||
('APP002', 'CON002', '种植设备', '李四', 'ID_CARD', '110101199002020002',
|
||||
'sheep', '项圈设备', 'completed', NOW(), NOW(),
|
||||
'测试安装任务2', '安装员2', '14000140000', '北京市海淀区农场',
|
||||
2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加installation_tasks测试数据成功');
|
||||
|
||||
// 添加completed_supervisions测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO completed_supervisions (
|
||||
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
|
||||
assetType, assetQuantity, totalRepaymentPeriods, settlementStatus, settlementDate,
|
||||
importTime, settlementAmount, remainingAmount, settlementNotes,
|
||||
createdBy, updatedBy, createdAt, updatedAt
|
||||
) VALUES
|
||||
('APP001', 'CON001', '养殖贷款', '张三', 'ID_CARD', '110101199001010001',
|
||||
'cattle', 10, 12, 'partial', '2024-01-15',
|
||||
NOW(), 30000.00, 20000.00, '已结清部分',
|
||||
2, 2, NOW(), NOW()),
|
||||
('APP002', 'CON002', '种植贷款', '李四', 'ID_CARD', '110101199002020002',
|
||||
'sheep', 20, 10, 'settled', '2024-01-20',
|
||||
NOW(), 30000.00, 0.00, '已完全结清',
|
||||
2, 2, NOW(), NOW())
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加completed_supervisions测试数据成功');
|
||||
|
||||
// 添加loan_applications测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO loan_applications (
|
||||
customer_name, customer_phone, customer_id_card, loan_amount, loan_term,
|
||||
interest_rate, application_date, status
|
||||
) VALUES
|
||||
('张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'pending'),
|
||||
('李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'approved')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加loan_applications测试数据成功');
|
||||
|
||||
// 添加loan_contracts测试数据
|
||||
await sequelize.query(`
|
||||
INSERT INTO loan_contracts (
|
||||
contract_number, customer_name, customer_phone, customer_id_card, loan_amount,
|
||||
loan_term, interest_rate, contract_date, status
|
||||
) VALUES
|
||||
('CON001', '张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'active'),
|
||||
('CON002', '李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'completed')
|
||||
`, { type: sequelize.QueryTypes.RAW });
|
||||
console.log('✅ 添加loan_contracts测试数据成功');
|
||||
|
||||
console.log('\n✅ 所有测试数据修复完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ 修复测试数据失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
fixTestData();
|
||||
@@ -55,7 +55,7 @@ module.exports = {
|
||||
allowNull: false,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
model: 'bank_users',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
|
||||
124
bank-backend/migrations/20241220000010-create-employees.js
Normal file
124
bank-backend/migrations/20241220000010-create-employees.js
Normal file
@@ -0,0 +1,124 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('bank_employees', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
employeeNumber: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '员工编号'
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '员工姓名'
|
||||
},
|
||||
phone: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
},
|
||||
email: {
|
||||
type: Sequelize.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '邮箱'
|
||||
},
|
||||
password: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '密码'
|
||||
},
|
||||
isLoanSpecialist: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: '是否为贷款专员'
|
||||
},
|
||||
department: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '部门'
|
||||
},
|
||||
position: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '职位'
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('active', 'inactive', 'locked'),
|
||||
defaultValue: 'active',
|
||||
comment: '账号状态'
|
||||
},
|
||||
lastLogin: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '最后登录时间'
|
||||
},
|
||||
loginAttempts: {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: '登录尝试次数'
|
||||
},
|
||||
lockedUntil: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
comment: '锁定到期时间'
|
||||
},
|
||||
createdBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
deletedAt: {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true
|
||||
}
|
||||
});
|
||||
|
||||
// 添加索引
|
||||
await queryInterface.addIndex('bank_employees', ['employeeNumber'], {
|
||||
unique: true,
|
||||
name: 'idx_employees_employee_number'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('bank_employees', ['phone'], {
|
||||
unique: true,
|
||||
name: 'idx_employees_phone'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('bank_employees', ['status'], {
|
||||
name: 'idx_employees_status'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('bank_employees', ['isLoanSpecialist'], {
|
||||
name: 'idx_employees_loan_specialist'
|
||||
});
|
||||
|
||||
await queryInterface.addIndex('bank_employees', ['deletedAt'], {
|
||||
name: 'idx_employees_deleted_at'
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('bank_employees');
|
||||
}
|
||||
};
|
||||
@@ -101,9 +101,27 @@ const CompletedSupervision = sequelize.define('CompletedSupervision', {
|
||||
}, {
|
||||
tableName: 'completed_supervisions',
|
||||
timestamps: true,
|
||||
underscored: false,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '监管任务已结项表'
|
||||
});
|
||||
|
||||
// 定义关联关系
|
||||
CompletedSupervision.associate = (models) => {
|
||||
// 监管任务已结项与用户关联(创建人)
|
||||
CompletedSupervision.belongsTo(models.User, {
|
||||
foreignKey: { name: 'createdBy', field: 'createdBy' },
|
||||
targetKey: 'id',
|
||||
as: 'creator'
|
||||
});
|
||||
|
||||
// 监管任务已结项与用户关联(更新人)
|
||||
CompletedSupervision.belongsTo(models.User, {
|
||||
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
|
||||
targetKey: 'id',
|
||||
as: 'updater'
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = CompletedSupervision;
|
||||
|
||||
@@ -1,82 +1,141 @@
|
||||
/**
|
||||
* 员工模型
|
||||
* @file Employee.js
|
||||
* @description 员工数据模型
|
||||
*/
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const Employee = sequelize.define('Employee', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '员工姓名'
|
||||
},
|
||||
employee_id: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '员工编号'
|
||||
},
|
||||
department_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '部门ID'
|
||||
},
|
||||
position_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '职位ID'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: true,
|
||||
comment: '联系电话'
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '邮箱地址'
|
||||
},
|
||||
hire_date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '入职日期'
|
||||
},
|
||||
salary: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
comment: '薪资(分)'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'resigned'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '员工状态:在职、离职、已辞职'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updated_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
tableName: 'bank_employees',
|
||||
modelName: 'Employee',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
module.exports = (sequelize) => {
|
||||
const Employee = sequelize.define('Employee', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
employeeNumber: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '员工编号'
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: '员工姓名'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '邮箱'
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
comment: '密码'
|
||||
},
|
||||
isLoanSpecialist: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: '是否为贷款专员'
|
||||
},
|
||||
department: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '部门'
|
||||
},
|
||||
position: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '职位'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'locked'),
|
||||
defaultValue: 'active',
|
||||
comment: '账号状态'
|
||||
},
|
||||
lastLogin: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '最后登录时间'
|
||||
},
|
||||
loginAttempts: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: '登录尝试次数'
|
||||
},
|
||||
lockedUntil: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '锁定到期时间'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'bank_employees',
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
comment: '银行员工表',
|
||||
hooks: {
|
||||
beforeSave: async (employee) => {
|
||||
if (employee.changed('password')) {
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
employee.password = await bcrypt.hash(employee.password, salt);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Employee;
|
||||
// 实例方法:验证密码
|
||||
Employee.prototype.validPassword = async function(password) {
|
||||
try {
|
||||
return await bcrypt.compare(password, this.password);
|
||||
} catch (error) {
|
||||
console.error('密码验证错误:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 实例方法:检查账号是否被锁定
|
||||
Employee.prototype.isLocked = function() {
|
||||
return !!(this.lockedUntil && this.lockedUntil > Date.now());
|
||||
};
|
||||
|
||||
// 实例方法:增加登录尝试次数
|
||||
Employee.prototype.incLoginAttempts = async function() {
|
||||
// 如果已经锁定且锁定时间已过,则重置
|
||||
if (this.lockedUntil && this.lockedUntil < Date.now()) {
|
||||
return this.update({
|
||||
loginAttempts: 1,
|
||||
lockedUntil: null
|
||||
});
|
||||
}
|
||||
|
||||
const updates = { loginAttempts: this.loginAttempts + 1 };
|
||||
|
||||
// 如果达到最大尝试次数,则锁定账号
|
||||
if (this.loginAttempts + 1 >= 5 && !this.isLocked()) {
|
||||
updates.lockedUntil = new Date(Date.now() + 30 * 60 * 1000); // 锁定30分钟
|
||||
}
|
||||
|
||||
return this.update(updates);
|
||||
};
|
||||
|
||||
// 实例方法:重置登录尝试次数
|
||||
Employee.prototype.resetLoginAttempts = async function() {
|
||||
return this.update({
|
||||
loginAttempts: 0,
|
||||
lockedUntil: null
|
||||
});
|
||||
};
|
||||
|
||||
return Employee;
|
||||
};
|
||||
@@ -99,9 +99,27 @@ const InstallationTask = sequelize.define('InstallationTask', {
|
||||
}, {
|
||||
tableName: 'installation_tasks',
|
||||
timestamps: true,
|
||||
underscored: false,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '待安装任务表'
|
||||
});
|
||||
|
||||
// 定义关联关系
|
||||
InstallationTask.associate = (models) => {
|
||||
// 待安装任务与用户关联(创建人)
|
||||
InstallationTask.belongsTo(models.User, {
|
||||
foreignKey: { name: 'createdBy', field: 'createdBy' },
|
||||
targetKey: 'id',
|
||||
as: 'creator'
|
||||
});
|
||||
|
||||
// 待安装任务与用户关联(更新人)
|
||||
InstallationTask.belongsTo(models.User, {
|
||||
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
|
||||
targetKey: 'id',
|
||||
as: 'updater'
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = InstallationTask;
|
||||
@@ -13,34 +13,20 @@ class LoanApplication extends BaseModel {
|
||||
*/
|
||||
getStatusText() {
|
||||
const statusMap = {
|
||||
pending_review: '待初审',
|
||||
verification_pending: '核验待放款',
|
||||
pending_binding: '待绑定',
|
||||
pending: '待审核',
|
||||
approved: '已通过',
|
||||
rejected: '已拒绝'
|
||||
rejected: '已拒绝',
|
||||
completed: '已完成'
|
||||
};
|
||||
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)}元`;
|
||||
return `${this.loan_amount.toFixed(2)}元`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,144 +37,55 @@ LoanApplication.init({
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
applicationNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
},
|
||||
productName: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
farmerName: {
|
||||
customer_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请养殖户姓名'
|
||||
comment: '客户姓名'
|
||||
},
|
||||
borrowerName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '贷款人姓名'
|
||||
},
|
||||
borrowerIdNumber: {
|
||||
customer_phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '贷款人身份证号'
|
||||
comment: '客户电话'
|
||||
},
|
||||
assetType: {
|
||||
type: DataTypes.STRING(50),
|
||||
customer_id_card: {
|
||||
type: DataTypes.STRING(18),
|
||||
allowNull: false,
|
||||
comment: '生资种类'
|
||||
comment: '客户身份证号'
|
||||
},
|
||||
applicationQuantity: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请数量'
|
||||
},
|
||||
amount: {
|
||||
loan_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '申请额度'
|
||||
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: {
|
||||
loan_term: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '申请期限(月)'
|
||||
comment: '贷款期限(月)'
|
||||
},
|
||||
interestRate: {
|
||||
interest_rate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '预计利率'
|
||||
comment: '贷款利率(%)'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
application_date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
comment: '申请日期'
|
||||
},
|
||||
purpose: {
|
||||
type: DataTypes.TEXT,
|
||||
status: {
|
||||
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'completed'),
|
||||
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: '拒绝原因'
|
||||
defaultValue: 'pending',
|
||||
comment: '申请状态'
|
||||
}
|
||||
}, {
|
||||
sequelize: require('../config/database').sequelize,
|
||||
modelName: 'LoanApplication',
|
||||
tableName: 'bank_loan_applications',
|
||||
tableName: '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;
|
||||
}
|
||||
}
|
||||
}
|
||||
underscored: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
});
|
||||
|
||||
module.exports = LoanApplication;
|
||||
|
||||
@@ -14,7 +14,6 @@ class LoanContract extends BaseModel {
|
||||
getStatusText() {
|
||||
const statusMap = {
|
||||
active: '已放款',
|
||||
pending: '待放款',
|
||||
completed: '已完成',
|
||||
defaulted: '违约',
|
||||
cancelled: '已取消'
|
||||
@@ -22,43 +21,12 @@ class LoanContract extends BaseModel {
|
||||
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);
|
||||
return `${this.loan_amount.toFixed(2)}元`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,167 +37,63 @@ LoanContract.init({
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
contractNumber: {
|
||||
contract_number: {
|
||||
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: {
|
||||
customer_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请养殖户姓名'
|
||||
comment: '客户姓名'
|
||||
},
|
||||
borrowerName: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '贷款人姓名'
|
||||
},
|
||||
borrowerIdNumber: {
|
||||
customer_phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '贷款人身份证号'
|
||||
comment: '客户电话'
|
||||
},
|
||||
assetType: {
|
||||
type: DataTypes.STRING(50),
|
||||
customer_id_card: {
|
||||
type: DataTypes.STRING(18),
|
||||
allowNull: false,
|
||||
comment: '生资种类'
|
||||
comment: '客户身份证号'
|
||||
},
|
||||
applicationQuantity: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '申请数量'
|
||||
},
|
||||
amount: {
|
||||
loan_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '合同金额'
|
||||
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: {
|
||||
loan_term: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '合同期限(月)'
|
||||
comment: '贷款期限(月)'
|
||||
},
|
||||
interestRate: {
|
||||
interest_rate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
comment: '利率'
|
||||
comment: '贷款利率(%)'
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
contract_date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
comment: '联系电话'
|
||||
comment: '合同签订日期'
|
||||
},
|
||||
purpose: {
|
||||
type: DataTypes.TEXT,
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'completed', 'defaulted', 'cancelled'),
|
||||
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'
|
||||
}
|
||||
defaultValue: 'active',
|
||||
comment: '合同状态'
|
||||
}
|
||||
}, {
|
||||
sequelize: require('../config/database').sequelize,
|
||||
modelName: 'LoanContract',
|
||||
tableName: 'bank_loan_contracts',
|
||||
tableName: '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;
|
||||
}
|
||||
}
|
||||
}
|
||||
underscored: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
// 移除不存在的字段
|
||||
omitNull: true
|
||||
});
|
||||
|
||||
module.exports = LoanContract;
|
||||
module.exports = LoanContract;
|
||||
@@ -11,108 +11,127 @@ const LoanProduct = sequelize.define('LoanProduct', {
|
||||
productName: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
field: 'product_name',
|
||||
comment: '贷款产品名称'
|
||||
},
|
||||
loanAmount: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
field: 'loan_amount',
|
||||
comment: '贷款额度'
|
||||
},
|
||||
loanTerm: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'loan_term',
|
||||
comment: '贷款周期(月)'
|
||||
},
|
||||
interestRate: {
|
||||
type: DataTypes.DECIMAL(5, 2),
|
||||
allowNull: false,
|
||||
field: 'interest_rate',
|
||||
comment: '贷款利率(%)'
|
||||
},
|
||||
serviceArea: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
field: 'service_area',
|
||||
comment: '服务区域'
|
||||
},
|
||||
servicePhone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
field: 'service_phone',
|
||||
comment: '服务电话'
|
||||
},
|
||||
totalCustomers: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'total_customers',
|
||||
comment: '服务客户总数量'
|
||||
},
|
||||
supervisionCustomers: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'supervision_customers',
|
||||
comment: '监管中客户数量'
|
||||
},
|
||||
completedCustomers: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'completed_customers',
|
||||
comment: '已结项客户数量'
|
||||
},
|
||||
onSaleStatus: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
field: 'on_sale_status',
|
||||
comment: '在售状态'
|
||||
},
|
||||
productDescription: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'product_description',
|
||||
comment: '产品描述'
|
||||
},
|
||||
applicationRequirements: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'application_requirements',
|
||||
comment: '申请条件'
|
||||
},
|
||||
requiredDocuments: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'required_documents',
|
||||
comment: '所需材料'
|
||||
},
|
||||
approvalProcess: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'approval_process',
|
||||
comment: '审批流程'
|
||||
},
|
||||
riskLevel: {
|
||||
type: DataTypes.ENUM('LOW', 'MEDIUM', 'HIGH'),
|
||||
allowNull: false,
|
||||
defaultValue: 'MEDIUM',
|
||||
field: 'risk_level',
|
||||
comment: '风险等级'
|
||||
},
|
||||
minLoanAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'min_loan_amount',
|
||||
comment: '最小贷款金额'
|
||||
},
|
||||
maxLoanAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
field: 'max_loan_amount',
|
||||
comment: '最大贷款金额'
|
||||
},
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'created_by',
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'updated_by',
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'loan_products',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
comment: '贷款商品表'
|
||||
});
|
||||
|
||||
|
||||
@@ -21,61 +21,72 @@ const Project = sequelize.define('Project', {
|
||||
farmName: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: false,
|
||||
field: 'farmName',
|
||||
comment: '养殖场名称'
|
||||
},
|
||||
supervisionObject: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'supervisionObject',
|
||||
comment: '监管对象'
|
||||
},
|
||||
supervisionQuantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'supervisionQuantity',
|
||||
comment: '监管数量'
|
||||
},
|
||||
supervisionPeriod: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
field: 'supervisionPeriod',
|
||||
comment: '监管周期'
|
||||
},
|
||||
supervisionAmount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0.00,
|
||||
field: 'supervisionAmount',
|
||||
comment: '监管金额'
|
||||
},
|
||||
startTime: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
field: 'startTime',
|
||||
comment: '起始时间'
|
||||
},
|
||||
endTime: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
field: 'endTime',
|
||||
comment: '结束时间'
|
||||
},
|
||||
earTag: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'earTag',
|
||||
comment: '耳标数量'
|
||||
},
|
||||
collar: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'collar',
|
||||
comment: '项圈数量'
|
||||
},
|
||||
host: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'host',
|
||||
comment: '主机数量'
|
||||
},
|
||||
loanOfficer: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
field: 'loanOfficer',
|
||||
comment: '贷款专员'
|
||||
},
|
||||
description: {
|
||||
@@ -86,16 +97,19 @@ const Project = sequelize.define('Project', {
|
||||
createdBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'createdBy',
|
||||
comment: '创建人ID'
|
||||
},
|
||||
updatedBy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'updatedBy',
|
||||
comment: '更新人ID'
|
||||
}
|
||||
}, {
|
||||
tableName: 'projects',
|
||||
timestamps: true,
|
||||
underscored: false,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '项目清单表'
|
||||
@@ -105,13 +119,15 @@ const Project = sequelize.define('Project', {
|
||||
Project.associate = (models) => {
|
||||
// 项目与用户关联(创建人)
|
||||
Project.belongsTo(models.User, {
|
||||
foreignKey: 'createdBy',
|
||||
foreignKey: { name: 'createdBy', field: 'createdBy' },
|
||||
targetKey: 'id',
|
||||
as: 'creator'
|
||||
});
|
||||
|
||||
// 项目与用户关联(更新人)
|
||||
Project.belongsTo(models.User, {
|
||||
foreignKey: 'updatedBy',
|
||||
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
|
||||
targetKey: 'id',
|
||||
as: 'updater'
|
||||
});
|
||||
};
|
||||
|
||||
@@ -19,7 +19,8 @@ SupervisionTask.init({
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '申请单号'
|
||||
comment: '申请单号',
|
||||
field: 'applicationNumber'
|
||||
},
|
||||
contractNumber: {
|
||||
type: DataTypes.STRING(50),
|
||||
@@ -145,7 +146,7 @@ SupervisionTask.init({
|
||||
modelName: 'SupervisionTask',
|
||||
tableName: 'supervision_tasks',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
underscored: false,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
comment: '监管任务表'
|
||||
@@ -155,11 +156,13 @@ SupervisionTask.init({
|
||||
SupervisionTask.associate = (models) => {
|
||||
SupervisionTask.belongsTo(models.User, {
|
||||
foreignKey: 'createdBy',
|
||||
as: 'creator'
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
SupervisionTask.belongsTo(models.User, {
|
||||
foreignKey: 'updatedBy',
|
||||
as: 'updater'
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -172,6 +172,10 @@ User.init({
|
||||
sequelize,
|
||||
tableName: 'bank_users',
|
||||
modelName: 'User',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
hooks: {
|
||||
beforeCreate: async (user) => {
|
||||
if (user.password) {
|
||||
|
||||
@@ -11,7 +11,7 @@ const Role = require('./Role');
|
||||
const Account = require('./Account');
|
||||
const Transaction = require('./Transaction');
|
||||
const LoanProduct = require('./LoanProduct');
|
||||
const Employee = require('./Employee');
|
||||
const Employee = require('./Employee')(sequelize);
|
||||
const Department = require('./Department');
|
||||
const Position = require('./Position');
|
||||
const Report = require('./Report');
|
||||
@@ -62,29 +62,7 @@ Transaction.belongsTo(Account, {
|
||||
// 交易记录与用户关联(通过账户)
|
||||
// 移除不合理的Transaction->User through Account的belongsTo定义,避免错误外键映射
|
||||
|
||||
// 员工与部门关联
|
||||
Employee.belongsTo(Department, {
|
||||
foreignKey: 'department_id',
|
||||
as: 'department',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
Department.hasMany(Employee, {
|
||||
foreignKey: 'department_id',
|
||||
as: 'employees'
|
||||
});
|
||||
|
||||
// 员工与职位关联
|
||||
Employee.belongsTo(Position, {
|
||||
foreignKey: 'position_id',
|
||||
as: 'position',
|
||||
targetKey: 'id'
|
||||
});
|
||||
|
||||
Position.hasMany(Employee, {
|
||||
foreignKey: 'position_id',
|
||||
as: 'employees'
|
||||
});
|
||||
// 员工关联关系(Employee模型使用字符串字段存储部门和职位,不需要关联)
|
||||
|
||||
// 报表与用户关联
|
||||
Report.belongsTo(User, {
|
||||
@@ -100,7 +78,7 @@ User.hasMany(Report, {
|
||||
|
||||
// 项目与用户关联(创建人)
|
||||
Project.belongsTo(User, {
|
||||
foreignKey: 'createdBy',
|
||||
foreignKey: { name: 'createdBy', field: 'createdBy' },
|
||||
as: 'creator',
|
||||
targetKey: 'id'
|
||||
});
|
||||
@@ -112,7 +90,7 @@ User.hasMany(Project, {
|
||||
|
||||
// 项目与用户关联(更新人)
|
||||
Project.belongsTo(User, {
|
||||
foreignKey: 'updatedBy',
|
||||
foreignKey: { name: 'updatedBy', field: 'updatedBy' },
|
||||
as: 'updater',
|
||||
targetKey: 'id'
|
||||
});
|
||||
@@ -218,41 +196,8 @@ User.hasMany(LoanProduct, {
|
||||
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, {
|
||||
@@ -278,29 +223,8 @@ User.hasMany(AuditRecord, {
|
||||
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 = {
|
||||
|
||||
26
bank-backend/quick-test-projects.js
Normal file
26
bank-backend/quick-test-projects.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const { sequelize, Project, User } = require('./models');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('DB should be ningxia_bank test:');
|
||||
const [dbRow] = await sequelize.query('SELECT DATABASE() AS db');
|
||||
console.log('DB:', dbRow[0].db);
|
||||
|
||||
console.log('Testing Project.findAndCountAll with includes...');
|
||||
const result = await Project.findAndCountAll({
|
||||
include: [
|
||||
{ model: User, as: 'creator', attributes: ['id', 'username'] },
|
||||
{ model: User, as: 'updater', attributes: ['id', 'username'] }
|
||||
],
|
||||
limit: 5
|
||||
});
|
||||
console.log('count:', result.count);
|
||||
console.log('rows length:', result.rows.length);
|
||||
} catch (err) {
|
||||
console.error('ERROR:', err.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,316 +1,116 @@
|
||||
/**
|
||||
* 员工路由
|
||||
* @file employees.js
|
||||
* @description 员工相关的路由定义
|
||||
*/
|
||||
const express = require('express');
|
||||
const { body } = require('express-validator');
|
||||
const { authMiddleware, roleMiddleware, adminMiddleware, managerMiddleware } = require('../middleware/auth');
|
||||
const employeeController = require('../controllers/employeeController');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
const {
|
||||
getEmployees,
|
||||
getEmployeeById,
|
||||
createEmployee,
|
||||
updateEmployee,
|
||||
resetPassword,
|
||||
deleteEmployee,
|
||||
batchUpdateStatus,
|
||||
getEmployeeStats
|
||||
} = require('../controllers/employeeController');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 所有路由都需要认证
|
||||
// 验证规则
|
||||
const createEmployeeValidation = [
|
||||
body('employeeNumber')
|
||||
.notEmpty()
|
||||
.withMessage('员工编号不能为空')
|
||||
.isLength({ min: 3, max: 20 })
|
||||
.withMessage('员工编号长度在3-20个字符'),
|
||||
body('name')
|
||||
.notEmpty()
|
||||
.withMessage('员工姓名不能为空')
|
||||
.isLength({ min: 2, max: 50 })
|
||||
.withMessage('员工姓名长度在2-50个字符'),
|
||||
body('phone')
|
||||
.notEmpty()
|
||||
.withMessage('联系电话不能为空')
|
||||
.matches(/^1[3-9]\d{9}$/)
|
||||
.withMessage('请输入正确的手机号码'),
|
||||
body('email')
|
||||
.optional()
|
||||
.isEmail()
|
||||
.withMessage('请输入正确的邮箱地址'),
|
||||
body('password')
|
||||
.optional()
|
||||
.isLength({ min: 6, max: 20 })
|
||||
.withMessage('密码长度在6-20个字符'),
|
||||
body('isLoanSpecialist')
|
||||
.optional()
|
||||
.isBoolean()
|
||||
.withMessage('贷款专员标识必须是布尔值'),
|
||||
body('department')
|
||||
.optional()
|
||||
.isLength({ max: 50 })
|
||||
.withMessage('部门名称不能超过50个字符'),
|
||||
body('position')
|
||||
.optional()
|
||||
.isLength({ max: 50 })
|
||||
.withMessage('职位名称不能超过50个字符')
|
||||
];
|
||||
|
||||
const updateEmployeeValidation = [
|
||||
body('name')
|
||||
.optional()
|
||||
.isLength({ min: 2, max: 50 })
|
||||
.withMessage('员工姓名长度在2-50个字符'),
|
||||
body('phone')
|
||||
.optional()
|
||||
.matches(/^1[3-9]\d{9}$/)
|
||||
.withMessage('请输入正确的手机号码'),
|
||||
body('email')
|
||||
.optional()
|
||||
.isEmail()
|
||||
.withMessage('请输入正确的邮箱地址'),
|
||||
body('isLoanSpecialist')
|
||||
.optional()
|
||||
.isBoolean()
|
||||
.withMessage('贷款专员标识必须是布尔值'),
|
||||
body('department')
|
||||
.optional()
|
||||
.isLength({ max: 50 })
|
||||
.withMessage('部门名称不能超过50个字符'),
|
||||
body('position')
|
||||
.optional()
|
||||
.isLength({ max: 50 })
|
||||
.withMessage('职位名称不能超过50个字符'),
|
||||
body('status')
|
||||
.optional()
|
||||
.isIn(['active', 'inactive', 'locked'])
|
||||
.withMessage('状态值无效')
|
||||
];
|
||||
|
||||
const resetPasswordValidation = [
|
||||
body('newPassword')
|
||||
.optional()
|
||||
.isLength({ min: 6, max: 20 })
|
||||
.withMessage('密码长度在6-20个字符')
|
||||
];
|
||||
|
||||
const batchUpdateStatusValidation = [
|
||||
body('ids')
|
||||
.isArray({ min: 1 })
|
||||
.withMessage('请选择要更新的员工'),
|
||||
body('status')
|
||||
.isIn(['active', 'inactive', 'locked'])
|
||||
.withMessage('状态值无效')
|
||||
];
|
||||
|
||||
// 应用认证中间件
|
||||
router.use(authMiddleware);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Employees
|
||||
* description: 员工管理
|
||||
*/
|
||||
// 路由定义
|
||||
router.get('/', getEmployees); // 获取员工列表
|
||||
router.get('/stats', getEmployeeStats); // 获取员工统计
|
||||
router.get('/:id', getEmployeeById); // 获取员工详情
|
||||
router.post('/', createEmployeeValidation, createEmployee); // 创建员工
|
||||
router.put('/:id', updateEmployeeValidation, updateEmployee); // 更新员工信息
|
||||
router.put('/:id/reset-password', resetPasswordValidation, resetPassword); // 重设密码
|
||||
router.delete('/:id', deleteEmployee); // 删除员工
|
||||
router.put('/batch/status', batchUpdateStatusValidation, batchUpdateStatus); // 批量更新状态
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees:
|
||||
* get:
|
||||
* summary: 获取员工列表
|
||||
* tags: [Employees]
|
||||
* 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: department
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 部门筛选
|
||||
* - in: query
|
||||
* name: position
|
||||
* schema:
|
||||
* type: string
|
||||
* description: 职位筛选
|
||||
* - in: query
|
||||
* name: status
|
||||
* schema:
|
||||
* type: string
|
||||
* enum: [active, inactive, resigned]
|
||||
* description: 状态筛选
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* message:
|
||||
* type: string
|
||||
* data:
|
||||
* type: object
|
||||
* properties:
|
||||
* employees:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Employee'
|
||||
* pagination:
|
||||
* $ref: '#/components/schemas/Pagination'
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployees);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees:
|
||||
* post:
|
||||
* summary: 创建员工
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required:
|
||||
* - name
|
||||
* - employee_id
|
||||
* - department_id
|
||||
* - position_id
|
||||
* - hire_date
|
||||
* - salary
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 员工姓名
|
||||
* employee_id:
|
||||
* type: string
|
||||
* description: 员工编号
|
||||
* department_id:
|
||||
* type: integer
|
||||
* description: 部门ID
|
||||
* position_id:
|
||||
* type: integer
|
||||
* description: 职位ID
|
||||
* phone:
|
||||
* type: string
|
||||
* description: 联系电话
|
||||
* email:
|
||||
* type: string
|
||||
* description: 邮箱地址
|
||||
* hire_date:
|
||||
* type: string
|
||||
* format: date
|
||||
* description: 入职日期
|
||||
* salary:
|
||||
* type: number
|
||||
* description: 薪资
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, inactive, resigned]
|
||||
* description: 员工状态
|
||||
* responses:
|
||||
* 201:
|
||||
* description: 创建成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.post('/',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').notEmpty().withMessage('员工姓名不能为空'),
|
||||
body('employee_id').notEmpty().withMessage('员工编号不能为空'),
|
||||
body('department_id').isInt().withMessage('部门ID必须是整数'),
|
||||
body('position_id').isInt().withMessage('职位ID必须是整数'),
|
||||
body('phone').optional().isMobilePhone('zh-CN').withMessage('手机号格式不正确'),
|
||||
body('email').optional().isEmail().withMessage('邮箱格式不正确'),
|
||||
body('hire_date').isISO8601().withMessage('入职日期格式不正确'),
|
||||
body('salary').isNumeric().withMessage('薪资必须是数字'),
|
||||
body('status').optional().isIn(['active', 'inactive', 'resigned']).withMessage('状态值无效')
|
||||
],
|
||||
employeeController.createEmployee
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/{id}:
|
||||
* get:
|
||||
* summary: 获取员工详情
|
||||
* tags: [Employees]
|
||||
* 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']), employeeController.getEmployeeById);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/{id}:
|
||||
* put:
|
||||
* summary: 更新员工
|
||||
* tags: [Employees]
|
||||
* 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
|
||||
* employee_id:
|
||||
* type: string
|
||||
* department_id:
|
||||
* type: integer
|
||||
* position_id:
|
||||
* type: integer
|
||||
* phone:
|
||||
* type: string
|
||||
* email:
|
||||
* type: string
|
||||
* hire_date:
|
||||
* type: string
|
||||
* format: date
|
||||
* salary:
|
||||
* type: number
|
||||
* status:
|
||||
* type: string
|
||||
* enum: [active, inactive, resigned]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 更新成功
|
||||
* 400:
|
||||
* description: 请求参数错误
|
||||
* 404:
|
||||
* description: 员工不存在
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 403:
|
||||
* description: 权限不足
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.put('/:id',
|
||||
adminMiddleware,
|
||||
[
|
||||
body('name').optional().notEmpty().withMessage('员工姓名不能为空'),
|
||||
body('employee_id').optional().notEmpty().withMessage('员工编号不能为空'),
|
||||
body('department_id').optional().isInt().withMessage('部门ID必须是整数'),
|
||||
body('position_id').optional().isInt().withMessage('职位ID必须是整数'),
|
||||
body('phone').optional().isMobilePhone('zh-CN').withMessage('手机号格式不正确'),
|
||||
body('email').optional().isEmail().withMessage('邮箱格式不正确'),
|
||||
body('hire_date').optional().isISO8601().withMessage('入职日期格式不正确'),
|
||||
body('salary').optional().isNumeric().withMessage('薪资必须是数字'),
|
||||
body('status').optional().isIn(['active', 'inactive', 'resigned']).withMessage('状态值无效')
|
||||
],
|
||||
employeeController.updateEmployee
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/{id}:
|
||||
* delete:
|
||||
* summary: 删除员工
|
||||
* tags: [Employees]
|
||||
* 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, employeeController.deleteEmployee);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/employees/stats/overview:
|
||||
* get:
|
||||
* summary: 获取员工统计
|
||||
* tags: [Employees]
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* responses:
|
||||
* 200:
|
||||
* description: 获取成功
|
||||
* 401:
|
||||
* description: 未授权
|
||||
* 500:
|
||||
* description: 服务器内部错误
|
||||
*/
|
||||
router.get('/stats/overview', roleMiddleware(['admin', 'manager', 'teller']), employeeController.getEmployeeStats);
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
173
bank-backend/scripts/seed-employees.js
Normal file
173
bank-backend/scripts/seed-employees.js
Normal file
@@ -0,0 +1,173 @@
|
||||
const { sequelize, Employee } = require('../models');
|
||||
|
||||
/**
|
||||
* 添加员工测试数据
|
||||
*/
|
||||
async function seedEmployees() {
|
||||
try {
|
||||
console.log('开始添加员工测试数据...');
|
||||
|
||||
// 检查数据库连接
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 银行系统数据库连接成功');
|
||||
|
||||
// 清空现有员工数据
|
||||
await Employee.destroy({
|
||||
where: {},
|
||||
force: true
|
||||
});
|
||||
console.log('✅ 清空现有员工数据');
|
||||
|
||||
// 员工测试数据
|
||||
const employeesData = [
|
||||
{
|
||||
employeeNumber: 'EMP001',
|
||||
name: '刘超',
|
||||
phone: '15012341368',
|
||||
email: 'liuchao@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: false,
|
||||
department: '风险管理部',
|
||||
position: '风险专员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP002',
|
||||
name: '张明',
|
||||
phone: '13812345678',
|
||||
email: 'zhangming@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: true,
|
||||
department: '信贷部',
|
||||
position: '贷款专员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP003',
|
||||
name: '李红',
|
||||
phone: '13987654321',
|
||||
email: 'lihong@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: true,
|
||||
department: '信贷部',
|
||||
position: '高级贷款专员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP004',
|
||||
name: '王强',
|
||||
phone: '13611111111',
|
||||
email: 'wangqiang@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: false,
|
||||
department: '运营部',
|
||||
position: '运营专员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP005',
|
||||
name: '陈静',
|
||||
phone: '13722222222',
|
||||
email: 'chenjing@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: true,
|
||||
department: '信贷部',
|
||||
position: '贷款专员',
|
||||
status: 'inactive'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP006',
|
||||
name: '赵磊',
|
||||
phone: '13833333333',
|
||||
email: 'zhaolei@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: false,
|
||||
department: '技术部',
|
||||
position: '系统管理员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP007',
|
||||
name: '孙丽',
|
||||
phone: '13944444444',
|
||||
email: 'sunli@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: true,
|
||||
department: '信贷部',
|
||||
position: '贷款专员',
|
||||
status: 'locked'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP008',
|
||||
name: '周涛',
|
||||
phone: '13555555555',
|
||||
email: 'zhoutao@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: false,
|
||||
department: '财务部',
|
||||
position: '财务专员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP009',
|
||||
name: '吴敏',
|
||||
phone: '13466666666',
|
||||
email: 'wumin@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: true,
|
||||
department: '信贷部',
|
||||
position: '贷款专员',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
employeeNumber: 'EMP010',
|
||||
name: '郑华',
|
||||
phone: '13377777777',
|
||||
email: 'zhenghua@bank.com',
|
||||
password: '123456',
|
||||
isLoanSpecialist: false,
|
||||
department: '人事部',
|
||||
position: '人事专员',
|
||||
status: 'active'
|
||||
}
|
||||
];
|
||||
|
||||
// 创建员工
|
||||
const employees = await Employee.bulkCreate(employeesData);
|
||||
console.log(`✅ 成功创建${employees.length}个员工`);
|
||||
|
||||
// 统计信息
|
||||
const activeCount = await Employee.count({ where: { status: 'active' } });
|
||||
const inactiveCount = await Employee.count({ where: { status: 'inactive' } });
|
||||
const lockedCount = await Employee.count({ where: { status: 'locked' } });
|
||||
const loanSpecialistCount = await Employee.count({ where: { isLoanSpecialist: true } });
|
||||
|
||||
console.log('\n📊 员工数据统计:');
|
||||
console.log(`- 活跃员工:${activeCount}个`);
|
||||
console.log(`- 停用员工:${inactiveCount}个`);
|
||||
console.log(`- 锁定员工:${lockedCount}个`);
|
||||
console.log(`- 贷款专员:${loanSpecialistCount}个`);
|
||||
console.log(`- 总员工数:${employees.length}个`);
|
||||
|
||||
console.log('\n🎉 员工测试数据添加完成!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 添加员工测试数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
seedEmployees()
|
||||
.then(() => {
|
||||
console.log('脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedEmployees;
|
||||
161
bank-backend/scripts/seed-personal-center-test-data.js
Normal file
161
bank-backend/scripts/seed-personal-center-test-data.js
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 个人中心测试数据种子脚本
|
||||
* @file seed-personal-center-test-data.js
|
||||
* @description 为个人中心功能添加测试数据
|
||||
*/
|
||||
|
||||
const { User, Role } = require('../models');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
async function seedPersonalCenterTestData() {
|
||||
try {
|
||||
console.log('=== 开始添加个人中心测试数据 ===\n');
|
||||
|
||||
// 1. 确保admin角色存在
|
||||
console.log('1. 检查admin角色...');
|
||||
let adminRole = await Role.findOne({ where: { name: 'admin' } });
|
||||
if (!adminRole) {
|
||||
adminRole = await Role.create({
|
||||
name: 'admin',
|
||||
display_name: '系统管理员',
|
||||
description: '系统管理员角色',
|
||||
permissions: JSON.stringify(['*'])
|
||||
});
|
||||
console.log('✅ 创建admin角色成功');
|
||||
} else {
|
||||
console.log('✅ admin角色已存在');
|
||||
}
|
||||
|
||||
// 2. 确保manager角色存在
|
||||
console.log('2. 检查manager角色...');
|
||||
let managerRole = await Role.findOne({ where: { name: 'manager' } });
|
||||
if (!managerRole) {
|
||||
managerRole = await Role.create({
|
||||
name: 'manager',
|
||||
display_name: '经理',
|
||||
description: '经理角色',
|
||||
permissions: JSON.stringify(['read', 'write', 'approve'])
|
||||
});
|
||||
console.log('✅ 创建manager角色成功');
|
||||
} else {
|
||||
console.log('✅ manager角色已存在');
|
||||
}
|
||||
|
||||
// 3. 创建测试用户 - 银行经理
|
||||
console.log('3. 创建测试用户 - 银行经理...');
|
||||
const managerPassword = await bcrypt.hash('Manager123456', 10);
|
||||
const managerUser = await User.findOrCreate({
|
||||
where: { username: 'manager001' },
|
||||
defaults: {
|
||||
username: 'manager001',
|
||||
password: managerPassword,
|
||||
real_name: '李经理',
|
||||
email: 'manager001@bank.com',
|
||||
phone: '15004901368',
|
||||
id_card: '110101198001010001',
|
||||
status: 'active',
|
||||
role_id: managerRole.id,
|
||||
position: '部门经理',
|
||||
department: '风险管理部'
|
||||
}
|
||||
});
|
||||
|
||||
if (managerUser[1]) {
|
||||
console.log('✅ 创建银行经理用户成功');
|
||||
} else {
|
||||
console.log('✅ 银行经理用户已存在');
|
||||
}
|
||||
|
||||
// 4. 创建测试用户 - 银行员工
|
||||
console.log('4. 创建测试用户 - 银行员工...');
|
||||
const employeePassword = await bcrypt.hash('Employee123456', 10);
|
||||
const employeeUser = await User.findOrCreate({
|
||||
where: { username: 'employee001' },
|
||||
defaults: {
|
||||
username: 'employee001',
|
||||
password: employeePassword,
|
||||
real_name: '王员工',
|
||||
email: 'employee001@bank.com',
|
||||
phone: '13800138000',
|
||||
id_card: '110101199001010002',
|
||||
status: 'active',
|
||||
role_id: managerRole.id,
|
||||
position: '业务专员',
|
||||
department: '客户服务部'
|
||||
}
|
||||
});
|
||||
|
||||
if (employeeUser[1]) {
|
||||
console.log('✅ 创建银行员工用户成功');
|
||||
} else {
|
||||
console.log('✅ 银行员工用户已存在');
|
||||
}
|
||||
|
||||
// 5. 更新admin用户信息
|
||||
console.log('5. 更新admin用户信息...');
|
||||
const adminUser = await User.findOne({ where: { username: 'admin' } });
|
||||
if (adminUser) {
|
||||
await adminUser.update({
|
||||
real_name: '系统管理员',
|
||||
phone: '15004901368',
|
||||
position: '系统管理员',
|
||||
department: '信息技术部'
|
||||
});
|
||||
console.log('✅ 更新admin用户信息成功');
|
||||
} else {
|
||||
console.log('⚠️ admin用户不存在,请先运行create-admin-user.js');
|
||||
}
|
||||
|
||||
// 6. 显示测试用户信息
|
||||
console.log('\n=== 测试用户信息 ===');
|
||||
const testUsers = await User.findAll({
|
||||
where: {
|
||||
username: ['admin', 'manager001', 'employee001']
|
||||
},
|
||||
include: [{
|
||||
model: Role,
|
||||
as: 'role'
|
||||
}]
|
||||
});
|
||||
|
||||
testUsers.forEach(user => {
|
||||
console.log(`\n用户名: ${user.username}`);
|
||||
console.log(`姓名: ${user.real_name}`);
|
||||
console.log(`手机: ${user.phone}`);
|
||||
console.log(`邮箱: ${user.email}`);
|
||||
console.log(`角色: ${user.role ? user.role.display_name : '未知'}`);
|
||||
console.log(`部门: ${user.department || '未设置'}`);
|
||||
console.log(`职位: ${user.position || '未设置'}`);
|
||||
console.log(`状态: ${user.status}`);
|
||||
});
|
||||
|
||||
console.log('\n=== 测试登录信息 ===');
|
||||
console.log('管理员账号: admin / Admin123456');
|
||||
console.log('经理账号: manager001 / Manager123456');
|
||||
console.log('员工账号: employee001 / Employee123456');
|
||||
|
||||
console.log('\n=== 个人中心测试数据添加完成 ===');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 添加个人中心测试数据失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行脚本
|
||||
if (require.main === module) {
|
||||
seedPersonalCenterTestData()
|
||||
.then(() => {
|
||||
console.log('✅ 脚本执行完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 脚本执行失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = seedPersonalCenterTestData;
|
||||
@@ -1,10 +0,0 @@
|
||||
const authController = require('./controllers/authController');
|
||||
const authMiddleware = require('./middleware/auth');
|
||||
|
||||
console.log('authController type:', typeof authController);
|
||||
console.log('authController.logout type:', typeof authController.logout);
|
||||
console.log('authMiddleware type:', typeof authMiddleware);
|
||||
console.log('authMiddleware.authMiddleware type:', typeof authMiddleware.authMiddleware);
|
||||
|
||||
console.log('authController keys:', Object.keys(authController));
|
||||
console.log('authMiddleware keys:', Object.keys(authMiddleware));
|
||||
38
bank-backend/test-installation-tasks-direct.js
Normal file
38
bank-backend/test-installation-tasks-direct.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const { sequelize, InstallationTask, User } = require('./models');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log('测试InstallationTask模型...');
|
||||
|
||||
// 测试基本查询
|
||||
console.log('1. 测试基本count查询...');
|
||||
const count = await InstallationTask.count();
|
||||
console.log('InstallationTask count:', count);
|
||||
|
||||
// 测试findAndCountAll查询
|
||||
console.log('2. 测试findAndCountAll查询...');
|
||||
const result = await InstallationTask.findAndCountAll({
|
||||
limit: 5
|
||||
});
|
||||
console.log('findAndCountAll result count:', result.count);
|
||||
console.log('findAndCountAll rows length:', result.rows.length);
|
||||
|
||||
// 测试带include的查询
|
||||
console.log('3. 测试带include的查询...');
|
||||
const resultWithInclude = await InstallationTask.findAndCountAll({
|
||||
include: [
|
||||
{ model: User, as: 'creator', attributes: ['id', 'username'] },
|
||||
{ model: User, as: 'updater', attributes: ['id', 'username'] }
|
||||
],
|
||||
limit: 5
|
||||
});
|
||||
console.log('include查询 count:', resultWithInclude.count);
|
||||
console.log('include查询 rows length:', resultWithInclude.rows.length);
|
||||
|
||||
} catch (err) {
|
||||
console.error('错误:', err.message);
|
||||
console.error('SQL:', err.sql);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user