Files
yunhaifinance/backend/migrate-procurement-logistics.js
T

353 lines
15 KiB
JavaScript
Raw Normal View History

/**
* 数据库迁移执行脚本
* 用于执行采购-付款-物流-退库一体化流程的数据库表创建和字段扩展
* 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.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();