178 lines
7.5 KiB
JavaScript
178 lines
7.5 KiB
JavaScript
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(`\nTEAM IDs: ${teamIds.join(', ')}\n`);
|
||
|
||
const allResults = [];
|
||
|
||
for (const teamId of teamIds) {
|
||
console.log(`${'='.repeat(50)}\n 处理 ${teamId}\n${'='.repeat(50)}`);
|
||
|
||
const accounts = await querySettlementAccountIds(teamId);
|
||
if (!accounts.length) { console.log(' ⚠ 未找到'); continue; }
|
||
|
||
for (const account of accounts) {
|
||
const accountId = account.id;
|
||
console.log(`\n --> 结算单列表 settlementAccountId: ${accountId}`);
|
||
const statements = await listReceiveStatements(accountId);
|
||
console.log(` --> 共 ${statements.length} 条`);
|
||
|
||
for (const st of statements) {
|
||
const { id, statementSeq, statementName, receivableTotalPrice, statementStatus } = st;
|
||
console.log(`\n --- [${statementSeq}] ${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 });
|
||
}
|
||
}
|
||
}
|
||
|
||
// ======================== 汇总 ========================
|
||
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); });
|