diff --git a/bank-backend/add-test-data.js b/bank-backend/add-test-data.js
new file mode 100644
index 0000000..69a1790
--- /dev/null
+++ b/bank-backend/add-test-data.js
@@ -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();
diff --git a/bank-backend/check-data.js b/bank-backend/check-data.js
new file mode 100644
index 0000000..674a324
--- /dev/null
+++ b/bank-backend/check-data.js
@@ -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();
diff --git a/bank-backend/config/config.json b/bank-backend/config/config.json
index dbe83b7..a5cc419 100644
--- a/bank-backend/config/config.json
+++ b/bank-backend/config/config.json
@@ -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",
diff --git a/bank-backend/config/database.js b/bank-backend/config/database.js
index 709a372..d5910de 100644
--- a/bank-backend/config/database.js
+++ b/bank-backend/config/database.js
@@ -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
}
};
diff --git a/bank-backend/controllers/employeeController.js b/bank-backend/controllers/employeeController.js
index 90f365c..80a1eff 100644
--- a/bank-backend/controllers/employeeController.js
+++ b/bank-backend/controllers/employeeController.js
@@ -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
+};
\ No newline at end of file
diff --git a/bank-backend/controllers/loanApplicationController.js b/bank-backend/controllers/loanApplicationController.js
index e906596..faf22ba 100644
--- a/bank-backend/controllers/loanApplicationController.js
+++ b/bank-backend/controllers/loanApplicationController.js
@@ -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({
diff --git a/bank-backend/controllers/loanContractController.js b/bank-backend/controllers/loanContractController.js
index ce9a02a..b8dc98e 100644
--- a/bank-backend/controllers/loanContractController.js
+++ b/bank-backend/controllers/loanContractController.js
@@ -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({
diff --git a/bank-backend/controllers/loanProductController.js b/bank-backend/controllers/loanProductController.js
index 23629ed..2174a18 100644
--- a/bank-backend/controllers/loanProductController.js
+++ b/bank-backend/controllers/loanProductController.js
@@ -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)
});
diff --git a/bank-backend/create-admin-user.js b/bank-backend/create-admin-user.js
index ff30b87..727e733 100644
--- a/bank-backend/create-admin-user.js
+++ b/bank-backend/create-admin-user.js
@@ -28,6 +28,7 @@ async function createAdminUser() {
real_name: '系统管理员',
email: 'admin@bank.com',
phone: '13800138000',
+ id_card: '110101199001010001',
status: 'active',
role_id: 1
})
diff --git a/bank-backend/create-audit-records-table.js b/bank-backend/create-audit-records-table.js
new file mode 100644
index 0000000..3eee4c5
--- /dev/null
+++ b/bank-backend/create-audit-records-table.js
@@ -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();
diff --git a/bank-backend/create-missing-tables.sql b/bank-backend/create-missing-tables.sql
new file mode 100644
index 0000000..ef4064a
--- /dev/null
+++ b/bank-backend/create-missing-tables.sql
@@ -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');
diff --git a/bank-backend/create-tables.js b/bank-backend/create-tables.js
new file mode 100644
index 0000000..00fdaf3
--- /dev/null
+++ b/bank-backend/create-tables.js
@@ -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();
diff --git a/bank-backend/env.example b/bank-backend/env.example
index 2167738..32f78a0 100644
--- a/bank-backend/env.example
+++ b/bank-backend/env.example
@@ -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配置
diff --git a/bank-backend/fix-supervision-tasks.js b/bank-backend/fix-supervision-tasks.js
new file mode 100644
index 0000000..f00f4aa
--- /dev/null
+++ b/bank-backend/fix-supervision-tasks.js
@@ -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();
diff --git a/bank-backend/fix-test-data.js b/bank-backend/fix-test-data.js
new file mode 100644
index 0000000..d90f63f
--- /dev/null
+++ b/bank-backend/fix-test-data.js
@@ -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();
diff --git a/bank-backend/migrations/20241220000001-create-reports.js b/bank-backend/migrations/20241220000001-create-reports.js
index 4a270fc..64e3870 100644
--- a/bank-backend/migrations/20241220000001-create-reports.js
+++ b/bank-backend/migrations/20241220000001-create-reports.js
@@ -55,7 +55,7 @@ module.exports = {
allowNull: false,
comment: '创建人ID',
references: {
- model: 'users',
+ model: 'bank_users',
key: 'id'
},
onUpdate: 'CASCADE',
diff --git a/bank-backend/migrations/20241220000010-create-employees.js b/bank-backend/migrations/20241220000010-create-employees.js
new file mode 100644
index 0000000..038d562
--- /dev/null
+++ b/bank-backend/migrations/20241220000010-create-employees.js
@@ -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');
+ }
+};
diff --git a/bank-backend/models/CompletedSupervision.js b/bank-backend/models/CompletedSupervision.js
index a453acc..97113cf 100644
--- a/bank-backend/models/CompletedSupervision.js
+++ b/bank-backend/models/CompletedSupervision.js
@@ -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;
diff --git a/bank-backend/models/Employee.js b/bank-backend/models/Employee.js
index b1eb0f2..6f07ab4 100644
--- a/bank-backend/models/Employee.js
+++ b/bank-backend/models/Employee.js
@@ -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;
+};
\ No newline at end of file
diff --git a/bank-backend/models/InstallationTask.js b/bank-backend/models/InstallationTask.js
index f7e3e6c..8b77de6 100644
--- a/bank-backend/models/InstallationTask.js
+++ b/bank-backend/models/InstallationTask.js
@@ -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;
\ No newline at end of file
diff --git a/bank-backend/models/LoanApplication.js b/bank-backend/models/LoanApplication.js
index 8397537..88419be 100644
--- a/bank-backend/models/LoanApplication.js
+++ b/bank-backend/models/LoanApplication.js
@@ -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;
diff --git a/bank-backend/models/LoanContract.js b/bank-backend/models/LoanContract.js
index 37c7fba..3877996 100644
--- a/bank-backend/models/LoanContract.js
+++ b/bank-backend/models/LoanContract.js
@@ -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;
\ No newline at end of file
diff --git a/bank-backend/models/LoanProduct.js b/bank-backend/models/LoanProduct.js
index e46de12..ba8b5b1 100644
--- a/bank-backend/models/LoanProduct.js
+++ b/bank-backend/models/LoanProduct.js
@@ -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: '贷款商品表'
});
diff --git a/bank-backend/models/Project.js b/bank-backend/models/Project.js
index cc99922..e65b0ba 100644
--- a/bank-backend/models/Project.js
+++ b/bank-backend/models/Project.js
@@ -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'
});
};
diff --git a/bank-backend/models/SupervisionTask.js b/bank-backend/models/SupervisionTask.js
index de1f898..07dc26d 100644
--- a/bank-backend/models/SupervisionTask.js
+++ b/bank-backend/models/SupervisionTask.js
@@ -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'
});
};
diff --git a/bank-backend/models/User.js b/bank-backend/models/User.js
index 8c2ee15..a7b2c68 100644
--- a/bank-backend/models/User.js
+++ b/bank-backend/models/User.js
@@ -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) {
diff --git a/bank-backend/models/index.js b/bank-backend/models/index.js
index a830383..c0074f3 100644
--- a/bank-backend/models/index.js
+++ b/bank-backend/models/index.js
@@ -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 = {
diff --git a/bank-backend/quick-test-projects.js b/bank-backend/quick-test-projects.js
new file mode 100644
index 0000000..450bb84
--- /dev/null
+++ b/bank-backend/quick-test-projects.js
@@ -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();
+ }
+})();
+
+
diff --git a/bank-backend/routes/employees.js b/bank-backend/routes/employees.js
index aa12451..4a3f764 100644
--- a/bank-backend/routes/employees.js
+++ b/bank-backend/routes/employees.js
@@ -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;
\ No newline at end of file
diff --git a/bank-backend/scripts/seed-employees.js b/bank-backend/scripts/seed-employees.js
new file mode 100644
index 0000000..7978d47
--- /dev/null
+++ b/bank-backend/scripts/seed-employees.js
@@ -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;
diff --git a/bank-backend/scripts/seed-personal-center-test-data.js b/bank-backend/scripts/seed-personal-center-test-data.js
new file mode 100644
index 0000000..d739c14
--- /dev/null
+++ b/bank-backend/scripts/seed-personal-center-test-data.js
@@ -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;
diff --git a/bank-backend/test-controller.js b/bank-backend/test-controller.js
deleted file mode 100644
index 0989f5c..0000000
--- a/bank-backend/test-controller.js
+++ /dev/null
@@ -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));
diff --git a/bank-backend/test-installation-tasks-direct.js b/bank-backend/test-installation-tasks-direct.js
new file mode 100644
index 0000000..297293f
--- /dev/null
+++ b/bank-backend/test-installation-tasks-direct.js
@@ -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();
+ }
+})();
diff --git a/bank-frontend/src/utils/api.js b/bank-frontend/src/utils/api.js
index 7e88bb1..0885873 100644
--- a/bank-frontend/src/utils/api.js
+++ b/bank-frontend/src/utils/api.js
@@ -1134,6 +1134,82 @@ export const api = {
async batchUpdateStatus(data) {
return api.put('/loan-contracts/batch/status', data)
}
+ },
+
+ // 员工管理API
+ employees: {
+ /**
+ * 获取员工列表
+ * @param {Object} params - 查询参数
+ * @returns {Promise} 员工列表
+ */
+ async getList(params = {}) {
+ return api.get('/employees', { params })
+ },
+
+ /**
+ * 获取员工详情
+ * @param {number} id - 员工ID
+ * @returns {Promise} 员工详情
+ */
+ async getById(id) {
+ return api.get(`/employees/${id}`)
+ },
+
+ /**
+ * 创建员工
+ * @param {Object} data - 员工数据
+ * @returns {Promise} 创建结果
+ */
+ async create(data) {
+ return api.post('/employees', data)
+ },
+
+ /**
+ * 更新员工信息
+ * @param {number} id - 员工ID
+ * @param {Object} data - 员工数据
+ * @returns {Promise} 更新结果
+ */
+ async update(id, data) {
+ return api.put(`/employees/${id}`, data)
+ },
+
+ /**
+ * 重设密码
+ * @param {number} id - 员工ID
+ * @param {Object} data - 密码数据
+ * @returns {Promise} 重设结果
+ */
+ async resetPassword(id, data) {
+ return api.put(`/employees/${id}/reset-password`, data)
+ },
+
+ /**
+ * 删除员工
+ * @param {number} id - 员工ID
+ * @returns {Promise} 删除结果
+ */
+ async delete(id) {
+ return api.delete(`/employees/${id}`)
+ },
+
+ /**
+ * 获取员工统计信息
+ * @returns {Promise} 统计信息
+ */
+ async getStats() {
+ return api.get('/employees/stats')
+ },
+
+ /**
+ * 批量更新员工状态
+ * @param {Object} data - 批量操作数据
+ * @returns {Promise} 更新结果
+ */
+ async batchUpdateStatus(data) {
+ return api.put('/employees/batch/status', data)
+ }
}
}
diff --git a/bank-frontend/src/views/EmployeeManagement.vue b/bank-frontend/src/views/EmployeeManagement.vue
index 66a7b38..5c2efef 100644
--- a/bank-frontend/src/views/EmployeeManagement.vue
+++ b/bank-frontend/src/views/EmployeeManagement.vue
@@ -1,26 +1,36 @@
{{ getPositionText(selectedEmployee.position) }} - {{ getDepartmentText(selectedEmployee.department) }}员工管理
+ {{ selectedEmployee.name }}
- 工作经历
-
岗位: {{ currentPosition?.name }}
+权限设置示例
+