geely-kaipiao/merge_kaipiao.js
2026-07-25 20:53:20 +08:00

186 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const XLSX = require('xlsx');
const fs = require('fs');
const path = require('path');
/**
* 合并「团队开票申请表」脚本
*
* 目录结构假设:
* <根目录>/
* 7.xxx/
* *团队开票申请表*.xlsx (含4个sheet其中一个含"商旅",一个含"吉智")
* 其它两个表格...
* 7.yyy/
* ...
*
* 用法:
* node merge_kaipiao.js <根目录> [输出文件路径]
*
* 例如:
* node merge_kaipiao.js "D:\\某个目录" "D:\\合并结果.xlsx"
*/
// ---------- 参数 ----------
const rootDir = process.argv[2];
const outFile = process.argv[3] || path.join(process.cwd(), '团队开票申请表-合并.xlsx');
if (!rootDir) {
console.error('用法: node merge_kaipiao.js <根目录> [输出文件路径]');
process.exit(1);
}
if (!fs.existsSync(rootDir) || !fs.statSync(rootDir).isDirectory()) {
console.error(`根目录不存在或不是文件夹: ${rootDir}`);
process.exit(1);
}
// ---------- 工具函数 ----------
// 判断是否为 Excel 文件(排除临时文件 ~$ 开头)
function isExcelFile(name) {
return /\.(xlsx|xls)$/i.test(name) && !name.startsWith('~$');
}
// 在一个工作簿里按关键字查找 sheet 名
function findSheetName(workbook, keyword) {
return workbook.SheetNames.find(n => n.includes(keyword));
}
// 把一个 sheet 读成二维数组(含表头行)
function sheetToRows(sheet) {
return XLSX.utils.sheet_to_json(sheet, { header: 1, defval: null, blankrows: false });
}
// 去掉指定列(按表头名精确匹配)单元格值中的所有冒号(含全角)
function stripColonInColumn(rows, columnName) {
if (!rows || rows.length === 0) return 0;
const header = rows[0];
const colIndex = header.findIndex(h => h != null && String(h).trim() === columnName);
if (colIndex === -1) {
console.warn(` [警告] 未找到列 "${columnName}",跳过冒号清理`);
return 0;
}
let changed = 0;
for (let i = 1; i < rows.length; i++) {
const v = rows[i][colIndex];
if (v == null) continue;
const str = String(v);
if (str.includes(':') || str.includes('')) {
rows[i][colIndex] = str.replace(/[:]/g, '');
changed++;
}
}
return changed;
}
// ---------- 收集子文件夹 ----------
const subDirs = fs.readdirSync(rootDir, { withFileTypes: true })
.filter(d => d.isDirectory() && d.name.startsWith('7.'))
.map(d => d.name)
.sort();
if (subDirs.length === 0) {
console.error('未找到以 "7." 开头的子文件夹');
process.exit(1);
}
console.log(`找到 ${subDirs.length} 个 "7." 子文件夹`);
// 合并结果:分别累积 商旅 / 吉智 两组数据
let shangluHeader = null;
const shangluRows = [];
let jizhiHeader = null;
const jizhiRows = [];
let processedCount = 0;
for (const sub of subDirs) {
const subPath = path.join(rootDir, sub);
// 找到名字含「团队开票申请表」的表格
const files = fs.readdirSync(subPath).filter(isExcelFile);
const targetFile = files.find(f => f.includes('团队开票申请表'));
if (!targetFile) {
console.warn(` [跳过] ${sub}: 未找到含"团队开票申请表"的表格`);
continue;
}
const filePath = path.join(subPath, targetFile);
let workbook;
try {
workbook = XLSX.readFile(filePath);
} catch (e) {
console.warn(` [跳过] ${sub}: 读取失败 - ${e.message}`);
continue;
}
const shangluName = findSheetName(workbook, '商旅');
const jizhiName = findSheetName(workbook, '吉智');
if (!shangluName) console.warn(` [警告] ${sub}: 未找到含"商旅"的sheet`);
if (!jizhiName) console.warn(` [警告] ${sub}: 未找到含"吉智"的sheet`);
// 处理 商旅
if (shangluName) {
const rows = sheetToRows(workbook.Sheets[shangluName]);
if (rows.length > 0) {
const [header, ...body] = rows;
if (!shangluHeader) shangluHeader = header;
shangluRows.push(...body);
}
}
// 处理 吉智
if (jizhiName) {
const rows = sheetToRows(workbook.Sheets[jizhiName]);
if (rows.length > 0) {
const [header, ...body] = rows;
if (!jizhiHeader) jizhiHeader = header;
jizhiRows.push(...body);
}
}
processedCount++;
console.log(` [完成] ${sub}: ${targetFile}`);
}
if (processedCount === 0) {
console.error('没有可合并的数据');
process.exit(1);
}
// ---------- 生成输出工作簿 ----------
const outWb = XLSX.utils.book_new();
if (shangluHeader) {
const data = [shangluHeader, ...shangluRows];
const n1 = stripColonInColumn(data, '结算账户');
if (n1) console.log(` 商旅"结算账户"列清理冒号: ${n1} 个单元格`);
const ws = XLSX.utils.aoa_to_sheet(data);
XLSX.utils.book_append_sheet(outWb, ws, '商旅');
} else {
console.warn('未收集到任何"商旅"数据');
}
if (jizhiHeader) {
const data = [jizhiHeader, ...jizhiRows];
const n2 = stripColonInColumn(data, '*对账单/项目名称');
if (n2) console.log(` 吉智"*对账单/项目名称"列清理冒号: ${n2} 个单元格`);
const ws = XLSX.utils.aoa_to_sheet(data);
XLSX.utils.book_append_sheet(outWb, ws, '吉智');
} else {
console.warn('未收集到任何"吉智"数据');
}
if (outWb.SheetNames.length === 0) {
console.error('没有生成任何 sheet未写出文件');
process.exit(1);
}
XLSX.writeFile(outWb, outFile);
console.log('\n合并完成:');
console.log(` 商旅: ${shangluRows.length} 行数据`);
console.log(` 吉智: ${jizhiRows.length} 行数据`);
console.log(` 输出文件: ${outFile}`);