备份:修复前完整项目快照 2026-04-19
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* 执行采购-付款-物流-退库一体化流程数据库迁移
|
||||
* 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.md
|
||||
*
|
||||
* 运行方式:node execute-procurement-logistics-migration.js
|
||||
*/
|
||||
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(__dirname, 'company_finance.db');
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('数据库连接失败:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('SQLite数据库连接成功:', dbPath);
|
||||
});
|
||||
|
||||
const runSQL = (sql, params = []) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function(err) {
|
||||
if (err) {
|
||||
resolve({ skipped: true, message: err.message });
|
||||
} 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} 已存在或创建失败: ${result.message}`);
|
||||
} 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 tableName of tables) {
|
||||
const rows = await runAllSQL(`SELECT COUNT(*) as count FROM ${tableName}`);
|
||||
console.log(`表 ${tableName}: ${rows[0].count} 条记录`);
|
||||
}
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('迁移完成!');
|
||||
console.log('========================================\n');
|
||||
|
||||
db.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('迁移失败:', error);
|
||||
db.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
Reference in New Issue
Block a user