'use strict'; // 先手动增加 audit_order、audit_type 字段。 // node db_script/bak/advance_audit_order.js uat [--tid=标段ID] [--vid=预付款ID] [--apply] // 默认预览,--apply 写入;逐期逐轮按 order 排序、按人员去重回填。 const common = require('../../app/const/audit').auditType.key.common; function buildUpdates(rows) { const sorted = rows.slice().sort((a, b) => a.order - b.order || a.id - b.id); const ids = []; sorted.forEach(row => { if (!ids.includes(row.audit_id)) ids.push(row.audit_id); }); if (sorted.some(row => (row.audit_order > 0 && row.audit_order !== ids.indexOf(row.audit_id) + 1) || (row.audit_type > 0 && row.audit_type !== common))) { return { skip: '已有节点序号或审批类型冲突,请人工核对' }; } return { updates: sorted.filter(row => !(row.audit_order > 0) || !(row.audit_type > 0)).map(row => ({ id: row.id, audit_id: row.audit_id, before: { audit_order: row.audit_order, audit_type: row.audit_type }, audit_order: ids.indexOf(row.audit_id) + 1, audit_type: common, })) }; } async function main() { const env = process.argv[2]; if (!['local', 'uat', 'default'].includes(env)) throw new Error('环境仅支持 local / uat / default'); const filters = {}; let apply = false; for (const arg of process.argv.slice(3)) { if (arg === '--apply') { apply = true; continue; } const match = /^--(vid|tid)=(\d+)$/.exec(arg); if (!match || !Number.isSafeInteger(Number(match[2])) || Number(match[2]) <= 0) throw new Error('无效参数:' + arg); filters[match[1]] = Number(match[2]); } const mysql = require('mysql'); // 使用与 baseUtils 相同的配置入口,独立连接确保每轮更新处于同一事务。 const path = require('path'); const root = path.resolve(__dirname, '../..'); const config = require(path.join(root, 'config/config.' + env))({ baseDir: root, root, name: 'calc' }); const connection = mysql.createConnection(config.mysql.client); const query = (sql, params = []) => new Promise((resolve, reject) => { connection.query(sql, params, (err, rows) => err ? reject(err) : resolve(rows)); }); const log = data => { console.log(JSON.stringify(data)); }; try { const columns = await query('SHOW COLUMNS FROM zh_advance_audit'); if (!['audit_order', 'audit_type'].every(name => columns.some(x => x.Field === name))) { throw new Error('请先为 zh_advance_audit 增加 audit_order 和 audit_type 字段'); } log({ env, apply, filters }); const where = ['1 = 1'], params = []; for (const key of ['tid', 'vid']) { if (filters[key]) { where.push(key + ' = ?'); params.push(filters[key]); } } const rounds = await query('SELECT DISTINCT tid, vid, times FROM zh_advance_audit WHERE ' + where.join(' AND ') + ' ORDER BY tid, vid, times', params); let updated = 0; for (const round of rounds) { await query('START TRANSACTION'); try { const rows = await query('SELECT id, audit_id, `order`, audit_order, audit_type FROM zh_advance_audit WHERE tid = ? AND vid = ? AND times = ? ORDER BY `order`, id FOR UPDATE', [round.tid, round.vid, round.times]); const plan = buildUpdates(rows); log({ phase: 'plan', ...round, ...plan }); if (!apply || plan.skip) { await query('ROLLBACK'); continue; } for (const row of plan.updates) { const result = await query('UPDATE zh_advance_audit SET audit_order = ?, audit_type = ? WHERE id = ?', [row.audit_order, row.audit_type, row.id]); if (result.affectedRows !== 1) throw new Error('回填失败:' + row.id); } await query('COMMIT'); updated += plan.updates.length; log({ phase: 'committed', ...round, updated: plan.updates.length }); } catch (err) { await query('ROLLBACK'); throw err; } } log({ phase: 'done', rounds: rounds.length, updated }); } finally { connection.end(); } } module.exports = { buildUpdates }; if (require.main === module) main().catch(err => { console.error(err); process.exitCode = 1; });