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-26 09:44:54 +08:00
|
|
|
|
await seedBusinessData()
|
2026-06-28 20:43:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ============ 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',
|
|
|
|
|
|
'product:read', 'supplier:read', 'employee:read',
|
|
|
|
|
|
],
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============ 4. 种子数据:默认用户 ============
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
const seedEmployees = [
|
|
|
|
|
|
{ name: '张超', gender: '男', age: 42, education: '硕士', department: '总经理办公室', entry_date: '2015-03-01', position: '总经理', salary: 50000, phone: '13800001001', email: 'zhangchao@mixue.com' },
|
|
|
|
|
|
{ name: '李明', gender: '男', age: 35, education: '本科', department: '招商部', entry_date: '2018-06-15', position: '招商经理', salary: 18000, phone: '13800001002', email: 'liming@mixue.com' },
|
|
|
|
|
|
{ name: '王丽', gender: '女', age: 33, education: '本科', department: '运营部', entry_date: '2019-01-10', position: '运营经理', salary: 18000, phone: '13800001003', email: 'wangli@mixue.com' },
|
|
|
|
|
|
{ name: '赵强', gender: '男', age: 36, education: '本科', department: '采购部', entry_date: '2017-09-20', position: '采购经理', salary: 18000, phone: '13800001004', email: 'zhaoqiang@mixue.com' },
|
|
|
|
|
|
{ name: '陈芳', gender: '女', age: 30, education: '本科', department: '财务部', entry_date: '2020-03-05', position: '财务主管', salary: 15000, phone: '13800001005', email: 'chenfang@mixue.com' },
|
|
|
|
|
|
{ name: '系统管理员', gender: '男', age: 28, education: '本科', department: '信息技术部', entry_date: '2022-07-01', position: '系统管理员', salary: 12000, phone: '13800001006', email: 'admin@mixue.com' },
|
2026-06-29 11:32:54 +08:00
|
|
|
|
{ name: '杨帆', gender: '女', age: 34, education: '本科', department: '人力资源部', entry_date: '2019-05-01', position: '人力资源经理', salary: 15000, phone: '13800001007', email: 'yangfan@mixue.com' },
|
2026-06-24 22:44:14 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const seedUsers = [
|
2026-06-26 09:44:54 +08:00
|
|
|
|
{ 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' },
|
2026-06-29 11:32:54 +08:00
|
|
|
|
{ username: 'yangfan', deptName: 'hr' },
|
2026-06-24 22:44:14 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < seedUsers.length; i++) {
|
|
|
|
|
|
const emp = seedEmployees[i]
|
|
|
|
|
|
const usr = seedUsers[i]
|
|
|
|
|
|
|
|
|
|
|
|
// 逐个检查用户是否存在(不依赖全局检查,避免部分用户被误删后跳过全部重建)
|
|
|
|
|
|
const [userCheck] = await pool.query('SELECT id FROM users WHERE username = ?', [usr.username])
|
|
|
|
|
|
if (userCheck.length > 0) continue
|
|
|
|
|
|
|
|
|
|
|
|
// 先插入 employees
|
|
|
|
|
|
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]
|
|
|
|
|
|
)
|
|
|
|
|
|
const empId = empResult.insertId
|
|
|
|
|
|
|
2026-06-26 09:44:54 +08:00
|
|
|
|
// 再插入 users,关联 employee 和 department
|
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, ?, ?)`,
|
|
|
|
|
|
[usr.username, hash, deptIdMap[usr.deptName], empId]
|
2026-06-15 20:46:48 +08:00
|
|
|
|
)
|
2026-06-24 22:44:14 +08:00
|
|
|
|
console.log(`[init] 已创建用户 ${usr.username} (${emp.name} - ${emp.department})`)
|
2026-06-15 20:46:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-26 09:44:54 +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 条员工数据')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 5.1b 为业务员工创建用户账号 ----
|
|
|
|
|
|
const hash = await bcrypt.hash('123456', 10)
|
|
|
|
|
|
const [deptRows] = await pool.query('SELECT id, name FROM departments')
|
|
|
|
|
|
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' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const [newEmps] = await pool.query("SELECT id, name, department FROM employees WHERE name IN ('刘洋','孙婷','周杰','吴敏','郑伟','黄磊','马丽','林峰')")
|
|
|
|
|
|
const newEmpMap = Object.fromEntries(newEmps.map(e => [e.name, e]))
|
|
|
|
|
|
|
|
|
|
|
|
for (const u of extraUsers) {
|
|
|
|
|
|
const emp = newEmpMap[u.empName]
|
|
|
|
|
|
if (!emp) continue
|
|
|
|
|
|
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]
|
|
|
|
|
|
)
|
|
|
|
|
|
console.log(`[init] 已创建用户 ${u.username} (${u.empName} - ${emp.department})`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询员工 ID 映射(用于合同和售后单)
|
|
|
|
|
|
const [allEmps] = await pool.query('SELECT id, name FROM employees')
|
|
|
|
|
|
const empIdMap = Object.fromEntries(allEmps.map(e => [e.name, e.id]))
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 5.2 供应商 ----
|
|
|
|
|
|
const [supCheck] = await pool.query("SELECT id FROM suppliers WHERE name = '郑州瑞丰食品原料有限公司' LIMIT 1")
|
|
|
|
|
|
if (supCheck.length === 0) {
|
|
|
|
|
|
const suppliers = [
|
|
|
|
|
|
{ name: '郑州瑞丰食品原料有限公司', contact: '张建国', phone: '0371-66881234', address: '郑州市经开区食品工业园A区12号', type: '原料', content: '主要供应茶叶、椰浆、果酱等茶饮原料,合作5年以上' },
|
|
|
|
|
|
{ name: '河南优品包装科技有限公司', contact: '李文华', phone: '0371-66885678', address: '郑州市高新区包装产业园B栋', type: '包装', content: '提供PET杯、PP杯、杯盖、吸管等包装材料' },
|
|
|
|
|
|
{ name: '广州冰泉制冰设备有限公司', contact: '陈志强', phone: '020-88661234', address: '广州市番禺区工业路88号', type: '设备', content: '制冰机、冷藏柜等冷链设备供应商' },
|
|
|
|
|
|
{ name: '武汉华源茶叶有限公司', contact: '王秀英', phone: '027-85661234', address: '武汉市江汉区茶业批发市场C区', type: '原料', content: '优质茶叶供应商,提供茉莉绿茶、红茶、乌龙茶等' },
|
|
|
|
|
|
{ name: '浙江鑫达纸杯制品有限公司', contact: '赵明', phone: '0571-88771234', address: '杭州市萧山区纸品工业园区6号', type: '包装', content: '定制印刷纸杯和包装袋' },
|
|
|
|
|
|
{ name: '山东齐鲁冷链物流有限公司', contact: '刘大海', phone: '0531-88661234', address: '济南市历城区物流产业园东区', type: '原料', content: '冷链物流服务,负责奶粉、椰浆等冷链原料配送' },
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const s of suppliers) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
'INSERT INTO suppliers (name, contact, phone, address, type, content, status) VALUES (?, ?, ?, ?, ?, ?, 1)',
|
|
|
|
|
|
[s.name, s.contact, s.phone, s.address, s.type, s.content]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化 6 家供应商')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询供应商名称(供货合同引用)
|
|
|
|
|
|
const [supRows] = await pool.query('SELECT id, name FROM suppliers')
|
|
|
|
|
|
const supIdMap = Object.fromEntries(supRows.map(s => [s.name, s.id]))
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 5.3 产品/原料 ----
|
|
|
|
|
|
const [prodCheck] = await pool.query("SELECT id FROM products WHERE name = '茉莉绿茶' LIMIT 1")
|
|
|
|
|
|
if (prodCheck.length === 0) {
|
|
|
|
|
|
const products = [
|
|
|
|
|
|
// 茶饮原料
|
|
|
|
|
|
{ name: '茉莉绿茶', type: '茶饮原料', quantity: 5000, price: 28.00, unit: 'kg', specification: '500g/袋', supplier: '武汉华源茶叶有限公司' },
|
|
|
|
|
|
{ name: '红茶', type: '茶饮原料', quantity: 3000, price: 32.00, unit: 'kg', specification: '500g/袋', supplier: '武汉华源茶叶有限公司' },
|
|
|
|
|
|
{ name: '乌龙茶', type: '茶饮原料', quantity: 2000, price: 45.00, unit: 'kg', specification: '500g/袋', supplier: '武汉华源茶叶有限公司' },
|
|
|
|
|
|
{ name: '椰浆', type: '茶饮原料', quantity: 8000, price: 12.50, unit: 'L', specification: '1L/盒', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
{ name: '果糖糖浆', type: '茶饮原料', quantity: 6000, price: 8.00, unit: 'L', specification: '2.5L/桶', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
{ name: '奶粉', type: '茶饮原料', quantity: 3000, price: 35.00, unit: 'kg', specification: '1kg/袋', supplier: '山东齐鲁冷链物流有限公司' },
|
|
|
|
|
|
{ name: '珍珠粉圆', type: '茶饮原料', quantity: 10000, price: 6.50, unit: 'kg', specification: '1kg/袋', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
{ name: '红豆罐头', type: '茶饮原料', quantity: 2000, price: 9.80, unit: '罐', specification: '400g/罐', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
{ name: '芒果果酱', type: '茶饮原料', quantity: 3000, price: 18.00, unit: 'kg', specification: '1kg/瓶', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
{ name: '草莓果酱', type: '茶饮原料', quantity: 2500, price: 20.00, unit: 'kg', specification: '1kg/瓶', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
{ name: '柠檬浓缩汁', type: '茶饮原料', quantity: 4000, price: 15.00, unit: 'L', specification: '1L/瓶', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
{ name: '芋泥粉', type: '茶饮原料', quantity: 1500, price: 22.00, unit: 'kg', specification: '500g/袋', supplier: '郑州瑞丰食品原料有限公司' },
|
|
|
|
|
|
// 包装物料
|
|
|
|
|
|
{ name: '500ml PET杯', type: '包装物料', quantity: 200000, price: 0.15, unit: '个', specification: '500ml 透明', supplier: '河南优品包装科技有限公司' },
|
|
|
|
|
|
{ name: '700ml PP杯', type: '包装物料', quantity: 150000, price: 0.20, unit: '个', specification: '700ml 白色', supplier: '浙江鑫达纸杯制品有限公司' },
|
|
|
|
|
|
{ name: '杯盖', type: '包装物料', quantity: 300000, price: 0.08, unit: '个', specification: '平盖+凸盖 各半', supplier: '河南优品包装科技有限公司' },
|
|
|
|
|
|
{ name: '吸管', type: '包装物料', quantity: 500000, price: 0.03, unit: '根', specification: '粗吸管 12mm', supplier: '河南优品包装科技有限公司' },
|
|
|
|
|
|
// 设备器具
|
|
|
|
|
|
{ name: '制冰机', type: '设备器具', quantity: 50, price: 5800.00, unit: '台', specification: '日产冰量100kg', supplier: '广州冰泉制冰设备有限公司' },
|
|
|
|
|
|
{ name: '封口机', type: '设备器具', quantity: 80, price: 2200.00, unit: '台', specification: '全自动杯装封口', supplier: '广州冰泉制冰设备有限公司' },
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const p of products) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
'INSERT INTO products (name, type, quantity, price, unit, specification, supplier) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
|
|
|
|
[p.name, p.type, p.quantity, p.price, p.unit, p.specification, p.supplier]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化 18 个产品')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 5.4 加盟商 (customers) ----
|
|
|
|
|
|
const [custCheck] = await pool.query("SELECT id FROM customers WHERE name = '赵鑫磊' LIMIT 1")
|
|
|
|
|
|
if (custCheck.length === 0) {
|
|
|
|
|
|
const customers = [
|
|
|
|
|
|
{ name: '赵鑫磊', phone: '13912345001', province: '河南省', city: '郑州市', district: '金水区', address: '花园路与农业路交叉口向南200米', email: 'zhaoxinlei@qq.com', remark: '老加盟商,经营3年,业绩优秀' },
|
|
|
|
|
|
{ name: '钱小燕', phone: '13912345002', province: '河南省', city: '洛阳市', district: '西工区', address: '中州中路王府井百货对面', email: 'qianxiaoyan@qq.com', remark: '2024年新开店,经营情况良好' },
|
|
|
|
|
|
{ name: '孙大伟', phone: '13912345003', province: '山东省', city: '济南市', district: '历下区', address: '泉城路188号', email: 'sundawei@qq.com', remark: '大学城附近,客流量大' },
|
|
|
|
|
|
{ name: '李美玲', phone: '13912345004', province: '山东省', city: '青岛市', district: '市南区', address: '香港中路佳世客商场一楼', email: 'limeiling@qq.com', remark: '商场店,租金较高' },
|
|
|
|
|
|
{ name: '周五六', phone: '13912345005', province: '江苏省', city: '南京市', district: '鼓楼区', address: '新街口中山南路128号', email: 'zhouwuliu@qq.com', remark: '核心商圈,品牌效应好' },
|
|
|
|
|
|
{ name: '吴建华', phone: '13912345006', province: '江苏省', city: '苏州市', district: '姑苏区', address: '观前街太监弄18号', email: 'wujianhua@qq.com', remark: '旅游区店铺,夏季旺季营业额高' },
|
|
|
|
|
|
{ name: '郑雪梅', phone: '13912345007', province: '四川省', city: '成都市', district: '锦江区', address: '春熙路步行街IFS旁', email: 'zhengxuemei@qq.com', remark: '新加盟商,正在筹备开业' },
|
|
|
|
|
|
{ name: '王鹏飞', phone: '13912345008', province: '四川省', city: '成都市', district: '武侯区', address: '科华北路65号川大旁', email: 'wangpengfei@qq.com', remark: '学校周边,学生客群为主' },
|
|
|
|
|
|
{ name: '冯国强', phone: '13912345009', province: '广东省', city: '广州市', district: '天河区', address: '天河路228号正佳广场B1层', email: 'fengguoqiang@qq.com', remark: '商场负一楼,人流密集' },
|
|
|
|
|
|
{ name: '陈小凤', phone: '13912345010', province: '广东省', city: '深圳市', district: '南山区', address: '南海大道与东滨路交汇处', email: 'chenxiaofeng@qq.com', remark: '写字楼区,白领消费为主' },
|
|
|
|
|
|
{ name: '褚明亮', phone: '13912345011', province: '浙江省', city: '杭州市', district: '西湖区', address: '文三路与学院路交叉口', email: 'chumingliang@qq.com', remark: '2023年开业,已回本' },
|
|
|
|
|
|
{ name: '卫国庆', phone: '13912345012', province: '湖北省', city: '武汉市', district: '武昌区', address: '中南路中商广场一楼', email: 'weiguoqing@qq.com', remark: '老加盟商,有意向开第二家' },
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const c of customers) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO customers (name, phone, province, city, district, address, email, remark)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[c.name, c.phone, c.province, c.city, c.district, c.address, c.email, c.remark]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化 12 个加盟商')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询加盟商 ID 映射
|
|
|
|
|
|
const [custRows] = await pool.query('SELECT id, name FROM customers')
|
|
|
|
|
|
const custIdMap = Object.fromEntries(custRows.map(c => [c.name, c.id]))
|
|
|
|
|
|
|
|
|
|
|
|
// 查询 liming 和 wangli 的 user.id
|
|
|
|
|
|
const [userRows] = await pool.query("SELECT id, username FROM users WHERE username IN ('liming', 'wangli', 'admin')")
|
|
|
|
|
|
const userIdMap = Object.fromEntries(userRows.map(u => [u.username, u.id]))
|
|
|
|
|
|
|
|
|
|
|
|
// 查询 employee_id → user_id 映射(用于合同种子数据:业务员即负责人)
|
|
|
|
|
|
const [userEmpRows] = await pool.query('SELECT id, employee_id FROM users WHERE employee_id IS NOT NULL')
|
|
|
|
|
|
const userByEmpIdMap = Object.fromEntries(userEmpRows.map(u => [u.employee_id, u.id]))
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 5.5 合同 (contracts) ----
|
|
|
|
|
|
const [contractCheck] = await pool.query("SELECT id FROM contracts WHERE contract_no = 'MX-JM-2024-001' LIMIT 1")
|
|
|
|
|
|
if (contractCheck.length === 0) {
|
|
|
|
|
|
// 加盟合同
|
|
|
|
|
|
const franchiseContracts = [
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(赵鑫磊-郑州花园路店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-001', customer_name: '赵鑫磊', employee_name: '刘洋',
|
|
|
|
|
|
amount: 98000.00, effective_date: '2023-06-01', expiry_date: '2028-05-31',
|
|
|
|
|
|
status: '生效', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在郑州市金水区花园路开设蜜雪冰城加盟门店,合同期限5年。加盟费8万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '经营3年,续约意愿强',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(钱小燕-洛阳中州路店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-002', customer_name: '钱小燕', employee_name: '刘洋',
|
|
|
|
|
|
amount: 88000.00, effective_date: '2024-03-15', expiry_date: '2029-03-14',
|
|
|
|
|
|
status: '生效', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在洛阳市西工区中州路开设蜜雪冰城加盟门店,合同期限5年。加盟费7万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '新加盟商,已完成培训',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(孙大伟-济南泉城路店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-003', customer_name: '孙大伟', employee_name: '孙婷',
|
|
|
|
|
|
amount: 108000.00, effective_date: '2023-09-01', expiry_date: '2028-08-31',
|
|
|
|
|
|
status: '生效', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在济南市历下区泉城路开设蜜雪冰城加盟门店,合同期限5年。加盟费9万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '大学城门店,日均出杯量600+',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(李美玲-青岛香港中路店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-004', customer_name: '李美玲', employee_name: '孙婷',
|
|
|
|
|
|
amount: 128000.00, effective_date: '2024-01-10', expiry_date: '2029-01-09',
|
|
|
|
|
|
status: '生效', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在青岛市市南区香港中路商场开设蜜雪冰城加盟门店,合同期限5年。加盟费11万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '商场店,租金较高但人流有保障',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(周五六-南京新街口店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-005', customer_name: '周五六', employee_name: '刘洋',
|
|
|
|
|
|
amount: 148000.00, effective_date: '2023-04-20', expiry_date: '2028-04-19',
|
|
|
|
|
|
status: '生效', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在南京市鼓楼区新街口商圈开设蜜雪冰城加盟门店,合同期限5年。加盟费12万元,保证金1.8万元,品牌使用费1万元。',
|
|
|
|
|
|
remark: '核心商圈旗舰店,月营业额稳定',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(吴建华-苏州观前街店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-006', customer_name: '吴建华', employee_name: '孙婷',
|
|
|
|
|
|
amount: 118000.00, effective_date: '2024-05-01', expiry_date: '2029-04-30',
|
|
|
|
|
|
status: '生效', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在苏州市姑苏区观前街开设蜜雪冰城加盟门店,合同期限5年。加盟费10万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '旅游区店铺,夏季月营业额翻倍',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(郑雪梅-成都春熙路店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-007', customer_name: '郑雪梅', employee_name: '刘洋',
|
|
|
|
|
|
amount: 138000.00, effective_date: '2025-01-15', expiry_date: '2030-01-14',
|
|
|
|
|
|
status: '草稿', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在成都市锦江区春熙路商圈开设蜜雪冰城加盟门店,合同期限5年。加盟费12万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '正在审核中,预计下月签约',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(王鹏飞-成都科华路店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-008', customer_name: '王鹏飞', employee_name: '孙婷',
|
|
|
|
|
|
amount: 88000.00, effective_date: '2023-08-10', expiry_date: '2028-08-09',
|
|
|
|
|
|
status: '已完成', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在成都市武侯区科华路(川大旁)开设蜜雪冰城加盟门店,合同期限5年。加盟费7万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '合同期满未续约',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(冯国强-广州正佳广场店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-009', customer_name: '冯国强', employee_name: '刘洋',
|
|
|
|
|
|
amount: 158000.00, effective_date: '2024-02-01', expiry_date: '2029-01-31',
|
|
|
|
|
|
status: '生效', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在广州市天河区正佳广场B1层开设蜜雪冰城加盟门店,合同期限5年。加盟费13万元,保证金1.8万元,品牌使用费1万元。',
|
|
|
|
|
|
remark: '商场负一楼黄金位置',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '蜜雪冰城加盟合同(陈小凤-深圳南山店)',
|
|
|
|
|
|
contract_no: 'MX-JM-2024-010', customer_name: '陈小凤', employee_name: '孙婷',
|
|
|
|
|
|
amount: 108000.00, effective_date: '2023-11-20', expiry_date: '2028-11-19',
|
|
|
|
|
|
status: '已完成', type: 'franchise',
|
|
|
|
|
|
contract_content: '甲方授权乙方在深圳市南山区南海大道开设蜜雪冰城加盟门店,合同期限5年。加盟费9万元,保证金1.8万元。',
|
|
|
|
|
|
remark: '合同正常到期完成',
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for (const c of franchiseContracts) {
|
|
|
|
|
|
const empId = empIdMap[c.employee_name]
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO contracts (contract_name, contract_no, customer_id, employee_id, amount, effective_date, expiry_date, status, type, contract_content, remark, responsible_user_id)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[c.contract_name, c.contract_no, custIdMap[c.customer_name], empId,
|
|
|
|
|
|
c.amount, c.effective_date, c.expiry_date, c.status, c.type, c.contract_content, c.remark,
|
|
|
|
|
|
userByEmpIdMap[empId]]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 供货合同
|
|
|
|
|
|
const supplyContracts = [
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '2024年度茶叶原料供货合同',
|
|
|
|
|
|
contract_no: 'MX-GH-2024-001', supplier_name: '武汉华源茶叶有限公司', employee_name: '黄磊',
|
|
|
|
|
|
amount: 360000.00, effective_date: '2024-01-01', expiry_date: '2024-12-31',
|
|
|
|
|
|
status: '生效', type: 'supply',
|
|
|
|
|
|
contract_content: '甲方向乙方采购茉莉绿茶、红茶、乌龙茶等茶叶原料,年度采购量不低于30吨,价格按季度协商调整。',
|
|
|
|
|
|
remark: '年度框架协议',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '2024年度包装材料供货合同',
|
|
|
|
|
|
contract_no: 'MX-GH-2024-002', supplier_name: '河南优品包装科技有限公司', employee_name: '黄磊',
|
|
|
|
|
|
amount: 280000.00, effective_date: '2024-01-01', expiry_date: '2024-12-31',
|
|
|
|
|
|
status: '生效', type: 'supply',
|
|
|
|
|
|
contract_content: '甲方向乙方采购PET杯、PP杯、杯盖、吸管等包装材料,按月下单,每次不低于10万只。',
|
|
|
|
|
|
remark: '长期合作伙伴',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '2024年度设备采购合同',
|
|
|
|
|
|
contract_no: 'MX-GH-2024-003', supplier_name: '广州冰泉制冰设备有限公司', employee_name: '黄磊',
|
|
|
|
|
|
amount: 520000.00, effective_date: '2024-03-01', expiry_date: '2025-02-28',
|
|
|
|
|
|
status: '生效', type: 'supply',
|
|
|
|
|
|
contract_content: '甲方向乙方采购制冰机、封口机、冷藏柜等门店设备,含安装调试和一年质保服务。',
|
|
|
|
|
|
remark: '新店开业批量采购',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '2023年度食品原料供货合同',
|
|
|
|
|
|
contract_no: 'MX-GH-2023-001', supplier_name: '郑州瑞丰食品原料有限公司', employee_name: '黄磊',
|
|
|
|
|
|
amount: 450000.00, effective_date: '2023-01-01', expiry_date: '2023-12-31',
|
|
|
|
|
|
status: '已完成', type: 'supply',
|
|
|
|
|
|
contract_content: '甲方向乙方采购椰浆、果糖糖浆、珍珠粉圆、果酱等茶饮辅料原料,年度框架协议。',
|
|
|
|
|
|
remark: '已正常履约完毕',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
contract_name: '2023年度冷链配送服务合同',
|
|
|
|
|
|
contract_no: 'MX-GH-2023-002', supplier_name: '山东齐鲁冷链物流有限公司', employee_name: '黄磊',
|
|
|
|
|
|
amount: 180000.00, effective_date: '2023-04-01', expiry_date: '2024-03-31',
|
|
|
|
|
|
status: '已完成', type: 'supply',
|
|
|
|
|
|
contract_content: '甲方委托乙方提供奶粉、椰浆等冷链原料的仓储和配送服务,覆盖华中和华东区域。',
|
|
|
|
|
|
remark: '服务评价良好',
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for (const c of supplyContracts) {
|
|
|
|
|
|
const empId = empIdMap[c.employee_name]
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO contracts (contract_name, contract_no, supplier_id, employee_id, amount, effective_date, expiry_date, status, type, contract_content, remark, responsible_user_id)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[c.contract_name, c.contract_no, supIdMap[c.supplier_name], empId,
|
|
|
|
|
|
c.amount, c.effective_date, c.expiry_date, c.status, c.type, c.contract_content, c.remark,
|
|
|
|
|
|
userByEmpIdMap[empId]]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化 15 份合同(10 份加盟 + 5 份供货)')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 5.6 售后单 (after_sales) ----
|
|
|
|
|
|
const [asCheck] = await pool.query('SELECT id FROM after_sales LIMIT 1')
|
|
|
|
|
|
if (asCheck.length === 0) {
|
|
|
|
|
|
const afterSales = [
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '赵鑫磊', employee_name: '郑伟',
|
|
|
|
|
|
feedback: '门店制冰机运行噪音突然变大,制冰速度明显下降,疑似压缩机故障。',
|
|
|
|
|
|
handle_method: '已安排售后工程师上门检修,确认为压缩机老化,已更换配件。',
|
|
|
|
|
|
handle_status: '已完成', service_date: '2024-11-15',
|
|
|
|
|
|
remark: '配件已更换,质保期内免费',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '孙大伟', employee_name: '郑伟',
|
|
|
|
|
|
feedback: '封口机经常卡杯,每天至少出现5次,严重影响出杯效率。',
|
|
|
|
|
|
handle_method: '远程指导清洁封口模具,如仍无法解决将安排上门维修。',
|
|
|
|
|
|
handle_status: '处理中', service_date: '2024-12-20',
|
|
|
|
|
|
remark: '已远程指导,待反馈效果',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '钱小燕', employee_name: '周杰',
|
|
|
|
|
|
feedback: '收到的珍珠粉圆有异味,疑似变质,要求退换货。',
|
|
|
|
|
|
handle_method: null,
|
|
|
|
|
|
handle_status: '待处理', service_date: '2025-01-05',
|
|
|
|
|
|
remark: '已联系供应商核实批次',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '周五六', employee_name: '周杰',
|
|
|
|
|
|
feedback: '新开店需要进行新品制作培训,包括冬季热饮系列和甜品系列。',
|
|
|
|
|
|
handle_method: '已安排运营专员下周到店进行3天驻店培训。',
|
|
|
|
|
|
handle_status: '处理中', service_date: '2025-01-10',
|
|
|
|
|
|
remark: '培训计划已发送',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '冯国强', employee_name: '吴敏',
|
|
|
|
|
|
feedback: '果糖机出糖量不稳定,有时偏多有时偏少,导致饮品口感不一致。',
|
|
|
|
|
|
handle_method: '已安排校准果糖机,更换流量计。',
|
|
|
|
|
|
handle_status: '已完成', service_date: '2024-10-28',
|
|
|
|
|
|
remark: '校准后恢复正常',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '李美玲', employee_name: '吴敏',
|
|
|
|
|
|
feedback: '新店开业前需要门店装修指导和设备布局方案。',
|
|
|
|
|
|
handle_method: '已派运营专员到店指导装修,提供标准门店布局图纸。',
|
|
|
|
|
|
handle_status: '已完成', service_date: '2024-01-05',
|
|
|
|
|
|
remark: '装修已完成,符合标准',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '吴建华', employee_name: '郑伟',
|
|
|
|
|
|
feedback: '冷藏展示柜温度显示异常,实际温度与显示温度相差5度。',
|
|
|
|
|
|
handle_method: null,
|
|
|
|
|
|
handle_status: '待处理', service_date: '2025-01-08',
|
|
|
|
|
|
remark: '已安排就近工程师上门',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '陈小凤', employee_name: '周杰',
|
|
|
|
|
|
feedback: '希望总部提供暑期营销活动策划支持和宣传物料。',
|
|
|
|
|
|
handle_method: '已提供暑期活动方案和配套宣传海报、立牌设计稿。',
|
|
|
|
|
|
handle_status: '已完成', service_date: '2024-06-15',
|
|
|
|
|
|
remark: '活动期间营业额提升30%',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '王鹏飞', employee_name: '吴敏',
|
|
|
|
|
|
feedback: '草莓果酱批次口感偏酸,与之前供货品质不一致,学生客户投诉较多。',
|
|
|
|
|
|
handle_method: null,
|
|
|
|
|
|
handle_status: '待处理', service_date: '2025-01-12',
|
|
|
|
|
|
remark: '需要取样送检',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
customer_name: '郑雪梅', employee_name: '周杰',
|
|
|
|
|
|
feedback: '新店筹备中,需要开业前全套培训,包括设备操作、产品制作、门店管理等。',
|
|
|
|
|
|
handle_method: '已制定5天培训计划,安排运营专员驻店指导开业。',
|
|
|
|
|
|
handle_status: '处理中', service_date: '2025-01-15',
|
|
|
|
|
|
remark: '预计2月初完成培训并开业',
|
|
|
|
|
|
responsible_user: 'wangli',
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for (const a of afterSales) {
|
|
|
|
|
|
const empId = empIdMap[a.employee_name]
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO after_sales (customer_id, employee_id, feedback, handle_method, handle_status, service_date, remark, responsible_user_id)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[custIdMap[a.customer_name], empId, a.feedback,
|
|
|
|
|
|
a.handle_method, a.handle_status, a.service_date, a.remark, userByEmpIdMap[empId]]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化 10 条售后单')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 5.7 测试/预览用补充数据 ----
|
|
|
|
|
|
// 补充加盟商(分配给运营部用户,测试跨部门可见性)
|
|
|
|
|
|
const [custCheck2] = await pool.query("SELECT id FROM customers WHERE name = '测试加盟商-OPS' LIMIT 1")
|
|
|
|
|
|
if (custCheck2.length === 0) {
|
|
|
|
|
|
const testCustomers = [
|
|
|
|
|
|
{ name: '测试加盟商-OPS', phone: '13999990001', province: '河南省', city: '郑州市', district: '金水区', address: '测试路1号' },
|
|
|
|
|
|
{ name: '测试加盟商-PROC', phone: '13999990002', province: '山东省', city: '济南市', district: '历下区', address: '测试路2号' },
|
|
|
|
|
|
]
|
|
|
|
|
|
for (const c of testCustomers) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
'INSERT INTO customers (name, phone, province, city, district, address) VALUES (?, ?, ?, ?, ?, ?)',
|
|
|
|
|
|
[c.name, c.phone, c.province, c.city, c.district, c.address]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化 2 条测试加盟商')
|
|
|
|
|
|
|
|
|
|
|
|
// 补充一个采购合同(归属采购部,用于测试数据范围)
|
|
|
|
|
|
const [custOPS] = await pool.query("SELECT id FROM customers WHERE name = '测试加盟商-OPS' LIMIT 1")
|
|
|
|
|
|
const [empZhao] = await pool.query("SELECT id FROM employees WHERE name = '赵强' LIMIT 1")
|
|
|
|
|
|
if (custOPS.length > 0) {
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO contracts (contract_name, contract_no, customer_id, employee_id, amount, effective_date, expiry_date, status, type, responsible_user_id, remark)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
['测试采购合同-2025', 'MX-GH-TEST-001', custOPS[0].id, empZhao.length > 0 ? empZhao[0].id : null,
|
|
|
|
|
|
150000, '2025-03-01', '2026-02-28', '生效', 'supply',
|
|
|
|
|
|
empZhao.length > 0 ? (userByEmpIdMap[empZhao[0].id] || uidMap['zhaoqiang']) : null, '测试用采购合同']
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 补充售后单(归属运营部)
|
|
|
|
|
|
const [custA] = await pool.query("SELECT id FROM customers WHERE name = '赵鑫磊' LIMIT 1")
|
|
|
|
|
|
const [empZheng] = await pool.query("SELECT id FROM employees WHERE name = '郑伟' LIMIT 1")
|
|
|
|
|
|
if (custA.length > 0) {
|
|
|
|
|
|
const empZhengId = empZheng.length > 0 ? empZheng[0].id : null
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
|
`INSERT INTO after_sales (customer_id, employee_id, feedback, handle_method, handle_status, service_date, remark, responsible_user_id)
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[custA[0].id, empZhengId,
|
|
|
|
|
|
'测试售后-夏季冰块供应不足', '协调增加配送频次', '处理中', '2025-06-01', '旺季保障',
|
|
|
|
|
|
empZhengId ? userByEmpIdMap[empZhengId] : uidMap['wangli']]
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('[init] 已初始化测试用合同和售后单')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 20:46:48 +08:00
|
|
|
|
module.exports = { pool, initDB }
|