2026-06-15 20:46:48 +08:00
|
|
|
|
// db.js —— 只做两件事:导出连接池;启动时建库/建表/插 admin
|
|
|
|
|
|
require('dotenv').config() //自动寻找.env文件,并把里面的键值对全部导入进来
|
|
|
|
|
|
const mysql = require('mysql2/promise')
|
|
|
|
|
|
const bcrypt = require('bcryptjs') //用来给密码做哈希加密
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 1. 连接池 ============
|
|
|
|
|
|
// 业务代码里用 pool.query() / pool.execute(),会自动从池里取/还连接
|
|
|
|
|
|
const pool = mysql.createPool({
|
|
|
|
|
|
host: process.env.DB_HOST,
|
|
|
|
|
|
port: Number(process.env.DB_PORT) || 3306,
|
|
|
|
|
|
user: process.env.DB_USER,
|
|
|
|
|
|
password: process.env.DB_PASSWORD,
|
|
|
|
|
|
database: process.env.DB_NAME,
|
|
|
|
|
|
waitForConnections: true, // 池里没连接时排队等
|
|
|
|
|
|
connectionLimit: 10, // 最多 10 个连接
|
|
|
|
|
|
decimalNumbers: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 2. 启动时初始化 ============
|
|
|
|
|
|
// 思路:先用一个"无 database"的连接建库,再回到 pool 建表、插 admin
|
|
|
|
|
|
async function initDB() {
|
|
|
|
|
|
const { DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME } = process.env
|
|
|
|
|
|
|
|
|
|
|
|
// 2.1 建库(如果不存在)
|
|
|
|
|
|
const conn = await mysql.createConnection({
|
|
|
|
|
|
host: DB_HOST,
|
|
|
|
|
|
port: Number(DB_PORT) || 3306,
|
|
|
|
|
|
user: DB_USER,
|
|
|
|
|
|
password: DB_PASSWORD,
|
|
|
|
|
|
})
|
|
|
|
|
|
await conn.query(
|
|
|
|
|
|
`CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` DEFAULT CHARACTER SET utf8mb4`
|
|
|
|
|
|
)
|
|
|
|
|
|
await conn.end()
|
|
|
|
|
|
|
2026-06-22 20:48:29 +08:00
|
|
|
|
// 2.2 建表(如果不存在)
|
2026-06-15 20:46:48 +08:00
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
2026-06-22 20:48:29 +08:00
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID (主键)',
|
|
|
|
|
|
username VARCHAR(50) NOT NULL COMMENT '用户名 (登录账号)',
|
|
|
|
|
|
password VARCHAR(255) NOT NULL COMMENT '登录密码 (应存储加密后的值)',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '账号状态: 0-禁用, 1-启用',
|
2026-06-26 09:44:54 +08:00
|
|
|
|
department_id INT DEFAULT NULL COMMENT '部门ID (关联departments表)',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
employee_id INT DEFAULT NULL COMMENT '员工ID (关联employees表)',
|
2026-06-22 20:48:29 +08:00
|
|
|
|
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),
|
|
|
|
|
|
UNIQUE KEY uk_username (username)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'
|
2026-06-15 20:46:48 +08:00
|
|
|
|
`)
|
|
|
|
|
|
|
2026-06-22 20:48:29 +08:00
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS customers (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '加盟商ID (主键)',
|
|
|
|
|
|
name VARCHAR(100) NOT NULL COMMENT '加盟商姓名',
|
|
|
|
|
|
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
|
|
|
|
|
|
province VARCHAR(50) DEFAULT NULL COMMENT '省',
|
|
|
|
|
|
city VARCHAR(50) DEFAULT NULL COMMENT '市',
|
|
|
|
|
|
district VARCHAR(50) DEFAULT NULL COMMENT '区',
|
|
|
|
|
|
address VARCHAR(200) DEFAULT NULL COMMENT '详细地址',
|
|
|
|
|
|
email VARCHAR(100) DEFAULT NULL COMMENT '电子邮箱',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注信息',
|
|
|
|
|
|
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),
|
|
|
|
|
|
KEY idx_customers_name (name),
|
|
|
|
|
|
KEY idx_customers_phone (phone)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='加盟商信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS employees (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '员工ID (主键)',
|
|
|
|
|
|
name VARCHAR(100) NOT NULL COMMENT '员工姓名',
|
|
|
|
|
|
gender VARCHAR(4) DEFAULT NULL COMMENT '性别: 男/女',
|
|
|
|
|
|
age INT DEFAULT NULL COMMENT '年龄',
|
|
|
|
|
|
education VARCHAR(50) DEFAULT NULL COMMENT '学历: 高中/专科/本科/硕士/博士',
|
|
|
|
|
|
department VARCHAR(100) DEFAULT NULL COMMENT '所属部门',
|
|
|
|
|
|
entry_date DATE DEFAULT NULL COMMENT '入职时间',
|
|
|
|
|
|
position VARCHAR(100) DEFAULT NULL COMMENT '职务/岗位',
|
|
|
|
|
|
salary DECIMAL(10,2) DEFAULT NULL COMMENT '工资金额',
|
|
|
|
|
|
phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
|
|
|
|
|
|
email VARCHAR(100) DEFAULT NULL COMMENT '电子邮箱',
|
|
|
|
|
|
status TINYINT(1) NOT NULL DEFAULT 1 COMMENT '在职状态: 0-离职, 1-在职',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
|
|
|
|
|
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),
|
|
|
|
|
|
KEY idx_employees_name (name),
|
|
|
|
|
|
KEY idx_employees_department (department)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS products (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '产品ID (主键)',
|
|
|
|
|
|
name VARCHAR(200) NOT NULL COMMENT '产品名称',
|
|
|
|
|
|
type VARCHAR(100) DEFAULT NULL COMMENT '产品类型/分类',
|
|
|
|
|
|
quantity INT NOT NULL DEFAULT 0 COMMENT '产品库存数量',
|
|
|
|
|
|
price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '产品单价',
|
|
|
|
|
|
unit VARCHAR(20) DEFAULT '件' COMMENT '计量单位',
|
|
|
|
|
|
specification VARCHAR(200) DEFAULT NULL COMMENT '产品规格/型号',
|
|
|
|
|
|
supplier VARCHAR(200) DEFAULT NULL COMMENT '供应商',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
|
|
|
|
|
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),
|
|
|
|
|
|
KEY idx_products_name (name),
|
|
|
|
|
|
KEY idx_products_type (type)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
// contracts 依赖 customers 和 employees,after_sales 同样依赖
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS contracts (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '合同ID (主键)',
|
|
|
|
|
|
customer_id INT NOT NULL COMMENT '客户ID (关联客户表)',
|
|
|
|
|
|
contract_name VARCHAR(200) NOT NULL COMMENT '合同名称',
|
|
|
|
|
|
contract_no VARCHAR(100) DEFAULT NULL COMMENT '合同编号',
|
|
|
|
|
|
contract_content TEXT DEFAULT NULL COMMENT '合同内容/条款',
|
|
|
|
|
|
amount DECIMAL(12,2) DEFAULT NULL COMMENT '合同金额',
|
|
|
|
|
|
effective_date DATE DEFAULT NULL COMMENT '合同生效日期',
|
|
|
|
|
|
expiry_date DATE DEFAULT NULL COMMENT '合同有效期 (截止日期)',
|
|
|
|
|
|
employee_id INT DEFAULT NULL COMMENT '业务员ID (关联员工表)',
|
|
|
|
|
|
status VARCHAR(20) NOT NULL DEFAULT '生效' COMMENT '合同状态: 草稿/生效/完成/作废',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
2026-06-24 23:12:43 +08:00
|
|
|
|
type VARCHAR(20) NOT NULL DEFAULT 'franchise' COMMENT '合同类型: franchise-加盟合同, supply-采购合同',
|
|
|
|
|
|
responsible_user_id INT DEFAULT NULL COMMENT '负责人用户ID (用于数据范围隔离)',
|
2026-06-22 20:48:29 +08:00
|
|
|
|
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),
|
|
|
|
|
|
KEY idx_contracts_customer_id (customer_id),
|
|
|
|
|
|
KEY idx_contracts_employee_id (employee_id),
|
|
|
|
|
|
KEY idx_contracts_effective_date (effective_date)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS after_sales (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '售后记录ID (主键)',
|
|
|
|
|
|
customer_id INT NOT NULL COMMENT '客户ID (关联客户表)',
|
|
|
|
|
|
feedback TEXT NOT NULL COMMENT '客户反馈意见/售后内容',
|
|
|
|
|
|
employee_id INT DEFAULT NULL COMMENT '处理业务员ID (关联员工表)',
|
|
|
|
|
|
handle_method TEXT DEFAULT NULL COMMENT '处理方式/解决方案',
|
|
|
|
|
|
handle_status VARCHAR(20) NOT NULL DEFAULT '待处理' COMMENT '处理状态: 待处理/处理中/已完成',
|
|
|
|
|
|
service_date DATE DEFAULT NULL COMMENT '售后日期',
|
|
|
|
|
|
remark TEXT DEFAULT NULL COMMENT '备注',
|
2026-06-24 23:12:43 +08:00
|
|
|
|
responsible_user_id INT DEFAULT NULL COMMENT '负责人用户ID (用于数据范围隔离)',
|
2026-06-22 20:48:29 +08:00
|
|
|
|
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),
|
|
|
|
|
|
KEY idx_after_sales_customer_id (customer_id),
|
|
|
|
|
|
KEY idx_after_sales_employee_id (employee_id),
|
|
|
|
|
|
KEY idx_after_sales_status (handle_status)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='售后信息表'
|
|
|
|
|
|
`)
|
2026-06-25 21:18:13 +08:00
|
|
|
|
|
|
|
|
|
|
// ============ 2.2.1 添加缺失的字段(兼容已存在的表) ============
|
|
|
|
|
|
// contracts 表允许 customer_id 为空(采购合同可不关联客户)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await pool.query(`ALTER TABLE contracts MODIFY COLUMN customer_id INT DEFAULT NULL COMMENT '客户ID (关联客户表)'`)
|
|
|
|
|
|
console.log('[init] contracts 表 customer_id 已允许为空')
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 修改失败则忽略
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// contracts 表添加 supplier_id(采购合同关联供应商)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await pool.query(`ALTER TABLE contracts ADD COLUMN supplier_id INT DEFAULT NULL COMMENT '供应商ID (关联供应商表)' AFTER customer_id`)
|
|
|
|
|
|
console.log('[init] contracts 表已添加 supplier_id 字段')
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 字段已存在则忽略
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// customers 表删除 responsible_user_id(加盟商不需要负责人字段)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await pool.query(`ALTER TABLE customers DROP COLUMN responsible_user_id`)
|
|
|
|
|
|
console.log('[init] customers 表已删除 responsible_user_id 字段')
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// 列已删除则忽略
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-22 20:48:29 +08:00
|
|
|
|
console.log('[init] 数据表初始化完成')
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// ============ 2.3 部门与权限相关表 ============
|
|
|
|
|
|
// 迁移旧表
|
|
|
|
|
|
await pool.query('DROP TABLE IF EXISTS role_permissions')
|
|
|
|
|
|
await pool.query('DROP TABLE IF EXISTS roles')
|
|
|
|
|
|
|
2026-06-24 22:44:14 +08:00
|
|
|
|
await pool.query(`
|
2026-06-26 09:44:54 +08:00
|
|
|
|
CREATE TABLE IF NOT EXISTS departments (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '部门ID (主键)',
|
|
|
|
|
|
name VARCHAR(50) NOT NULL COMMENT '部门标识 (英文)',
|
|
|
|
|
|
description VARCHAR(200) DEFAULT NULL COMMENT '部门名称 (中文)',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
|
|
|
|
|
PRIMARY KEY (id),
|
2026-06-26 09:44:54 +08:00
|
|
|
|
UNIQUE KEY uk_departments_name (name)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表'
|
2026-06-24 22:44:14 +08:00
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS permissions (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '权限ID (主键)',
|
|
|
|
|
|
name VARCHAR(100) NOT NULL COMMENT '权限标识 (resource:action)',
|
|
|
|
|
|
description VARCHAR(200) DEFAULT NULL COMMENT '权限描述 (中文)',
|
|
|
|
|
|
resource VARCHAR(50) NOT NULL COMMENT '资源名称',
|
|
|
|
|
|
action VARCHAR(50) NOT NULL COMMENT '操作类型: read/create/update/delete',
|
|
|
|
|
|
PRIMARY KEY (id),
|
|
|
|
|
|
UNIQUE KEY uk_permissions_name (name)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
|
|
await pool.query(`
|
2026-06-26 09:44:54 +08:00
|
|
|
|
CREATE TABLE IF NOT EXISTS department_permissions (
|
|
|
|
|
|
department_id INT NOT NULL COMMENT '部门ID',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
permission_id INT NOT NULL COMMENT '权限ID',
|
2026-06-26 09:44:54 +08:00
|
|
|
|
PRIMARY KEY (department_id, permission_id),
|
|
|
|
|
|
KEY idx_dp_permission_id (permission_id)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门-权限关联表'
|
2026-06-24 22:44:14 +08:00
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
|
|
await pool.query(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS suppliers (
|
|
|
|
|
|
id INT NOT NULL AUTO_INCREMENT COMMENT '供应商ID (主键)',
|
|
|
|
|
|
name VARCHAR(200) NOT NULL COMMENT '供应商名称',
|
|
|
|
|
|
contact VARCHAR(100) DEFAULT NULL COMMENT '联系人',
|
|
|
|
|
|
phone VARCHAR(50) DEFAULT NULL COMMENT '联系电话',
|
|
|
|
|
|
address VARCHAR(500) DEFAULT NULL COMMENT '地址',
|
|
|
|
|
|
type VARCHAR(50) DEFAULT NULL COMMENT '类型: 原材料/包装/设备',
|
|
|
|
|
|
content TEXT DEFAULT NULL COMMENT '备注说明',
|
|
|
|
|
|
status TINYINT(1) NOT NULL DEFAULT 1 COMMENT '状态: 0-停用, 1-正常',
|
|
|
|
|
|
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),
|
|
|
|
|
|
KEY idx_suppliers_name (name)
|
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商信息表'
|
|
|
|
|
|
`)
|
|
|
|
|
|
|
2026-06-24 23:12:43 +08:00
|
|
|
|
// ============ 2.4 种子数据 ============
|
2026-06-26 09:44:54 +08:00
|
|
|
|
await seedDepartmentsAndPermissions()
|
2026-06-24 22:44:14 +08:00
|
|
|
|
await seedDefaultUsers()
|
2026-06-29 15:07:53 +08:00
|
|
|
|
// await seedBusinessData()
|
|
|
|
|
|
|
|
|
|
|
|
// // ============ 2.5 数据库迁移(兼容已有数据的旧库) ============
|
|
|
|
|
|
// // 为财务部补上 product:read 权限
|
|
|
|
|
|
// try {
|
|
|
|
|
|
// const [financeDept] = await pool.query("SELECT id FROM departments WHERE name = 'finance' LIMIT 1")
|
|
|
|
|
|
// if (financeDept.length > 0) {
|
|
|
|
|
|
// const [prodPerm] = await pool.query("SELECT id FROM permissions WHERE name = 'product:read' LIMIT 1")
|
|
|
|
|
|
// if (prodPerm.length > 0) {
|
|
|
|
|
|
// await pool.query(
|
|
|
|
|
|
// 'INSERT IGNORE INTO department_permissions (department_id, permission_id) VALUES (?, ?)',
|
|
|
|
|
|
// [financeDept[0].id, prodPerm[0].id]
|
|
|
|
|
|
// )
|
|
|
|
|
|
// console.log('[migrate] 财务部已补上 product:read 权限')
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// } catch (e) {
|
|
|
|
|
|
// console.log('[migrate] 财务部权限迁移跳过:', e.message)
|
|
|
|
|
|
// }
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// ============ 3. 种子数据:部门与权限 ============
|
|
|
|
|
|
async function seedDepartmentsAndPermissions() {
|
|
|
|
|
|
const [depts] = await pool.query('SELECT id FROM departments LIMIT 1')
|
|
|
|
|
|
if (depts.length > 0) return // 已初始化过,跳过
|
|
|
|
|
|
|
|
|
|
|
|
const deptList = [
|
|
|
|
|
|
{ name: 'admin', description: '信息技术部' },
|
|
|
|
|
|
{ name: 'general_manager', description: '总经理办公室' },
|
|
|
|
|
|
{ name: 'franchise_manager', description: '招商部' },
|
|
|
|
|
|
{ name: 'operations_manager', description: '运营部' },
|
|
|
|
|
|
{ name: 'procurement_manager', description: '采购部' },
|
|
|
|
|
|
{ name: 'finance', description: '财务部' },
|
2026-06-29 11:32:54 +08:00
|
|
|
|
{ name: 'hr', description: '人力资源部' },
|
2026-06-24 22:44:14 +08:00
|
|
|
|
]
|
2026-06-26 09:44:54 +08:00
|
|
|
|
for (const d of deptList) {
|
|
|
|
|
|
await pool.query('INSERT INTO departments (name, description) VALUES (?, ?)', [d.name, d.description])
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
2026-06-29 11:32:54 +08:00
|
|
|
|
console.log('[init] 已初始化 7 个部门')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
|
|
|
|
|
const permissionList = [
|
|
|
|
|
|
{ name: 'customer:read', description: '查看加盟商', resource: 'customer', action: 'read' },
|
|
|
|
|
|
{ name: 'customer:create', description: '新增加盟商', resource: 'customer', action: 'create' },
|
|
|
|
|
|
{ name: 'customer:update', description: '修改加盟商', resource: 'customer', action: 'update' },
|
|
|
|
|
|
{ name: 'customer:delete', description: '删除加盟商', resource: 'customer', action: 'delete' },
|
|
|
|
|
|
{ name: 'contract:read', description: '查看合同', resource: 'contract', action: 'read' },
|
|
|
|
|
|
{ name: 'contract:create', description: '新增合同', resource: 'contract', action: 'create' },
|
|
|
|
|
|
{ name: 'contract:update', description: '修改合同', resource: 'contract', action: 'update' },
|
|
|
|
|
|
{ name: 'contract:delete', description: '删除合同', resource: 'contract', action: 'delete' },
|
|
|
|
|
|
{ name: 'after_sale:read', description: '查看售后', resource: 'after_sale', action: 'read' },
|
|
|
|
|
|
{ name: 'after_sale:create',description: '新增售后', resource: 'after_sale', action: 'create' },
|
|
|
|
|
|
{ name: 'after_sale:update',description: '修改售后', resource: 'after_sale', action: 'update' },
|
|
|
|
|
|
{ name: 'after_sale:delete',description: '删除售后', resource: 'after_sale', action: 'delete' },
|
|
|
|
|
|
{ name: 'product:read', description: '查看产品原料', resource: 'product', action: 'read' },
|
|
|
|
|
|
{ name: 'product:create', description: '新增产品原料', resource: 'product', action: 'create' },
|
|
|
|
|
|
{ name: 'product:update', description: '修改产品原料', resource: 'product', action: 'update' },
|
|
|
|
|
|
{ name: 'product:delete', description: '删除产品原料', resource: 'product', action: 'delete' },
|
|
|
|
|
|
{ name: 'supplier:read', description: '查看供应商', resource: 'supplier', action: 'read' },
|
|
|
|
|
|
{ name: 'supplier:create', description: '新增供应商', resource: 'supplier', action: 'create' },
|
|
|
|
|
|
{ name: 'supplier:update', description: '修改供应商', resource: 'supplier', action: 'update' },
|
|
|
|
|
|
{ name: 'supplier:delete', description: '删除供应商', resource: 'supplier', action: 'delete' },
|
|
|
|
|
|
{ name: 'employee:read', description: '查看员工', resource: 'employee', action: 'read' },
|
|
|
|
|
|
{ name: 'employee:create', description: '新增员工', resource: 'employee', action: 'create' },
|
|
|
|
|
|
{ name: 'employee:update', description: '修改员工', resource: 'employee', action: 'update' },
|
|
|
|
|
|
{ name: 'employee:delete', description: '删除员工', resource: 'employee', action: 'delete' },
|
|
|
|
|
|
{ name: 'user:manage', description: '管理用户账号', resource: 'user', action: 'manage' },
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const p of permissionList) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
'INSERT INTO permissions (name, description, resource, action) VALUES (?, ?, ?, ?)',
|
|
|
|
|
|
[p.name, p.description, p.resource, p.action]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化 25 个权限')
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 部门-权限映射
|
|
|
|
|
|
const deptPermMap = {
|
2026-06-29 11:32:54 +08:00
|
|
|
|
admin: [
|
|
|
|
|
|
'user:manage',
|
|
|
|
|
|
'customer:read', 'contract:read', 'after_sale:read',
|
|
|
|
|
|
'product:read', 'supplier:read', 'employee:read',
|
|
|
|
|
|
],
|
2026-06-24 22:44:14 +08:00
|
|
|
|
general_manager: [
|
|
|
|
|
|
'customer:read', 'contract:read', 'after_sale:read',
|
2026-06-29 12:52:01 +08:00
|
|
|
|
'product:read', 'supplier:read',
|
|
|
|
|
|
'employee:read', 'employee:create', 'employee:update', 'employee:delete',
|
|
|
|
|
|
'user:manage',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
],
|
|
|
|
|
|
franchise_manager: [
|
|
|
|
|
|
'customer:read', 'customer:create', 'customer:update', 'customer:delete',
|
|
|
|
|
|
'contract:read', 'contract:create', 'contract:update', 'contract:delete',
|
|
|
|
|
|
'employee:read',
|
|
|
|
|
|
],
|
|
|
|
|
|
operations_manager: [
|
2026-06-26 09:44:54 +08:00
|
|
|
|
'customer:read',
|
2026-06-29 11:32:54 +08:00
|
|
|
|
'contract:read',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
'after_sale:read', 'after_sale:create', 'after_sale:update', 'after_sale:delete',
|
|
|
|
|
|
'employee:read',
|
|
|
|
|
|
],
|
|
|
|
|
|
procurement_manager: [
|
2026-06-26 09:44:54 +08:00
|
|
|
|
'customer:read',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
'contract:read', 'contract:create', 'contract:update', 'contract:delete',
|
|
|
|
|
|
'product:read', 'product:create', 'product:update', 'product:delete',
|
|
|
|
|
|
'supplier:read', 'supplier:create', 'supplier:update', 'supplier:delete',
|
|
|
|
|
|
'employee:read',
|
|
|
|
|
|
],
|
|
|
|
|
|
finance: [
|
2026-06-28 20:43:14 +08:00
|
|
|
|
'customer:read', 'contract:read', 'after_sale:read', 'product:read', 'employee:read',
|
2026-06-29 11:32:54 +08:00
|
|
|
|
'supplier:read',
|
|
|
|
|
|
],
|
|
|
|
|
|
hr: [
|
|
|
|
|
|
'employee:read', 'employee:create', 'employee:update', 'employee:delete',
|
|
|
|
|
|
'user:manage',
|
|
|
|
|
|
'customer:read',
|
2026-06-24 22:44:14 +08:00
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const [deptRows] = await pool.query('SELECT id, name FROM departments')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
const [permRows] = await pool.query('SELECT id, name FROM permissions')
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const deptIdMap = Object.fromEntries(deptRows.map(d => [d.name, d.id]))
|
2026-06-24 22:44:14 +08:00
|
|
|
|
const permIdMap = Object.fromEntries(permRows.map(p => [p.name, p.id]))
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
for (const [deptName, permNames] of Object.entries(deptPermMap)) {
|
2026-06-24 22:44:14 +08:00
|
|
|
|
for (const permName of permNames) {
|
|
|
|
|
|
await pool.query(
|
2026-06-26 09:44:54 +08:00
|
|
|
|
'INSERT IGNORE INTO department_permissions (department_id, permission_id) VALUES (?, ?)',
|
|
|
|
|
|
[deptIdMap[deptName], permIdMap[permName]]
|
2026-06-24 22:44:14 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-26 09:44:54 +08:00
|
|
|
|
console.log('[init] 已初始化部门-权限映射')
|
2026-06-24 22:44:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 15:07:53 +08:00
|
|
|
|
// ============ 4. 种子数据:默认用户(仅保留总经理账号) ============
|
2026-06-24 22:44:14 +08:00
|
|
|
|
async function seedDefaultUsers() {
|
|
|
|
|
|
const hash = await bcrypt.hash('123456', 10)
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
const [deptRows] = await pool.query('SELECT id, name FROM departments')
|
|
|
|
|
|
const deptIdMap = Object.fromEntries(deptRows.map(d => [d.name, d.id]))
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-29 15:07:53 +08:00
|
|
|
|
// 总经理:张超
|
|
|
|
|
|
const emp = { name: '张超', gender: '男', age: 42, education: '硕士', department: '总经理办公室', entry_date: '2015-03-01', position: '总经理', salary: 50000, phone: '13800001001', email: 'zhangchao@mixue.com' }
|
|
|
|
|
|
const usr = { username: 'zhangchao', deptName: 'general_manager' }
|
2026-06-24 22:44:14 +08:00
|
|
|
|
|
2026-06-29 15:07:53 +08:00
|
|
|
|
const [userCheck] = await pool.query('SELECT id FROM users WHERE username = ?', [usr.username])
|
|
|
|
|
|
if (userCheck.length === 0) {
|
2026-06-24 22:44:14 +08:00
|
|
|
|
const [empResult] = await pool.query(
|
|
|
|
|
|
`INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
|
|
|
|
|
|
[emp.name, emp.gender, emp.age, emp.education, emp.department, emp.entry_date, emp.position, emp.salary, emp.phone, emp.email]
|
|
|
|
|
|
)
|
2026-06-15 20:46:48 +08:00
|
|
|
|
await pool.query(
|
2026-06-26 09:44:54 +08:00
|
|
|
|
`INSERT INTO users (username, password, is_active, department_id, employee_id)
|
|
|
|
|
|
VALUES (?, ?, 1, ?, ?)`,
|
2026-06-29 15:07:53 +08:00
|
|
|
|
[usr.username, hash, deptIdMap[usr.deptName], empResult.insertId]
|
2026-06-15 20:46:48 +08:00
|
|
|
|
)
|
2026-06-29 15:07:53 +08:00
|
|
|
|
console.log(`[init] 已创建用户 ${usr.username} (${emp.name})`)
|
2026-06-15 20:46:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 15:07:53 +08:00
|
|
|
|
// ============ 5. 种子数据:业务数据(已注释,仅保留总经理账号) ============
|
|
|
|
|
|
// async function seedBusinessData() {
|
|
|
|
|
|
// // ---- 5.1 新增员工 ----
|
|
|
|
|
|
// const [empCheck] = await pool.query("SELECT id FROM employees WHERE name = '刘洋' LIMIT 1")
|
|
|
|
|
|
// if (empCheck.length === 0) {
|
|
|
|
|
|
// const extraEmployees = [
|
|
|
|
|
|
// { name: '刘洋', gender: '男', age: 28, education: '本科', department: '招商部', entry_date: '2021-03-15', position: '招商专员', salary: 8000, phone: '13800002001', email: 'liuyang@mixue.com' },
|
|
|
|
|
|
// { name: '孙婷', gender: '女', age: 26, education: '本科', department: '招商部', entry_date: '2022-02-20', position: '招商专员', salary: 7500, phone: '13800002002', email: 'sunting@mixue.com' },
|
|
|
|
|
|
// { name: '周杰', gender: '男', age: 30, education: '本科', department: '运营部', entry_date: '2021-06-01', position: '运营专员', salary: 8500, phone: '13800002003', email: 'zhoujie@mixue.com' },
|
|
|
|
|
|
// { name: '吴敏', gender: '女', age: 25, education: '本科', department: '运营部', entry_date: '2023-01-10', position: '运营专员', salary: 7000, phone: '13800002004', email: 'wumin@mixue.com' },
|
|
|
|
|
|
// { name: '郑伟', gender: '男', age: 32, education: '大专', department: '运营部', entry_date: '2020-08-15', position: '售后工程师', salary: 9000, phone: '13800002005', email: 'zhengwei@mixue.com' },
|
|
|
|
|
|
// { name: '黄磊', gender: '男', age: 29, education: '本科', department: '采购部', entry_date: '2022-04-01', position: '采购专员', salary: 8000, phone: '13800002006', email: 'huanglei@mixue.com' },
|
|
|
|
|
|
// { name: '马丽', gender: '女', age: 27, education: '本科', department: '财务部', entry_date: '2021-09-01', position: '会计', salary: 7500, phone: '13800002007', email: 'mali@mixue.com' },
|
|
|
|
|
|
// { name: '林峰', gender: '男', age: 45, education: '硕士', department: '总经理办公室', entry_date: '2016-05-01', position: '副总经理', salary: 35000, phone: '13800002008', email: 'linfeng@mixue.com' },
|
|
|
|
|
|
// ]
|
|
|
|
|
|
// for (const e of extraEmployees) {
|
|
|
|
|
|
// await pool.query(
|
|
|
|
|
|
// `INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status)
|
|
|
|
|
|
// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
|
|
|
|
|
|
// [e.name, e.gender, e.age, e.education, e.department, e.entry_date, e.position, e.salary, e.phone, e.email]
|
|
|
|
|
|
// )
|
|
|
|
|
|
// }
|
|
|
|
|
|
// console.log('[init] 已初始化 8 条员工数据')
|
|
|
|
|
|
// }
|
|
|
|
|
|
// // ... 其余业务种子数据代码
|
|
|
|
|
|
// }
|
2026-06-26 09:44:54 +08:00
|
|
|
|
|
2026-06-15 20:46:48 +08:00
|
|
|
|
module.exports = { pool, initDB }
|