geely-kaipiao/duizhang.js
2026-07-13 21:47:00 +08:00

201 lines
8.2 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 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); });