diff --git a/backend/check_orders_structure.js b/backend/check_orders_structure.js new file mode 100644 index 0000000..221527a --- /dev/null +++ b/backend/check_orders_structure.js @@ -0,0 +1,48 @@ +/** + * 检查orders表的详细结构 + */ + +require('dotenv').config(); +const { Sequelize } = require('sequelize'); + +const sequelize = new Sequelize( + process.env.DB_NAME || 'niumall', + process.env.DB_USERNAME || 'jiebanke', + process.env.DB_PASSWORD || 'aiot741$12346', + { + host: process.env.DB_HOST || 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: process.env.DB_PORT || 20784, + dialect: 'mysql', + logging: false + } +); + +async function checkOrdersStructure() { + try { + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + + // 获取orders表的详细结构 + const [columns] = await sequelize.query("SHOW COLUMNS FROM orders"); + + console.log('\n📋 orders表详细结构:'); + columns.forEach(col => { + console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'YES' ? '(可空)' : '(非空)'} ${col.Default ? `默认值: ${col.Default}` : ''}`); + }); + + // 检查现有订单数据 + const [orders] = await sequelize.query("SELECT orderNo, paymentStatus, orderStatus FROM orders LIMIT 5"); + + console.log('\n📊 现有订单状态示例:'); + orders.forEach(order => { + console.log(` - ${order.orderNo}: 支付状态=${order.paymentStatus}, 订单状态=${order.orderStatus}`); + }); + + } catch (error) { + console.error('❌ 检查失败:', error.message); + } finally { + await sequelize.close(); + } +} + +checkOrdersStructure(); \ No newline at end of file diff --git a/backend/compatible_data_insert.js b/backend/compatible_data_insert.js new file mode 100644 index 0000000..e020544 --- /dev/null +++ b/backend/compatible_data_insert.js @@ -0,0 +1,549 @@ +/** + * 兼容现有表结构的数据插入脚本 + * 根据实际表结构插入测试数据 + */ + +require('dotenv').config(); +const { Sequelize } = require('sequelize'); +const bcrypt = require('bcrypt'); +const { v4: uuidv4 } = require('uuid'); + +// 使用.env文件中的配置 +const sequelize = new Sequelize( + process.env.DB_NAME || 'niumall', + process.env.DB_USERNAME || 'jiebanke', + process.env.DB_PASSWORD || 'aiot741$12346', + { + host: process.env.DB_HOST || 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: process.env.DB_PORT || 20784, + dialect: 'mysql', + logging: (msg) => console.log(`[SQL] ${msg}`), + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } + } +); + +/** + * 检查现有表结构 + */ +async function checkTableStructure() { + console.log('\n🔍 检查现有表结构...'); + + try { + // 检查suppliers表结构 + const [supplierColumns] = await sequelize.query("DESCRIBE suppliers"); + console.log('🏭 suppliers表字段:'); + const supplierFields = supplierColumns.map(col => col.Field); + supplierFields.forEach(field => console.log(` - ${field}`)); + + // 检查orders表结构 + const [orderColumns] = await sequelize.query("DESCRIBE orders"); + console.log('\n📋 orders表字段:'); + const orderFields = orderColumns.map(col => col.Field); + orderFields.forEach(field => console.log(` - ${field}`)); + + return { supplierFields, orderFields }; + + } catch (error) { + console.error('❌ 检查表结构失败:', error.message); + throw error; + } +} + +/** + * 插入兼容的测试数据 + */ +async function insertCompatibleData() { + console.log('\n📊 开始插入兼容的测试数据...'); + + try { + // 1. 插入管理员和测试用户 + console.log('👤 插入用户数据...'); + + // 检查管理员是否存在 + const [existingAdmin] = await sequelize.query( + "SELECT id FROM users WHERE username = 'admin'" + ); + + if (existingAdmin.length === 0) { + const adminPassword = await bcrypt.hash('admin123', 10); + const adminUuid = uuidv4(); + + await sequelize.query(` + INSERT INTO users ( + uuid, username, password_hash, openid, nickname, real_name, + user_type, status, registration_source, login_count, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + adminUuid, 'admin', adminPassword, 'admin_' + adminUuid.substring(0, 8), + '系统管理员', '管理员', 'admin', 'active', 'admin_create', 0 + ] + }); + console.log('✅ 管理员用户创建成功'); + } else { + console.log('✅ 管理员用户已存在'); + } + + // 测试用户数据 - 使用不同的手机号避免冲突 + const testUsers = [ + { + uuid: uuidv4(), + username: 'buyer001', + password: await bcrypt.hash('123456', 10), + nickname: '采购商张三', + real_name: '张三', + phone: '13900139001', + email: 'buyer001@niumall.com', + user_type: 'buyer', + company_name: '北京牛肉加工厂', + company_address: '北京市朝阳区xxx街道' + }, + { + uuid: uuidv4(), + username: 'trader001', + password: await bcrypt.hash('123456', 10), + nickname: '贸易商李四', + real_name: '李四', + phone: '13900139002', + email: 'trader001@niumall.com', + user_type: 'trader', + company_name: '上海牛只贸易有限公司', + company_address: '上海市浦东新区xxx路' + }, + { + uuid: uuidv4(), + username: 'supplier001', + password: await bcrypt.hash('123456', 10), + nickname: '供应商王五', + real_name: '王五', + phone: '13900139003', + email: 'supplier001@niumall.com', + user_type: 'supplier', + company_name: '内蒙古草原牧业', + company_address: '内蒙古呼和浩特市' + }, + { + uuid: uuidv4(), + username: 'driver001', + password: await bcrypt.hash('123456', 10), + nickname: '司机赵六', + real_name: '赵六', + phone: '13900139004', + email: 'driver001@niumall.com', + user_type: 'driver', + id_card: '110101199001011234' + }, + { + uuid: uuidv4(), + username: 'staff001', + password: await bcrypt.hash('123456', 10), + nickname: '员工孙七', + real_name: '孙七', + phone: '13900139005', + email: 'staff001@niumall.com', + user_type: 'staff', + company_name: '牛商城运营中心' + } + ]; + + for (const user of testUsers) { + // 检查用户是否已存在(检查用户名和手机号) + const [existing] = await sequelize.query( + "SELECT id FROM users WHERE username = ? OR phone = ?", + { replacements: [user.username, user.phone] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO users ( + uuid, username, password_hash, openid, nickname, real_name, phone, email, + user_type, company_name, company_address, id_card, status, + registration_source, login_count, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + user.uuid, user.username, user.password, user.username + '_openid', + user.nickname, user.real_name, user.phone, user.email, user.user_type, + user.company_name || null, user.company_address || null, user.id_card || null, + 'active', 'admin_create', 0 + ] + }); + console.log(`✅ 用户 ${user.username} 创建成功`); + } else { + console.log(`✅ 用户 ${user.username} 已存在`); + } + } + + // 2. 插入供应商数据(使用现有表结构字段) + console.log('🏭 插入供应商数据...'); + const suppliers = [ + { + name: '内蒙古草原牧业有限公司', + code: 'SUP001', + contact: '赵大牛', + phone: '13900139001', + address: '内蒙古呼和浩特市赛罕区草原路123号', + region: '内蒙古', + qualificationLevel: 'A', + cattleTypes: JSON.stringify(['西门塔尔牛', '安格斯牛', '夏洛莱牛']), + capacity: 500, + rating: 4.8, + cooperationStartDate: '2023-01-01' + }, + { + name: '新疆天山畜牧合作社', + code: 'SUP002', + contact: '马小羊', + phone: '13900139002', + address: '新疆乌鲁木齐市天山区畜牧街456号', + region: '新疆', + qualificationLevel: 'A', + cattleTypes: JSON.stringify(['哈萨克牛', '新疆褐牛']), + capacity: 300, + rating: 4.5, + cooperationStartDate: '2023-03-15' + }, + { + name: '山东鲁西黄牛养殖场', + code: 'SUP003', + contact: '孙大强', + phone: '13900139003', + address: '山东省济南市历城区养殖园区789号', + region: '山东', + qualificationLevel: 'B', + cattleTypes: JSON.stringify(['鲁西黄牛', '利木赞牛']), + capacity: 200, + rating: 4.2, + cooperationStartDate: '2023-06-01' + }, + { + name: '四川成都优质牧场', + code: 'SUP004', + contact: '李小川', + phone: '13900139004', + address: '四川省成都市双流区牧场路101号', + region: '四川', + qualificationLevel: 'B', + cattleTypes: JSON.stringify(['西门塔尔牛', '本地黄牛']), + capacity: 150, + rating: 4.0, + cooperationStartDate: '2023-08-01' + }, + { + name: '河北承德绿色牧业', + code: 'SUP005', + contact: '张承德', + phone: '13900139005', + address: '河北省承德市双桥区绿色牧场街202号', + region: '河北', + qualificationLevel: 'C', + cattleTypes: JSON.stringify(['夏洛莱牛', '安格斯牛']), + capacity: 100, + rating: 3.8, + cooperationStartDate: '2023-09-15' + } + ]; + + for (const supplier of suppliers) { + // 检查供应商是否已存在 + const [existing] = await sequelize.query( + "SELECT id FROM suppliers WHERE code = ?", + { replacements: [supplier.code] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO suppliers ( + name, code, contact, phone, address, region, qualification_level, + cattle_types, capacity, rating, cooperation_start_date, status, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + supplier.name, supplier.code, supplier.contact, supplier.phone, + supplier.address, supplier.region, supplier.qualificationLevel, supplier.cattleTypes, + supplier.capacity, supplier.rating, supplier.cooperationStartDate, 'active' + ] + }); + console.log(`✅ 供应商 ${supplier.code} 创建成功`); + } else { + console.log(`✅ 供应商 ${supplier.code} 已存在`); + } + } + + // 3. 插入订单数据(使用现有表结构) + console.log('📋 插入订单数据...'); + + // 获取用户和供应商ID + const [buyers] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'buyer' LIMIT 1"); + const [traders] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'trader' LIMIT 1"); + const [supplierList] = await sequelize.query("SELECT id, name FROM suppliers WHERE code LIKE 'SUP%' LIMIT 3"); + + if (buyers.length > 0 && supplierList.length > 0) { + const orders = [ + { + orderNo: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '001', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList[0].id, + supplierName: supplierList[0].name, + traderId: traders.length > 0 ? traders[0].id : null, + traderName: traders.length > 0 ? traders[0].nickname : null, + cattleBreed: '西门塔尔牛', + cattleCount: 50, + expectedWeight: 25000.00, + unitPrice: 32.50, + totalAmount: 812500.00, + paidAmount: 200000.00, + remainingAmount: 612500.00, + deliveryAddress: '北京市朝阳区屠宰场', + expectedDeliveryDate: '2024-02-15 08:00:00', + status: 'confirmed', + notes: '要求健康证明齐全,质量等级A级,重量范围450-550kg' + }, + { + orderNo: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '002', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList.length > 1 ? supplierList[1].id : supplierList[0].id, + supplierName: supplierList.length > 1 ? supplierList[1].name : supplierList[0].name, + traderId: null, + traderName: null, + cattleBreed: '安格斯牛', + cattleCount: 30, + expectedWeight: 16500.00, + unitPrice: 35.00, + totalAmount: 577500.00, + paidAmount: 150000.00, + remainingAmount: 427500.00, + deliveryAddress: '上海市浦东新区加工厂', + expectedDeliveryDate: '2024-02-20 10:00:00', + status: 'pending', + notes: '需要冷链运输,重量范围500-600kg' + }, + { + orderNo: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '003', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList.length > 2 ? supplierList[2].id : supplierList[0].id, + supplierName: supplierList.length > 2 ? supplierList[2].name : supplierList[0].name, + traderId: null, + traderName: null, + cattleBreed: '鲁西黄牛', + cattleCount: 20, + expectedWeight: 9000.00, + unitPrice: 30.00, + totalAmount: 270000.00, + paidAmount: 80000.00, + remainingAmount: 190000.00, + deliveryAddress: '天津市滨海新区肉类加工园', + expectedDeliveryDate: '2024-02-25 14:00:00', + status: 'pending', + notes: '本地优质黄牛,肉质鲜美,重量范围400-500kg' + } + ]; + + for (const order of orders) { + // 检查订单是否已存在 + const [existing] = await sequelize.query( + "SELECT id FROM orders WHERE orderNo = ?", + { replacements: [order.orderNo] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO orders ( + orderNo, buyerId, buyerName, supplierId, supplierName, traderId, traderName, + cattleBreed, cattleCount, expectedWeight, unitPrice, totalAmount, paidAmount, + remainingAmount, deliveryAddress, expectedDeliveryDate, status, notes, + created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + order.orderNo, order.buyerId, order.buyerName, order.supplierId, order.supplierName, + order.traderId, order.traderName, order.cattleBreed, order.cattleCount, + order.expectedWeight, order.unitPrice, order.totalAmount, order.paidAmount, + order.remainingAmount, order.deliveryAddress, order.expectedDeliveryDate, + order.status, order.notes + ] + }); + console.log(`✅ 订单 ${order.orderNo} 创建成功`); + } else { + console.log(`✅ 订单 ${order.orderNo} 已存在`); + } + } + } + + // 4. 插入系统配置 + console.log('⚙️ 插入系统配置...'); + const configs = [ + ['system.name', '活牛采购智能数字化系统', 'string', 'system', '系统名称'], + ['system.version', '1.0.0', 'string', 'system', '系统版本'], + ['system.company', '牛商城科技有限公司', 'string', 'system', '公司名称'], + ['order.auto_confirm_hours', '24', 'number', 'order', '订单自动确认时间(小时)'], + ['order.max_quantity', '1000', 'number', 'order', '单笔订单最大数量'], + ['payment.timeout_minutes', '30', 'number', 'payment', '支付超时时间(分钟)'], + ['payment.min_prepaid_ratio', '0.2', 'number', 'payment', '最低预付比例'], + ['transport.tracking_interval', '300', 'number', 'transport', '位置跟踪间隔(秒)'], + ['transport.max_distance', '2000', 'number', 'transport', '最大运输距离(公里)'], + ['quality.min_score', '80', 'number', 'quality', '质量检验最低分数'], + ['quality.pass_rate_threshold', '0.95', 'number', 'quality', '合格率阈值'], + ['notification.sms_enabled', 'true', 'boolean', 'notification', '是否启用短信通知'], + ['notification.email_enabled', 'true', 'boolean', 'notification', '是否启用邮件通知'], + ['notification.push_enabled', 'true', 'boolean', 'notification', '是否启用推送通知'], + ['security.session_timeout', '7200', 'number', 'security', '会话超时时间(秒)'], + ['security.max_login_attempts', '5', 'number', 'security', '最大登录尝试次数'] + ]; + + for (const [key, value, type, category, description] of configs) { + // 检查配置是否已存在 + const [existing] = await sequelize.query( + "SELECT id FROM system_configs WHERE config_key = ?", + { replacements: [key] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO system_configs ( + config_key, config_value, config_type, category, description, + is_public, is_editable, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [key, value, type, category, description, true, true] + }); + console.log(`✅ 配置 ${key} 创建成功`); + } else { + console.log(`✅ 配置 ${key} 已存在`); + } + } + + console.log('✅ 兼容数据插入完成'); + + } catch (error) { + console.error('❌ 插入数据失败:', error.message); + throw error; + } +} + +/** + * 验证数据完整性 + */ +async function validateData() { + console.log('\n🔍 验证数据完整性...'); + + try { + const tables = ['users', 'suppliers', 'orders', 'payments', 'system_configs']; + + console.log('📊 数据统计:'); + for (const table of tables) { + try { + const [result] = await sequelize.query(`SELECT COUNT(*) as count FROM ${table}`); + console.log(` ${table}: ${result[0].count} 条记录`); + } catch (error) { + console.log(` ${table}: 表不存在或查询失败`); + } + } + + // 检查关键数据 + const [adminUsers] = await sequelize.query( + "SELECT id, username, nickname FROM users WHERE user_type = 'admin'" + ); + + console.log('\n👤 管理员用户:'); + adminUsers.forEach(user => { + console.log(` - ID: ${user.id}, 用户名: ${user.username}, 昵称: ${user.nickname}`); + }); + + const [supplierStats] = await sequelize.query(` + SELECT qualification_level, COUNT(*) as count + FROM suppliers + WHERE code LIKE 'SUP%' + GROUP BY qualification_level + ORDER BY qualification_level + `); + + console.log('\n🏭 新增供应商等级分布:'); + supplierStats.forEach(stat => { + console.log(` - 等级${stat.qualification_level}: ${stat.count}家`); + }); + + const [orderStats] = await sequelize.query(` + SELECT status, COUNT(*) as count + FROM orders + WHERE orderNo LIKE 'ORD2024%' + GROUP BY status + ORDER BY status + `); + + console.log('\n📋 新增订单状态分布:'); + orderStats.forEach(stat => { + console.log(` - ${stat.status}: ${stat.count}个`); + }); + + } catch (error) { + console.error('❌ 数据验证失败:', error.message); + } +} + +/** + * 主函数 + */ +async function main() { + try { + console.log('\n🚀 ===== 兼容数据插入开始 ====='); + + // 1. 测试连接 + console.log('\n📡 连接远程MySQL数据库...'); + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + console.log(`📍 连接信息: ${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`); + + // 2. 检查表结构 + await checkTableStructure(); + + // 3. 插入兼容数据 + await insertCompatibleData(); + + // 4. 验证数据 + await validateData(); + + console.log('\n🎉 ===== 数据插入完成 ====='); + console.log('\n📋 系统信息:'); + console.log('🌐 后端服务: http://localhost:4330'); + console.log('🎨 管理后台: http://localhost:3000'); + console.log('👤 管理员账户: admin / admin123'); + console.log('📚 API文档: http://localhost:4330/api-docs'); + console.log('💓 健康检查: http://localhost:4330/health'); + + console.log('\n🔑 测试账户:'); + console.log(' 采购商: buyer001 / 123456'); + console.log(' 贸易商: trader001 / 123456'); + console.log(' 供应商: supplier001 / 123456'); + console.log(' 司机: driver001 / 123456'); + console.log(' 员工: staff001 / 123456'); + + console.log('\n📈 数据概览:'); + console.log(' - 5个不同类型的测试用户'); + console.log(' - 5家不同等级的供应商'); + console.log(' - 3个不同状态的订单'); + console.log(' - 16项系统配置参数'); + console.log(' - 完全兼容现有表结构'); + + } catch (error) { + console.error('\n❌ 数据插入失败:', error); + } finally { + await sequelize.close(); + console.log('\n🔌 数据库连接已关闭'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { main }; \ No newline at end of file diff --git a/backend/complete_db_setup.js b/backend/complete_db_setup.js new file mode 100644 index 0000000..f354a3c --- /dev/null +++ b/backend/complete_db_setup.js @@ -0,0 +1,843 @@ +/** + * 完整的数据库设置脚本 + * 连接远程MySQL,更新表结构,插入测试数据 + */ + +require('dotenv').config(); +const { Sequelize, DataTypes } = require('sequelize'); +const bcrypt = require('bcrypt'); +const { v4: uuidv4 } = require('uuid'); + +// 创建数据库连接 +const sequelize = new Sequelize( + process.env.DB_NAME || 'niumall', + process.env.DB_USERNAME || 'root', + process.env.DB_PASSWORD || 'aiotAiot123!', + { + host: process.env.DB_HOST || '129.211.213.226', + port: process.env.DB_PORT || 9527, + dialect: 'mysql', + logging: (msg) => console.log(`[SQL] ${msg}`), + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } + } +); + +/** + * 检查并创建/更新表结构 + */ +async function updateTableStructures() { + console.log('\n🔧 开始更新表结构...'); + + try { + // 1. 更新用户表结构 + console.log('👤 更新用户表结构...'); + await sequelize.query(` + CREATE TABLE IF NOT EXISTS users ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', + uuid VARCHAR(36) UNIQUE COMMENT '用户唯一标识符', + username VARCHAR(50) UNIQUE COMMENT '用户名', + password_hash VARCHAR(255) COMMENT '密码哈希值', + openid VARCHAR(64) COMMENT '微信小程序OpenID', + unionid VARCHAR(64) COMMENT '微信UnionID', + nickname VARCHAR(50) NOT NULL COMMENT '用户昵称', + real_name VARCHAR(50) COMMENT '真实姓名', + avatar VARCHAR(255) COMMENT '头像URL', + gender ENUM('male', 'female', 'other') COMMENT '性别', + birthday DATETIME COMMENT '生日', + phone VARCHAR(20) UNIQUE COMMENT '手机号码', + email VARCHAR(100) UNIQUE COMMENT '邮箱地址', + user_type ENUM('buyer', 'trader', 'supplier', 'driver', 'staff', 'admin') DEFAULT 'buyer' COMMENT '用户类型', + company_name VARCHAR(100) COMMENT '公司名称', + company_address VARCHAR(200) COMMENT '公司地址', + business_license VARCHAR(255) COMMENT '营业执照文件路径', + id_card VARCHAR(18) COMMENT '身份证号', + status ENUM('active', 'inactive', 'suspended', 'pending_approval') DEFAULT 'pending_approval' COMMENT '用户状态', + last_login_at DATETIME COMMENT '最后登录时间', + login_count INT DEFAULT 0 COMMENT '登录次数', + registration_source ENUM('miniprogram', 'web', 'admin_create') DEFAULT 'miniprogram' COMMENT '注册来源', + approval_notes TEXT COMMENT '审核备注', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + + INDEX idx_uuid (uuid), + INDEX idx_username (username), + INDEX idx_phone (phone), + INDEX idx_email (email), + INDEX idx_openid (openid), + INDEX idx_user_type (user_type), + INDEX idx_status (status) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户基础表' + `); + + // 2. 更新供应商表结构 + console.log('🏭 更新供应商表结构...'); + await sequelize.query(` + CREATE TABLE IF NOT EXISTS suppliers ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '供应商ID', + name VARCHAR(100) NOT NULL COMMENT '供应商名称', + code VARCHAR(20) UNIQUE NOT NULL COMMENT '供应商编码', + contact VARCHAR(50) NOT NULL COMMENT '联系人姓名', + phone VARCHAR(20) UNIQUE NOT NULL COMMENT '联系电话', + email VARCHAR(100) COMMENT '邮箱地址', + address VARCHAR(200) NOT NULL COMMENT '详细地址', + region VARCHAR(20) NOT NULL COMMENT '所属区域', + business_license VARCHAR(255) COMMENT '营业执照文件路径', + animal_quarantine_certificate VARCHAR(255) COMMENT '动物防疫条件合格证文件路径', + qualification_level ENUM('A', 'B', 'C', 'D') DEFAULT 'C' COMMENT '资质等级', + certifications JSON COMMENT '其他认证证书信息', + cattle_types JSON COMMENT '可供应的牛只品种', + capacity INT DEFAULT 0 COMMENT '月供应能力(头数)', + rating DECIMAL(3,2) DEFAULT 0.00 COMMENT '综合评分', + cooperation_start_date DATE COMMENT '合作开始日期', + status ENUM('active', 'inactive', 'suspended', 'blacklisted') DEFAULT 'active' COMMENT '供应商状态', + bank_account VARCHAR(50) COMMENT '银行账号', + bank_name VARCHAR(100) COMMENT '开户银行', + tax_number VARCHAR(30) COMMENT '税务登记号', + notes TEXT COMMENT '备注信息', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + + INDEX idx_code (code), + INDEX idx_phone (phone), + INDEX idx_region (region), + INDEX idx_qualification_level (qualification_level), + INDEX idx_status (status), + INDEX idx_rating (rating) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商表' + `); + + // 3. 更新订单表结构 + console.log('📋 更新订单表结构...'); + await sequelize.query(` + CREATE TABLE IF NOT EXISTS orders ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID', + order_no VARCHAR(50) UNIQUE NOT NULL COMMENT '订单号', + client_id BIGINT NOT NULL COMMENT '采购人ID', + trader_id BIGINT COMMENT '贸易商ID', + supplier_id BIGINT COMMENT '供应商ID', + cattle_type VARCHAR(50) NOT NULL COMMENT '牛只品种', + quantity INT NOT NULL COMMENT '数量(头)', + weight_range VARCHAR(50) COMMENT '重量范围', + estimated_weight DECIMAL(8,2) COMMENT '预估总重量(kg)', + actual_weight DECIMAL(8,2) COMMENT '实际总重量(kg)', + unit_price DECIMAL(10,2) NOT NULL COMMENT '单价(元/kg或元/头)', + price_type ENUM('per_kg', 'per_head') DEFAULT 'per_kg' COMMENT '计价方式', + total_amount DECIMAL(12,2) NOT NULL COMMENT '订单总金额', + prepaid_amount DECIMAL(12,2) DEFAULT 0 COMMENT '预付金额', + final_amount DECIMAL(12,2) COMMENT '最终结算金额', + pickup_address TEXT COMMENT '取货地址', + delivery_address TEXT NOT NULL COMMENT '交货地址', + pickup_time DATETIME COMMENT '取货时间', + delivery_time DATETIME COMMENT '要求交货时间', + actual_delivery_time DATETIME COMMENT '实际交货时间', + status ENUM('draft', 'pending', 'confirmed', 'preparing', 'loading', 'transporting', 'arrived', 'inspecting', 'accepted', 'completed', 'cancelled') DEFAULT 'draft' COMMENT '订单状态', + cancel_reason TEXT COMMENT '取消原因', + special_requirements TEXT COMMENT '特殊要求', + quality_standards JSON COMMENT '质量标准JSON', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + confirmed_at TIMESTAMP NULL COMMENT '确认时间', + completed_at TIMESTAMP NULL COMMENT '完成时间', + + INDEX idx_order_no (order_no), + INDEX idx_client_id (client_id), + INDEX idx_trader_id (trader_id), + INDEX idx_supplier_id (supplier_id), + INDEX idx_status (status), + INDEX idx_created_at (created_at), + INDEX idx_delivery_time (delivery_time), + INDEX idx_cattle_type (cattle_type) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单主表' + `); + + // 4. 创建支付记录表 + console.log('💰 创建支付记录表...'); + await sequelize.query(` + CREATE TABLE IF NOT EXISTS payments ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '支付ID', + payment_no VARCHAR(50) UNIQUE NOT NULL COMMENT '支付单号', + order_id BIGINT NOT NULL COMMENT '订单ID', + user_id BIGINT NOT NULL COMMENT '付款用户ID', + amount DECIMAL(12,2) NOT NULL COMMENT '支付金额', + paid_amount DECIMAL(12,2) COMMENT '实际支付金额', + currency VARCHAR(10) DEFAULT 'CNY' COMMENT '货币类型', + payment_method ENUM('bank_transfer', 'alipay', 'wechat_pay', 'cash', 'check', 'other') NOT NULL COMMENT '支付方式', + payment_channel VARCHAR(100) COMMENT '支付渠道', + third_party_order_no VARCHAR(100) COMMENT '第三方订单号', + third_party_transaction_id VARCHAR(100) COMMENT '第三方交易ID', + payer_bank_account VARCHAR(50) COMMENT '付款账户', + payer_bank_name VARCHAR(200) COMMENT '付款银行', + payee_bank_account VARCHAR(50) COMMENT '收款账户', + payee_bank_name VARCHAR(200) COMMENT '收款银行', + status ENUM('pending', 'processing', 'success', 'failed', 'cancelled', 'refunded') DEFAULT 'pending' COMMENT '支付状态', + failure_reason TEXT COMMENT '失败原因', + payment_time DATETIME COMMENT '支付时间', + confirmed_time DATETIME COMMENT '确认时间', + notes TEXT COMMENT '支付备注', + receipt_url VARCHAR(500) COMMENT '支付凭证URL', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + + INDEX idx_payment_no (payment_no), + INDEX idx_order_id (order_id), + INDEX idx_user_id (user_id), + INDEX idx_status (status), + INDEX idx_payment_method (payment_method), + INDEX idx_payment_time (payment_time), + INDEX idx_third_party_order_no (third_party_order_no) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付记录表' + `); + + // 5. 创建运输任务表 + console.log('🚛 创建运输任务表...'); + await sequelize.query(` + CREATE TABLE IF NOT EXISTS transport_tasks ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '任务ID', + task_no VARCHAR(50) UNIQUE NOT NULL COMMENT '任务编号', + order_id BIGINT NOT NULL COMMENT '订单ID', + driver_id BIGINT NOT NULL COMMENT '司机ID', + vehicle_no VARCHAR(20) NOT NULL COMMENT '车牌号', + vehicle_type VARCHAR(50) COMMENT '车辆类型', + vehicle_capacity DECIMAL(8,2) COMMENT '载重量(吨)', + driver_license VARCHAR(50) COMMENT '驾驶证号', + start_location VARCHAR(200) COMMENT '起始地点', + end_location VARCHAR(200) COMMENT '目的地点', + start_latitude DECIMAL(10,6) COMMENT '起始纬度', + start_longitude DECIMAL(10,6) COMMENT '起始经度', + end_latitude DECIMAL(10,6) COMMENT '目的纬度', + end_longitude DECIMAL(10,6) COMMENT '目的经度', + planned_distance DECIMAL(8,2) COMMENT '计划距离(公里)', + actual_distance DECIMAL(8,2) COMMENT '实际距离(公里)', + planned_start_time DATETIME COMMENT '计划开始时间', + actual_start_time DATETIME COMMENT '实际开始时间', + planned_end_time DATETIME COMMENT '计划结束时间', + actual_end_time DATETIME COMMENT '实际结束时间', + estimated_arrival_time DATETIME COMMENT '预计到达时间', + status ENUM('assigned', 'preparing', 'loading', 'started', 'transporting', 'arrived', 'unloading', 'completed', 'cancelled') DEFAULT 'assigned' COMMENT '任务状态', + transport_fee DECIMAL(10,2) COMMENT '运输费用', + fuel_cost DECIMAL(10,2) COMMENT '燃油费用', + toll_cost DECIMAL(10,2) COMMENT '过路费', + other_cost DECIMAL(10,2) COMMENT '其他费用', + notes TEXT COMMENT '备注', + cancel_reason TEXT COMMENT '取消原因', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + + INDEX idx_task_no (task_no), + INDEX idx_order_id (order_id), + INDEX idx_driver_id (driver_id), + INDEX idx_vehicle_no (vehicle_no), + INDEX idx_status (status), + INDEX idx_planned_start_time (planned_start_time), + INDEX idx_created_at (created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='运输任务表' + `); + + // 6. 创建质量检验表 + console.log('🔍 创建质量检验表...'); + await sequelize.query(` + CREATE TABLE IF NOT EXISTS quality_inspections ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '检验ID', + inspection_no VARCHAR(50) UNIQUE NOT NULL COMMENT '检验单号', + order_id BIGINT NOT NULL COMMENT '订单ID', + inspector_id BIGINT NOT NULL COMMENT '检验员ID', + inspection_time DATETIME NOT NULL COMMENT '检验时间', + inspection_location VARCHAR(200) COMMENT '检验地点', + cattle_count INT NOT NULL COMMENT '检验牛只数量', + passed_count INT DEFAULT 0 COMMENT '合格数量', + failed_count INT DEFAULT 0 COMMENT '不合格数量', + average_weight DECIMAL(8,2) COMMENT '平均重量', + total_weight DECIMAL(10,2) COMMENT '总重量', + health_status ENUM('excellent', 'good', 'fair', 'poor') DEFAULT 'good' COMMENT '健康状况', + quality_grade ENUM('A+', 'A', 'B+', 'B', 'C', 'D') DEFAULT 'B' COMMENT '质量等级', + inspection_result ENUM('passed', 'failed', 'conditional_pass') DEFAULT 'passed' COMMENT '检验结果', + defect_description TEXT COMMENT '缺陷描述', + improvement_suggestions TEXT COMMENT '改进建议', + inspector_notes TEXT COMMENT '检验员备注', + photos JSON COMMENT '检验照片URLs', + certificates JSON COMMENT '相关证书信息', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + + INDEX idx_inspection_no (inspection_no), + INDEX idx_order_id (order_id), + INDEX idx_inspector_id (inspector_id), + INDEX idx_inspection_time (inspection_time), + INDEX idx_quality_grade (quality_grade), + INDEX idx_inspection_result (inspection_result) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='质量检验表' + `); + + // 7. 创建系统配置表 + console.log('⚙️ 创建系统配置表...'); + await sequelize.query(` + CREATE TABLE IF NOT EXISTS system_configs ( + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '配置ID', + config_key VARCHAR(100) UNIQUE NOT NULL COMMENT '配置键', + config_value TEXT NOT NULL COMMENT '配置值', + config_type ENUM('string', 'number', 'boolean', 'json', 'array') DEFAULT 'string' COMMENT '配置类型', + category VARCHAR(50) NOT NULL COMMENT '配置分类', + description TEXT COMMENT '配置描述', + is_public BOOLEAN DEFAULT FALSE COMMENT '是否公开配置', + is_editable BOOLEAN DEFAULT TRUE COMMENT '是否可编辑', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + + INDEX idx_config_key (config_key), + INDEX idx_category (category), + INDEX idx_is_public (is_public) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表' + `); + + console.log('✅ 表结构更新完成'); + + } catch (error) { + console.error('❌ 表结构更新失败:', error.message); + throw error; + } +} + +/** + * 插入完整测试数据 + */ +async function insertCompleteTestData() { + console.log('\n📊 开始插入完整测试数据...'); + + try { + // 1. 插入管理员和测试用户 + console.log('👤 插入用户数据...'); + + // 管理员用户 + const adminPassword = await bcrypt.hash('admin123', 10); + const adminUuid = uuidv4(); + + await sequelize.query(` + INSERT IGNORE INTO users ( + uuid, username, password_hash, openid, nickname, real_name, + user_type, status, registration_source, login_count, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + adminUuid, 'admin', adminPassword, 'admin_' + adminUuid.substring(0, 8), + '系统管理员', '管理员', 'admin', 'active', 'admin_create', 0 + ] + }); + + // 测试用户数据 + const testUsers = [ + { + uuid: uuidv4(), + username: 'buyer001', + password: await bcrypt.hash('123456', 10), + nickname: '采购商张三', + real_name: '张三', + phone: '13800138001', + email: 'buyer001@niumall.com', + user_type: 'buyer', + company_name: '北京牛肉加工厂', + company_address: '北京市朝阳区xxx街道' + }, + { + uuid: uuidv4(), + username: 'trader001', + password: await bcrypt.hash('123456', 10), + nickname: '贸易商李四', + real_name: '李四', + phone: '13800138002', + email: 'trader001@niumall.com', + user_type: 'trader', + company_name: '上海牛只贸易有限公司', + company_address: '上海市浦东新区xxx路' + }, + { + uuid: uuidv4(), + username: 'supplier001', + password: await bcrypt.hash('123456', 10), + nickname: '供应商王五', + real_name: '王五', + phone: '13800138003', + email: 'supplier001@niumall.com', + user_type: 'supplier', + company_name: '内蒙古草原牧业', + company_address: '内蒙古呼和浩特市' + }, + { + uuid: uuidv4(), + username: 'driver001', + password: await bcrypt.hash('123456', 10), + nickname: '司机赵六', + real_name: '赵六', + phone: '13800138004', + email: 'driver001@niumall.com', + user_type: 'driver', + id_card: '110101199001011234' + }, + { + uuid: uuidv4(), + username: 'staff001', + password: await bcrypt.hash('123456', 10), + nickname: '员工孙七', + real_name: '孙七', + phone: '13800138005', + email: 'staff001@niumall.com', + user_type: 'staff', + company_name: '牛商城运营中心' + } + ]; + + for (const user of testUsers) { + await sequelize.query(` + INSERT IGNORE INTO users ( + uuid, username, password_hash, openid, nickname, real_name, phone, email, + user_type, company_name, company_address, id_card, status, + registration_source, login_count, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + user.uuid, user.username, user.password, user.username + '_openid', + user.nickname, user.real_name, user.phone, user.email, user.user_type, + user.company_name || null, user.company_address || null, user.id_card || null, + 'active', 'admin_create', 0 + ] + }); + } + + // 2. 插入供应商数据 + console.log('🏭 插入供应商数据...'); + const suppliers = [ + { + name: '内蒙古草原牧业有限公司', + code: 'SUP001', + contact: '赵大牛', + phone: '13900139001', + email: 'sup001@niumall.com', + address: '内蒙古呼和浩特市赛罕区草原路123号', + region: '内蒙古', + qualification_level: 'A', + cattle_types: JSON.stringify(['西门塔尔牛', '安格斯牛', '夏洛莱牛']), + capacity: 500, + rating: 4.8, + cooperation_start_date: '2023-01-01', + bank_account: '6228480402564890018', + bank_name: '农业银行呼和浩特分行', + tax_number: '91150100MA0N2XQJ2K' + }, + { + name: '新疆天山畜牧合作社', + code: 'SUP002', + contact: '马小羊', + phone: '13900139002', + email: 'sup002@niumall.com', + address: '新疆乌鲁木齐市天山区畜牧街456号', + region: '新疆', + qualification_level: 'A', + cattle_types: JSON.stringify(['哈萨克牛', '新疆褐牛']), + capacity: 300, + rating: 4.5, + cooperation_start_date: '2023-03-15', + bank_account: '6228480402564890019', + bank_name: '建设银行乌鲁木齐分行', + tax_number: '91650100MA0N2XQJ3L' + }, + { + name: '山东鲁西黄牛养殖场', + code: 'SUP003', + contact: '孙大强', + phone: '13900139003', + email: 'sup003@niumall.com', + address: '山东省济南市历城区养殖园区789号', + region: '山东', + qualification_level: 'B', + cattle_types: JSON.stringify(['鲁西黄牛', '利木赞牛']), + capacity: 200, + rating: 4.2, + cooperation_start_date: '2023-06-01', + bank_account: '6228480402564890020', + bank_name: '工商银行济南分行', + tax_number: '91370100MA0N2XQJ4M' + }, + { + name: '四川成都优质牧场', + code: 'SUP004', + contact: '李小川', + phone: '13900139004', + email: 'sup004@niumall.com', + address: '四川省成都市双流区牧场路101号', + region: '四川', + qualification_level: 'B', + cattle_types: JSON.stringify(['西门塔尔牛', '本地黄牛']), + capacity: 150, + rating: 4.0, + cooperation_start_date: '2023-08-01', + bank_account: '6228480402564890021', + bank_name: '中国银行成都分行', + tax_number: '91510100MA0N2XQJ5N' + }, + { + name: '河北承德绿色牧业', + code: 'SUP005', + contact: '张承德', + phone: '13900139005', + email: 'sup005@niumall.com', + address: '河北省承德市双桥区绿色牧场街202号', + region: '河北', + qualification_level: 'C', + cattle_types: JSON.stringify(['夏洛莱牛', '安格斯牛']), + capacity: 100, + rating: 3.8, + cooperation_start_date: '2023-09-15', + bank_account: '6228480402564890022', + bank_name: '交通银行承德分行', + tax_number: '91130800MA0N2XQJ6O' + } + ]; + + for (const supplier of suppliers) { + await sequelize.query(` + INSERT IGNORE INTO suppliers ( + name, code, contact, phone, email, address, region, qualification_level, + cattle_types, capacity, rating, cooperation_start_date, status, + bank_account, bank_name, tax_number, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + supplier.name, supplier.code, supplier.contact, supplier.phone, supplier.email, + supplier.address, supplier.region, supplier.qualification_level, supplier.cattle_types, + supplier.capacity, supplier.rating, supplier.cooperation_start_date, 'active', + supplier.bank_account, supplier.bank_name, supplier.tax_number + ] + }); + } + + // 3. 插入订单数据 + console.log('📋 插入订单数据...'); + + // 获取用户和供应商ID + const [buyers] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'buyer' LIMIT 1"); + const [traders] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'trader' LIMIT 1"); + const [supplierList] = await sequelize.query("SELECT id, name FROM suppliers LIMIT 3"); + + if (buyers.length > 0 && supplierList.length > 0) { + const orders = [ + { + order_no: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '001', + client_id: buyers[0].id, + trader_id: traders.length > 0 ? traders[0].id : null, + supplier_id: supplierList[0].id, + cattle_type: '西门塔尔牛', + quantity: 50, + weight_range: '450-550kg', + estimated_weight: 25000.00, + unit_price: 32.50, + price_type: 'per_kg', + total_amount: 812500.00, + prepaid_amount: 200000.00, + pickup_address: '内蒙古呼和浩特市赛罕区草原路123号', + delivery_address: '北京市朝阳区屠宰场', + delivery_time: '2024-02-15 08:00:00', + status: 'confirmed', + special_requirements: '要求健康证明齐全,质量等级A级', + quality_standards: JSON.stringify({ + min_weight: 450, + max_weight: 550, + health_grade: 'A', + age_range: '18-24个月' + }) + }, + { + order_no: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '002', + client_id: buyers[0].id, + supplier_id: supplierList.length > 1 ? supplierList[1].id : supplierList[0].id, + cattle_type: '安格斯牛', + quantity: 30, + weight_range: '500-600kg', + estimated_weight: 16500.00, + unit_price: 35.00, + price_type: 'per_kg', + total_amount: 577500.00, + prepaid_amount: 150000.00, + pickup_address: '新疆乌鲁木齐市天山区畜牧街456号', + delivery_address: '上海市浦东新区加工厂', + delivery_time: '2024-02-20 10:00:00', + status: 'pending', + special_requirements: '需要冷链运输,重量范围500-600kg', + quality_standards: JSON.stringify({ + min_weight: 500, + max_weight: 600, + health_grade: 'A', + age_range: '20-26个月' + }) + }, + { + order_no: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '003', + client_id: buyers[0].id, + supplier_id: supplierList.length > 2 ? supplierList[2].id : supplierList[0].id, + cattle_type: '鲁西黄牛', + quantity: 20, + weight_range: '400-500kg', + estimated_weight: 9000.00, + unit_price: 30.00, + price_type: 'per_kg', + total_amount: 270000.00, + prepaid_amount: 80000.00, + pickup_address: '山东省济南市历城区养殖园区789号', + delivery_address: '天津市滨海新区肉类加工园', + delivery_time: '2024-02-25 14:00:00', + status: 'draft', + special_requirements: '本地优质黄牛,肉质鲜美', + quality_standards: JSON.stringify({ + min_weight: 400, + max_weight: 500, + health_grade: 'B+', + age_range: '16-22个月' + }) + } + ]; + + for (const order of orders) { + await sequelize.query(` + INSERT IGNORE INTO orders ( + order_no, client_id, trader_id, supplier_id, cattle_type, quantity, + weight_range, estimated_weight, unit_price, price_type, total_amount, prepaid_amount, + pickup_address, delivery_address, delivery_time, status, special_requirements, + quality_standards, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + order.order_no, order.client_id, order.trader_id, order.supplier_id, + order.cattle_type, order.quantity, order.weight_range, order.estimated_weight, + order.unit_price, order.price_type, order.total_amount, order.prepaid_amount, + order.pickup_address, order.delivery_address, order.delivery_time, + order.status, order.special_requirements, order.quality_standards + ] + }); + } + } + + // 4. 插入支付记录 + console.log('💰 插入支付记录...'); + const [orderList] = await sequelize.query("SELECT id, order_no, client_id, prepaid_amount FROM orders WHERE prepaid_amount > 0 LIMIT 2"); + + for (const order of orderList) { + const paymentNo = 'PAY' + Date.now() + Math.floor(Math.random() * 1000); + await sequelize.query(` + INSERT IGNORE INTO payments ( + payment_no, order_id, user_id, amount, paid_amount, payment_method, + payment_channel, status, payment_time, confirmed_time, notes, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + paymentNo, order.id, order.client_id, order.prepaid_amount, order.prepaid_amount, + 'bank_transfer', '银行转账', 'success', new Date(), new Date(), + `订单${order.order_no}的预付款支付` + ] + }); + } + + // 5. 插入运输任务 + console.log('🚛 插入运输任务...'); + const [drivers] = await sequelize.query("SELECT id FROM users WHERE user_type = 'driver' LIMIT 1"); + const [confirmedOrders] = await sequelize.query("SELECT id, order_no FROM orders WHERE status = 'confirmed' LIMIT 1"); + + if (drivers.length > 0 && confirmedOrders.length > 0) { + const taskNo = 'TASK' + Date.now(); + await sequelize.query(` + INSERT IGNORE INTO transport_tasks ( + task_no, order_id, driver_id, vehicle_no, vehicle_type, vehicle_capacity, + start_location, end_location, planned_distance, planned_start_time, planned_end_time, + status, transport_fee, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + taskNo, confirmedOrders[0].id, drivers[0].id, '蒙A12345', '大型货车', 25.0, + '内蒙古呼和浩特市', '北京市朝阳区', 450.5, '2024-02-14 06:00:00', '2024-02-15 08:00:00', + 'assigned', 8000.00 + ] + }); + } + + // 6. 插入质量检验记录 + console.log('🔍 插入质量检验记录...'); + const [staff] = await sequelize.query("SELECT id FROM users WHERE user_type = 'staff' LIMIT 1"); + + if (staff.length > 0 && confirmedOrders.length > 0) { + const inspectionNo = 'INS' + Date.now(); + await sequelize.query(` + INSERT IGNORE INTO quality_inspections ( + inspection_no, order_id, inspector_id, inspection_time, inspection_location, + cattle_count, passed_count, failed_count, average_weight, total_weight, + health_status, quality_grade, inspection_result, inspector_notes, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + inspectionNo, confirmedOrders[0].id, staff[0].id, new Date(), '北京市朝阳区检验站', + 50, 48, 2, 502.5, 25125.0, 'excellent', 'A', 'passed', + '整体质量优秀,2头牛只重量略低于标准,但健康状况良好' + ] + }); + } + + // 7. 插入系统配置 + console.log('⚙️ 插入系统配置...'); + const configs = [ + ['system.name', '活牛采购智能数字化系统', 'string', 'system', '系统名称'], + ['system.version', '1.0.0', 'string', 'system', '系统版本'], + ['system.company', '牛商城科技有限公司', 'string', 'system', '公司名称'], + ['order.auto_confirm_hours', '24', 'number', 'order', '订单自动确认时间(小时)'], + ['order.max_quantity', '1000', 'number', 'order', '单笔订单最大数量'], + ['payment.timeout_minutes', '30', 'number', 'payment', '支付超时时间(分钟)'], + ['payment.min_prepaid_ratio', '0.2', 'number', 'payment', '最低预付比例'], + ['transport.tracking_interval', '300', 'number', 'transport', '位置跟踪间隔(秒)'], + ['transport.max_distance', '2000', 'number', 'transport', '最大运输距离(公里)'], + ['quality.min_score', '80', 'number', 'quality', '质量检验最低分数'], + ['quality.pass_rate_threshold', '0.95', 'number', 'quality', '合格率阈值'], + ['notification.sms_enabled', 'true', 'boolean', 'notification', '是否启用短信通知'], + ['notification.email_enabled', 'true', 'boolean', 'notification', '是否启用邮件通知'], + ['notification.push_enabled', 'true', 'boolean', 'notification', '是否启用推送通知'], + ['security.session_timeout', '7200', 'number', 'security', '会话超时时间(秒)'], + ['security.max_login_attempts', '5', 'number', 'security', '最大登录尝试次数'] + ]; + + for (const [key, value, type, category, description] of configs) { + await sequelize.query(` + INSERT IGNORE INTO system_configs ( + config_key, config_value, config_type, category, description, + is_public, is_editable, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [key, value, type, category, description, true, true] + }); + } + + console.log('✅ 测试数据插入完成'); + + } catch (error) { + console.error('❌ 插入测试数据失败:', error.message); + throw error; + } +} + +/** + * 验证数据完整性 + */ +async function validateDatabase() { + console.log('\n🔍 验证数据库完整性...'); + + try { + const tables = [ + 'users', 'suppliers', 'orders', 'payments', + 'transport_tasks', 'quality_inspections', 'system_configs' + ]; + + console.log('📊 数据统计:'); + for (const table of tables) { + try { + const [result] = await sequelize.query(`SELECT COUNT(*) as count FROM ${table}`); + console.log(` ${table}: ${result[0].count} 条记录`); + } catch (error) { + console.log(` ${table}: 表不存在或查询失败`); + } + } + + // 检查关键数据 + const [adminUsers] = await sequelize.query( + "SELECT id, username, nickname FROM users WHERE user_type = 'admin'" + ); + + console.log('\n👤 管理员用户:'); + adminUsers.forEach(user => { + console.log(` - ID: ${user.id}, 用户名: ${user.username}, 昵称: ${user.nickname}`); + }); + + const [supplierStats] = await sequelize.query(` + SELECT qualification_level, COUNT(*) as count + FROM suppliers + GROUP BY qualification_level + ORDER BY qualification_level + `); + + console.log('\n🏭 供应商等级分布:'); + supplierStats.forEach(stat => { + console.log(` - 等级${stat.qualification_level}: ${stat.count}家`); + }); + + const [orderStats] = await sequelize.query(` + SELECT status, COUNT(*) as count + FROM orders + GROUP BY status + ORDER BY status + `); + + console.log('\n📋 订单状态分布:'); + orderStats.forEach(stat => { + console.log(` - ${stat.status}: ${stat.count}个`); + }); + + } catch (error) { + console.error('❌ 数据验证失败:', error.message); + } +} + +/** + * 主函数 + */ +async function main() { + try { + console.log('\n🚀 ===== 远程MySQL数据库完整设置开始 ====='); + + // 1. 测试连接 + console.log('\n📡 连接远程MySQL数据库...'); + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + console.log(`📍 连接信息: ${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`); + + // 2. 更新表结构 + await updateTableStructures(); + + // 3. 插入测试数据 + await insertCompleteTestData(); + + // 4. 验证数据 + await validateDatabase(); + + console.log('\n🎉 ===== 数据库设置完成 ====='); + console.log('\n📋 系统信息:'); + console.log('🌐 后端服务: http://localhost:4330'); + console.log('🎨 管理后台: http://localhost:3000'); + console.log('👤 管理员账户: admin / admin123'); + console.log('📚 API文档: http://localhost:4330/api-docs'); + console.log('💓 健康检查: http://localhost:4330/health'); + + console.log('\n🔑 测试账户:'); + console.log(' 采购商: buyer001 / 123456'); + console.log(' 贸易商: trader001 / 123456'); + console.log(' 供应商: supplier001 / 123456'); + console.log(' 司机: driver001 / 123456'); + console.log(' 员工: staff001 / 123456'); + + console.log('\n📈 数据概览:'); + console.log(' - 5个不同类型的测试用户'); + console.log(' - 5家不同等级的供应商'); + console.log(' - 3个不同状态的订单'); + console.log(' - 完整的支付、运输、质检记录'); + console.log(' - 16项系统配置参数'); + + } catch (error) { + console.error('\n❌ 数据库设置失败:', error); + } finally { + await sequelize.close(); + console.log('\n🔌 数据库连接已关闭'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { main }; \ No newline at end of file diff --git a/backend/correct_data_insert.js b/backend/correct_data_insert.js new file mode 100644 index 0000000..56f8e53 --- /dev/null +++ b/backend/correct_data_insert.js @@ -0,0 +1,329 @@ +/** + * 使用正确枚举值的数据插入脚本 + */ + +require('dotenv').config(); +const { Sequelize } = require('sequelize'); +const bcrypt = require('bcrypt'); +const { v4: uuidv4 } = require('uuid'); + +const sequelize = new Sequelize( + process.env.DB_NAME || 'niumall', + process.env.DB_USERNAME || 'jiebanke', + process.env.DB_PASSWORD || 'aiot741$12346', + { + host: process.env.DB_HOST || 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: process.env.DB_PORT || 20784, + dialect: 'mysql', + logging: (msg) => console.log(`[SQL] ${msg}`), + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } + } +); + +/** + * 插入正确的测试数据 + */ +async function insertCorrectData() { + console.log('\n📊 开始插入正确的测试数据...'); + + try { + // 1. 插入供应商数据 + console.log('🏭 插入供应商数据...'); + const suppliers = [ + { + name: '内蒙古草原牧业有限公司', + code: 'SUP001', + contact: '赵大牛', + phone: '15900159001', + email: 'sup001@niumall.com', + address: '内蒙古呼和浩特市赛罕区草原路123号', + region: '内蒙古', + qualification_level: 'A', + cattle_types: JSON.stringify(['西门塔尔牛', '安格斯牛', '夏洛莱牛']), + capacity: 500, + rating: 4.8, + cooperation_start_date: '2023-01-01' + }, + { + name: '新疆天山畜牧合作社', + code: 'SUP002', + contact: '马小羊', + phone: '15900159002', + email: 'sup002@niumall.com', + address: '新疆乌鲁木齐市天山区畜牧街456号', + region: '新疆', + qualification_level: 'A', + cattle_types: JSON.stringify(['哈萨克牛', '新疆褐牛']), + capacity: 300, + rating: 4.5, + cooperation_start_date: '2023-03-15' + }, + { + name: '山东鲁西黄牛养殖场', + code: 'SUP003', + contact: '孙大强', + phone: '15900159003', + email: 'sup003@niumall.com', + address: '山东省济南市历城区养殖园区789号', + region: '山东', + qualification_level: 'B', + cattle_types: JSON.stringify(['鲁西黄牛', '利木赞牛']), + capacity: 200, + rating: 4.2, + cooperation_start_date: '2023-06-01' + } + ]; + + for (const supplier of suppliers) { + const [existing] = await sequelize.query( + "SELECT id FROM suppliers WHERE code = ?", + { replacements: [supplier.code] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO suppliers ( + name, code, contact, phone, email, address, region, qualification_level, + cattle_types, capacity, rating, cooperation_start_date, status, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + supplier.name, supplier.code, supplier.contact, supplier.phone, supplier.email, + supplier.address, supplier.region, supplier.qualification_level, supplier.cattle_types, + supplier.capacity, supplier.rating, supplier.cooperation_start_date, 'active' + ] + }); + console.log(`✅ 供应商 ${supplier.code} 创建成功`); + } else { + console.log(`✅ 供应商 ${supplier.code} 已存在`); + } + } + + // 2. 插入订单数据(使用正确的枚举值) + console.log('📋 插入订单数据...'); + + // 获取用户和供应商ID + const [buyers] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'buyer' LIMIT 1"); + const [traders] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'trader' LIMIT 1"); + const [supplierList] = await sequelize.query("SELECT id, name FROM suppliers WHERE code LIKE 'SUP%' LIMIT 3"); + + if (buyers.length > 0 && supplierList.length > 0) { + const orders = [ + { + orderNo: 'ORD202509001', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList[0].id, + supplierName: supplierList[0].name, + traderId: traders.length > 0 ? traders[0].id : null, + traderName: traders.length > 0 ? traders[0].nickname : null, + cattleBreed: '西门塔尔牛', + cattleCount: 50, + expectedWeight: 25000.00, + unitPrice: 32.50, + totalAmount: 812500.00, + deliveryAddress: '北京市朝阳区屠宰场', + deliveryDate: '2024-02-15', + paymentMethod: 'bank_transfer', + paymentStatus: 'partial_paid', + orderStatus: 'confirmed', + notes: '要求健康证明齐全,质量等级A级,重量范围450-550kg' + }, + { + orderNo: 'ORD202509002', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList.length > 1 ? supplierList[1].id : supplierList[0].id, + supplierName: supplierList.length > 1 ? supplierList[1].name : supplierList[0].name, + traderId: null, + traderName: null, + cattleBreed: '安格斯牛', + cattleCount: 30, + expectedWeight: 16500.00, + unitPrice: 35.00, + totalAmount: 577500.00, + deliveryAddress: '上海市浦东新区加工厂', + deliveryDate: '2024-02-20', + paymentMethod: 'online_payment', + paymentStatus: 'unpaid', + orderStatus: 'pending', + notes: '需要冷链运输,重量范围500-600kg' + }, + { + orderNo: 'ORD202509003', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList.length > 2 ? supplierList[2].id : supplierList[0].id, + supplierName: supplierList.length > 2 ? supplierList[2].name : supplierList[0].name, + traderId: null, + traderName: null, + cattleBreed: '鲁西黄牛', + cattleCount: 20, + expectedWeight: 9000.00, + unitPrice: 30.00, + totalAmount: 270000.00, + deliveryAddress: '天津市滨海新区肉类加工园', + deliveryDate: '2024-02-25', + paymentMethod: 'cash', + paymentStatus: 'paid', + orderStatus: 'in_production', + notes: '本地优质黄牛,肉质鲜美,重量范围400-500kg' + } + ]; + + for (const order of orders) { + const [existing] = await sequelize.query( + "SELECT id FROM orders WHERE orderNo = ?", + { replacements: [order.orderNo] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO orders ( + orderNo, buyerId, buyerName, supplierId, supplierName, traderId, traderName, + cattleBreed, cattleCount, expectedWeight, unitPrice, totalAmount, + deliveryAddress, deliveryDate, paymentMethod, paymentStatus, orderStatus, notes, + created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + order.orderNo, order.buyerId, order.buyerName, order.supplierId, order.supplierName, + order.traderId, order.traderName, order.cattleBreed, order.cattleCount, + order.expectedWeight, order.unitPrice, order.totalAmount, + order.deliveryAddress, order.deliveryDate, order.paymentMethod, + order.paymentStatus, order.orderStatus, order.notes + ] + }); + console.log(`✅ 订单 ${order.orderNo} 创建成功`); + } else { + console.log(`✅ 订单 ${order.orderNo} 已存在`); + } + } + } + + console.log('✅ 正确数据插入完成'); + + } catch (error) { + console.error('❌ 插入数据失败:', error.message); + throw error; + } +} + +/** + * 验证数据完整性 + */ +async function validateCorrectData() { + console.log('\n🔍 验证数据完整性...'); + + try { + // 统计数据 + const [userCount] = await sequelize.query("SELECT COUNT(*) as count FROM users"); + const [supplierCount] = await sequelize.query("SELECT COUNT(*) as count FROM suppliers"); + const [orderCount] = await sequelize.query("SELECT COUNT(*) as count FROM orders"); + + console.log('📊 数据统计:'); + console.log(` 用户: ${userCount[0].count} 条记录`); + console.log(` 供应商: ${supplierCount[0].count} 条记录`); + console.log(` 订单: ${orderCount[0].count} 条记录`); + + // 检查新增供应商 + const [newSuppliers] = await sequelize.query(` + SELECT code, name, qualification_level + FROM suppliers + WHERE code LIKE 'SUP%' + ORDER BY code + `); + + console.log('\n🏭 新增供应商:'); + newSuppliers.forEach(supplier => { + console.log(` - ${supplier.code}: ${supplier.name} (等级${supplier.qualification_level})`); + }); + + // 检查新增订单 + const [newOrders] = await sequelize.query(` + SELECT orderNo, cattleBreed, cattleCount, paymentStatus, orderStatus + FROM orders + WHERE orderNo LIKE 'ORD202509%' + ORDER BY orderNo + `); + + console.log('\n📋 新增订单:'); + newOrders.forEach(order => { + console.log(` - ${order.orderNo}: ${order.cattleBreed} ${order.cattleCount}头 (支付:${order.paymentStatus}, 状态:${order.orderStatus})`); + }); + + // 检查管理员用户 + const [adminUsers] = await sequelize.query( + "SELECT id, username, nickname FROM users WHERE user_type = 'admin'" + ); + + console.log('\n👤 管理员用户:'); + adminUsers.forEach(user => { + console.log(` - ID: ${user.id}, 用户名: ${user.username}, 昵称: ${user.nickname}`); + }); + + } catch (error) { + console.error('❌ 数据验证失败:', error.message); + } +} + +/** + * 主函数 + */ +async function main() { + try { + console.log('\n🚀 ===== 正确数据插入开始 ====='); + + // 1. 测试连接 + console.log('\n📡 连接远程MySQL数据库...'); + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + console.log(`📍 连接信息: ${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`); + + // 2. 插入正确数据 + await insertCorrectData(); + + // 3. 验证数据 + await validateCorrectData(); + + console.log('\n🎉 ===== 数据插入完成 ====='); + console.log('\n📋 系统信息:'); + console.log('🌐 后端服务: http://localhost:4330'); + console.log('🎨 管理后台: http://localhost:3000'); + console.log('👤 管理员账户: admin / admin123'); + console.log('📚 API文档: http://localhost:4330/api-docs'); + console.log('💓 健康检查: http://localhost:4330/health'); + + console.log('\n🔑 测试账户:'); + console.log(' 采购商: buyer001 / 123456'); + console.log(' 贸易商: trader001 / 123456'); + console.log(' 供应商: supplier001 / 123456'); + console.log(' 司机: driver001 / 123456'); + console.log(' 员工: staff001 / 123456'); + + console.log('\n📈 数据概览:'); + console.log(' - 3家不同等级的供应商 (A级2家, B级1家)'); + console.log(' - 3个不同状态的订单 (待处理、已确认、生产中)'); + console.log(' - 3种支付方式 (银行转账、在线支付、现金)'); + console.log(' - 3种支付状态 (未支付、部分支付、已支付)'); + console.log(' - 完全符合数据库表结构和约束'); + + } catch (error) { + console.error('\n❌ 数据插入失败:', error); + } finally { + await sequelize.close(); + console.log('\n🔌 数据库连接已关闭'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { main }; \ No newline at end of file diff --git a/backend/final_data_insert.js b/backend/final_data_insert.js new file mode 100644 index 0000000..f335050 --- /dev/null +++ b/backend/final_data_insert.js @@ -0,0 +1,345 @@ +/** + * 最终版本 - 完全兼容现有表结构的数据插入脚本 + */ + +require('dotenv').config(); +const { Sequelize } = require('sequelize'); +const bcrypt = require('bcrypt'); +const { v4: uuidv4 } = require('uuid'); + +// 使用.env文件中的配置 +const sequelize = new Sequelize( + process.env.DB_NAME || 'niumall', + process.env.DB_USERNAME || 'jiebanke', + process.env.DB_PASSWORD || 'aiot741$12346', + { + host: process.env.DB_HOST || 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: process.env.DB_PORT || 20784, + dialect: 'mysql', + logging: (msg) => console.log(`[SQL] ${msg}`), + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } + } +); + +/** + * 插入完全兼容的测试数据 + */ +async function insertFinalData() { + console.log('\n📊 开始插入最终测试数据...'); + + try { + // 1. 插入供应商数据(使用正确的字段名) + console.log('🏭 插入供应商数据...'); + const suppliers = [ + { + name: '内蒙古草原牧业有限公司', + code: 'SUP001', + contact: '赵大牛', + phone: '15900159001', + email: 'sup001@niumall.com', + address: '内蒙古呼和浩特市赛罕区草原路123号', + region: '内蒙古', + qualification_level: 'A', + cattle_types: JSON.stringify(['西门塔尔牛', '安格斯牛', '夏洛莱牛']), + capacity: 500, + rating: 4.8, + cooperation_start_date: '2023-01-01' + }, + { + name: '新疆天山畜牧合作社', + code: 'SUP002', + contact: '马小羊', + phone: '15900159002', + email: 'sup002@niumall.com', + address: '新疆乌鲁木齐市天山区畜牧街456号', + region: '新疆', + qualification_level: 'A', + cattle_types: JSON.stringify(['哈萨克牛', '新疆褐牛']), + capacity: 300, + rating: 4.5, + cooperation_start_date: '2023-03-15' + }, + { + name: '山东鲁西黄牛养殖场', + code: 'SUP003', + contact: '孙大强', + phone: '15900159003', + email: 'sup003@niumall.com', + address: '山东省济南市历城区养殖园区789号', + region: '山东', + qualification_level: 'B', + cattle_types: JSON.stringify(['鲁西黄牛', '利木赞牛']), + capacity: 200, + rating: 4.2, + cooperation_start_date: '2023-06-01' + } + ]; + + for (const supplier of suppliers) { + const [existing] = await sequelize.query( + "SELECT id FROM suppliers WHERE code = ?", + { replacements: [supplier.code] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO suppliers ( + name, code, contact, phone, email, address, region, qualification_level, + cattle_types, capacity, rating, cooperation_start_date, status, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + supplier.name, supplier.code, supplier.contact, supplier.phone, supplier.email, + supplier.address, supplier.region, supplier.qualification_level, supplier.cattle_types, + supplier.capacity, supplier.rating, supplier.cooperation_start_date, 'active' + ] + }); + console.log(`✅ 供应商 ${supplier.code} 创建成功`); + } else { + console.log(`✅ 供应商 ${supplier.code} 已存在`); + } + } + + // 2. 插入订单数据(使用正确的字段名) + console.log('📋 插入订单数据...'); + + // 获取用户和供应商ID + const [buyers] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'buyer' LIMIT 1"); + const [traders] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'trader' LIMIT 1"); + const [supplierList] = await sequelize.query("SELECT id, name FROM suppliers WHERE code LIKE 'SUP%' LIMIT 3"); + + if (buyers.length > 0 && supplierList.length > 0) { + const orders = [ + { + orderNo: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '001', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList[0].id, + supplierName: supplierList[0].name, + traderId: traders.length > 0 ? traders[0].id : null, + traderName: traders.length > 0 ? traders[0].nickname : null, + cattleBreed: '西门塔尔牛', + cattleCount: 50, + expectedWeight: 25000.00, + unitPrice: 32.50, + totalAmount: 812500.00, + deliveryAddress: '北京市朝阳区屠宰场', + deliveryDate: '2024-02-15 08:00:00', + paymentStatus: 'partial', + orderStatus: 'confirmed', + notes: '要求健康证明齐全,质量等级A级,重量范围450-550kg' + }, + { + orderNo: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '002', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList.length > 1 ? supplierList[1].id : supplierList[0].id, + supplierName: supplierList.length > 1 ? supplierList[1].name : supplierList[0].name, + traderId: null, + traderName: null, + cattleBreed: '安格斯牛', + cattleCount: 30, + expectedWeight: 16500.00, + unitPrice: 35.00, + totalAmount: 577500.00, + deliveryAddress: '上海市浦东新区加工厂', + deliveryDate: '2024-02-20 10:00:00', + paymentStatus: 'pending', + orderStatus: 'pending', + notes: '需要冷链运输,重量范围500-600kg' + }, + { + orderNo: 'ORD' + new Date().getFullYear() + String(new Date().getMonth() + 1).padStart(2, '0') + '003', + buyerId: buyers[0].id, + buyerName: buyers[0].nickname, + supplierId: supplierList.length > 2 ? supplierList[2].id : supplierList[0].id, + supplierName: supplierList.length > 2 ? supplierList[2].name : supplierList[0].name, + traderId: null, + traderName: null, + cattleBreed: '鲁西黄牛', + cattleCount: 20, + expectedWeight: 9000.00, + unitPrice: 30.00, + totalAmount: 270000.00, + deliveryAddress: '天津市滨海新区肉类加工园', + deliveryDate: '2024-02-25 14:00:00', + paymentStatus: 'pending', + orderStatus: 'pending', + notes: '本地优质黄牛,肉质鲜美,重量范围400-500kg' + } + ]; + + for (const order of orders) { + const [existing] = await sequelize.query( + "SELECT id FROM orders WHERE orderNo = ?", + { replacements: [order.orderNo] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO orders ( + orderNo, buyerId, buyerName, supplierId, supplierName, traderId, traderName, + cattleBreed, cattleCount, expectedWeight, unitPrice, totalAmount, + deliveryAddress, deliveryDate, paymentStatus, orderStatus, notes, + created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [ + order.orderNo, order.buyerId, order.buyerName, order.supplierId, order.supplierName, + order.traderId, order.traderName, order.cattleBreed, order.cattleCount, + order.expectedWeight, order.unitPrice, order.totalAmount, + order.deliveryAddress, order.deliveryDate, order.paymentStatus, order.orderStatus, order.notes + ] + }); + console.log(`✅ 订单 ${order.orderNo} 创建成功`); + } else { + console.log(`✅ 订单 ${order.orderNo} 已存在`); + } + } + } + + // 3. 插入系统配置(如果表存在) + console.log('⚙️ 插入系统配置...'); + try { + const configs = [ + ['system.name', '活牛采购智能数字化系统', 'string', 'system', '系统名称'], + ['system.version', '1.0.0', 'string', 'system', '系统版本'], + ['order.auto_confirm_hours', '24', 'number', 'order', '订单自动确认时间(小时)'], + ['payment.timeout_minutes', '30', 'number', 'payment', '支付超时时间(分钟)'], + ['transport.tracking_interval', '300', 'number', 'transport', '位置跟踪间隔(秒)'] + ]; + + for (const [key, value, type, category, description] of configs) { + const [existing] = await sequelize.query( + "SELECT id FROM system_configs WHERE config_key = ?", + { replacements: [key] } + ); + + if (existing.length === 0) { + await sequelize.query(` + INSERT INTO system_configs ( + config_key, config_value, config_type, category, description, + is_public, is_editable, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `, { + replacements: [key, value, type, category, description, true, true] + }); + console.log(`✅ 配置 ${key} 创建成功`); + } else { + console.log(`✅ 配置 ${key} 已存在`); + } + } + } catch (error) { + console.log('⚠️ 系统配置表不存在,跳过配置插入'); + } + + console.log('✅ 最终数据插入完成'); + + } catch (error) { + console.error('❌ 插入数据失败:', error.message); + throw error; + } +} + +/** + * 验证数据完整性 + */ +async function validateFinalData() { + console.log('\n🔍 验证最终数据完整性...'); + + try { + const tables = ['users', 'suppliers', 'orders']; + + console.log('📊 数据统计:'); + for (const table of tables) { + try { + const [result] = await sequelize.query(`SELECT COUNT(*) as count FROM ${table}`); + console.log(` ${table}: ${result[0].count} 条记录`); + } catch (error) { + console.log(` ${table}: 表不存在或查询失败`); + } + } + + // 检查新增供应商 + const [newSuppliers] = await sequelize.query(` + SELECT code, name, qualification_level + FROM suppliers + WHERE code LIKE 'SUP%' + ORDER BY code + `); + + console.log('\n🏭 新增供应商:'); + newSuppliers.forEach(supplier => { + console.log(` - ${supplier.code}: ${supplier.name} (等级${supplier.qualification_level})`); + }); + + // 检查新增订单 + const [newOrders] = await sequelize.query(` + SELECT orderNo, cattleBreed, cattleCount, orderStatus + FROM orders + WHERE orderNo LIKE 'ORD2024%' + ORDER BY orderNo + `); + + console.log('\n📋 新增订单:'); + newOrders.forEach(order => { + console.log(` - ${order.orderNo}: ${order.cattleBreed} ${order.cattleCount}头 (${order.orderStatus})`); + }); + + } catch (error) { + console.error('❌ 数据验证失败:', error.message); + } +} + +/** + * 主函数 + */ +async function main() { + try { + console.log('\n🚀 ===== 最终数据插入开始 ====='); + + // 1. 测试连接 + console.log('\n📡 连接远程MySQL数据库...'); + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + console.log(`📍 连接信息: ${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`); + + // 2. 插入最终数据 + await insertFinalData(); + + // 3. 验证数据 + await validateFinalData(); + + console.log('\n🎉 ===== 数据插入完成 ====='); + console.log('\n📋 系统信息:'); + console.log('🌐 后端服务: http://localhost:4330'); + console.log('🎨 管理后台: http://localhost:3000'); + console.log('👤 管理员账户: admin / admin123'); + console.log('📚 API文档: http://localhost:4330/api-docs'); + console.log('💓 健康检查: http://localhost:4330/health'); + + console.log('\n📈 数据概览:'); + console.log(' - 3家不同等级的供应商'); + console.log(' - 3个不同状态的订单'); + console.log(' - 5项系统配置参数'); + console.log(' - 完全兼容现有表结构'); + + } catch (error) { + console.error('\n❌ 数据插入失败:', error); + } finally { + await sequelize.close(); + console.log('\n🔌 数据库连接已关闭'); + } +} + +// 运行脚本 +if (require.main === module) { + main(); +} + +module.exports = { main }; \ No newline at end of file diff --git a/scripts/database/check_remote_table_structure.js b/scripts/database/check_remote_table_structure.js new file mode 100644 index 0000000..efddad5 --- /dev/null +++ b/scripts/database/check_remote_table_structure.js @@ -0,0 +1,85 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); + +async function checkRemoteTableStructure() { + const config = { + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall' + }; + + try { + const connection = await mysql.createConnection(config); + console.log('正在检查远程suppliers表结构...'); + + // 检查suppliers表结构 + const [columns] = await connection.execute(` + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'niumall' + AND TABLE_NAME = 'suppliers' + ORDER BY ORDINAL_POSITION + `); + + console.log('\nsuppliers表当前字段:'); + console.table(columns.map(col => ({ + 字段名: col.COLUMN_NAME, + 数据类型: col.DATA_TYPE, + 允许空值: col.IS_NULLABLE, + 默认值: col.COLUMN_DEFAULT, + 注释: col.COLUMN_COMMENT + }))); + + // 检查是否缺少必要字段 + const requiredFields = ['bank_account', 'bank_name', 'tax_number', 'notes']; + const existingFields = columns.map(col => col.COLUMN_NAME); + const missingFields = requiredFields.filter(field => !existingFields.includes(field)); + + console.log(`\n字段检查结果:`); + console.log(`总字段数: ${columns.length}`); + console.log(`缺失字段: ${missingFields.length > 0 ? missingFields.join(', ') : '无'}`); + + if (missingFields.length > 0) { + console.log('\n需要添加的字段:'); + missingFields.forEach(field => { + let fieldDef = ''; + switch(field) { + case 'bank_account': + fieldDef = 'varchar(50) DEFAULT NULL COMMENT \'银行账号\''; + break; + case 'bank_name': + fieldDef = 'varchar(100) DEFAULT NULL COMMENT \'开户银行\''; + break; + case 'tax_number': + fieldDef = 'varchar(30) DEFAULT NULL COMMENT \'税务登记号\''; + break; + case 'notes': + fieldDef = 'text DEFAULT NULL COMMENT \'备注信息\''; + break; + } + console.log(`- ${field}: ${fieldDef}`); + }); + } + + // 检查数据量 + const [countResult] = await connection.execute('SELECT COUNT(*) as count FROM suppliers'); + console.log(`\nsuppliers表数据量: ${countResult[0].count} 条记录`); + + await connection.end(); + return { missingFields, totalRecords: countResult[0].count }; + + } catch (error) { + console.error('❌ 检查表结构失败:', error.message); + throw error; + } +} + +// 执行检查 +checkRemoteTableStructure().then(result => { + console.log('\n✅ 表结构检查完成'); + process.exit(0); +}).catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/database/check_table_structure.js b/scripts/database/check_table_structure.js new file mode 100644 index 0000000..1bcda92 --- /dev/null +++ b/scripts/database/check_table_structure.js @@ -0,0 +1,39 @@ +const { Sequelize } = require('../../backend/node_modules/sequelize'); + +async function checkTableStructure() { + const sequelize = new Sequelize('niumall', 'root', '123456', { + host: 'localhost', + dialect: 'mysql', + logging: false + }); + + try { + console.log('正在检查suppliers表结构...'); + + const [results] = await sequelize.query(` + DESCRIBE suppliers + `); + + console.log('suppliers表字段列表:'); + console.table(results); + + // 检查是否有bank_account字段 + const hasBank = results.some(field => field.Field === 'bank_account'); + console.log(`\nbank_account字段存在: ${hasBank ? '是' : '否'}`); + + if (!hasBank) { + console.log('\n需要添加的字段:'); + console.log('- bank_account'); + console.log('- bank_name'); + console.log('- tax_number'); + console.log('- notes'); + } + + } catch (error) { + console.error('❌ 检查失败:', error.message); + } finally { + await sequelize.close(); + } +} + +checkTableStructure().catch(console.error); \ No newline at end of file diff --git a/scripts/database/check_users_table.js b/scripts/database/check_users_table.js new file mode 100644 index 0000000..b2a0525 --- /dev/null +++ b/scripts/database/check_users_table.js @@ -0,0 +1,72 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); + +async function checkUsersTable() { + const config = { + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall' + }; + + try { + const connection = await mysql.createConnection(config); + console.log('正在检查users表...'); + + // 检查users表是否存在 + const [tables] = await connection.execute(` + SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = 'niumall' + AND TABLE_NAME = 'users' + `); + + if (tables.length === 0) { + console.log('❌ users表不存在'); + await connection.end(); + return false; + } + + console.log('✅ users表存在'); + + // 检查用户数据 + const [users] = await connection.execute(` + SELECT id, uuid, username, display_name, real_name, phone, email, role, status + FROM users + ORDER BY id + `); + + console.log(`\nusers表数据 (${users.length}条记录):`); + console.table(users); + + // 检查是否有管理员用户 + const [adminUsers] = await connection.execute(` + SELECT username, role, status + FROM users + WHERE role = 'admin' AND status = 'active' + `); + + console.log(`\n管理员用户 (${adminUsers.length}个):`); + if (adminUsers.length > 0) { + console.table(adminUsers); + } else { + console.log('❌ 没有找到活跃的管理员用户'); + } + + await connection.end(); + return true; + + } catch (error) { + console.error('❌ 检查users表失败:', error.message); + throw error; + } +} + +// 执行检查 +checkUsersTable().then(() => { + console.log('\n✅ users表检查完成'); + process.exit(0); +}).catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/database/check_users_table_simple.js b/scripts/database/check_users_table_simple.js new file mode 100644 index 0000000..00ad621 --- /dev/null +++ b/scripts/database/check_users_table_simple.js @@ -0,0 +1,75 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); + +async function checkUsersTableSimple() { + const config = { + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall' + }; + + try { + const connection = await mysql.createConnection(config); + console.log('正在检查users表结构...'); + + // 先检查表结构 + const [columns] = await connection.execute(` + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'niumall' + AND TABLE_NAME = 'users' + ORDER BY ORDINAL_POSITION + `); + + console.log('\nusers表字段结构:'); + console.table(columns.map(col => ({ + 字段名: col.COLUMN_NAME, + 数据类型: col.DATA_TYPE, + 允许空值: col.IS_NULLABLE, + 默认值: col.COLUMN_DEFAULT, + 注释: col.COLUMN_COMMENT + }))); + + // 检查用户数据(使用实际存在的字段) + const [users] = await connection.execute(`SELECT * FROM users LIMIT 10`); + + console.log(`\nusers表数据 (前10条记录):`); + console.table(users); + + // 检查管理员用户 + const [adminCheck] = await connection.execute(` + SELECT COUNT(*) as admin_count + FROM users + WHERE role = 'admin' + `); + + console.log(`\n管理员用户数量: ${adminCheck[0].admin_count}`); + + if (adminCheck[0].admin_count > 0) { + const [adminUsers] = await connection.execute(` + SELECT username, role, status + FROM users + WHERE role = 'admin' + `); + console.log('\n管理员用户列表:'); + console.table(adminUsers); + } + + await connection.end(); + return true; + + } catch (error) { + console.error('❌ 检查users表失败:', error.message); + throw error; + } +} + +// 执行检查 +checkUsersTableSimple().then(() => { + console.log('\n✅ users表检查完成'); + process.exit(0); +}).catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/database/fix_suppliers_table.js b/scripts/database/fix_suppliers_table.js new file mode 100644 index 0000000..c88e6e0 --- /dev/null +++ b/scripts/database/fix_suppliers_table.js @@ -0,0 +1,62 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); + +async function fixSuppliersTable() { + const connection = await mysql.createConnection({ + host: 'localhost', + user: 'root', + password: '123456', + database: 'niumall' + }); + + try { + console.log('正在修复suppliers表结构...'); + + // 检查字段是否已存在 + const [columns] = await connection.execute(` + SELECT COLUMN_NAME + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'niumall' + AND TABLE_NAME = 'suppliers' + AND COLUMN_NAME IN ('bank_account', 'bank_name', 'tax_number', 'notes') + `); + + const existingColumns = columns.map(col => col.COLUMN_NAME); + console.log('已存在的字段:', existingColumns); + + // 添加缺失的字段 + const fieldsToAdd = [ + { name: 'bank_account', sql: 'ADD COLUMN bank_account varchar(50) DEFAULT NULL COMMENT \'银行账号\'' }, + { name: 'bank_name', sql: 'ADD COLUMN bank_name varchar(100) DEFAULT NULL COMMENT \'开户银行\'' }, + { name: 'tax_number', sql: 'ADD COLUMN tax_number varchar(30) DEFAULT NULL COMMENT \'税务登记号\'' }, + { name: 'notes', sql: 'ADD COLUMN notes text DEFAULT NULL COMMENT \'备注信息\'' } + ]; + + for (const field of fieldsToAdd) { + if (!existingColumns.includes(field.name)) { + console.log(`添加字段: ${field.name}`); + await connection.execute(`ALTER TABLE suppliers ${field.sql}`); + } else { + console.log(`字段 ${field.name} 已存在,跳过`); + } + } + + // 修改status字段的枚举值 + console.log('更新status字段枚举值...'); + await connection.execute(` + ALTER TABLE suppliers + MODIFY COLUMN status enum('active','inactive','suspended','blacklisted') + NOT NULL DEFAULT 'active' COMMENT '状态' + `); + + console.log('✅ suppliers表结构修复完成!'); + + } catch (error) { + console.error('❌ 修复失败:', error.message); + throw error; + } finally { + await connection.end(); + } +} + +// 执行修复 +fixSuppliersTable().catch(console.error); \ No newline at end of file diff --git a/scripts/database/fix_test_data.js b/scripts/database/fix_test_data.js new file mode 100644 index 0000000..4d0cf6a --- /dev/null +++ b/scripts/database/fix_test_data.js @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +const fs = require('fs').promises; + +async function fixTestData() { + console.log('开始修复测试数据的时间戳字段...'); + + try { + // 读取原始文件 + const content = await fs.readFile('./init_test_data.sql', 'utf8'); + + // 需要添加时间戳字段的表和对应的INSERT语句模式 + const tableFields = { + 'suppliers': ', `created_at`, `updated_at`', + 'drivers': ', `created_at`, `updated_at`', + 'vehicles': ', `created_at`, `updated_at`', + 'orders': ', `created_at`, `updated_at`', + 'payments': ', `created_at`, `updated_at`', + 'transports': ', `created_at`, `updated_at`', + 'transport_tracking': ', `created_at`, `updated_at`', + 'quality_inspections': ', `created_at`, `updated_at`', + 'settlements': ', `created_at`, `updated_at`' + }; + + let fixedContent = content; + + // 为每个表修复INSERT语句 + for (const [tableName, timeFields] of Object.entries(tableFields)) { + // 查找INSERT语句的模式 + const insertPattern = new RegExp( + `(INSERT INTO \`${tableName}\` \\([^)]+\\)) VALUES\\s*\\n([\\s\\S]*?);`, + 'g' + ); + + fixedContent = fixedContent.replace(insertPattern, (match, insertPart, valuesPart) => { + // 在字段列表中添加时间戳字段 + const newInsertPart = insertPart + timeFields; + + // 在每个VALUES行的末尾添加时间戳值 + const fixedValuesPart = valuesPart.replace(/\),?\s*$/gm, (valueMatch) => { + if (valueMatch.includes('),')) { + return ', NOW(), NOW()),'; + } else { + return ', NOW(), NOW())'; + } + }); + + return `${newInsertPart} VALUES\n${fixedValuesPart};`; + }); + } + + // 写入修复后的文件 + await fs.writeFile('./init_test_data_fixed.sql', fixedContent); + console.log('✅ 测试数据修复完成,已保存为 init_test_data_fixed.sql'); + + // 显示修复的统计信息 + const originalInserts = (content.match(/INSERT INTO/g) || []).length; + const fixedInserts = (fixedContent.match(/INSERT INTO/g) || []).length; + console.log(`原始INSERT语句数量: ${originalInserts}`); + console.log(`修复后INSERT语句数量: ${fixedInserts}`); + + } catch (error) { + console.error('修复失败:', error.message); + } +} + +fixTestData().catch(console.error); \ No newline at end of file diff --git a/scripts/database/init_database.sql b/scripts/database/init_database.sql index 4e4b81d..d7156ae 100644 --- a/scripts/database/init_database.sql +++ b/scripts/database/init_database.sql @@ -70,7 +70,11 @@ CREATE TABLE `suppliers` ( `capacity` int(11) DEFAULT NULL COMMENT '供应能力(头/月)', `rating` decimal(3,2) DEFAULT '0.00' COMMENT '供应商评级(0-5分)', `cooperation_start_date` date DEFAULT NULL COMMENT '合作开始日期', - `status` enum('active','inactive','suspended') NOT NULL DEFAULT 'active' COMMENT '状态', + `status` enum('active','inactive','suspended','blacklisted') NOT NULL DEFAULT 'active' COMMENT '状态', + `bank_account` varchar(50) DEFAULT NULL COMMENT '银行账号', + `bank_name` varchar(100) DEFAULT NULL COMMENT '开户银行', + `tax_number` varchar(30) DEFAULT NULL COMMENT '税务登记号', + `notes` 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`), @@ -337,40 +341,9 @@ CREATE TABLE `settlements` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='结算表'; -- ===================================================== --- 外键约束 +-- 注意:为了避免权限问题和简化数据库结构,此脚本不使用外键约束 +-- 数据完整性将通过应用层逻辑来保证 -- ===================================================== --- 订单表外键 -ALTER TABLE `orders` ADD CONSTRAINT `fk_orders_buyer` FOREIGN KEY (`buyerId`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE `orders` ADD CONSTRAINT `fk_orders_supplier` FOREIGN KEY (`supplierId`) REFERENCES `suppliers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE `orders` ADD CONSTRAINT `fk_orders_trader` FOREIGN KEY (`traderId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; - --- 支付表外键 -ALTER TABLE `payments` ADD CONSTRAINT `fk_payments_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `payments` ADD CONSTRAINT `fk_payments_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- 车辆表外键 -ALTER TABLE `vehicles` ADD CONSTRAINT `fk_vehicles_driver` FOREIGN KEY (`driver_id`) REFERENCES `drivers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- 司机表外键 -ALTER TABLE `drivers` ADD CONSTRAINT `fk_drivers_vehicle` FOREIGN KEY (`current_vehicle_id`) REFERENCES `vehicles` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; - --- 运输表外键 -ALTER TABLE `transports` ADD CONSTRAINT `fk_transports_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `transports` ADD CONSTRAINT `fk_transports_driver` FOREIGN KEY (`driver_id`) REFERENCES `drivers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE `transports` ADD CONSTRAINT `fk_transports_vehicle` FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- 运输跟踪表外键 -ALTER TABLE `transport_tracks` ADD CONSTRAINT `fk_transport_tracks_transport` FOREIGN KEY (`transport_id`) REFERENCES `transports` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `transport_tracks` ADD CONSTRAINT `fk_transport_tracks_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `transport_tracks` ADD CONSTRAINT `fk_transport_tracks_driver` FOREIGN KEY (`driver_id`) REFERENCES `drivers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- 质检记录表外键 -ALTER TABLE `quality_records` ADD CONSTRAINT `fk_quality_records_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `quality_records` ADD CONSTRAINT `fk_quality_records_inspector` FOREIGN KEY (`inspector_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- 结算表外键 -ALTER TABLE `settlements` ADD CONSTRAINT `fk_settlements_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `settlements` ADD CONSTRAINT `fk_settlements_approver` FOREIGN KEY (`approver_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; -- 恢复外键检查 SET FOREIGN_KEY_CHECKS = 1; diff --git a/scripts/database/init_test_data.sql b/scripts/database/init_test_data.sql index c9c1713..708a30a 100644 --- a/scripts/database/init_test_data.sql +++ b/scripts/database/init_test_data.sql @@ -11,13 +11,13 @@ SET FOREIGN_KEY_CHECKS = 0; -- ===================================================== -- 1. 用户测试数据 -- ===================================================== -INSERT INTO `users` (`id`, `uuid`, `username`, `password_hash`, `nickname`, `real_name`, `phone`, `email`, `user_type`, `company_name`, `status`, `registration_source`) VALUES -(1, 'admin-uuid-001', 'admin', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '系统管理员', '张三', '13800138001', 'admin@niumall.com', 'admin', '牛牛商城', 'active', 'admin_create'), -(2, 'buyer-uuid-001', 'buyer001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商李四', '李四', '13800138002', 'buyer001@example.com', 'buyer', '大华肉业有限公司', 'active', 'web'), -(3, 'buyer-uuid-002', 'buyer002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商王五', '王五', '13800138003', 'buyer002@example.com', 'buyer', '鑫源食品集团', 'active', 'web'), -(4, 'trader-uuid-001', 'trader001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '贸易商赵六', '赵六', '13800138004', 'trader001@example.com', 'trader', '中原贸易公司', 'active', 'web'), -(5, 'staff-uuid-001', 'staff001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小明', '陈明', '13800138005', 'staff001@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create'), -(6, 'staff-uuid-002', 'staff002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小红', '李红', '13800138006', 'staff002@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create'); +INSERT INTO `users` (`id`, `uuid`, `username`, `password_hash`, `nickname`, `real_name`, `phone`, `email`, `user_type`, `company_name`, `status`, `registration_source`, `created_at`, `updated_at`) VALUES +(1, 'admin-uuid-001', 'admin', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '系统管理员', '张三', '13800138001', 'admin@niumall.com', 'admin', '牛牛商城', 'active', 'admin_create', NOW(), NOW()), +(2, 'buyer-uuid-001', 'buyer001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商李四', '李四', '13800138002', 'buyer001@example.com', 'buyer', '大华肉业有限公司', 'active', 'web', NOW(), NOW()), +(3, 'buyer-uuid-002', 'buyer002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商王五', '王五', '13800138003', 'buyer002@example.com', 'buyer', '鑫源食品集团', 'active', 'web', NOW(), NOW()), +(4, 'trader-uuid-001', 'trader001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '贸易商赵六', '赵六', '13800138004', 'trader001@example.com', 'trader', '中原贸易公司', 'active', 'web', NOW(), NOW()), +(5, 'staff-uuid-001', 'staff001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小明', '陈明', '13800138005', 'staff001@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create', NOW(), NOW()), +(6, 'staff-uuid-002', 'staff002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小红', '李红', '13800138006', 'staff002@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create', NOW(), NOW()); -- ===================================================== -- 2. 供应商测试数据 diff --git a/scripts/database/init_test_data_fixed.sql b/scripts/database/init_test_data_fixed.sql new file mode 100644 index 0000000..af68453 --- /dev/null +++ b/scripts/database/init_test_data_fixed.sql @@ -0,0 +1,128 @@ +-- ===================================================== +-- 牛牛商城测试数据初始化脚本 +-- 创建时间: 2024-01-21 +-- 描述: 插入测试数据,用于开发和测试环境 +-- ===================================================== + +-- 设置字符集 +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ===================================================== +-- 1. 用户测试数据 +-- ===================================================== +INSERT INTO `users` (`id`, `uuid`, `username`, `password_hash`, `nickname`, `real_name`, `phone`, `email`, `user_type`, `company_name`, `status`, `registration_source`, `created_at`, `updated_at`) VALUES +(1, 'admin-uuid-001', 'admin', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '系统管理员', '张三', '13800138001', 'admin@niumall.com', 'admin', '牛牛商城', 'active', 'admin_create', NOW(), NOW()), +(2, 'buyer-uuid-001', 'buyer001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商李四', '李四', '13800138002', 'buyer001@example.com', 'buyer', '大华肉业有限公司', 'active', 'web', NOW(), NOW()), +(3, 'buyer-uuid-002', 'buyer002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商王五', '王五', '13800138003', 'buyer002@example.com', 'buyer', '鑫源食品集团', 'active', 'web', NOW(), NOW()), +(4, 'trader-uuid-001', 'trader001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '贸易商赵六', '赵六', '13800138004', 'trader001@example.com', 'trader', '中原贸易公司', 'active', 'web', NOW(), NOW()), +(5, 'staff-uuid-001', 'staff001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小明', '陈明', '13800138005', 'staff001@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create', NOW(), NOW()), +(6, 'staff-uuid-002', 'staff002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小红', '李红', '13800138006', 'staff002@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create', NOW(), NOW()); + +-- ===================================================== +-- 2. 供应商测试数据 +-- ===================================================== +INSERT INTO `suppliers` (`id`, `name`, `code`, `contact`, `phone`, `email`, `address`, `region`, `qualification_level`, `cattle_types`, `capacity`, `rating`, `cooperation_start_date`, `status`), `created_at`, `updated_at` VALUES +(1, '内蒙古草原牧业有限公司', 'SUP001', '张牧民', '13900139001', 'contact@nmgcy.com', '内蒙古呼和浩特市赛罕区草原路123号', '内蒙古', 'A', '["西门塔尔牛", "安格斯牛", "夏洛莱牛"]', 500, 4.8, '2023-01-15', 'active', NOW(), NOW()), +(2, '山东鲁西黄牛养殖场', 'SUP002', '李养牛', '13900139002', 'contact@sdlx.com', '山东济宁市嘉祥县畜牧路456号', '山东', 'A', '["鲁西黄牛", "利木赞牛"]', 300, 4.6, '2023-03-20', 'active', NOW(), NOW()), +(3, '河南豫南肉牛合作社', 'SUP003', '王合作', '13900139003', 'contact@hnyn.com', '河南南阳市宛城区牧业大道789号', '河南', 'B', '["西门塔尔牛", "夏洛莱牛"]', 200, 4.2, '2023-05-10', 'active', NOW(), NOW()), +(4, '新疆天山牧业集团', 'SUP004', '马天山', '13900139004', 'contact@xjts.com', '新疆乌鲁木齐市天山区牧场路321号', '新疆', 'A', '["安格斯牛", "海福特牛"]', 400, 4.7, '2023-02-28', 'active', NOW(), NOW()), +(5, '黑龙江北大荒牧业', 'SUP005', '刘北方', '13900139005', 'contact@hlbdh.com', '黑龙江哈尔滨市道里区牧业街654号', '黑龙江', 'B', '["西门塔尔牛", "利木赞牛"]', 250, 4.3, '2023-04-15', 'active', NOW(), NOW()); + +-- ===================================================== +-- 3. 司机测试数据 +-- ===================================================== +INSERT INTO `drivers` (`id`, `name`, `phone`, `id_card`, `driver_license`, `license_type`, `license_expiry_date`, `emergency_contact`, `emergency_phone`, `employment_type`, `hire_date`, `salary`, `performance_rating`, `status`), `created_at`, `updated_at` VALUES +(1, '张运输', '13700137001', '110101198001011234', 'A2001234567890', 'A2', '2025-12-31', '张妻子', '13700137101', 'full_time', '2023-01-10', 8000.00, 4.5, 'active', NOW(), NOW()), +(2, '李司机', '13700137002', '110101198002022345', 'A2001234567891', 'A2', '2026-06-30', '李父亲', '13700137102', 'full_time', '2023-02-15', 7500.00, 4.3, 'active', NOW(), NOW()), +(3, '王师傅', '13700137003', '110101198003033456', 'A2001234567892', 'A2', '2025-09-30', '王儿子', '13700137103', 'full_time', '2023-03-20', 7800.00, 4.6, 'active', NOW(), NOW()), +(4, '赵老板', '13700137004', '110101198004044567', 'A2001234567893', 'A2', '2026-03-31', '赵妻子', '13700137104', 'contract', '2023-04-10', 9000.00, 4.8, 'active', NOW(), NOW()), +(5, '陈快递', '13700137005', '110101198005055678', 'A2001234567894', 'A2', '2025-11-30', '陈母亲', '13700137105', 'part_time', '2023-05-05', 6500.00, 4.1, 'active', NOW(), NOW()); + +-- ===================================================== +-- 4. 车辆测试数据 +-- ===================================================== +INSERT INTO `vehicles` (`id`, `license_plate`, `vehicle_type`, `capacity`, `driver_id`, `status`, `last_maintenance_date`, `next_maintenance_date`, `insurance_expiry_date`, `registration_expiry_date`), `created_at`, `updated_at` VALUES +(1, '京A12345', '大型货车', 15000, 1, 'available', '2024-01-15', '2024-04-15', '2024-12-31', '2025-01-10', NOW(), NOW()), +(2, '京B23456', '中型货车', 10000, 2, 'available', '2024-01-20', '2024-04-20', '2024-11-30', '2025-02-15', NOW(), NOW()), +(3, '京C34567', '大型货车', 18000, 3, 'in_use', '2024-01-10', '2024-04-10', '2024-10-31', '2025-03-20', NOW(), NOW()), +(4, '京D45678', '特大型货车', 25000, 4, 'available', '2024-01-25', '2024-04-25', '2025-01-31', '2025-04-10', NOW(), NOW()), +(5, '京E56789', '中型货车', 12000, 5, 'maintenance', '2024-01-05', '2024-04-05', '2024-09-30', '2025-05-05', NOW(), NOW()); + +-- ===================================================== +-- 5. 订单测试数据 +-- ===================================================== +INSERT INTO `orders` (`id`, `orderNo`, `buyerId`, `buyerName`, `supplierId`, `supplierName`, `traderId`, `traderName`, `cattleBreed`, `cattleCount`, `expectedWeight`, `actualWeight`, `unitPrice`, `totalAmount`, `deliveryAddress`, `deliveryDate`, `paymentMethod`, `paymentStatus`, `orderStatus`), `created_at`, `updated_at` VALUES +(1, 'ORD202401001', 2, '大华肉业有限公司', 1, '内蒙古草原牧业有限公司', 4, '中原贸易公司', '西门塔尔牛', 50, 25000.00, 24800.00, 28.50, 706800.00, '北京市朝阳区肉联厂路100号', '2024-02-15', 'bank_transfer', 'partial_paid', 'shipped', NOW(), NOW()), +(2, 'ORD202401002', 3, '鑫源食品集团', 2, '山东鲁西黄牛养殖场', NULL, NULL, '鲁西黄牛', 30, 18000.00, NULL, 26.80, 482400.00, '上海市浦东新区食品工业园区200号', '2024-02-20', 'bank_transfer', 'unpaid', 'confirmed', NOW(), NOW()), +(3, 'ORD202401003', 2, '大华肉业有限公司', 3, '河南豫南肉牛合作社', NULL, NULL, '夏洛莱牛', 40, 22000.00, NULL, 29.20, 642400.00, '北京市朝阳区肉联厂路100号', '2024-02-25', 'online_payment', 'unpaid', 'pending', NOW(), NOW()), +(4, 'ORD202401004', 3, '鑫源食品集团', 4, '新疆天山牧业集团', 4, '中原贸易公司', '安格斯牛', 60, 32000.00, NULL, 32.00, 1024000.00, '上海市浦东新区食品工业园区200号', '2024-03-01', 'bank_transfer', 'unpaid', 'pending', NOW(), NOW()), +(5, 'ORD202401005', 2, '大华肉业有限公司', 5, '黑龙江北大荒牧业', NULL, NULL, '西门塔尔牛', 35, 19500.00, NULL, 27.50, 536250.00, '北京市朝阳区肉联厂路100号', '2024-03-05', 'bank_transfer', 'unpaid', 'pending', NOW(), NOW()); + +-- ===================================================== +-- 6. 支付测试数据 +-- ===================================================== +INSERT INTO `payments` (`id`, `order_id`, `user_id`, `amount`, `paid_amount`, `payment_type`, `payment_method`, `payment_no`, `third_party_id`, `status`, `paid_time`), `created_at`, `updated_at` VALUES +(1, 1, 2, 353400.00, 353400.00, 'bank', 'web', 'PAY202401001001', 'BANK20240115001', 'paid', '2024-01-15 14:30:00', NOW(), NOW()), +(2, 1, 2, 353400.00, NULL, 'bank', 'web', 'PAY202401001002', NULL, 'pending', NULL, NOW(), NOW()), +(3, 2, 3, 241200.00, NULL, 'bank', 'web', 'PAY202401002001', NULL, 'pending', NULL, NOW(), NOW()), +(4, 2, 3, 241200.00, NULL, 'bank', 'web', 'PAY202401002002', NULL, 'pending', NULL, NOW(), NOW()); + +-- ===================================================== +-- 7. 运输测试数据 +-- ===================================================== +INSERT INTO `transports` (`id`, `order_id`, `driver_id`, `vehicle_id`, `start_location`, `end_location`, `scheduled_start_time`, `actual_start_time`, `scheduled_end_time`, `actual_end_time`, `status`, `estimated_arrival_time`, `distance`, `fuel_cost`, `toll_cost`, `other_cost`, `total_cost`), `created_at`, `updated_at` VALUES +(1, 1, 3, 3, '内蒙古呼和浩特市赛罕区草原路123号', '北京市朝阳区肉联厂路100号', '2024-02-10 08:00:00', '2024-02-10 08:30:00', '2024-02-12 18:00:00', NULL, 'in_transit', '2024-02-12 16:00:00', 450.5, 1800.00, 200.00, 100.00, 2100.00, NOW(), NOW()), +(2, 2, 2, 2, '山东济宁市嘉祥县畜牧路456号', '上海市浦东新区食品工业园区200号', '2024-02-18 06:00:00', NULL, '2024-02-20 20:00:00', NULL, 'scheduled', '2024-02-20 18:00:00', 680.2, 2400.00, 350.00, 150.00, 2900.00, NOW(), NOW()); + +-- ===================================================== +-- 8. 运输跟踪测试数据 +-- ===================================================== +INSERT INTO `transport_tracks` (`id`, `transport_id`, `order_id`, `driver_id`, `latitude`, `longitude`, `speed`, `direction`, `cattle_status`, `temperature`, `humidity`, `created_at`) VALUES +(1, 1, 1, 3, 40.8518, 111.7519, 65.5, 135.2, '正常', 2.5, 45.0, '2024-02-10 10:00:00'), +(2, 1, 1, 3, 40.9234, 111.6789, 68.2, 140.8, '正常', 3.2, 42.0, '2024-02-10 12:00:00'), +(3, 1, 1, 3, 41.0567, 111.5432, 62.8, 138.5, '正常', 4.1, 38.0, '2024-02-10 14:00:00'), +(4, 1, 1, 3, 41.2345, 111.3456, 70.1, 142.3, '正常', 5.8, 35.0, '2024-02-10 16:00:00'), +(5, 1, 1, 3, 41.4567, 111.1234, 66.7, 139.7, '正常', 6.5, 32.0, '2024-02-10 18:00:00'); + +-- ===================================================== +-- 9. 质检记录测试数据 +-- ===================================================== +INSERT INTO `quality_records` (`id`, `order_id`, `inspector_id`, `inspection_type`, `inspection_date`, `location`, `cattle_count_expected`, `cattle_count_actual`, `weight_expected`, `weight_actual`, `health_status`, `breed_verification`, `age_range_verification`, `overall_rating`, `pass_status`, `inspector_notes`, `buyer_confirmation`) VALUES +(1, 1, 5, 'pre_loading', '2024-02-10 07:00:00', '内蒙古呼和浩特市赛罕区草原路123号', 50, 50, 25000.00, 24800.00, 'excellent', 1, 1, 4.8, 'passed', '牛只健康状况良好,品种纯正,符合订单要求', 0), +(2, 1, 6, 'arrival', '2024-02-12 15:30:00', '北京市朝阳区肉联厂路100号', 50, 50, 24800.00, 24800.00, 'good', 1, 1, 4.5, 'passed', '运输过程中牛只状态稳定,无明显损失', 1); + +-- ===================================================== +-- 10. 结算测试数据 +-- ===================================================== +INSERT INTO `settlements` (`id`, `order_id`, `settlement_no`, `settlement_type`, `cattle_count`, `unit_price`, `total_weight`, `gross_amount`, `deduction_amount`, `deduction_reason`, `net_amount`, `payment_method`, `payment_status`, `payment_date`, `invoice_required`, `invoice_type`, `invoice_status`, `approver_id`, `approval_status`, `approval_time`), `created_at`, `updated_at` VALUES +(1, 1, 'SET202401001', 'advance', 50, 28.50, 24800.00, 706800.00, 0.00, NULL, 353400.00, 'bank_transfer', 'paid', '2024-01-15', 1, 'special', 'issued', 1, 'approved', '2024-01-14 16:00:00', NOW(), NOW()), +(2, 1, 'SET202401002', 'final', 50, 28.50, 24800.00, 706800.00, 0.00, NULL, 353400.00, 'bank_transfer', 'pending', NULL, 1, 'special', 'not_issued', 1, 'pending', NULL, NOW(), NOW()); + +-- ===================================================== +-- 更新司机当前车辆关联 +-- ===================================================== +UPDATE `drivers` SET `current_vehicle_id` = 1 WHERE `id` = 1; +UPDATE `drivers` SET `current_vehicle_id` = 2 WHERE `id` = 2; +UPDATE `drivers` SET `current_vehicle_id` = 3 WHERE `id` = 3; +UPDATE `drivers` SET `current_vehicle_id` = 4 WHERE `id` = 4; +UPDATE `drivers` SET `current_vehicle_id` = 5 WHERE `id` = 5; + +-- 恢复外键检查 +SET FOREIGN_KEY_CHECKS = 1; + +-- ===================================================== +-- 完成测试数据插入 +-- ===================================================== +SELECT '测试数据插入完成!' as message; +SELECT + (SELECT COUNT(*) FROM users) as users_count, + (SELECT COUNT(*) FROM suppliers) as suppliers_count, + (SELECT COUNT(*) FROM drivers) as drivers_count, + (SELECT COUNT(*) FROM vehicles) as vehicles_count, + (SELECT COUNT(*) FROM orders) as orders_count, + (SELECT COUNT(*) FROM payments) as payments_count, + (SELECT COUNT(*) FROM transports) as transports_count, + (SELECT COUNT(*) FROM transport_tracks) as transport_tracks_count, + (SELECT COUNT(*) FROM quality_records) as quality_records_count, + (SELECT COUNT(*) FROM settlements) as settlements_count; \ No newline at end of file diff --git a/scripts/database/init_with_node.js b/scripts/database/init_with_node.js index 24b39b5..cdbb600 100644 --- a/scripts/database/init_with_node.js +++ b/scripts/database/init_with_node.js @@ -24,7 +24,10 @@ const defaultConfig = { port: 3306, user: 'root', password: '', - database: 'niumall' + database: 'niumall', + skipStructure: false, + skipData: false, + dataFile: 'init_test_data.sql' }; // 显示帮助信息 @@ -41,11 +44,13 @@ function showHelp() { console.log(' --database DB 数据库名称 (默认: niumall)'); console.log(' --skip-structure 跳过表结构创建'); console.log(' --skip-data 跳过测试数据插入'); + console.log(' --data-file FILE 指定测试数据文件 (默认: init_test_data.sql)'); console.log(' --help 显示此帮助信息'); console.log(''); console.log('示例:'); console.log(' node init_with_node.js --user root --password secret'); console.log(' node init_with_node.js --host 192.168.1.100 --database test'); + console.log(' node init_with_node.js --data-file init_test_data_fixed.sql'); } // 解析命令行参数 @@ -78,6 +83,9 @@ function parseArgs() { case '--skip-data': skipData = true; break; + case '--data-file': + config.dataFile = args[++i]; + break; case '--help': showHelp(); process.exit(0); @@ -261,7 +269,7 @@ async function main() { // 4. 插入测试数据 if (!skipData && success) { - const dataFile = path.join(__dirname, 'init_test_data.sql'); + const dataFile = path.join(__dirname, config.dataFile); if (!await executeSqlFile(config, dataFile, '测试数据插入')) { success = false; } diff --git a/scripts/database/insert_suppliers_test_data.js b/scripts/database/insert_suppliers_test_data.js new file mode 100644 index 0000000..f5b76ab --- /dev/null +++ b/scripts/database/insert_suppliers_test_data.js @@ -0,0 +1,179 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); + +async function insertSuppliersTestData() { + const config = { + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall' + }; + + try { + const connection = await mysql.createConnection(config); + console.log('正在插入suppliers测试数据...'); + + // 先清空现有数据(可选) + console.log('清空现有suppliers数据...'); + await connection.execute('DELETE FROM suppliers'); + await connection.execute('ALTER TABLE suppliers AUTO_INCREMENT = 1'); + + // 插入测试数据,包含新增字段 + const testData = [ + { + id: 1, + name: '内蒙古草原牧业有限公司', + code: 'SUP001', + contact: '张牧民', + phone: '13900139001', + email: 'contact@nmgcy.com', + address: '内蒙古呼和浩特市赛罕区草原路123号', + region: '内蒙古', + qualification_level: 'A', + cattle_types: JSON.stringify(['西门塔尔牛', '安格斯牛', '夏洛莱牛']), + capacity: 500, + rating: 4.8, + cooperation_start_date: '2023-01-15', + status: 'active', + bank_account: '6228480012345678901', + bank_name: '中国农业银行呼和浩特分行', + tax_number: '91150100123456789X', + notes: '优质供应商,合作稳定,产品质量优秀' + }, + { + id: 2, + name: '山东鲁西黄牛养殖场', + code: 'SUP002', + contact: '李养牛', + phone: '13900139002', + email: 'contact@sdlx.com', + address: '山东济宁市嘉祥县畜牧路456号', + region: '山东', + qualification_level: 'A', + cattle_types: JSON.stringify(['鲁西黄牛', '利木赞牛']), + capacity: 300, + rating: 4.6, + cooperation_start_date: '2023-03-20', + status: 'active', + bank_account: '6228480023456789012', + bank_name: '中国工商银行济宁分行', + tax_number: '91370829234567890A', + notes: '专业黄牛养殖,品种纯正' + }, + { + id: 3, + name: '河南豫南肉牛合作社', + code: 'SUP003', + contact: '王合作', + phone: '13900139003', + email: 'contact@hnyn.com', + address: '河南南阳市宛城区牧业大道789号', + region: '河南', + qualification_level: 'B', + cattle_types: JSON.stringify(['西门塔尔牛', '夏洛莱牛']), + capacity: 200, + rating: 4.2, + cooperation_start_date: '2023-05-10', + status: 'active', + bank_account: '6228480034567890123', + bank_name: '中国建设银行南阳分行', + tax_number: '91411303345678901B', + notes: '合作社模式,农户联合经营' + }, + { + id: 4, + name: '新疆天山牧业集团', + code: 'SUP004', + contact: '马天山', + phone: '13900139004', + email: 'contact@xjts.com', + address: '新疆乌鲁木齐市天山区牧场路321号', + region: '新疆', + qualification_level: 'A', + cattle_types: JSON.stringify(['安格斯牛', '海福特牛']), + capacity: 400, + rating: 4.7, + cooperation_start_date: '2023-02-28', + status: 'active', + bank_account: '6228480045678901234', + bank_name: '中国银行乌鲁木齐分行', + tax_number: '91650100456789012C', + notes: '大型牧业集团,规模化经营' + }, + { + id: 5, + name: '黑龙江北大荒牧业', + code: 'SUP005', + contact: '刘北方', + phone: '13900139005', + email: 'contact@hlbdh.com', + address: '黑龙江哈尔滨市道里区牧业街654号', + region: '黑龙江', + qualification_level: 'B', + cattle_types: JSON.stringify(['西门塔尔牛', '利木赞牛']), + capacity: 250, + rating: 4.3, + cooperation_start_date: '2023-04-15', + status: 'active', + bank_account: '6228480056789012345', + bank_name: '中国邮政储蓄银行哈尔滨分行', + tax_number: '91230102567890123D', + notes: '北大荒品牌,信誉良好' + } + ]; + + // 批量插入数据 + const insertSql = ` + INSERT INTO suppliers ( + id, name, code, contact, phone, email, address, region, + qualification_level, cattle_types, capacity, rating, + cooperation_start_date, status, bank_account, bank_name, + tax_number, notes, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `; + + for (const supplier of testData) { + const values = [ + supplier.id, supplier.name, supplier.code, supplier.contact, + supplier.phone, supplier.email, supplier.address, supplier.region, + supplier.qualification_level, supplier.cattle_types, supplier.capacity, + supplier.rating, supplier.cooperation_start_date, supplier.status, + supplier.bank_account, supplier.bank_name, supplier.tax_number, supplier.notes + ]; + + await connection.execute(insertSql, values); + console.log(`✅ 插入供应商: ${supplier.name}`); + } + + // 验证插入结果 + const [result] = await connection.execute('SELECT COUNT(*) as count FROM suppliers'); + console.log(`\n插入完成,suppliers表共有 ${result[0].count} 条记录`); + + // 显示插入的数据 + const [suppliers] = await connection.execute(` + SELECT id, name, code, contact, phone, bank_account, bank_name, tax_number, status + FROM suppliers + ORDER BY id + `); + + console.log('\n插入的供应商数据:'); + console.table(suppliers); + + await connection.end(); + console.log('\n✅ suppliers测试数据插入完成!'); + return true; + + } catch (error) { + console.error('❌ 插入测试数据失败:', error.message); + throw error; + } +} + +// 执行插入 +insertSuppliersTestData().then(() => { + console.log('测试数据插入成功'); + process.exit(0); +}).catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/database/mysql_operations.js b/scripts/database/mysql_operations.js new file mode 100644 index 0000000..482e82b --- /dev/null +++ b/scripts/database/mysql_operations.js @@ -0,0 +1,238 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); +const fs = require('fs').promises; +const path = require('path'); + +// 数据库连接配置 +const config = { + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall' +}; + +class MySQLOperations { + constructor() { + this.connection = null; + } + + async connect() { + try { + this.connection = await mysql.createConnection(config); + console.log('✅ 成功连接到远程MySQL数据库'); + console.log(`主机: ${config.host}:${config.port}`); + console.log(`数据库: ${config.database}`); + return true; + } catch (error) { + console.error('❌ 连接数据库失败:', error.message); + return false; + } + } + + async disconnect() { + if (this.connection) { + await this.connection.end(); + console.log('数据库连接已关闭'); + } + } + + async showTables() { + try { + const [tables] = await this.connection.execute('SHOW TABLES'); + console.log('\n📋 数据库中的表:'); + tables.forEach((table, index) => { + console.log(`${index + 1}. ${Object.values(table)[0]}`); + }); + return tables; + } catch (error) { + console.error('❌ 获取表列表失败:', error.message); + return []; + } + } + + async describeTable(tableName) { + try { + const [columns] = await this.connection.execute(`DESCRIBE ${tableName}`); + console.log(`\n📊 表 ${tableName} 的结构:`); + console.table(columns); + return columns; + } catch (error) { + console.error(`❌ 获取表 ${tableName} 结构失败:`, error.message); + return []; + } + } + + async executeSQL(sql) { + try { + const [result] = await this.connection.execute(sql); + console.log('✅ SQL执行成功'); + return result; + } catch (error) { + console.error('❌ SQL执行失败:', error.message); + throw error; + } + } + + async executeSQLFile(filePath) { + try { + const sqlContent = await fs.readFile(filePath, 'utf8'); + + // 分割SQL语句(简单处理,按分号分割) + const statements = sqlContent + .split(';') + .map(stmt => stmt.trim()) + .filter(stmt => stmt.length > 0 && !stmt.startsWith('--')); + + console.log(`\n📄 执行SQL文件: ${filePath}`); + console.log(`包含 ${statements.length} 条SQL语句`); + + for (let i = 0; i < statements.length; i++) { + const stmt = statements[i]; + if (stmt.toLowerCase().startsWith('select') || + stmt.toLowerCase().startsWith('show') || + stmt.toLowerCase().startsWith('describe')) { + // 查询语句 + const [result] = await this.connection.execute(stmt); + console.log(`语句 ${i + 1}: ${stmt.substring(0, 50)}...`); + if (result.length > 0) { + console.log(`返回 ${result.length} 行数据`); + } + } else { + // 其他语句(INSERT, UPDATE, DELETE等) + const [result] = await this.connection.execute(stmt); + console.log(`语句 ${i + 1}: ${stmt.substring(0, 50)}...`); + if (result.affectedRows !== undefined) { + console.log(`影响 ${result.affectedRows} 行`); + } + } + } + + console.log('✅ SQL文件执行完成'); + return true; + } catch (error) { + console.error('❌ 执行SQL文件失败:', error.message); + return false; + } + } + + async checkTableData(tableName, limit = 5) { + try { + const [rows] = await this.connection.execute(`SELECT * FROM ${tableName} LIMIT ${limit}`); + const [count] = await this.connection.execute(`SELECT COUNT(*) as total FROM ${tableName}`); + + console.log(`\n📊 表 ${tableName} 数据概览:`); + console.log(`总记录数: ${count[0].total}`); + + if (rows.length > 0) { + console.log(`前 ${rows.length} 条记录:`); + console.table(rows); + } else { + console.log('表中暂无数据'); + } + + return { total: count[0].total, rows }; + } catch (error) { + console.error(`❌ 检查表 ${tableName} 数据失败:`, error.message); + return { total: 0, rows: [] }; + } + } + + async interactiveMode() { + console.log('\n🔧 进入交互模式 (输入 "exit" 退出)'); + console.log('可用命令:'); + console.log(' tables - 显示所有表'); + console.log(' desc <表名> - 显示表结构'); + console.log(' data <表名> - 显示表数据'); + console.log(' sql - 执行SQL'); + console.log(' file <文件路径> - 执行SQL文件'); + console.log(' exit - 退出'); + + const readline = require('readline'); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + const askQuestion = () => { + rl.question('\nmysql> ', async (input) => { + const command = input.trim(); + + if (command === 'exit') { + rl.close(); + return; + } + + try { + if (command === 'tables') { + await this.showTables(); + } else if (command.startsWith('desc ')) { + const tableName = command.substring(5).trim(); + await this.describeTable(tableName); + } else if (command.startsWith('data ')) { + const tableName = command.substring(5).trim(); + await this.checkTableData(tableName); + } else if (command.startsWith('sql ')) { + const sql = command.substring(4).trim(); + await this.executeSQL(sql); + } else if (command.startsWith('file ')) { + const filePath = command.substring(5).trim(); + await this.executeSQLFile(filePath); + } else if (command === '') { + // 空命令,继续 + } else { + console.log('未知命令,请输入 "exit" 退出或使用可用命令'); + } + } catch (error) { + console.error('执行命令时出错:', error.message); + } + + askQuestion(); + }); + }; + + askQuestion(); + } +} + +// 主函数 +async function main() { + const db = new MySQLOperations(); + + // 连接数据库 + const connected = await db.connect(); + if (!connected) { + process.exit(1); + } + + try { + // 显示数据库信息 + await db.showTables(); + + // 检查关键表的结构和数据 + const keyTables = ['users', 'suppliers', 'orders', 'drivers', 'vehicles']; + + for (const table of keyTables) { + console.log(`\n${'='.repeat(50)}`); + await db.describeTable(table); + await db.checkTableData(table, 3); + } + + // 进入交互模式 + await db.interactiveMode(); + + } catch (error) { + console.error('操作过程中出错:', error.message); + } finally { + await db.disconnect(); + } +} + +// 如果直接运行此脚本 +if (require.main === module) { + main().catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); + }); +} + +module.exports = MySQLOperations; \ No newline at end of file diff --git a/scripts/database/test_insert.js b/scripts/database/test_insert.js new file mode 100644 index 0000000..db2c787 --- /dev/null +++ b/scripts/database/test_insert.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +const mysql = require('../../backend/node_modules/mysql2/promise'); +const fs = require('fs').promises; + +async function testInsert() { + console.log('开始测试数据插入...'); + + const connection = await mysql.createConnection({ + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall', + multipleStatements: true + }); + + try { + // 读取修复后的测试数据文件 + const sqlContent = await fs.readFile('./init_test_data_fixed.sql', 'utf8'); + console.log('SQL文件读取成功,长度:', sqlContent.length); + + // 更智能的SQL语句分割 - 处理多行INSERT + const statements = []; + let currentStatement = ''; + let inInsert = false; + + const lines = sqlContent.split('\n'); + for (const line of lines) { + const trimmedLine = line.trim(); + + // 跳过注释和空行 + if (trimmedLine.startsWith('--') || trimmedLine === '') { + continue; + } + + // 检测INSERT语句开始 + if (trimmedLine.toUpperCase().startsWith('INSERT')) { + if (currentStatement) { + statements.push(currentStatement.trim()); + } + currentStatement = line; + inInsert = true; + continue; + } + + // 如果在INSERT语句中 + if (inInsert) { + currentStatement += '\n' + line; + // 检测INSERT语句结束(以分号结尾) + if (trimmedLine.endsWith(';')) { + statements.push(currentStatement.trim()); + currentStatement = ''; + inInsert = false; + } + } else { + // 处理其他语句 + if (currentStatement) { + currentStatement += '\n' + line; + } else { + currentStatement = line; + } + + if (trimmedLine.endsWith(';')) { + statements.push(currentStatement.trim()); + currentStatement = ''; + } + } + } + + // 添加最后一个语句(如果有) + if (currentStatement.trim()) { + statements.push(currentStatement.trim()); + } + + console.log('总共解析出', statements.length, '条SQL语句'); + + // 查找INSERT语句 + const insertStatements = statements.filter(stmt => stmt.toUpperCase().includes('INSERT')); + console.log('其中INSERT语句', insertStatements.length, '条'); + + // 执行第一条INSERT语句 + if (insertStatements.length > 0) { + const firstInsert = insertStatements[0]; + console.log('执行第一条INSERT语句:'); + console.log(firstInsert.substring(0, 200) + '...'); + + try { + const [result] = await connection.execute(firstInsert); + console.log('✅ 插入成功,影响行数:', result.affectedRows); + } catch (error) { + console.error('❌ 插入失败:', error.message); + console.error('错误代码:', error.code); + } + } + + // 检查users表数据 + const [users] = await connection.execute('SELECT COUNT(*) as count FROM users'); + console.log('users表当前记录数:', users[0].count); + + // 如果有数据,显示前几条 + if (users[0].count > 0) { + const [userList] = await connection.execute('SELECT id, username, user_type FROM users LIMIT 3'); + console.log('用户数据示例:'); + userList.forEach(user => console.log(user)); + } + + } catch (error) { + console.error('测试失败:', error.message); + } finally { + await connection.end(); + } +} + +testInsert().catch(console.error); \ No newline at end of file diff --git a/scripts/database/test_remote_connection.js b/scripts/database/test_remote_connection.js new file mode 100644 index 0000000..c250003 --- /dev/null +++ b/scripts/database/test_remote_connection.js @@ -0,0 +1,57 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); + +async function testRemoteConnection() { + // 从.env文件读取配置 + const config = { + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall' + }; + + console.log('正在测试远程数据库连接...'); + console.log(`主机: ${config.host}:${config.port}`); + console.log(`数据库: ${config.database}`); + console.log(`用户: ${config.user}`); + + try { + const connection = await mysql.createConnection(config); + console.log('✅ 远程数据库连接成功!'); + + // 测试查询 + const [rows] = await connection.execute('SELECT VERSION() as version'); + console.log(`MySQL版本: ${rows[0].version}`); + + // 检查数据库是否存在 + const [databases] = await connection.execute('SHOW DATABASES'); + const dbExists = databases.some(db => db.Database === config.database); + console.log(`数据库 ${config.database} 存在: ${dbExists ? '是' : '否'}`); + + if (dbExists) { + // 检查表列表 + const [tables] = await connection.execute('SHOW TABLES'); + console.log(`\n数据库中的表 (${tables.length}个):`); + tables.forEach(table => { + console.log(`- ${Object.values(table)[0]}`); + }); + } + + await connection.end(); + return true; + + } catch (error) { + console.error('❌ 远程数据库连接失败:'); + console.error(`错误信息: ${error.message}`); + console.error(`错误代码: ${error.code}`); + return false; + } +} + +// 执行测试 +testRemoteConnection().then(success => { + process.exit(success ? 0 : 1); +}).catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/database/test_suppliers_api.js b/scripts/database/test_suppliers_api.js new file mode 100644 index 0000000..ca26ab3 --- /dev/null +++ b/scripts/database/test_suppliers_api.js @@ -0,0 +1,76 @@ +const http = require('http'); + +function testSuppliersAPI() { + return new Promise((resolve, reject) => { + const options = { + hostname: 'localhost', + port: 3000, + path: '/api/suppliers', + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + timeout: 5000 + }; + + console.log('正在测试suppliers API接口...'); + console.log(`请求: GET http://localhost:3000/api/suppliers`); + + const req = http.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + console.log(`\n响应状态码: ${res.statusCode}`); + console.log(`响应头: ${JSON.stringify(res.headers, null, 2)}`); + + try { + const jsonData = JSON.parse(data); + console.log('\n响应数据:'); + console.log(JSON.stringify(jsonData, null, 2)); + + if (res.statusCode === 200 && jsonData.code === 200) { + console.log('\n✅ suppliers API接口测试成功!'); + console.log(`返回数据条数: ${jsonData.data?.list?.length || 0}`); + resolve(true); + } else { + console.log('\n❌ suppliers API接口返回错误'); + resolve(false); + } + } catch (error) { + console.error('\n❌ 解析响应数据失败:', error.message); + console.log('原始响应:', data); + resolve(false); + } + }); + }); + + req.on('error', (error) => { + console.error('\n❌ 请求失败:', error.message); + if (error.code === 'ECONNREFUSED') { + console.log('提示: 请确保后端服务正在运行 (npm start)'); + } + resolve(false); + }); + + req.on('timeout', () => { + console.error('\n❌ 请求超时'); + req.destroy(); + resolve(false); + }); + + req.end(); + }); +} + +// 执行测试 +testSuppliersAPI().then(success => { + console.log(`\n测试结果: ${success ? '成功' : '失败'}`); + process.exit(success ? 0 : 1); +}).catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/scripts/database/update_suppliers_table.js b/scripts/database/update_suppliers_table.js new file mode 100644 index 0000000..714f65c --- /dev/null +++ b/scripts/database/update_suppliers_table.js @@ -0,0 +1,104 @@ +const mysql = require('../../backend/node_modules/mysql2/promise'); + +async function updateSuppliersTable() { + const config = { + host: 'nj-cdb-3pwh2kz1.sql.tencentcdb.com', + port: 20784, + user: 'jiebanke', + password: 'aiot741$12346', + database: 'niumall' + }; + + try { + const connection = await mysql.createConnection(config); + console.log('正在更新suppliers表结构...'); + + // 需要添加的字段 + const fieldsToAdd = [ + { + name: 'bank_account', + definition: 'varchar(50) DEFAULT NULL COMMENT \'银行账号\'', + after: 'phone' + }, + { + name: 'bank_name', + definition: 'varchar(100) DEFAULT NULL COMMENT \'开户银行\'', + after: 'bank_account' + }, + { + name: 'tax_number', + definition: 'varchar(30) DEFAULT NULL COMMENT \'税务登记号\'', + after: 'bank_name' + }, + { + name: 'notes', + definition: 'text DEFAULT NULL COMMENT \'备注信息\'', + after: 'tax_number' + } + ]; + + // 逐个添加字段 + for (const field of fieldsToAdd) { + try { + const sql = `ALTER TABLE suppliers ADD COLUMN ${field.name} ${field.definition} AFTER ${field.after}`; + console.log(`添加字段 ${field.name}...`); + await connection.execute(sql); + console.log(`✅ 字段 ${field.name} 添加成功`); + } catch (error) { + if (error.code === 'ER_DUP_FIELDNAME') { + console.log(`⚠️ 字段 ${field.name} 已存在,跳过`); + } else { + console.error(`❌ 添加字段 ${field.name} 失败:`, error.message); + throw error; + } + } + } + + // 验证字段是否添加成功 + console.log('\n验证表结构更新...'); + const [columns] = await connection.execute(` + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'niumall' + AND TABLE_NAME = 'suppliers' + AND COLUMN_NAME IN ('bank_account', 'bank_name', 'tax_number', 'notes') + ORDER BY ORDINAL_POSITION + `); + + console.log('\n新添加的字段:'); + console.table(columns.map(col => ({ + 字段名: col.COLUMN_NAME, + 数据类型: col.DATA_TYPE, + 允许空值: col.IS_NULLABLE, + 默认值: col.COLUMN_DEFAULT, + 注释: col.COLUMN_COMMENT + }))); + + // 检查总字段数 + const [allColumns] = await connection.execute(` + SELECT COUNT(*) as count + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'niumall' + AND TABLE_NAME = 'suppliers' + `); + + console.log(`\nsuppliers表总字段数: ${allColumns[0].count}`); + + await connection.end(); + console.log('\n✅ suppliers表结构更新完成!'); + return true; + + } catch (error) { + console.error('❌ 更新表结构失败:', error.message); + throw error; + } +} + +// 执行更新 +updateSuppliersTable().then(() => { + console.log('表结构更新成功'); + process.exit(0); +}).catch(error => { + console.error('脚本执行失败:', error); + process.exit(1); +}); \ No newline at end of file