添加银行政府后端接口

This commit is contained in:
2025-09-25 15:53:44 +08:00
parent b17bdcc24c
commit 5b6b7e0a96
60 changed files with 5345 additions and 1920 deletions

View File

@@ -0,0 +1,104 @@
const { sequelize } = require('./config/database');
async function addTestData() {
try {
console.log('开始添加测试数据...');
// 添加supervision_tasks测试数据
await sequelize.query(`
INSERT INTO supervision_tasks (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, assetQuantity, supervisionStatus, importTime, startTime, endTime,
loanAmount, interestRate, loanTerm, supervisorName, supervisorPhone, farmAddress,
remarks, createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖贷款', '张三', 'id_card', '110101199001010001',
'cattle', 10, 'pending', NOW(), '2024-01-01', '2024-12-31',
50000.00, 0.05, 12, '李监督员', '13800138000', '北京市朝阳区农场',
'测试监管任务', 2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植贷款', '李四', 'id_card', '110101199002020002',
'sheep', 20, 'supervising', NOW(), '2024-02-01', '2024-11-30',
30000.00, 0.06, 10, '王监督员', '13900139000', '北京市海淀区农场',
'测试监管任务2', 2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加supervision_tasks测试数据成功');
// 添加projects测试数据
await sequelize.query(`
INSERT INTO projects (
name, status, farmName, supervisionObject, supervisionQuantity, supervisionPeriod,
supervisionAmount, startTime, endTime, earTag, collar, host, loanOfficer,
description, createdBy, updatedBy, createdAt, updatedAt
) VALUES
('养殖项目1', 'supervision', '张三农场', 'cattle', 50, '12个月',
100000.00, '2024-01-01', '2024-12-31', 30, 20, 10, '李贷款员',
'测试养殖项目', 2, 2, NOW(), NOW()),
('种植项目1', 'completed', '李四农场', 'sheep', 100, '10个月',
80000.00, '2024-02-01', '2024-11-30', 60, 40, 20, '王贷款员',
'测试种植项目', 2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加projects测试数据成功');
// 添加installation_tasks测试数据
await sequelize.query(`
INSERT INTO installation_tasks (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, equipmentToInstall, installationStatus, taskGenerationTime, completionTime,
installationNotes, installerName, installerPhone, installationAddress,
createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖设备', '张三', 'ID_CARD', '110101199001010001',
'cattle', '耳标设备', 'pending', NOW(), NULL,
'测试安装任务', '安装员1', '13900139000', '北京市朝阳区农场',
2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植设备', '李四', 'ID_CARD', '110101199002020002',
'sheep', '项圈设备', 'completed', NOW(), NOW(),
'测试安装任务2', '安装员2', '14000140000', '北京市海淀区农场',
2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加installation_tasks测试数据成功');
// 添加completed_supervisions测试数据
await sequelize.query(`
INSERT INTO completed_supervisions (
applicationNumber, contractNumber, customerName, supervisionPeriod, totalAmount,
paidAmount, remainingAmount, settlementNotes, createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '张三', '12个月', 50000.00,
30000.00, 20000.00, '已结清部分', 2, 2, NOW(), NOW()),
('APP002', 'CON002', '李四', '10个月', 30000.00,
30000.00, 0.00, '已完全结清', 2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加completed_supervisions测试数据成功');
// 添加loan_applications测试数据
await sequelize.query(`
INSERT INTO loan_applications (
customer_name, customer_phone, customer_id_card, loan_amount, loan_term,
interest_rate, application_date, status
) VALUES
('张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'pending'),
('李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'approved')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_applications测试数据成功');
// 添加loan_contracts测试数据
await sequelize.query(`
INSERT INTO loan_contracts (
contract_number, customer_name, customer_phone, customer_id_card, loan_amount,
loan_term, interest_rate, contract_date, status
) VALUES
('CON001', '张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'active'),
('CON002', '李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'completed')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_contracts测试数据成功');
console.log('\n✅ 所有测试数据添加完成!');
} catch (error) {
console.error('❌ 添加测试数据失败:', error.message);
} finally {
await sequelize.close();
}
}
addTestData();

View File

@@ -0,0 +1,32 @@
const { sequelize } = require('./config/database');
async function checkData() {
try {
console.log('检查各表数据统计...\n');
const tables = [
'supervision_tasks',
'projects',
'installation_tasks',
'completed_supervisions',
'loan_applications',
'loan_contracts'
];
for (const table of tables) {
const result = await sequelize.query(
`SELECT COUNT(*) as count FROM ${table}`,
{ type: sequelize.QueryTypes.SELECT }
);
console.log(`${table}: ${result[0].count} 条记录`);
}
console.log('\n检查完成');
} catch (error) {
console.error('错误:', error.message);
} finally {
await sequelize.close();
}
}
checkData();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,7 @@ async function createAdminUser() {
real_name: '系统管理员',
email: 'admin@bank.com',
phone: '13800138000',
id_card: '110101199001010001',
status: 'active',
role_id: 1
})

View File

@@ -0,0 +1,43 @@
const { sequelize } = require('./config/database');
async function createAuditRecordsTable() {
try {
console.log('创建bank_audit_records表...');
await sequelize.query(`
CREATE TABLE IF NOT EXISTS bank_audit_records (
id INT(11) NOT NULL AUTO_INCREMENT,
applicationId INT(11) NOT NULL,
action VARCHAR(50) NOT NULL,
auditor VARCHAR(100) NOT NULL,
auditorId INT(11) NOT NULL,
comment TEXT,
auditTime DATETIME NOT NULL,
previousStatus VARCHAR(50),
newStatus VARCHAR(50),
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_application_id (applicationId),
KEY idx_auditor_id (auditorId),
KEY idx_audit_time (auditTime)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ bank_audit_records表创建成功');
// 标记迁移为完成
await sequelize.query(`
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000008-create-audit-records.js')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 迁移记录已标记为完成');
} catch (error) {
console.error('❌ 创建表失败:', error.message);
} finally {
await sequelize.close();
}
}
createAuditRecordsTable();

View File

@@ -0,0 +1,39 @@
-- 创建缺失的表
-- 创建贷款申请表
CREATE TABLE IF NOT EXISTS loan_applications (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
application_date DATE NOT NULL COMMENT '申请日期',
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending' COMMENT '申请状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 创建贷款合同表
CREATE TABLE IF NOT EXISTS loan_contracts (
id INT AUTO_INCREMENT PRIMARY KEY,
contract_number VARCHAR(50) NOT NULL UNIQUE COMMENT '合同编号',
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
contract_date DATE NOT NULL COMMENT '合同签订日期',
status ENUM('active', 'completed', 'defaulted', 'cancelled') DEFAULT 'active' COMMENT '合同状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 标记迁移为已完成
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000006-create-loan-products.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000007-create-loan-applications.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000008-create-audit-records.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000009-create-loan-contracts.js');
INSERT IGNORE INTO SequelizeMeta (name) VALUES ('20241220000010-create-employees.js');

View File

@@ -0,0 +1,68 @@
const { sequelize } = require('./config/database');
async function createMissingTables() {
try {
console.log('开始创建缺失的表...');
// 创建loan_applications表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS loan_applications (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
application_date DATE NOT NULL COMMENT '申请日期',
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending' COMMENT '申请状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 创建loan_applications表成功');
// 创建loan_contracts表
await sequelize.query(`
CREATE TABLE IF NOT EXISTS loan_contracts (
id INT AUTO_INCREMENT PRIMARY KEY,
contract_number VARCHAR(50) NOT NULL UNIQUE COMMENT '合同编号',
customer_name VARCHAR(100) NOT NULL COMMENT '客户姓名',
customer_phone VARCHAR(20) NOT NULL COMMENT '客户电话',
customer_id_card VARCHAR(18) NOT NULL COMMENT '客户身份证号',
loan_amount DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
loan_term INT NOT NULL COMMENT '贷款期限(月)',
interest_rate DECIMAL(5,2) NOT NULL COMMENT '贷款利率(%',
contract_date DATE NOT NULL COMMENT '合同签订日期',
status ENUM('active', 'completed', 'defaulted', 'cancelled') DEFAULT 'active' COMMENT '合同状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 创建loan_contracts表成功');
// 标记迁移为已完成
await sequelize.query(`
INSERT IGNORE INTO SequelizeMeta (name) VALUES
('20241220000006-create-loan-products.js'),
('20241220000007-create-loan-applications.js'),
('20241220000008-create-audit-records.js'),
('20241220000009-create-loan-contracts.js'),
('20241220000010-create-employees.js')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 标记迁移为已完成');
// 显示所有表
const tables = await sequelize.query('SHOW TABLES', { type: sequelize.QueryTypes.SELECT });
console.log('\n当前数据库表:');
tables.forEach(table => console.log('-', Object.values(table)[0]));
console.log('\n✅ 所有表创建完成!');
} catch (error) {
console.error('❌ 创建表失败:', error.message);
} finally {
await sequelize.close();
}
}
createMissingTables();

View File

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

View File

@@ -0,0 +1,52 @@
const { sequelize } = require('./config/database');
async function fixSupervisionTasks() {
try {
console.log('开始修复supervision_tasks表...');
// 检查并添加created_by字段
const createdByExists = await sequelize.query(
'SHOW COLUMNS FROM supervision_tasks LIKE "created_by"',
{ type: sequelize.QueryTypes.SELECT }
);
if (createdByExists.length === 0) {
await sequelize.query(
'ALTER TABLE supervision_tasks ADD COLUMN created_by INT(11) AFTER remarks',
{ type: sequelize.QueryTypes.RAW }
);
console.log('✅ 添加created_by字段成功');
} else {
console.log('✅ created_by字段已存在');
}
// 检查并添加updated_by字段
const updatedByExists = await sequelize.query(
'SHOW COLUMNS FROM supervision_tasks LIKE "updated_by"',
{ type: sequelize.QueryTypes.SELECT }
);
if (updatedByExists.length === 0) {
await sequelize.query(
'ALTER TABLE supervision_tasks ADD COLUMN updated_by INT(11) AFTER created_by',
{ type: sequelize.QueryTypes.RAW }
);
console.log('✅ 添加updated_by字段成功');
} else {
console.log('✅ updated_by字段已存在');
}
// 显示表结构
const columns = await sequelize.query('DESCRIBE supervision_tasks', { type: sequelize.QueryTypes.SELECT });
console.log('\nsupervision_tasks表结构:');
columns.forEach(col => console.log('-', col.Field, col.Type));
console.log('\n✅ supervision_tasks表修复完成');
} catch (error) {
console.error('❌ 修复失败:', error.message);
} finally {
await sequelize.close();
}
}
fixSupervisionTasks();

View File

@@ -0,0 +1,75 @@
const { sequelize } = require('./config/database');
async function fixTestData() {
try {
console.log('开始修复测试数据...');
// 添加installation_tasks测试数据
await sequelize.query(`
INSERT INTO installation_tasks (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, equipmentToInstall, installationStatus, taskGenerationTime, completionTime,
installationNotes, installerName, installerPhone, installationAddress,
createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖设备', '张三', 'ID_CARD', '110101199001010001',
'cattle', '耳标设备', 'pending', NOW(), NULL,
'测试安装任务', '安装员1', '13900139000', '北京市朝阳区农场',
2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植设备', '李四', 'ID_CARD', '110101199002020002',
'sheep', '项圈设备', 'completed', NOW(), NOW(),
'测试安装任务2', '安装员2', '14000140000', '北京市海淀区农场',
2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加installation_tasks测试数据成功');
// 添加completed_supervisions测试数据
await sequelize.query(`
INSERT INTO completed_supervisions (
applicationNumber, contractNumber, productName, customerName, idType, idNumber,
assetType, assetQuantity, totalRepaymentPeriods, settlementStatus, settlementDate,
importTime, settlementAmount, remainingAmount, settlementNotes,
createdBy, updatedBy, createdAt, updatedAt
) VALUES
('APP001', 'CON001', '养殖贷款', '张三', 'ID_CARD', '110101199001010001',
'cattle', 10, 12, 'partial', '2024-01-15',
NOW(), 30000.00, 20000.00, '已结清部分',
2, 2, NOW(), NOW()),
('APP002', 'CON002', '种植贷款', '李四', 'ID_CARD', '110101199002020002',
'sheep', 20, 10, 'settled', '2024-01-20',
NOW(), 30000.00, 0.00, '已完全结清',
2, 2, NOW(), NOW())
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加completed_supervisions测试数据成功');
// 添加loan_applications测试数据
await sequelize.query(`
INSERT INTO loan_applications (
customer_name, customer_phone, customer_id_card, loan_amount, loan_term,
interest_rate, application_date, status
) VALUES
('张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'pending'),
('李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'approved')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_applications测试数据成功');
// 添加loan_contracts测试数据
await sequelize.query(`
INSERT INTO loan_contracts (
contract_number, customer_name, customer_phone, customer_id_card, loan_amount,
loan_term, interest_rate, contract_date, status
) VALUES
('CON001', '张三', '13800138000', '110101199001010001', 50000.00, 12, 0.05, '2024-01-01', 'active'),
('CON002', '李四', '13900139000', '110101199002020002', 30000.00, 10, 0.06, '2024-01-02', 'completed')
`, { type: sequelize.QueryTypes.RAW });
console.log('✅ 添加loan_contracts测试数据成功');
console.log('\n✅ 所有测试数据修复完成!');
} catch (error) {
console.error('❌ 修复测试数据失败:', error.message);
} finally {
await sequelize.close();
}
}
fixTestData();

View File

@@ -55,7 +55,7 @@ module.exports = {
allowNull: false,
comment: '创建人ID',
references: {
model: 'users',
model: 'bank_users',
key: 'id'
},
onUpdate: 'CASCADE',

View File

@@ -0,0 +1,124 @@
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('bank_employees', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
employeeNumber: {
type: Sequelize.STRING(20),
allowNull: false,
unique: true,
comment: '员工编号'
},
name: {
type: Sequelize.STRING(50),
allowNull: false,
comment: '员工姓名'
},
phone: {
type: Sequelize.STRING(20),
allowNull: false,
comment: '联系电话'
},
email: {
type: Sequelize.STRING(100),
allowNull: true,
comment: '邮箱'
},
password: {
type: Sequelize.STRING(255),
allowNull: false,
comment: '密码'
},
isLoanSpecialist: {
type: Sequelize.BOOLEAN,
defaultValue: false,
comment: '是否为贷款专员'
},
department: {
type: Sequelize.STRING(50),
allowNull: true,
comment: '部门'
},
position: {
type: Sequelize.STRING(50),
allowNull: true,
comment: '职位'
},
status: {
type: Sequelize.ENUM('active', 'inactive', 'locked'),
defaultValue: 'active',
comment: '账号状态'
},
lastLogin: {
type: Sequelize.DATE,
allowNull: true,
comment: '最后登录时间'
},
loginAttempts: {
type: Sequelize.INTEGER,
defaultValue: 0,
comment: '登录尝试次数'
},
lockedUntil: {
type: Sequelize.DATE,
allowNull: true,
comment: '锁定到期时间'
},
createdBy: {
type: Sequelize.INTEGER,
allowNull: true,
comment: '创建人ID'
},
updatedBy: {
type: Sequelize.INTEGER,
allowNull: true,
comment: '更新人ID'
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
deletedAt: {
type: Sequelize.DATE,
allowNull: true
}
});
// 添加索引
await queryInterface.addIndex('bank_employees', ['employeeNumber'], {
unique: true,
name: 'idx_employees_employee_number'
});
await queryInterface.addIndex('bank_employees', ['phone'], {
unique: true,
name: 'idx_employees_phone'
});
await queryInterface.addIndex('bank_employees', ['status'], {
name: 'idx_employees_status'
});
await queryInterface.addIndex('bank_employees', ['isLoanSpecialist'], {
name: 'idx_employees_loan_specialist'
});
await queryInterface.addIndex('bank_employees', ['deletedAt'], {
name: 'idx_employees_deleted_at'
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('bank_employees');
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: '贷款商品表'
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
const { sequelize, Project, User } = require('./models');
(async () => {
try {
console.log('DB should be ningxia_bank test:');
const [dbRow] = await sequelize.query('SELECT DATABASE() AS db');
console.log('DB:', dbRow[0].db);
console.log('Testing Project.findAndCountAll with includes...');
const result = await Project.findAndCountAll({
include: [
{ model: User, as: 'creator', attributes: ['id', 'username'] },
{ model: User, as: 'updater', attributes: ['id', 'username'] }
],
limit: 5
});
console.log('count:', result.count);
console.log('rows length:', result.rows.length);
} catch (err) {
console.error('ERROR:', err.message);
} finally {
await sequelize.close();
}
})();

View File

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

View File

@@ -0,0 +1,173 @@
const { sequelize, Employee } = require('../models');
/**
* 添加员工测试数据
*/
async function seedEmployees() {
try {
console.log('开始添加员工测试数据...');
// 检查数据库连接
await sequelize.authenticate();
console.log('✅ 银行系统数据库连接成功');
// 清空现有员工数据
await Employee.destroy({
where: {},
force: true
});
console.log('✅ 清空现有员工数据');
// 员工测试数据
const employeesData = [
{
employeeNumber: 'EMP001',
name: '刘超',
phone: '15012341368',
email: 'liuchao@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '风险管理部',
position: '风险专员',
status: 'active'
},
{
employeeNumber: 'EMP002',
name: '张明',
phone: '13812345678',
email: 'zhangming@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'active'
},
{
employeeNumber: 'EMP003',
name: '李红',
phone: '13987654321',
email: 'lihong@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '高级贷款专员',
status: 'active'
},
{
employeeNumber: 'EMP004',
name: '王强',
phone: '13611111111',
email: 'wangqiang@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '运营部',
position: '运营专员',
status: 'active'
},
{
employeeNumber: 'EMP005',
name: '陈静',
phone: '13722222222',
email: 'chenjing@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'inactive'
},
{
employeeNumber: 'EMP006',
name: '赵磊',
phone: '13833333333',
email: 'zhaolei@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '技术部',
position: '系统管理员',
status: 'active'
},
{
employeeNumber: 'EMP007',
name: '孙丽',
phone: '13944444444',
email: 'sunli@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'locked'
},
{
employeeNumber: 'EMP008',
name: '周涛',
phone: '13555555555',
email: 'zhoutao@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '财务部',
position: '财务专员',
status: 'active'
},
{
employeeNumber: 'EMP009',
name: '吴敏',
phone: '13466666666',
email: 'wumin@bank.com',
password: '123456',
isLoanSpecialist: true,
department: '信贷部',
position: '贷款专员',
status: 'active'
},
{
employeeNumber: 'EMP010',
name: '郑华',
phone: '13377777777',
email: 'zhenghua@bank.com',
password: '123456',
isLoanSpecialist: false,
department: '人事部',
position: '人事专员',
status: 'active'
}
];
// 创建员工
const employees = await Employee.bulkCreate(employeesData);
console.log(`✅ 成功创建${employees.length}个员工`);
// 统计信息
const activeCount = await Employee.count({ where: { status: 'active' } });
const inactiveCount = await Employee.count({ where: { status: 'inactive' } });
const lockedCount = await Employee.count({ where: { status: 'locked' } });
const loanSpecialistCount = await Employee.count({ where: { isLoanSpecialist: true } });
console.log('\n📊 员工数据统计:');
console.log(`- 活跃员工:${activeCount}`);
console.log(`- 停用员工:${inactiveCount}`);
console.log(`- 锁定员工:${lockedCount}`);
console.log(`- 贷款专员:${loanSpecialistCount}`);
console.log(`- 总员工数:${employees.length}`);
console.log('\n🎉 员工测试数据添加完成!');
} catch (error) {
console.error('❌ 添加员工测试数据失败:', error);
throw error;
}
}
// 如果直接运行此脚本
if (require.main === module) {
seedEmployees()
.then(() => {
console.log('脚本执行完成');
process.exit(0);
})
.catch((error) => {
console.error('❌ 脚本执行失败:', error);
process.exit(1);
});
}
module.exports = seedEmployees;

View File

@@ -0,0 +1,161 @@
/**
* 个人中心测试数据种子脚本
* @file seed-personal-center-test-data.js
* @description 为个人中心功能添加测试数据
*/
const { User, Role } = require('../models');
const bcrypt = require('bcryptjs');
const { sequelize } = require('../config/database');
async function seedPersonalCenterTestData() {
try {
console.log('=== 开始添加个人中心测试数据 ===\n');
// 1. 确保admin角色存在
console.log('1. 检查admin角色...');
let adminRole = await Role.findOne({ where: { name: 'admin' } });
if (!adminRole) {
adminRole = await Role.create({
name: 'admin',
display_name: '系统管理员',
description: '系统管理员角色',
permissions: JSON.stringify(['*'])
});
console.log('✅ 创建admin角色成功');
} else {
console.log('✅ admin角色已存在');
}
// 2. 确保manager角色存在
console.log('2. 检查manager角色...');
let managerRole = await Role.findOne({ where: { name: 'manager' } });
if (!managerRole) {
managerRole = await Role.create({
name: 'manager',
display_name: '经理',
description: '经理角色',
permissions: JSON.stringify(['read', 'write', 'approve'])
});
console.log('✅ 创建manager角色成功');
} else {
console.log('✅ manager角色已存在');
}
// 3. 创建测试用户 - 银行经理
console.log('3. 创建测试用户 - 银行经理...');
const managerPassword = await bcrypt.hash('Manager123456', 10);
const managerUser = await User.findOrCreate({
where: { username: 'manager001' },
defaults: {
username: 'manager001',
password: managerPassword,
real_name: '李经理',
email: 'manager001@bank.com',
phone: '15004901368',
id_card: '110101198001010001',
status: 'active',
role_id: managerRole.id,
position: '部门经理',
department: '风险管理部'
}
});
if (managerUser[1]) {
console.log('✅ 创建银行经理用户成功');
} else {
console.log('✅ 银行经理用户已存在');
}
// 4. 创建测试用户 - 银行员工
console.log('4. 创建测试用户 - 银行员工...');
const employeePassword = await bcrypt.hash('Employee123456', 10);
const employeeUser = await User.findOrCreate({
where: { username: 'employee001' },
defaults: {
username: 'employee001',
password: employeePassword,
real_name: '王员工',
email: 'employee001@bank.com',
phone: '13800138000',
id_card: '110101199001010002',
status: 'active',
role_id: managerRole.id,
position: '业务专员',
department: '客户服务部'
}
});
if (employeeUser[1]) {
console.log('✅ 创建银行员工用户成功');
} else {
console.log('✅ 银行员工用户已存在');
}
// 5. 更新admin用户信息
console.log('5. 更新admin用户信息...');
const adminUser = await User.findOne({ where: { username: 'admin' } });
if (adminUser) {
await adminUser.update({
real_name: '系统管理员',
phone: '15004901368',
position: '系统管理员',
department: '信息技术部'
});
console.log('✅ 更新admin用户信息成功');
} else {
console.log('⚠️ admin用户不存在请先运行create-admin-user.js');
}
// 6. 显示测试用户信息
console.log('\n=== 测试用户信息 ===');
const testUsers = await User.findAll({
where: {
username: ['admin', 'manager001', 'employee001']
},
include: [{
model: Role,
as: 'role'
}]
});
testUsers.forEach(user => {
console.log(`\n用户名: ${user.username}`);
console.log(`姓名: ${user.real_name}`);
console.log(`手机: ${user.phone}`);
console.log(`邮箱: ${user.email}`);
console.log(`角色: ${user.role ? user.role.display_name : '未知'}`);
console.log(`部门: ${user.department || '未设置'}`);
console.log(`职位: ${user.position || '未设置'}`);
console.log(`状态: ${user.status}`);
});
console.log('\n=== 测试登录信息 ===');
console.log('管理员账号: admin / Admin123456');
console.log('经理账号: manager001 / Manager123456');
console.log('员工账号: employee001 / Employee123456');
console.log('\n=== 个人中心测试数据添加完成 ===');
} catch (error) {
console.error('❌ 添加个人中心测试数据失败:', error);
throw error;
} finally {
await sequelize.close();
}
}
// 运行脚本
if (require.main === module) {
seedPersonalCenterTestData()
.then(() => {
console.log('✅ 脚本执行完成');
process.exit(0);
})
.catch((error) => {
console.error('❌ 脚本执行失败:', error);
process.exit(1);
});
}
module.exports = seedPersonalCenterTestData;

View File

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

View File

@@ -0,0 +1,38 @@
const { sequelize, InstallationTask, User } = require('./models');
(async () => {
try {
console.log('测试InstallationTask模型...');
// 测试基本查询
console.log('1. 测试基本count查询...');
const count = await InstallationTask.count();
console.log('InstallationTask count:', count);
// 测试findAndCountAll查询
console.log('2. 测试findAndCountAll查询...');
const result = await InstallationTask.findAndCountAll({
limit: 5
});
console.log('findAndCountAll result count:', result.count);
console.log('findAndCountAll rows length:', result.rows.length);
// 测试带include的查询
console.log('3. 测试带include的查询...');
const resultWithInclude = await InstallationTask.findAndCountAll({
include: [
{ model: User, as: 'creator', attributes: ['id', 'username'] },
{ model: User, as: 'updater', attributes: ['id', 'username'] }
],
limit: 5
});
console.log('include查询 count:', resultWithInclude.count);
console.log('include查询 rows length:', resultWithInclude.rows.length);
} catch (err) {
console.error('错误:', err.message);
console.error('SQL:', err.sql);
} finally {
await sequelize.close();
}
})();