// 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() // 2.2 建表(如果不存在) await pool.query(` CREATE TABLE IF NOT EXISTS users ( id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID (主键)', username VARCHAR(50) NOT NULL COMMENT '用户名 (登录账号)', password VARCHAR(255) NOT NULL COMMENT '登录密码 (应存储加密后的值)', 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表)', 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='系统用户表' `) 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 '备注', type VARCHAR(20) NOT NULL DEFAULT 'franchise' COMMENT '合同类型: franchise-加盟合同, supply-采购合同', responsible_user_id INT DEFAULT NULL COMMENT '负责人用户ID (用于数据范围隔离)', 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 '备注', responsible_user_id INT DEFAULT NULL COMMENT '负责人用户ID (用于数据范围隔离)', 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='售后信息表' `) // ============ 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) { // 字段已存在则忽略 } // 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) { // 列已删除则忽略 } console.log('[init] 数据表初始化完成') // ============ 2.3 部门与权限相关表 ============ // 迁移旧表 await pool.query('DROP TABLE IF EXISTS role_permissions') await pool.query('DROP TABLE IF EXISTS roles') await pool.query(` 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 '部门名称 (中文)', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (id), UNIQUE KEY uk_departments_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表' `) 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(` CREATE TABLE IF NOT EXISTS department_permissions ( department_id INT NOT NULL COMMENT '部门ID', permission_id INT NOT NULL COMMENT '权限ID', PRIMARY KEY (department_id, permission_id), KEY idx_dp_permission_id (permission_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门-权限关联表' `) 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='供应商信息表' `) // ============ 2.4 种子数据 ============ await seedDepartmentsAndPermissions() await seedDefaultUsers() await seedBusinessData() } // ============ 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: '财务部' }, ] for (const d of deptList) { await pool.query('INSERT INTO departments (name, description) VALUES (?, ?)', [d.name, d.description]) } console.log('[init] 已初始化 6 个部门') 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 个权限') // 部门-权限映射 const allPerms = permissionList.map(p => p.name) const deptPermMap = { admin: allPerms, 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: [ 'customer:read', 'after_sale:read', 'after_sale:create', 'after_sale:update', 'after_sale:delete', 'employee:read', ], procurement_manager: [ 'customer:read', '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: [ 'customer:read', 'contract:read', 'after_sale:read', 'employee:read', ], } const [deptRows] = await pool.query('SELECT id, name FROM departments') const [permRows] = await pool.query('SELECT id, name FROM permissions') const deptIdMap = Object.fromEntries(deptRows.map(d => [d.name, d.id])) const permIdMap = Object.fromEntries(permRows.map(p => [p.name, p.id])) for (const [deptName, permNames] of Object.entries(deptPermMap)) { for (const permName of permNames) { await pool.query( 'INSERT IGNORE INTO department_permissions (department_id, permission_id) VALUES (?, ?)', [deptIdMap[deptName], permIdMap[permName]] ) } } console.log('[init] 已初始化部门-权限映射') } // ============ 4. 种子数据:默认用户 ============ async function seedDefaultUsers() { const hash = await bcrypt.hash('123456', 10) const [deptRows] = await pool.query('SELECT id, name FROM departments') const deptIdMap = Object.fromEntries(deptRows.map(d => [d.name, d.id])) 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' }, ] 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' }, ] 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 // 再插入 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] ) console.log(`[init] 已创建用户 ${usr.username} (${emp.name} - ${emp.department})`) } } // ============ 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] 已初始化测试用合同和售后单') } } module.exports = { pool, initDB }