353 lines
15 KiB
JavaScript
353 lines
15 KiB
JavaScript
/**
|
|
* 数据库迁移执行脚本
|
|
* 用于执行采购-付款-物流-退库一体化流程的数据库表创建和字段扩展
|
|
* 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.md
|
|
*/
|
|
const sqlite3 = require('sqlite3').verbose();
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const dbPath = path.join(__dirname, 'company_finance.db');
|
|
const db = new sqlite3.Database(dbPath);
|
|
|
|
console.log('开始执行数据库迁移...');
|
|
console.log('数据库路径:', dbPath);
|
|
|
|
const runSQL = (sql, params = []) => {
|
|
return new Promise((resolve, reject) => {
|
|
db.run(sql, params, function(err) {
|
|
if (err) {
|
|
if (err.message.includes('already exists') || err.message.includes('duplicate column name')) {
|
|
resolve({ skipped: true, message: err.message });
|
|
} else {
|
|
reject(err);
|
|
}
|
|
} else {
|
|
resolve({ success: true, lastID: this.lastID, changes: this.changes });
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
const runAllSQL = (sql, params = []) => {
|
|
return new Promise((resolve, reject) => {
|
|
db.all(sql, params, (err, rows) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(rows);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
async function migrate() {
|
|
try {
|
|
console.log('\n========================================');
|
|
console.log('第一部分:创建新表');
|
|
console.log('========================================\n');
|
|
|
|
const createTables = [
|
|
{
|
|
name: 'logistics_companies',
|
|
sql: `CREATE TABLE IF NOT EXISTS logistics_companies (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code TEXT UNIQUE NOT NULL,
|
|
name TEXT NOT NULL,
|
|
address TEXT,
|
|
phone TEXT,
|
|
email TEXT,
|
|
quotation_description TEXT,
|
|
status TEXT DEFAULT 'active',
|
|
remark TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)`
|
|
},
|
|
{
|
|
name: 'logistics_company_payment_infos',
|
|
sql: `CREATE TABLE IF NOT EXISTS logistics_company_payment_infos (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
logistics_company_id INTEGER NOT NULL,
|
|
account_name TEXT,
|
|
account_number TEXT,
|
|
bank_name TEXT,
|
|
qr_code TEXT,
|
|
is_default INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (logistics_company_id) REFERENCES logistics_companies(id) ON DELETE CASCADE
|
|
)`
|
|
},
|
|
{
|
|
name: 'logistics_records',
|
|
sql: `CREATE TABLE IF NOT EXISTS logistics_records (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code TEXT UNIQUE NOT NULL,
|
|
purchase_order_id INTEGER NOT NULL,
|
|
ship_from TEXT DEFAULT 'Laos',
|
|
logistics_company_id INTEGER,
|
|
logistics_company TEXT,
|
|
tracking_number TEXT,
|
|
ship_date DATE,
|
|
ship_location TEXT,
|
|
estimated_arrival_date DATE,
|
|
customs_arrival_date DATE,
|
|
customs_clearance_date DATE,
|
|
use_hub INTEGER DEFAULT 0,
|
|
hub_arrival_date DATE,
|
|
hub_receiver TEXT,
|
|
hub_verified_quantity REAL,
|
|
second_ship_date DATE,
|
|
primary_freight REAL DEFAULT 0,
|
|
primary_freight_currency TEXT DEFAULT 'CNY',
|
|
primary_freight_status TEXT DEFAULT 'pending',
|
|
primary_freight_document TEXT,
|
|
secondary_freight REAL DEFAULT 0,
|
|
secondary_freight_currency TEXT DEFAULT 'LAK',
|
|
secondary_freight_status TEXT DEFAULT 'pending',
|
|
driver_phone TEXT,
|
|
cargo_weight REAL,
|
|
transport_distance REAL,
|
|
final_arrival_date DATE,
|
|
final_location TEXT,
|
|
status TEXT DEFAULT 'pending',
|
|
remark TEXT,
|
|
created_by TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (purchase_order_id) REFERENCES purchase_orders(id),
|
|
FOREIGN KEY (logistics_company_id) REFERENCES logistics_companies(id)
|
|
)`
|
|
},
|
|
{
|
|
name: 'verification_records',
|
|
sql: `CREATE TABLE IF NOT EXISTS verification_records (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code TEXT UNIQUE NOT NULL,
|
|
purchase_order_id INTEGER NOT NULL,
|
|
logistics_record_id INTEGER,
|
|
verification_type TEXT DEFAULT 'direct',
|
|
verification_date DATE NOT NULL,
|
|
verifier TEXT NOT NULL,
|
|
items TEXT,
|
|
total_ordered REAL,
|
|
total_received REAL,
|
|
total_verified REAL,
|
|
total_rejected REAL DEFAULT 0,
|
|
project_id INTEGER,
|
|
storage_type TEXT,
|
|
status TEXT DEFAULT 'pending',
|
|
remark TEXT,
|
|
attachments TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (purchase_order_id) REFERENCES purchase_orders(id),
|
|
FOREIGN KEY (logistics_record_id) REFERENCES logistics_records(id),
|
|
FOREIGN KEY (project_id) REFERENCES projects(id)
|
|
)`
|
|
},
|
|
{
|
|
name: 'return_records',
|
|
sql: `CREATE TABLE IF NOT EXISTS return_records (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code TEXT UNIQUE NOT NULL,
|
|
project_id INTEGER NOT NULL,
|
|
return_type TEXT DEFAULT 'warehouse',
|
|
return_date DATE NOT NULL,
|
|
applicant TEXT NOT NULL,
|
|
items TEXT,
|
|
total_quantity REAL,
|
|
total_amount REAL,
|
|
cost_adjustment REAL DEFAULT 0,
|
|
refund_amount REAL DEFAULT 0,
|
|
status TEXT DEFAULT 'pending',
|
|
remark TEXT,
|
|
attachments TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (project_id) REFERENCES projects(id)
|
|
)`
|
|
},
|
|
{
|
|
name: 'material_price_history',
|
|
sql: `CREATE TABLE IF NOT EXISTS material_price_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
product_id INTEGER NOT NULL,
|
|
purchase_order_id INTEGER,
|
|
supplier_id INTEGER,
|
|
supplier_country TEXT,
|
|
unit_price REAL NOT NULL,
|
|
currency TEXT DEFAULT 'CNY',
|
|
quantity REAL,
|
|
purchase_date DATE NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (product_id) REFERENCES products(id),
|
|
FOREIGN KEY (purchase_order_id) REFERENCES purchase_orders(id),
|
|
FOREIGN KEY (supplier_id) REFERENCES suppliers(id)
|
|
)`
|
|
},
|
|
{
|
|
name: 'project_material_inventory',
|
|
sql: `CREATE TABLE IF NOT EXISTS project_material_inventory (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
project_id INTEGER NOT NULL,
|
|
product_id INTEGER NOT NULL,
|
|
product_name TEXT,
|
|
unit TEXT,
|
|
purchased_quantity REAL DEFAULT 0,
|
|
received_quantity REAL DEFAULT 0,
|
|
used_quantity REAL DEFAULT 0,
|
|
returned_quantity REAL DEFAULT 0,
|
|
current_quantity REAL DEFAULT 0,
|
|
total_amount REAL DEFAULT 0,
|
|
average_price REAL DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (project_id) REFERENCES projects(id),
|
|
FOREIGN KEY (product_id) REFERENCES products(id),
|
|
UNIQUE(project_id, product_id)
|
|
)`
|
|
}
|
|
];
|
|
|
|
for (const table of createTables) {
|
|
console.log(`创建表: ${table.name}...`);
|
|
const result = await runSQL(table.sql);
|
|
if (result.skipped) {
|
|
console.log(` 表 ${table.name} 已存在,跳过`);
|
|
} else {
|
|
console.log(` 表 ${table.name} 创建成功`);
|
|
}
|
|
}
|
|
|
|
console.log('\n========================================');
|
|
console.log('第二部分:创建索引');
|
|
console.log('========================================\n');
|
|
|
|
const createIndexes = [
|
|
'CREATE INDEX IF NOT EXISTS idx_logistics_companies_code ON logistics_companies(code)',
|
|
'CREATE INDEX IF NOT EXISTS idx_logistics_companies_status ON logistics_companies(status)',
|
|
'CREATE INDEX IF NOT EXISTS idx_lc_payment_infos_company ON logistics_company_payment_infos(logistics_company_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_logistics_records_code ON logistics_records(code)',
|
|
'CREATE INDEX IF NOT EXISTS idx_logistics_records_order ON logistics_records(purchase_order_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_logistics_records_status ON logistics_records(status)',
|
|
'CREATE INDEX IF NOT EXISTS idx_logistics_records_company ON logistics_records(logistics_company_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_verification_records_code ON verification_records(code)',
|
|
'CREATE INDEX IF NOT EXISTS idx_verification_records_order ON verification_records(purchase_order_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_verification_records_status ON verification_records(status)',
|
|
'CREATE INDEX IF NOT EXISTS idx_return_records_code ON return_records(code)',
|
|
'CREATE INDEX IF NOT EXISTS idx_return_records_project ON return_records(project_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_return_records_status ON return_records(status)',
|
|
'CREATE INDEX IF NOT EXISTS idx_material_price_history_product ON material_price_history(product_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_material_price_history_supplier ON material_price_history(supplier_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_material_price_history_date ON material_price_history(purchase_date)',
|
|
'CREATE INDEX IF NOT EXISTS idx_project_material_inventory_project ON project_material_inventory(project_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_project_material_inventory_product ON project_material_inventory(product_id)'
|
|
];
|
|
|
|
for (const indexSql of createIndexes) {
|
|
await runSQL(indexSql);
|
|
}
|
|
console.log('索引创建完成');
|
|
|
|
console.log('\n========================================');
|
|
console.log('第三部分:扩展现有表字段');
|
|
console.log('========================================\n');
|
|
|
|
const alterTableStatements = [
|
|
{ table: 'purchase_orders', column: 'project_id', type: 'INTEGER' },
|
|
{ table: 'purchase_orders', column: 'supplier_country', type: "TEXT DEFAULT 'Laos'" },
|
|
{ table: 'purchase_orders', column: 'estimated_amount', type: 'REAL DEFAULT 0' },
|
|
{ table: 'purchase_orders', column: 'paid_amount', type: 'REAL DEFAULT 0' },
|
|
{ table: 'purchase_orders', column: 'contract_url', type: 'TEXT' },
|
|
{ table: 'purchase_orders', column: 'quotation_url', type: 'TEXT' },
|
|
{ table: 'purchase_orders', column: 'actual_delivery_date', type: 'DATE' },
|
|
{ table: 'purchase_orders', column: 'remark', type: 'TEXT' },
|
|
|
|
{ table: 'purchase_order_items', column: 'received_quantity', type: 'REAL DEFAULT 0' },
|
|
{ table: 'purchase_order_items', column: 'verified_quantity', type: 'REAL DEFAULT 0' },
|
|
|
|
{ table: 'payment_plans', column: 'stage', type: 'TEXT' },
|
|
{ table: 'payment_plans', column: 'planned_date', type: 'DATE' },
|
|
{ table: 'payment_plans', column: 'planned_amount', type: 'REAL' },
|
|
{ table: 'payment_plans', column: 'planned_percentage', type: 'REAL' },
|
|
{ table: 'payment_plans', column: 'actual_amount', type: 'REAL DEFAULT 0' },
|
|
{ table: 'payment_plans', column: 'actual_date', type: 'DATE' },
|
|
{ table: 'payment_plans', column: 'payment_request_id', type: 'INTEGER' },
|
|
{ table: 'payment_plans', column: 'reminder_days', type: 'INTEGER DEFAULT 3' },
|
|
{ table: 'payment_plans', column: 'remark', type: 'TEXT' },
|
|
|
|
{ table: 'payment_requests', column: 'payment_type', type: "TEXT DEFAULT 'material'" },
|
|
{ table: 'payment_requests', column: 'purchase_order_id', type: 'INTEGER' },
|
|
{ table: 'payment_requests', column: 'logistics_company_id', type: 'INTEGER' },
|
|
{ table: 'payment_requests', column: 'logistics_document_url', type: 'TEXT' },
|
|
{ table: 'payment_requests', column: 'driver_phone', type: 'TEXT' },
|
|
{ table: 'payment_requests', column: 'cargo_weight', type: 'REAL' },
|
|
{ table: 'payment_requests', column: 'transport_distance', type: 'REAL' },
|
|
|
|
{ table: 'suppliers', column: 'supply_category', type: 'TEXT' },
|
|
{ table: 'suppliers', column: 'country', type: 'TEXT' },
|
|
{ table: 'suppliers', column: 'address', type: 'TEXT' },
|
|
{ table: 'suppliers', column: 'phone', type: 'TEXT' },
|
|
{ table: 'suppliers', column: 'email', type: 'TEXT' },
|
|
{ table: 'suppliers', column: 'status', type: "TEXT DEFAULT 'active'" },
|
|
|
|
{ table: 'purchase_requests', column: 'expected_date', type: 'DATE' }
|
|
];
|
|
|
|
for (const stmt of alterTableStatements) {
|
|
const sql = `ALTER TABLE ${stmt.table} ADD COLUMN ${stmt.column} ${stmt.type}`;
|
|
console.log(`扩展表 ${stmt.table} 添加字段 ${stmt.column}...`);
|
|
const result = await runSQL(sql);
|
|
if (result.skipped) {
|
|
console.log(` 字段 ${stmt.column} 已存在,跳过`);
|
|
} else {
|
|
console.log(` 字段 ${stmt.column} 添加成功`);
|
|
}
|
|
}
|
|
|
|
console.log('\n========================================');
|
|
console.log('第四部分:创建扩展字段索引');
|
|
console.log('========================================\n');
|
|
|
|
const extraIndexes = [
|
|
'CREATE INDEX IF NOT EXISTS idx_purchase_orders_project ON purchase_orders(project_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_purchase_orders_supplier_country ON purchase_orders(supplier_country)',
|
|
'CREATE INDEX IF NOT EXISTS idx_payment_requests_type ON payment_requests(payment_type)',
|
|
'CREATE INDEX IF NOT EXISTS idx_payment_requests_order ON payment_requests(purchase_order_id)',
|
|
'CREATE INDEX IF NOT EXISTS idx_suppliers_country ON suppliers(country)',
|
|
'CREATE INDEX IF NOT EXISTS idx_suppliers_status ON suppliers(status)'
|
|
];
|
|
|
|
for (const indexSql of extraIndexes) {
|
|
await runSQL(indexSql);
|
|
}
|
|
console.log('扩展字段索引创建完成');
|
|
|
|
console.log('\n========================================');
|
|
console.log('第五部分:验证表结构');
|
|
console.log('========================================\n');
|
|
|
|
const tables = [
|
|
'logistics_companies', 'logistics_company_payment_infos', 'logistics_records',
|
|
'verification_records', 'return_records', 'material_price_history', 'project_material_inventory'
|
|
];
|
|
|
|
for (const table of tables) {
|
|
const rows = await runAllSQL(`PRAGMA table_info(${table})`);
|
|
console.log(`表 ${table} 字段数: ${rows.length}`);
|
|
}
|
|
|
|
console.log('\n========================================');
|
|
console.log('迁移完成!');
|
|
console.log('========================================\n');
|
|
|
|
} catch (error) {
|
|
console.error('迁移失败:', error);
|
|
process.exit(1);
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
migrate();
|