最后小修

This commit is contained in:
2026-06-29 15:07:53 +08:00
parent ccb4c32614
commit d25cf862d8

558
db.js
View File

@@ -235,25 +235,25 @@ async function initDB() {
// ============ 2.4 种子数据 ============ // ============ 2.4 种子数据 ============
await seedDepartmentsAndPermissions() await seedDepartmentsAndPermissions()
await seedDefaultUsers() await seedDefaultUsers()
await seedBusinessData() // await seedBusinessData()
// ============ 2.5 数据库迁移(兼容已有数据的旧库) ============ // // ============ 2.5 数据库迁移(兼容已有数据的旧库) ============
// 为财务部补上 product:read 权限 // // 为财务部补上 product:read 权限
try { // try {
const [financeDept] = await pool.query("SELECT id FROM departments WHERE name = 'finance' LIMIT 1") // const [financeDept] = await pool.query("SELECT id FROM departments WHERE name = 'finance' LIMIT 1")
if (financeDept.length > 0) { // if (financeDept.length > 0) {
const [prodPerm] = await pool.query("SELECT id FROM permissions WHERE name = 'product:read' LIMIT 1") // const [prodPerm] = await pool.query("SELECT id FROM permissions WHERE name = 'product:read' LIMIT 1")
if (prodPerm.length > 0) { // if (prodPerm.length > 0) {
await pool.query( // await pool.query(
'INSERT IGNORE INTO department_permissions (department_id, permission_id) VALUES (?, ?)', // 'INSERT IGNORE INTO department_permissions (department_id, permission_id) VALUES (?, ?)',
[financeDept[0].id, prodPerm[0].id] // [financeDept[0].id, prodPerm[0].id]
) // )
console.log('[migrate] 财务部已补上 product:read 权限') // console.log('[migrate] 财务部已补上 product:read 权限')
} // }
} // }
} catch (e) { // } catch (e) {
console.log('[migrate] 财务部权限迁移跳过:', e.message) // console.log('[migrate] 财务部权限迁移跳过:', e.message)
} // }
} }
// ============ 3. 种子数据:部门与权限 ============ // ============ 3. 种子数据:部门与权限 ============
@@ -368,514 +368,58 @@ async function seedDepartmentsAndPermissions() {
console.log('[init] 已初始化部门-权限映射') console.log('[init] 已初始化部门-权限映射')
} }
// ============ 4. 种子数据:默认用户 ============ // ============ 4. 种子数据:默认用户(仅保留总经理账号) ============
async function seedDefaultUsers() { async function seedDefaultUsers() {
const hash = await bcrypt.hash('123456', 10) const hash = await bcrypt.hash('123456', 10)
const [deptRows] = await pool.query('SELECT id, name FROM departments') const [deptRows] = await pool.query('SELECT id, name FROM departments')
const deptIdMap = Object.fromEntries(deptRows.map(d => [d.name, d.id])) 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' }, const emp = { 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' }, const usr = { username: 'zhangchao', deptName: 'general_manager' }
{ 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' },
{ name: '杨帆', gender: '女', age: 34, education: '本科', department: '人力资源部', entry_date: '2019-05-01', position: '人力资源经理', salary: 15000, phone: '13800001007', email: 'yangfan@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' },
{ username: 'yangfan', deptName: 'hr' },
]
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]) const [userCheck] = await pool.query('SELECT id FROM users WHERE username = ?', [usr.username])
if (userCheck.length > 0) continue if (userCheck.length === 0) {
// 先插入 employees
const [empResult] = await pool.query( const [empResult] = await pool.query(
`INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status) `INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`, VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
[emp.name, emp.gender, emp.age, emp.education, emp.department, emp.entry_date, emp.position, emp.salary, emp.phone, emp.email] [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( await pool.query(
`INSERT INTO users (username, password, is_active, department_id, employee_id) `INSERT INTO users (username, password, is_active, department_id, employee_id)
VALUES (?, ?, 1, ?, ?)`, VALUES (?, ?, 1, ?, ?)`,
[usr.username, hash, deptIdMap[usr.deptName], empId] [usr.username, hash, deptIdMap[usr.deptName], empResult.insertId]
) )
console.log(`[init] 已创建用户 ${usr.username} (${emp.name} - ${emp.department})`) console.log(`[init] 已创建用户 ${usr.username} (${emp.name})`)
} }
} }
// ============ 5. 种子数据:业务数据 ============ // ============ 5. 种子数据:业务数据(已注释,仅保留总经理账号) ============
async function seedBusinessData() { // async function seedBusinessData() {
// ---- 5.1 新增员工 ---- // // ---- 5.1 新增员工 ----
const [empCheck] = await pool.query("SELECT id FROM employees WHERE name = '刘洋' LIMIT 1") // const [empCheck] = await pool.query("SELECT id FROM employees WHERE name = '刘洋' LIMIT 1")
if (empCheck.length === 0) { // if (empCheck.length === 0) {
const extraEmployees = [ // 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: 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: 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: 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: 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: 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: 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: 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' }, // { 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) { // for (const e of extraEmployees) {
await pool.query( // await pool.query(
`INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status) // `INSERT INTO employees (name, gender, age, education, department, entry_date, position, salary, phone, email, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`, // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
[e.name, e.gender, e.age, e.education, e.department, e.entry_date, e.position, e.salary, e.phone, e.email] // [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 条员工数据') // 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 } module.exports = { pool, initDB }