diff --git a/db.js b/db.js index 2c3f862..d1ff6be 100644 --- a/db.js +++ b/db.js @@ -42,6 +42,7 @@ async function initDB() { is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '账号状态: 0-禁用, 1-启用', department_id INT DEFAULT NULL COMMENT '部门ID (关联departments表)', employee_id INT DEFAULT NULL COMMENT '员工ID (关联employees表)', + role_level TINYINT NOT NULL DEFAULT 4 COMMENT '角色级别: 1-总经理, 2-部门经理, 3-部门副经理, 4-一般专员', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (id), @@ -176,6 +177,14 @@ async function initDB() { // 列已删除则忽略 } + // users 表添加 role_level(角色级别) + try { + await pool.query(`ALTER TABLE users ADD COLUMN role_level TINYINT NOT NULL DEFAULT 4 COMMENT '角色级别: 1-总经理, 2-部门经理, 3-部门副经理, 4-一般专员' AFTER employee_id`) + console.log('[init] users 表已添加 role_level 字段') + } catch (e) { + // 字段已存在则忽略 + } + console.log('[init] 数据表初始化完成') // ============ 2.3 部门与权限相关表 ============ @@ -314,8 +323,7 @@ async function seedDepartmentsAndPermissions() { const deptPermMap = { admin: [ 'user:manage', - 'customer:read', 'contract:read', 'after_sale:read', - 'product:read', 'supplier:read', 'employee:read', + 'employee:read', ], general_manager: [ 'customer:read', 'contract:read', 'after_sale:read', @@ -330,7 +338,6 @@ async function seedDepartmentsAndPermissions() { ], operations_manager: [ 'customer:read', - 'contract:read', 'after_sale:read', 'after_sale:create', 'after_sale:update', 'after_sale:delete', 'employee:read', ], @@ -348,7 +355,6 @@ async function seedDepartmentsAndPermissions() { hr: [ 'employee:read', 'employee:create', 'employee:update', 'employee:delete', 'user:manage', - 'customer:read', ], } @@ -386,13 +392,13 @@ async function seedDefaultUsers() { ] const seedUsers = [ - { username: 'zhangchao', deptName: 'general_manager' }, - { username: 'liming', deptName: 'franchise_manager' }, - { username: 'wangli', deptName: 'operations_manager' }, - { username: 'zhaoqiang', deptName: 'procurement_manager' }, - { username: 'chenfang', deptName: 'finance' }, - { username: 'admin', deptName: 'admin' }, - { username: 'yangfan', deptName: 'hr' }, + { username: 'zhangchao', deptName: 'general_manager', role_level: 1 }, + { username: 'liming', deptName: 'franchise_manager', role_level: 2 }, + { username: 'wangli', deptName: 'operations_manager', role_level: 2 }, + { username: 'zhaoqiang', deptName: 'procurement_manager',role_level: 2 }, + { username: 'chenfang', deptName: 'finance', role_level: 2 }, + { username: 'admin', deptName: 'admin', role_level: 2 }, + { username: 'yangfan', deptName: 'hr', role_level: 2 }, ] for (let i = 0; i < seedUsers.length; i++) { @@ -413,9 +419,9 @@ async function seedDefaultUsers() { // 再插入 users,关联 employee 和 department await pool.query( - `INSERT INTO users (username, password, is_active, department_id, employee_id) - VALUES (?, ?, 1, ?, ?)`, - [usr.username, hash, deptIdMap[usr.deptName], empId] + `INSERT INTO users (username, password, is_active, department_id, employee_id, role_level) + VALUES (?, ?, 1, ?, ?, ?)`, + [usr.username, hash, deptIdMap[usr.deptName], empId, usr.role_level] ) console.log(`[init] 已创建用户 ${usr.username} (${emp.name} - ${emp.department})`) } @@ -452,14 +458,14 @@ async function seedBusinessData() { const deptIdMap2 = Object.fromEntries(deptRows.map(d => [d.name, d.id])) const extraUsers = [ - { empName: '刘洋', username: 'liuyang', deptName: 'franchise_manager' }, - { empName: '孙婷', username: 'sunting', deptName: 'franchise_manager' }, - { empName: '周杰', username: 'zhoujie', deptName: 'operations_manager' }, - { empName: '吴敏', username: 'wumin', deptName: 'operations_manager' }, - { empName: '郑伟', username: 'zhengwei', deptName: 'operations_manager' }, - { empName: '黄磊', username: 'huanglei', deptName: 'procurement_manager' }, - { empName: '马丽', username: 'mali', deptName: 'finance' }, - { empName: '林峰', username: 'linfeng', deptName: 'general_manager' }, + { empName: '刘洋', username: 'liuyang', deptName: 'franchise_manager', role_level: 4 }, + { empName: '孙婷', username: 'sunting', deptName: 'franchise_manager', role_level: 4 }, + { empName: '周杰', username: 'zhoujie', deptName: 'operations_manager', role_level: 4 }, + { empName: '吴敏', username: 'wumin', deptName: 'operations_manager', role_level: 4 }, + { empName: '郑伟', username: 'zhengwei', deptName: 'operations_manager', role_level: 4 }, + { empName: '黄磊', username: 'huanglei', deptName: 'procurement_manager',role_level: 4 }, + { empName: '马丽', username: 'mali', deptName: 'finance', role_level: 4 }, + { empName: '林峰', username: 'linfeng', deptName: 'general_manager', role_level: 2 }, ] const [newEmps] = await pool.query("SELECT id, name, department FROM employees WHERE name IN ('刘洋','孙婷','周杰','吴敏','郑伟','黄磊','马丽','林峰')") @@ -471,8 +477,8 @@ async function seedBusinessData() { const [userCheck] = await pool.query('SELECT id FROM users WHERE username = ?', [u.username]) if (userCheck.length > 0) continue await pool.query( - 'INSERT INTO users (username, password, is_active, department_id, employee_id) VALUES (?, ?, 1, ?, ?)', - [u.username, hash, deptIdMap2[u.deptName], emp.id] + 'INSERT INTO users (username, password, is_active, department_id, employee_id, role_level) VALUES (?, ?, 1, ?, ?, ?)', + [u.username, hash, deptIdMap2[u.deptName], emp.id, u.role_level] ) console.log(`[init] 已创建用户 ${u.username} (${u.empName} - ${emp.department})`) } diff --git a/middleware/employees.js b/middleware/employees.js index 53a271c..5d964dc 100644 --- a/middleware/employees.js +++ b/middleware/employees.js @@ -4,7 +4,7 @@ * 允许在创建员工时一并创建系统用户账号(需 user:manage 权限) * 将用户创建参数挂到 req._createUser,由路由在写入 employee 后处理 */ -function allowUserCreation(req, res, next) { +async function allowUserCreation(req, res, next) { const { username, password, department_id } = req.body || {} // 没传用户相关字段 → 只创建员工,跳过 @@ -27,8 +27,37 @@ function allowUserCreation(req, res, next) { return res.status(400).json({ code: 400, message: '密码至少 6 位' }) } + // 检查是否是添加总经理办公室的用户 + try { + const { pool } = require('../db') + const [dept] = await pool.query('SELECT name FROM departments WHERE id = ?', [department_id]) + if (dept.length > 0 && dept[0].name === 'general_manager') { + const operatorLevel = req.user.role_level || 4 + const operatorDept = req.user.departmentName + + // 只有信息技术部和人力资源部的部门经理才能添加总经理办公室的用户 + if (operatorLevel > 2 || !['admin', 'hr'].includes(operatorDept)) { + return res.status(403).json({ code: 403, message: '只有信息技术部和人力资源部的部门经理才能添加总经理办公室的用户' }) + } + + // 一般科员不能添加总经理办公室的用户 + if (operatorLevel === 4) { + return res.status(403).json({ code: 403, message: '一般科员不能添加总经理办公室的用户' }) + } + + // 只能添加副总经理,总经理是系统预设的 + // 这个检查在员工创建时通过 position 字段控制 + } + } catch (e) { + console.error('[allowUserCreation] error:', e) + return res.status(500).json({ code: 500, message: e.message }) + } + + // 获取角色级别 + const { role_level } = req.body || {} + // 挂到 req,供路由在 INSERT employee 后使用 - req._createUser = { username, password, department_id } + req._createUser = { username, password, department_id, role_level: role_level || 4 } next() } diff --git a/middleware/permissions.js b/middleware/permissions.js index f71b8ee..c5d9f6c 100644 --- a/middleware/permissions.js +++ b/middleware/permissions.js @@ -1,9 +1,7 @@ -// middleware/permissions.js —— 接口权限 + 数据范围 + 合同类型校验 +// middleware/permissions.js —— 接口权限 + 数据范围 + 合同类型校验 + 层级检查 /** * 检查当前用户是否拥有指定权限 - * @param {string} resource - 资源名,如 'customer' - * @param {string} action - 操作名,如 'read' */ function checkPermission(resource, action) { return (req, res, next) => { @@ -16,50 +14,105 @@ function checkPermission(resource, action) { } /** - * 根据用户部门计算数据范围,挂到 req.scope - * @param {string} resource - 资源名:customers / contracts / after_sales / employees + * 根据用户角色级别和部门计算数据范围,挂到 req.scope + * + * 角色层级: + * - 总经理(level=1): 全量数据 + * - 部门经理(level=2): 本部门数据 + * - 部门副经理(level=3): 本部门内自己及以下的数据 + * - 一般专员(level=4): 仅自己的数据 */ function dataScope(resource) { - return (req, res, next) => { - const deptName = req.user.departmentName // 如 'franchise_manager' + return async (req, res, next) => { + const { pool } = require('../db') + const roleLevel = req.user.role_level || 4 + const deptName = req.user.departmentName + const deptDesc = req.user.departmentDesc - // 信息技术部和总经理办公室:全量数据 - if (deptName === 'admin' || deptName === 'general_manager') { + // 总经理:全量数据 + if (roleLevel === 1) { return next() } - let sql = null, params = [] + let sql = null + let params = [] switch (resource) { - case 'customers': - // 过渡期:招商部和运营部也能看全量加盟商 + case 'employees': + if (roleLevel === 1) { + // 总经理:全量数据(已在上面处理) + } else if (roleLevel === 2) { + if (deptName === 'hr') { + // 人力资源部经理:查看所有员工 + // 无过滤 + } else { + // 其他部门经理:本部门所有员工 + sql = 'AND department = ?' + params = [deptDesc] + } + } else if (roleLevel === 3) { + // 部门副经理:本部门内非经理级别的员工 + 自己 + sql = 'AND (department = ? AND position NOT LIKE ?) OR id = (SELECT employee_id FROM users WHERE id = ?)' + params = [deptDesc, '%经理%', req.user.id] + } else { + // 一般专员:不能看到总经理的信息,但能看到其他员工 + sql = 'AND position NOT LIKE ?' + params = ['%总经理%'] + } break case 'contracts': - if (deptName === 'franchise_manager') { - sql = 'AND type = ?' - params = ['franchise'] - } else if (deptName === 'procurement_manager') { - sql = 'AND type = ?' - params = ['supply'] + if (roleLevel === 2) { + // 部门经理:本部门人员负责的合同 + // 先查本部门的用户ID列表 + const [deptUsers] = await pool.query( + 'SELECT u.id FROM users u LEFT JOIN departments d ON u.department_id = d.id WHERE d.name = ?', + [deptName] + ) + const deptUserIds = deptUsers.map(u => u.id) + if (deptUserIds.length > 0) { + sql = `AND responsible_user_id IN (${deptUserIds.map(() => '?').join(',')})` + params = deptUserIds + } else { + sql = 'AND 1=0' // 无数据 + } + } else if (roleLevel >= 3) { + // 副经理和专员:自己负责的合同 + sql = 'AND responsible_user_id = ?' + params = [req.user.id] } - // finance 全量看;operations_manager 无合同权限(checkPermission 已拦截) + // 招商部/采购部的类型限制仍然通过 validateContractType 处理 break case 'after_sales': - // 过渡期:运营部看全量售后 + if (roleLevel === 2) { + // 部门经理:本部门人员负责的售后 + const [deptUsersAS] = await pool.query( + 'SELECT u.id FROM users u LEFT JOIN departments d ON u.department_id = d.id WHERE d.name = ?', + [deptName] + ) + const deptUserIdsAS = deptUsersAS.map(u => u.id) + if (deptUserIdsAS.length > 0) { + sql = `AND responsible_user_id IN (${deptUserIdsAS.map(() => '?').join(',')})` + params = deptUserIdsAS + } else { + sql = 'AND 1=0' + } + } else if (roleLevel >= 3) { + // 副经理和专员:自己负责的售后 + sql = 'AND responsible_user_id = ?' + params = [req.user.id] + } break - case 'employees': - if (deptName === 'hr') { - // 人力资源部可查看全量员工 - break + case 'customers': + // 加盟商是公共资源,所有有权限的人都能看 + // 但一般专员只能看自己关联的(通过合同/售后间接关联) + if (roleLevel === 4) { + // 一般专员:只能看到与自己负责的合同/售后关联的客户 + sql = 'AND id IN (SELECT customer_id FROM contracts WHERE responsible_user_id = ? UNION SELECT customer_id FROM after_sales WHERE responsible_user_id = ?)' + params = [req.user.id, req.user.id] } - if (['franchise_manager', 'operations_manager', 'procurement_manager'].includes(deptName)) { - sql = 'AND department = ?' - params = [req.user.departmentDesc] // 如 '招商部' - } - // admin / general_manager / finance 全量看员工 break } @@ -72,24 +125,23 @@ function dataScope(resource) { /** * 合同创建/修改时校验类型是否匹配部门 - * 招商部只能操作加盟合同,采购部只能操作采购合同 */ function validateContractType(req, res, next) { const deptName = req.user.departmentName const type = req.body.type - // 未传 type 则跳过(路由会默认 'franchise') if (type === undefined) return next() - // 管理员/总经理/财务不限制 - if (['admin', 'general_manager', 'finance'].includes(deptName)) return next() + // 总经理和财务不限制 + if (['general_manager', 'finance'].includes(deptName)) return next() + + // 信息技术部也不再是超级管理员,按部门权限走 + if (deptName === 'admin') return next() - // 采购部只能操作采购合同 if (deptName === 'procurement_manager' && type !== 'supply') { return res.status(403).json({ code: 403, message: '采购部只能操作采购合同' }) } - // 招商部只能操作加盟合同 if (deptName === 'franchise_manager' && type !== 'franchise') { return res.status(403).json({ code: 403, message: '招商部只能操作加盟合同' }) } @@ -97,4 +149,41 @@ function validateContractType(req, res, next) { next() } -module.exports = { checkPermission, dataScope, validateContractType } +/** + * 层级检查:确保操作者不能操作比自己级别高的用户 + * 用于用户管理的删除/修改操作 + */ +function checkHierarchy(req, res, next) { + return async (req, res, next) => { + try { + const { pool } = require('../db') + const operatorLevel = req.user.role_level || 4 + const targetId = Number(req.params.id) + + // 查询目标用户的role_level + const [rows] = await pool.query('SELECT role_level FROM users WHERE id = ?', [targetId]) + if (rows.length === 0) { + return res.status(404).json({ code: 404, message: '用户不存在' }) + } + + const targetLevel = rows[0].role_level || 4 + + // 不能操作比自己级别高的用户 + if (targetLevel < operatorLevel) { + return res.status(403).json({ code: 403, message: '不能操作比自己级别高的用户' }) + } + + // 最高只能操作同级 + if (targetLevel < operatorLevel) { + return res.status(403).json({ code: 403, message: '权限不足,无法操作该用户' }) + } + + next() + } catch (e) { + console.error('[checkHierarchy] error:', e) + return res.status(500).json({ code: 500, message: e.message }) + } + } +} + +module.exports = { checkPermission, dataScope, validateContractType, checkHierarchy } diff --git a/middleware/users.js b/middleware/users.js index c0b5ad1..8cde62e 100644 --- a/middleware/users.js +++ b/middleware/users.js @@ -20,29 +20,52 @@ function protectSelfUpdate(req, res, next) { } /** - * 删除用户时的自保护: + * 删除用户时的保护: * - 不能删除自己 - * - 不能删除最后一个管理员 + * - 不能删除比自己级别高的用户 + * - 最高只能删除同级用户 + * - 不能删除最后一个总经理 */ async function protectUserDelete(req, res, next) { - if (Number(req.params.id) === req.user.id) { + const targetId = Number(req.params.id) + const operatorLevel = req.user.role_level || 4 + + // 不能删除自己 + if (targetId === req.user.id) { return res.status(400).json({ code: 400, message: '不能删除自己' }) } - const [rows] = await pool.query('SELECT department_id FROM users WHERE id = ?', [req.params.id]) - if (rows.length > 0) { - const [adminDept] = await pool.query('SELECT id FROM departments WHERE name = ?', ['admin']) - if (adminDept.length > 0 && rows[0].department_id === adminDept[0].id) { + try { + const [rows] = await pool.query('SELECT role_level, department_id FROM users WHERE id = ?', [targetId]) + if (rows.length === 0) { + return res.status(404).json({ code: 404, message: '用户不存在' }) + } + + const targetLevel = rows[0].role_level || 4 + + // 不能删除比自己级别高的用户(数字越小级别越高) + if (targetLevel < operatorLevel) { + return res.status(403).json({ code: 403, message: '不能删除比自己级别高的用户' }) + } + + // 最高只能删除同级 + // 这个逻辑已经在上面的检查中覆盖了(targetLevel >= operatorLevel 才能继续) + + // 不能删除最后一个总经理 + if (targetLevel === 1) { const [[{ cnt }]] = await pool.query( - 'SELECT COUNT(*) AS cnt FROM users WHERE department_id = ?', - [adminDept[0].id] + 'SELECT COUNT(*) AS cnt FROM users WHERE role_level = 1' ) if (cnt <= 1) { - return res.status(400).json({ code: 400, message: '不能删除最后一个管理员' }) + return res.status(400).json({ code: 400, message: '不能删除最后一个总经理' }) } } + + next() + } catch (e) { + console.error('[protectUserDelete] error:', e) + return res.status(500).json({ code: 500, message: e.message }) } - next() } module.exports = { protectSelfUpdate, protectUserDelete } diff --git a/routes/contracts.js b/routes/contracts.js index 5e8fe95..0983e73 100644 --- a/routes/contracts.js +++ b/routes/contracts.js @@ -241,7 +241,7 @@ async function update(req, res) { let checkSql = 'SELECT id, employee_id FROM contracts WHERE id = ?' let checkParams = [id] if (req.scope && req.scope.sql) { - checkSql += ' ' + req.scope.sql + checkSql += ' ' + req.scope.sql.replace(/\btype\b/g, 'c.type') checkParams.push(...req.scope.params) } const [existing] = await pool.query(checkSql, checkParams) @@ -320,7 +320,7 @@ async function remove(req, res) { let checkSql = 'SELECT id FROM contracts WHERE id = ?' let checkParams = [id] if (req.scope && req.scope.sql) { - checkSql += ' ' + req.scope.sql + checkSql += ' ' + req.scope.sql.replace(/\btype\b/g, 'c.type') checkParams.push(...req.scope.params) } const [existing] = await pool.query(checkSql, checkParams) diff --git a/routes/employees.js b/routes/employees.js index 94e716d..13b3230 100644 --- a/routes/employees.js +++ b/routes/employees.js @@ -120,6 +120,27 @@ async function create(req, res) { return res.status(400).json({ code: 400, message: '员工姓名必填' }) } + // 检查是否是添加总经理办公室的员工 + if (department === '总经理办公室') { + const roleLevel = req.user.role_level || 4 + const deptName = req.user.departmentName + + // 只有信息技术部和人力资源部的部门经理才能添加总经理办公室的员工 + if (roleLevel > 2 || !['admin', 'hr'].includes(deptName)) { + return res.status(403).json({ code: 403, message: '只有信息技术部和人力资源部的部门经理才能添加总经理办公室的员工' }) + } + + // 一般科员不能添加员工到总经理办公室 + if (roleLevel === 4) { + return res.status(403).json({ code: 403, message: '一般科员不能添加总经理办公室的员工' }) + } + + // 只能添加副总经理,总经理是系统预设的 + if (position && position !== '副总经理') { + return res.status(400).json({ code: 400, message: '总经理办公室只能添加副总经理,总经理是系统预设的' }) + } + } + try { const [result] = await pool.query( `INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status, remark) @@ -141,11 +162,12 @@ async function create(req, res) { // 如果中间件 allowUserCreation 要求同步创建用户账号 if (req._createUser) { - const { username, password, department_id } = req._createUser + const { username, password, department_id, role_level } = req._createUser const hash = await bcrypt.hash(password, 10) + const validRoleLevel = [1, 2, 3, 4].includes(Number(role_level)) ? Number(role_level) : 4 await pool.query( - 'INSERT INTO users (username, password, is_active, department_id, employee_id) VALUES (?, ?, 1, ?, ?)', - [username, hash, department_id, result.insertId] + 'INSERT INTO users (username, password, is_active, department_id, employee_id, role_level) VALUES (?, ?, 1, ?, ?, ?)', + [username, hash, department_id, result.insertId, validRoleLevel] ) delete req._createUser // 清理,避免影响后续中间件 } @@ -255,14 +277,23 @@ async function remove(req, res) { // GET /api/employees/simple —— 简易员工列表(无数据范围限制,用于下拉选择) async function simpleList(req, res) { try { - const { department } = req.query - let sql = 'SELECT id, name, department, position FROM employees WHERE status = 1' + const { department, max_role_level } = req.query + // 关联users表获取role_level信息 + let sql = `SELECT e.id, e.name, e.department, e.position, COALESCE(u.role_level, 4) as role_level + FROM employees e + LEFT JOIN users u ON e.id = u.employee_id + WHERE e.status = 1` const params = [] if (department) { - sql += ' AND department = ?' + sql += ' AND e.department = ?' params.push(department) } - sql += ' ORDER BY id' + // 过滤角色级别(不能选择比自己职位高的员工) + if (max_role_level) { + sql += ' AND COALESCE(u.role_level, 4) >= ?' + params.push(Number(max_role_level)) + } + sql += ' ORDER BY e.id' const [rows] = await pool.query(sql, params) res.json({ code: 0, message: 'ok', data: rows }) } catch (e) { diff --git a/routes/users.js b/routes/users.js index e2c5704..09a1930 100644 --- a/routes/users.js +++ b/routes/users.js @@ -5,7 +5,7 @@ const { pool } = require('../db') // 安全字段:关联查询 departments 和 employees const USER_LIST_SQL = ` - SELECT u.id, u.username, u.is_active, u.department_id, u.employee_id, + SELECT u.id, u.username, u.is_active, u.department_id, u.employee_id, u.role_level, u.created_at, u.updated_at, d.name AS dept_name, d.description AS dept_desc, e.name AS real_name @@ -32,7 +32,7 @@ async function login(req, res) { try { const [rows] = await pool.query( - `SELECT u.id, u.username, u.password, u.is_active, u.department_id, u.employee_id, + `SELECT u.id, u.username, u.password, u.is_active, u.department_id, u.employee_id, u.role_level, d.name AS dept_name, d.description AS dept_desc, e.name AS real_name FROM users u LEFT JOIN departments d ON u.department_id = d.id @@ -74,6 +74,7 @@ async function login(req, res) { department_id: user.department_id, departmentName: user.dept_name, departmentDesc: user.dept_desc, + role_level: user.role_level || 4, permissions, }, process.env.JWT_SECRET, @@ -93,6 +94,7 @@ async function login(req, res) { department_id: user.department_id, departmentName: user.dept_name, departmentDesc: user.dept_desc, + role_level: user.role_level || 4, permissions, }, }, @@ -253,7 +255,7 @@ async function detail(req, res) { // POST /api/users —— 创建用户 async function create(req, res) { - const { username, password, department_id, employee_id, is_active = 1 } = req.body || {} + const { username, password, department_id, employee_id, is_active = 1, role_level = 4 } = req.body || {} if (!username || !password) { return res.status(400).json({ code: 400, message: '用户名和密码必填' }) @@ -267,10 +269,31 @@ async function create(req, res) { // 校验 department_id 是否存在 if (department_id) { - const [dept] = await pool.query('SELECT id FROM departments WHERE id = ?', [department_id]) + const [dept] = await pool.query('SELECT id, name FROM departments WHERE id = ?', [department_id]) if (dept.length === 0) { return res.status(400).json({ code: 400, message: '指定的部门不存在' }) } + + // 检查是否是添加总经理办公室的用户 + if (dept[0].name === 'general_manager') { + const operatorLevel = req.user.role_level || 4 + const operatorDept = req.user.departmentName + + // 只有信息技术部和人力资源部的部门经理才能添加总经理办公室的用户 + if (operatorLevel > 2 || !['admin', 'hr'].includes(operatorDept)) { + return res.status(403).json({ code: 403, message: '只有信息技术部和人力资源部的部门经理才能添加总经理办公室的用户' }) + } + + // 一般科员不能添加总经理办公室的用户 + if (operatorLevel === 4) { + return res.status(403).json({ code: 403, message: '一般科员不能添加总经理办公室的用户' }) + } + + // 只能添加副总经理(role_level=2),总经理是系统预设的 + if (role_level && Number(role_level) !== 2) { + return res.status(400).json({ code: 400, message: '总经理办公室只能添加副总经理(角色级别2),总经理是系统预设的' }) + } + } } // 校验 employee_id 是否存在 @@ -283,9 +306,11 @@ async function create(req, res) { try { const hash = await bcrypt.hash(password, 10) + // 验证role_level范围 + const validRoleLevel = [1, 2, 3, 4].includes(Number(role_level)) ? Number(role_level) : 4 const [result] = await pool.query( - 'INSERT INTO users (username, password, is_active, department_id, employee_id) VALUES (?, ?, ?, ?, ?)', - [username, hash, is_active, department_id || null, employee_id || null] + 'INSERT INTO users (username, password, is_active, department_id, employee_id, role_level) VALUES (?, ?, ?, ?, ?, ?)', + [username, hash, is_active, department_id || null, employee_id || null, validRoleLevel] ) const [rows] = await pool.query( @@ -305,7 +330,7 @@ async function create(req, res) { // PUT /api/users/:id —— 更新用户 async function update(req, res) { const { id } = req.params - const fields = ['username', 'department_id', 'employee_id', 'is_active'] + const fields = ['username', 'department_id', 'employee_id', 'is_active', 'role_level'] try { const [existing] = await pool.query('SELECT id, department_id, is_active, employee_id FROM users WHERE id = ?', [id]) diff --git a/server.js b/server.js index 646760c..1c7d395 100644 --- a/server.js +++ b/server.js @@ -13,7 +13,7 @@ const products = require('./routes/products') const suppliers = require('./routes/suppliers') // 导入中间件 -const { checkPermission, dataScope, validateContractType } = require('./middleware/permissions') +const { checkPermission, dataScope, validateContractType, checkHierarchy } = require('./middleware/permissions') const { protectSelfUpdate, protectUserDelete } = require('./middleware/users') const { allowUserCreation } = require('./middleware/employees') @@ -47,8 +47,8 @@ app.put('/api/user/password', auth, users.changePassword) app.get('/api/users', auth, checkPermission('user', 'manage'), users.list) app.get('/api/users/:id', auth, checkPermission('user', 'manage'), users.detail) app.post('/api/users', auth, checkPermission('user', 'manage'), users.create) -app.put('/api/users/:id', auth, checkPermission('user', 'manage'), protectSelfUpdate, users.update) -app.delete('/api/users/:id', auth, checkPermission('user', 'manage'), protectUserDelete, users.remove) +app.put('/api/users/:id', auth, checkPermission('user', 'manage'), checkHierarchy, protectSelfUpdate, users.update) +app.delete('/api/users/:id', auth, checkPermission('user', 'manage'), checkHierarchy, protectUserDelete, users.remove) // ============ 加盟商管理 /api/customers ============ app.get('/api/customers/simple', auth, checkPermission('customer', 'read'), customers.simpleList)