geely-kaipiao/compare.js
2026-07-25 20:33:34 +08:00

284 lines
9.1 KiB
JavaScript
Raw Permalink 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');
/**
* 功能读取Excel文件并解析为JSON
* 参数filePath - 文件路径
* 返回值:解析后的工作簿对象
*/
function readExcel(filePath) {
return XLSX.readFile(filePath);
}
/**
* 功能判断是否为有效订单号FR开头的订单号
* 参数orderNo - 订单号
* 返回值:是否有效
*/
function isValidOrderNo(orderNo) {
if (!orderNo) return false;
const str = String(orderNo).trim();
if (!str) return false;
if (str === '总计' || str === '合计' || str === '汇总') return false;
if (str.startsWith('FR')) return true;
return /^\d+$/.test(str);
}
/**
* 功能:从酒店账单提取订单号和金额
* 参数workbook - Excel工作簿对象
* 返回值订单Map {订单号: {金额, 入住人, 入住时间, 离店时间}}
*/
function extractHotelOrders(workbook) {
const sheet = workbook.Sheets['.'];
const data = XLSX.utils.sheet_to_json(sheet);
const orders = new Map();
data.forEach(row => {
const orderNo = row['吉利订单号'];
if (isValidOrderNo(orderNo)) {
orders.set(String(orderNo).trim(), {
结算金额: parseFloat(row['结算金额']) || 0,
系统金额: parseFloat(row['系统金额']) || 0,
差异: parseFloat(row['差异']) || 0,
入住人: row['入住人姓名'] || '',
入住时间: row['入住时间'] || '',
离店时间: row['离店时间'] || '',
房型: row['房型'] || '',
房号: row['房号'] || '',
房间数: row['房间数'] || 0,
间夜数: row['间夜数'] || 0
});
}
});
return orders;
}
/**
* 功能从商旅账单Export表提取订单号和金额合并同一订单的正负金额
* 参数workbook - Excel工作簿对象
* 返回值订单Map {订单号: 金额对象}
*/
function extractGeelyExportOrders(workbook) {
const sheet = workbook.Sheets['Export'];
const data = XLSX.utils.sheet_to_json(sheet, { header: 1 }); // 使用数组索引访问
const orders = new Map();
// 从第2行开始第0行是标题第1行是表头
for (let i = 2; i < data.length; i++) {
const row = data[i];
const orderNo = row[5]; // 供应商订单号在第5列
if (isValidOrderNo(orderNo)) {
// 金额在第11列纯数字已包含正负值
const amount = parseFloat(row[11]) || 0;
const key = String(orderNo).trim();
// 合并同一订单号的金额
if (orders.has(key)) {
const existing = orders.get(key);
existing.金额 += amount;
// 保留最后一条记录的其他信息
existing.对账明细名称 = row[6] || existing.对账明细名称;
existing.结算状态 = row[9] || existing.结算状态;
existing.对账单号 = row[0] || existing.对账单号;
} else {
orders.set(key, {
金额: amount,
对账明细名称: row[6] || '',
结算状态: row[9] || '',
对账单号: row[0] || '',
订单号: row[4] || ''
});
}
}
}
return orders;
}
/**
* 功能:对比两个订单集合的差异
* 参数hotelOrders - 酒店方订单Map, geelyOrders - 商旅方订单Map
* 返回值:差异结果对象
*/
function compareOrders(hotelOrders, geelyOrders) {
const hotelOnly = [];
const geelyOnly = [];
const amountDiff = [];
const matched = [];
// 检查酒店有但商旅没有的订单
for (const [orderNo, hotelInfo] of hotelOrders) {
if (!geelyOrders.has(orderNo)) {
hotelOnly.push({ 订单号: orderNo, ...hotelInfo });
} else {
const geelyInfo = geelyOrders.get(orderNo);
const hotelAmount = hotelInfo.结算金额;
const geelyAmount = geelyInfo.金额;
if (Math.abs(hotelAmount - geelyAmount) > 0.01) {
amountDiff.push({
订单号: orderNo,
酒店金额: hotelAmount,
商旅金额: geelyAmount,
差额: hotelAmount - geelyAmount,
入住人: hotelInfo.入住人,
酒店明细: hotelInfo,
商旅明细: geelyInfo
});
} else {
matched.push({
订单号: orderNo,
金额: hotelAmount,
入住人: hotelInfo.入住人
});
}
}
}
// 检查商旅有但酒店没有的订单
for (const [orderNo, geelyInfo] of geelyOrders) {
if (!hotelOrders.has(orderNo)) {
geelyOnly.push({ 订单号: orderNo, ...geelyInfo });
}
}
return { hotelOnly, geelyOnly, amountDiff, matched };
}
/**
* 功能:生成差异报告并输出
* 参数result - 对比结果对象
*/
function generateReport(result, hotelOrdersCount, geelyOrdersCount) {
console.log('\n' + '='.repeat(70));
console.log(' 吉利商旅 vs 酒店账单 差异报告');
console.log(' (仅使用Export表已合并同一订单的正负金额)');
console.log('='.repeat(70));
console.log(`\n【统计概览】`);
console.log(` 酒店账单有效订单数: ${hotelOrdersCount}`);
console.log(` 商旅账单有效订单数: ${geelyOrdersCount}`);
console.log(` ✓ 匹配成功订单数: ${result.matched.length}`);
console.log(` ⚠ 金额差异订单数: ${result.amountDiff.length}`);
console.log(` ✗ 酒店有-商旅无: ${result.hotelOnly.length}`);
console.log(` ? 商旅有-酒店无: ${result.geelyOnly.length}`);
if (result.hotelOnly.length > 0) {
console.log(`\n【酒店有但商旅没有的订单 - 共${result.hotelOnly.length}条】`);
console.log('-'.repeat(70));
result.hotelOnly.forEach((item, i) => {
console.log(`${i+1}. ${item.订单号} | ${item.入住人.padEnd(6)} | ¥${String(item.结算金额).padStart(6)} | ${item.房型}`);
});
}
if (result.geelyOnly.length > 0) {
console.log(`\n【商旅有但酒店没有的订单 - 共${result.geelyOnly.length}条】`);
console.log('-'.repeat(70));
result.geelyOnly.forEach((item, i) => {
console.log(`${i+1}. ${item.订单号} | ¥${String(item.金额).padStart(6)} | ${item.对账明细名称 || ''}`);
});
}
if (result.amountDiff.length > 0) {
console.log(`\n【金额存在差异的订单 - 共${result.amountDiff.length}条】`);
console.log('-'.repeat(70));
result.amountDiff.forEach((item, i) => {
console.log(`${i+1}. ${item.订单号} | ${item.入住人}`);
console.log(` 酒店: ¥${item.酒店金额} | 商旅: ¥${item.商旅金额} | 差额: ¥${item.差额}`);
});
}
console.log('\n' + '='.repeat(70));
// 计算总金额
let hotelTotal = 0;
let geelyTotal = 0;
for (const item of result.matched) {
hotelTotal += item.金额;
geelyTotal += item.金额;
}
for (const item of result.amountDiff) {
hotelTotal += item.酒店金额;
geelyTotal += item.商旅金额;
}
for (const item of result.hotelOnly) {
hotelTotal += item.结算金额;
}
for (const item of result.geelyOnly) {
geelyTotal += item.金额;
}
console.log(`\n【金额汇总】`);
console.log(` 酒店账单总金额: ¥${hotelTotal}`);
console.log(` 商旅账单总金额: ¥${geelyTotal}`);
console.log(` 总差额: ¥${hotelTotal - geelyTotal}`);
console.log('');
return {
统计: {
酒店订单总数: hotelOrdersCount,
商旅订单总数: geelyOrdersCount,
匹配成功: result.matched.length,
金额差异: result.amountDiff.length,
酒店有商旅无: result.hotelOnly.length,
商旅有酒店无: result.geelyOnly.length,
酒店总金额: hotelTotal,
商旅总金额: geelyTotal,
总差额: hotelTotal - geelyTotal
},
酒店有商旅无: result.hotelOnly,
商旅有酒店无: result.geelyOnly,
金额差异: result.amountDiff,
匹配成功: result.matched
};
}
/**
* 功能将结果保存为JSON文件
* 参数report - 报告对象, filename - 文件名
*/
function saveReportToJson(report, filename) {
const jsonReport = JSON.parse(JSON.stringify(report, (key, value) => {
if (typeof value === 'bigint') return value.toString();
return value;
}));
fs.writeFileSync(filename, JSON.stringify(jsonReport, null, 2), 'utf8');
console.log(`详细结果已保存到: ${filename}`);
}
// 主函数
function main() {
const geelyFile = '杭州碧丽酒店管理有限公司 2026年06月 对账单(0601-0630).xlsx';
const hotelFile = '杭州滨江希尔顿欢朋酒店-吉利商旅6月账单.xlsx';
console.log('正在读取Excel文件...');
const geelyWorkbook = readExcel(geelyFile);
const hotelWorkbook = readExcel(hotelFile);
console.log('正在提取订单数据...');
const hotelOrders = extractHotelOrders(hotelWorkbook);
const geelyOrders = extractGeelyExportOrders(geelyWorkbook);
console.log(`酒店有效订单数: ${hotelOrders.size}`);
console.log(`商旅Export表有效订单数(合并后): ${geelyOrders.size}`);
console.log('\n正在对比订单...');
const result = compareOrders(hotelOrders, geelyOrders);
const report = generateReport(result, hotelOrders.size, geelyOrders.size);
saveReportToJson(report, '账单差异报告.json');
console.log('\n对比完成');
}
main();