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