const https = require('https'); const XLSX = require('xlsx'); const path = require('path'); // ======================== 配置 ======================== const EXCEL_FILE = '新建 XLSX 工作表.xlsx'; const SHEET_NAME = '2026'; const BASE_HOST = 'superstar.geelytravel.com'; const API_BASE = '/api/tmc/tmc'; const STEP_DELAY_MS = 3000; const API_HEADERS = { 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9', 'authorization': 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKV1QtRC1TRVFfVVNFUl9UTUNfMDAwMDAxODMiLCJ1c2VyX2NvZGUiOiJTRVFfVVNFUl9UTUNfMDAwMDAxODMiLCJyb2xlcyI6WyLnu5PnrpfkuJPlkZgiLCLnu5PnrpfkuJPlkZgiXSwiZXhwIjoxNzg2NTMzNTUxLCJpYXQiOjE3ODM5NDE1NTF9.1Mcmf5IM07Scrry4FzPs6TAcfUu0Nqtjre_2iJlNd5c', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'requestsource': 'D', 'usercode': 'SEQ_USER_TMC_00000183', 'cookie': '_c_WBKFRo=CefecIUfnnuqejh60diYtytjDPwI9Qptslcwakc4; acw_tc=731dc87617839414337367659e81455072769ade5ba31e7a9d3e4d5d817123', 'Referer': 'https://superstar.geelytravel.com/new-tmc/finance/account/saleBill/list', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', }; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function httpRequest(method, path, headers, body) { return new Promise((resolve, reject) => { const opts = { hostname: BASE_HOST, path, method, headers: headers || API_HEADERS, rejectUnauthorized: false, }; const req = https.request(opts, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve({ status: res.statusCode, data: JSON.parse(data) }); } catch { resolve({ status: res.statusCode, data }); } }); }); req.on('error', reject); if (body) req.write(body); req.end(); }); } // ======================== 1. 读取 Excel ======================== function readTeamIds() { const workbook = XLSX.readFile(path.join(__dirname, EXCEL_FILE)); const sheet = workbook.Sheets[SHEET_NAME]; if (!sheet) { console.error('Sheet not found'); process.exit(1); } const rows = XLSX.utils.sheet_to_json(sheet, { defval: null }); const teamIds = []; for (const row of rows) { const val = row['团号']; const kaipiaoStatus = row['开票状态']; if (!val || !/^TEAM-\d+$/.test(String(val).trim())) continue; if (kaipiaoStatus && String(kaipiaoStatus).trim() !== '未开票') { console.log(` ⚠ ${String(val).trim()} 开票状态: "${kaipiaoStatus}",跳过`); continue; } teamIds.push(String(val).trim()); } return teamIds; } // ======================== 2. 获取 settlementAccountIds ======================== async function querySettlementAccountIds(teamId) { const path = `${API_BASE}/customerAccount/settlement/querySettlementAccountIds?customerSettlementAccountName=${encodeURIComponent(teamId)}`; console.log(` [GET] ${path}`); const res = await httpRequest('GET', path); console.log(` [OK]`, JSON.stringify(res.data)); return (res.data && res.data.code === 'Z000' && Array.isArray(res.data.result)) ? res.data.result : []; } // ======================== 3. 获取结算单列表 ======================== async function listReceiveStatements(settlementAccountId) { const path = `${API_BASE}/settlement/receiveStatements/listPageReceiveStatements?settlementAccountId=${settlementAccountId}&sortType=1&pageIndex=1&pageSize=10`; console.log(` [GET] ${path}`); const res = await httpRequest('GET', path); console.log(` [OK]`, JSON.stringify(res.data)); return (res.data && res.data.code === 'Z000' && res.data.result && res.data.result.list) ? res.data.result.list : []; } // ======================== 4. 自动对账:POST ======================== async function autoReconciliation(statementSeq, id) { const path = '/api/tmc/finance/frond/receiveStatements/autoReconciliation'; const headers = { ...API_HEADERS, 'content-type': 'application/json;charset=UTF-8', 'Referer': `https://superstar.geelytravel.com/new-tmc/finance/account/saleBill/detail/${id}/${statementSeq}`, }; const body = JSON.stringify({ statementSeq }); console.log(` [POST] ${path} body=${body}`); const res = await httpRequest('POST', path, headers, body); console.log(` [OK]`, JSON.stringify(res.data)); return res.data; } // ======================== 5. 确认对账:POST ======================== async function confirmReconciliation(statementSeq, id) { const path = `${API_BASE}/settlement/receiveStatementsGroup/confirmCompleteReceiveReconciliation?statementSeq=${statementSeq}`; const headers = { ...API_HEADERS, 'Referer': `https://superstar.geelytravel.com/new-tmc/finance/account/saleBill/detail/${id}/${statementSeq}`, }; console.log(` [POST] ${path}`); const res = await httpRequest('POST', path, headers, null); console.log(` [OK]`, JSON.stringify(res.data)); return res.data; } // ======================== 主流程 ======================== async function main() { const teamIds = readTeamIds(); console.log(` TEAM IDs: ${teamIds.join(', ')} `); // 预查询所有结算单,计算总任务数 console.log('预查询结算单列表...'); const allTasks = []; for (const teamId of teamIds) { const accounts = await querySettlementAccountIds(teamId); if (!accounts.length) continue; for (const account of accounts) { const statements = await listReceiveStatements(account.id); for (const st of statements) { allTasks.push({ teamId, accountId: account.id, statement: st }); } } } const total = allTasks.length; const pendingTasks = allTasks.filter(t => t.statement.statementStatus !== '5'); const pendingTotal = pendingTasks.length; const skipCount = total - pendingTotal; console.log(`总任务: ${total} | 跳过(已完成): ${skipCount} | 待处理: ${pendingTotal} `); if (pendingTotal === 0) { console.log('所有结算单已完成,无需处理。'); return; } const allResults = []; let completed = 0; for (const task of allTasks) { const { teamId, accountId, statement: st } = task; const { id, statementSeq, statementName, receivableTotalPrice, statementStatus } = st; const pct = Math.round((completed / pendingTotal) * 100); console.log(` ${'='.repeat(50)}`); console.log(` 进度: ${pct}% (${completed}/${pendingTotal}) | ${teamId} | ${statementSeq}`); console.log(`${'='.repeat(50)}`); console.log(` 结算单: ${statementName}`); console.log(` 应收: \u00a5${receivableTotalPrice} | 状态: ${statementStatus}`); if (statementStatus === '5') { console.log(` \u26a0 已完成,跳过`); allResults.push({ teamId, settlementAccountId: accountId, statementSeq, statementName, receivableTotalPrice, autoReconResult: { code: 'SKIP' }, confirmResult: { code: 'SKIP' } }); continue; } const autoResult = await autoReconciliation(statementSeq, id); console.log(` \u23f3 等待 ${STEP_DELAY_MS / 1000}s...`); await sleep(STEP_DELAY_MS); const confirmResult = await confirmReconciliation(statementSeq, id); allResults.push({ teamId, settlementAccountId: accountId, statementSeq, statementName, receivableTotalPrice, autoReconResult: autoResult, confirmResult }); completed++; console.log(` >>> 进度: ${Math.round((completed / pendingTotal) * 100)}% (${completed}/${pendingTotal})`); } // ======================== 汇总 ======================== console.log('\n\n' + '='.repeat(70)); console.log(' 最终汇总'); console.log('='.repeat(70)); for (const item of allResults) { const aOk = item.autoReconResult && (item.autoReconResult.code === 'Z000' || item.autoReconResult.code === 'SKIP'); const cOk = item.confirmResult && (item.confirmResult.code === 'Z000' || item.confirmResult.code === 'SKIP'); console.log(`\n${aOk ? '\u2705' : '\u274c'} 自动 | ${cOk ? '\u2705' : '\u274c'} 确认 | ${item.teamId} | ${item.statementSeq}`); console.log(` ${item.statementName} \u00a5${item.receivableTotalPrice}`); console.log(` 自动: ${JSON.stringify(item.autoReconResult)}`); console.log(` 确认: ${JSON.stringify(item.confirmResult)}`); } } main().catch(err => { console.error('Error:', err); process.exit(1); });