From c1e378a6313a844a9a1244464b68a82aea91c6af Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 14 Jul 2026 21:09:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E7=A5=A8=E4=B8=9A=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kaipiao.js | 371 ++++++++++++++++++++++++++++++++++++++++++ 新建 XLSX 工作表.xlsx | Bin 10069 -> 10068 bytes 2 files changed, 371 insertions(+) create mode 100644 kaipiao.js diff --git a/kaipiao.js b/kaipiao.js new file mode 100644 index 0000000..e0e8ad8 --- /dev/null +++ b/kaipiao.js @@ -0,0 +1,371 @@ +const XLSX = require('xlsx'); +const path = require('path'); + +// ========== 配置 ========== +const EXCEL_FILE = path.join(__dirname, '新建 XLSX 工作表.xlsx'); +const SHEET_NAME = '2026'; + +const BASE_URL = 'https://superstar.geelytravel.com'; +const AUTH_TOKEN = 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKV1QtRC1TRVFfVVNFUl9UTUNfMDAwMDAxODMiLCJ1c2VyX2NvZGUiOiJTRVFfVVNFUl9UTUNfMDAwMDAxODMiLCJyb2xlcyI6WyLnu5PnrpfkuJPlkZgiLCLnu5PnrpfkuJPlkZgiXSwiZXhwIjoxNzg2NTMzNTUxLCJpYXQiOjE3ODM5NDE1NTF9.1Mcmf5IM07Scrry4FzPs6TAcfUu0Nqtjre_2iJlNd5c'; + +const COMMON_HEADERS = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9', + 'authorization': AUTH_TOKEN, + 'cache-control': 'no-cache', + 'pragma': 'no-cache', + 'requestsource': 'D', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'usercode': 'SEQ_USER_TMC_00000183', + 'cookie': '_c_WBKFRo=CefecIUfnnuqejh60diYtytjDPwI9Qptslcwakc4; acw_tc=731dc87617840296932491914e815aef28b42e276e1a01eb53ed0f548bd5e3', +}; + +const INVOICE_APPLY_TITLE = '浙江吉利商务服务有限公司'; +const INVOICE_TITLE = '吉智(杭州)文化创意有限公司'; +const TAXPAYER_ID = '91330109MA2GM71J64'; +const ADDRESS = '杭州市萧山区经纪技术开发区启迪路198号A-B102-126B室'; +const TELEPHONE = '0571-85321518'; +const DEPOSIT_BANK = '中国民生银行股份有限公司杭州分行'; +const BANK_ACCOUNT = '631101003'; + +// ========== 1. 读取 Excel ========== +function readTeamIds() { + const workbook = XLSX.readFile(EXCEL_FILE); + const sheet = workbook.Sheets[SHEET_NAME]; + if (!sheet) { + console.error('Sheet "' + SHEET_NAME + '" 不存在'); + process.exit(1); + } + const rows = XLSX.utils.sheet_to_json(sheet, { defval: null }); + const teamIds = []; + for (const row of rows) { + const val = row['团号']; + if (val && /^TEAM-\d+$/.test(String(val).trim())) { + teamIds.push(String(val).trim()); + } + } + console.log('[Excel] 找到 ' + teamIds.length + ' 个 TEAM ID: ' + teamIds.join(', ') + '\n'); + return teamIds; +} + +// ========== 2. 查询 settlement account ========== +async function querySettlementAccountIds(teamId) { + const url = BASE_URL + '/api/tmc/tmc/customerAccount/settlement/querySettlementAccountIds?customerSettlementAccountName=' + teamId; + const resp = await fetch(url, { headers: COMMON_HEADERS, method: 'GET' }); + return resp.json(); +} + +// ========== 3. 查询 bill list ========== +async function queryBillList(settlementAccountId) { + const url = BASE_URL + '/api/tmc/tmc/settlement/receiveBill/queryBillList?settlementAccountId=' + settlementAccountId + '&pageIndex=1&pageSize=10'; + const resp = await fetch(url, { headers: COMMON_HEADERS, method: 'GET' }); + return resp.json(); +} + +// ========== 4. 批量调整发票类型 ========== +async function batchAdjustInvoiceOrderPrice(settlementSeq, receiveBillId, originInvoiceType, targetInvoiceType) { + const url = BASE_URL + '/api/tmc/tmc/settlement/receiveInvoice/batchAdjustInvoiceOrderPrice'; + const headers = { + ...COMMON_HEADERS, + 'content-type': 'application/json;charset=UTF-8', + 'Referer': BASE_URL + '/new-tmc/finance/settlement/pay/invoice/billing/' + receiveBillId, + }; + const body = JSON.stringify({ + settlementSeq: settlementSeq, + orderMode: '1', + originInvoiceType: originInvoiceType, + targetInvoiceType: targetInvoiceType, + }); + const resp = await fetch(url, { headers: headers, method: 'POST', body: body }); + return resp.json(); +} + +// ========== 5. 确认开票 ========== +async function confirmInvoiceOrder(settlementSeq, receiveBillId) { + const url = BASE_URL + '/api/tmc/tmc/settlement/receiveInvoice/confirmInvoiceOrder'; + const headers = { + ...COMMON_HEADERS, + 'content-type': 'application/json;charset=UTF-8', + 'Referer': BASE_URL + '/new-tmc/finance/settlement/pay/invoice/billing/' + receiveBillId, + }; + const body = JSON.stringify({ settlementSeq: settlementSeq, orderMode: '1' }); + const resp = await fetch(url, { headers: headers, method: 'POST', body: body }); + return resp.json(); +} + +// ========== 6. 提交开票 ========== +async function submitInvoiceApply(confirmInvoiceKey, invoiceInfoList, receiveBillId) { + const url = BASE_URL + '/api/tmc/tmc/settlement/receiveInvoice/submitInvoiceApply'; + const headers = { + ...COMMON_HEADERS, + 'content-type': 'application/json;charset=UTF-8', + 'Referer': BASE_URL + '/new-tmc/finance/settlement/pay/invoice/billing/' + receiveBillId, + }; + const body = JSON.stringify({ + confirmInvoiceKey: confirmInvoiceKey, + invoiceInfoList: invoiceInfoList, + }); + const resp = await fetch(url, { headers: headers, method: 'POST', body: body }); + return resp.json(); +} + +// ========== 7. 填充公司信息 ========== +function fillCompanyInfo(item) { + return { + ...item, + invoiceApplyTitle: INVOICE_APPLY_TITLE, + invoiceTitle: INVOICE_TITLE, + taxpayerIdentityNumber: TAXPAYER_ID, + address: ADDRESS, + telephone: TELEPHONE, + depositBank: DEPOSIT_BANK, + bankAccount: BANK_ACCOUNT, + }; +} + +// ========== 8. 映射火车票开票项 ========== +function mapTrainInvoice(serverItem) { + const invoiceType = serverItem.invoiceType || ''; + const item = { + invoiceSerialNum: serverItem.invoiceSerialNum, + invoiceType: invoiceType, + invoicePrice: serverItem.invoicePrice, + taxRate: invoiceType === '13' ? 6 : 0, + invoiceSettlementDetail: serverItem.settlementDetails || '', + invoiceQuantity: serverItem.invoiceQuantity || 0, + ticketList: serverItem.ticketList || null, + }; + if (invoiceType === '13') { + item.invoiceContent = '*生产生活服务*代订火车票服务费'; + } + return fillCompanyInfo(item); +} + +// ========== 9. 映射酒店开票项 ========== +function mapHotelInvoice(serverItem) { + const invoiceType = serverItem.invoiceType || ''; + const item = { + invoiceSerialNum: serverItem.invoiceSerialNum, + invoiceType: invoiceType, + invoicePrice: serverItem.invoicePrice, + taxRate: 6, + invoiceSettlementDetail: serverItem.settlementDetails || '', + invoiceQuantity: serverItem.invoiceQuantity || 0, + ticketList: serverItem.ticketList || null, + }; + if (invoiceType === '2' || invoiceType === '1') { + item.invoiceContent = '*生产生活服务*代订住宿费'; + } else if (invoiceType === '13') { + item.invoiceContent = '*生产生活服务*代订住宿服务费'; + } + return fillCompanyInfo(item); +} + +// ========== 10. 映射机票开票项(国内+国际通用) ========== +function mapFlightInvoice(serverItem) { + const invoiceType = serverItem.invoiceType || ''; + const item = { + invoiceSerialNum: serverItem.invoiceSerialNum, + invoiceType: invoiceType, + invoicePrice: serverItem.invoicePrice, + taxRate: 6, + invoiceSettlementDetail: serverItem.settlementDetails || '', + invoiceQuantity: serverItem.invoiceQuantity || 0, + ticketList: serverItem.ticketList || null, + }; + if (invoiceType === '2') { + item.invoiceContent = '*生产生活服务*代订机票'; + } else if (invoiceType === '13') { + item.invoiceContent = '*生产生活服务*代订机票服务费'; + } + return fillCompanyInfo(item); +} + +// ========== 11. 处理火车票 ========== +async function processTrainBill(bill, billIndex, totalBills) { + const settlementSeq = bill.settlementSeq; + const receiveBillId = bill.receiveBillId; + + console.log(' --- 火车票 [' + billIndex + '/' + totalBills + '] ' + bill.statementName + ' ---'); + + const confirmResp = await confirmInvoiceOrder(settlementSeq, receiveBillId); + console.log(' -> confirmInvoiceOrder:', JSON.stringify(confirmResp)); + + const confirmResult = confirmResp?.result; + if (!confirmResult || !confirmResult.invoiceAble) { + console.log(' ⚠ 跳过: ' + (confirmResp?.message || '') + '\n'); + return; + } + + const confirmInvoiceKey = confirmResult.confirmInvoiceKey; + const submitList = (confirmResult.invoiceInfoList || []).map(mapTrainInvoice); + + const submitResp = await submitInvoiceApply(confirmInvoiceKey, submitList, receiveBillId); + console.log(' -> submitInvoiceApply:', JSON.stringify(submitResp)); + console.log(''); +} + +// ========== 12. 处理酒店 ========== +async function processHotelBill(bill, billIndex, totalBills) { + const settlementSeq = bill.settlementSeq; + const receiveBillId = bill.receiveBillId; + + console.log(' --- 酒店 [' + billIndex + '/' + totalBills + '] ' + bill.statementName + ' ---'); + + const adjustResp = await batchAdjustInvoiceOrderPrice(settlementSeq, receiveBillId, '1', '2'); + console.log(' -> batchAdjust (1→2):', JSON.stringify(adjustResp)); + if (adjustResp?.code !== 'Z000') { + console.log(' ⚠ 调整失败: ' + (adjustResp?.message || '') + '\n'); + return; + } + + const confirmResp = await confirmInvoiceOrder(settlementSeq, receiveBillId); + console.log(' -> confirmInvoiceOrder:', JSON.stringify(confirmResp)); + + const confirmResult = confirmResp?.result; + if (!confirmResult || !confirmResult.invoiceAble) { + console.log(' ⚠ 跳过: ' + (confirmResp?.message || '') + '\n'); + return; + } + + const confirmInvoiceKey = confirmResult.confirmInvoiceKey; + const submitList = (confirmResult.invoiceInfoList || []).map(mapHotelInvoice); + + const submitResp = await submitInvoiceApply(confirmInvoiceKey, submitList, receiveBillId); + console.log(' -> submitInvoiceApply:', JSON.stringify(submitResp)); + console.log(''); +} + +// ========== 13. 处理国内机票 ========== +async function processFlightBill(bill, billIndex, totalBills) { + const settlementSeq = bill.settlementSeq; + const receiveBillId = bill.receiveBillId; + + console.log(' --- 国内机票 [' + billIndex + '/' + totalBills + '] ' + bill.statementName + ' ---'); + + const adjust1Resp = await batchAdjustInvoiceOrderPrice(settlementSeq, receiveBillId, '4', '2'); + console.log(' -> batchAdjust (4→2):', JSON.stringify(adjust1Resp)); + if (adjust1Resp?.code !== 'Z000') { + console.log(' ⚠ 调整失败: ' + (adjust1Resp?.message || '') + '\n'); + return; + } + + const adjust2Resp = await batchAdjustInvoiceOrderPrice(settlementSeq, receiveBillId, '30', '2'); + console.log(' -> batchAdjust (30→2):', JSON.stringify(adjust2Resp)); + if (adjust2Resp?.code !== 'Z000') { + console.log(' ⚠ 调整失败: ' + (adjust2Resp?.message || '') + '\n'); + return; + } + + const confirmResp = await confirmInvoiceOrder(settlementSeq, receiveBillId); + console.log(' -> confirmInvoiceOrder:', JSON.stringify(confirmResp)); + + const confirmResult = confirmResp?.result; + if (!confirmResult || !confirmResult.invoiceAble) { + console.log(' ⚠ 跳过: ' + (confirmResp?.message || '') + '\n'); + return; + } + + const confirmInvoiceKey = confirmResult.confirmInvoiceKey; + const submitList = (confirmResult.invoiceInfoList || []).map(mapFlightInvoice); + + const submitResp = await submitInvoiceApply(confirmInvoiceKey, submitList, receiveBillId); + console.log(' -> submitInvoiceApply:', JSON.stringify(submitResp)); + console.log(''); +} + +// ========== 14. 处理国际机票 ========== +async function processInternationalFlightBill(bill, billIndex, totalBills) { + const settlementSeq = bill.settlementSeq; + const receiveBillId = bill.receiveBillId; + + console.log(' --- 国际机票 [' + billIndex + '/' + totalBills + '] ' + bill.statementName + ' ---'); + + const confirmResp = await confirmInvoiceOrder(settlementSeq, receiveBillId); + console.log(' -> confirmInvoiceOrder:', JSON.stringify(confirmResp)); + + const confirmResult = confirmResp?.result; + if (!confirmResult || !confirmResult.invoiceAble) { + console.log(' ⚠ 跳过: ' + (confirmResp?.message || '') + '\n'); + return; + } + + const confirmInvoiceKey = confirmResult.confirmInvoiceKey; + const submitList = (confirmResult.invoiceInfoList || []).map(mapFlightInvoice); + + const submitResp = await submitInvoiceApply(confirmInvoiceKey, submitList, receiveBillId); + console.log(' -> submitInvoiceApply:', JSON.stringify(submitResp)); + console.log(''); +} + +// ========== 主流程 ========== +async function main() { + const teamIds = readTeamIds(); + if (teamIds.length === 0) { + console.log('没有找到 TEAM ID,退出'); + return; + } + + for (let i = 0; i < teamIds.length; i++) { + const teamId = teamIds[i]; + console.log('[' + (i + 1) + '/' + teamIds.length + '] 处理 ' + teamId + ' ...'); + + try { + const settlementResp = await querySettlementAccountIds(teamId); + const resultList = settlementResp?.result || []; + if (resultList.length === 0) { + console.log(' ⚠ ' + teamId + ' 没有 settlement account,跳过\n'); + continue; + } + const settlementAccountId = resultList[0]?.id; + console.log(' -> settlementAccountId: ' + settlementAccountId); + + const billResp = await queryBillList(settlementAccountId); + const bills = billResp?.result?.list || []; + const total = billResp?.result?.total || 0; + console.log(' -> 共 ' + total + ' 条对账单\n'); + + const trainBills = bills.filter(function (b) { return (b.statementName || '').includes('火车票'); }); + const hotelBills = bills.filter(function (b) { return (b.statementName || '').includes('酒店'); }); + const flightBills = bills.filter(function (b) { return (b.statementName || '').includes('国内机票'); }); + const intlFlightBills = bills.filter(function (b) { return (b.statementName || '').includes('国际机票'); }); + const otherBills = bills.filter(function (b) { + const name = b.statementName || ''; + return !name.includes('火车票') && !name.includes('酒店') && !name.includes('国内机票') && !name.includes('国际机票'); + }); + + console.log(' -> 火车票: ' + trainBills.length + ', 酒店: ' + hotelBills.length + ', 国内机票: ' + flightBills.length + ', 国际机票: ' + intlFlightBills.length + ', 其他: ' + otherBills.length + '\n'); + + for (let j = 0; j < trainBills.length; j++) { + await processTrainBill(trainBills[j], j + 1, trainBills.length); + } + for (let j = 0; j < hotelBills.length; j++) { + await processHotelBill(hotelBills[j], j + 1, hotelBills.length); + } + for (let j = 0; j < flightBills.length; j++) { + await processFlightBill(flightBills[j], j + 1, flightBills.length); + } + for (let j = 0; j < intlFlightBills.length; j++) { + await processInternationalFlightBill(intlFlightBills[j], j + 1, intlFlightBills.length); + } + + if (otherBills.length > 0) { + console.log(' === 以下对账单未处理(未知类型) ==='); + otherBills.forEach(function (b) { console.log(' - ' + b.statementName); }); + console.log(''); + } + } catch (err) { + console.error(' ✗ ' + teamId + ' 出错:', err.message); + } + console.log(''); + } + + console.log('========== 完成 =========='); +} + +main().catch(err => { + console.error('Fatal error:', err); + process.exit(1); +}); diff --git a/新建 XLSX 工作表.xlsx b/新建 XLSX 工作表.xlsx index 12b0302a65cf915df655e3559862b4fdca78c814..f31805bb593f90d6c9187ae23b7db78023a6ef34 100644 GIT binary patch delta 1319 zcmV+?1=#x4PSj4YwE+aO@G;MmxdAPIP(W6aN)&ZPB_xW9*je_pjY#asHl*2e7S`;! z5ZnSMX+tUyOV9uF=FiCSbXVlT3mBVel^{AIC;%n2%+w}9_se;Bi301ml04T65>$gl z)AaNt79|tfz^&0G7?*(!JV{w5$^>nlD;dUCY@y(GY3Yi$bd{L^f3$;A_Y-vAyH za*hjdTyn>80}RWai1tV+dSv-*@)jTkh8zk|&SE;E_{ZzO6!y=CRv9>p%+;meX&-mU zmZB4T_s(X$v8t+36}2&Zeu^I!H+Su-VWyf}2|#Hq1rr9i(U8VF&Rf8nS^w_q?FMK zBjm9eeAM40>`2( zr33%~7yy&sCKZ1(E_iKhjg-l5+b|G@?*;k}1n&_gXKM`0K^6op&;mi-q9QrO2br9qzWHaNR+o>hVF&O*+pgqAmU9N3vX$<3CI9*3?&uR|0cBT7 zV>>AM69j&_{`g_lTmKLm0AxhyLdhFMw-h3%23i?1>!5!lPi=iGk+yy(g7YA&^ipfY zBG1o6D|N>Q!qUHoVe48exV38ELWcvygON!3L!(_7X^+*rc$Jqu+hA+Vlx@Wz?RAzH;^MzNTlEU`MzLDyANKBuz>bpJYNPQfr8^1&eBtzcpY<+e zs(V&3D^P!NE&y-#0d0FLQS$XFb>QBM^{UcT#n^i0p)UDVvAkYzvZoWjH1uJ%W+=BC zFrW}B8Wqms2yCr=hzGxrDUa-QAQGiQeSjMfvt7qT!+*eFKLA(jNcoRz23A&hGOaZBnFTUf+H$aE#52~Hq!8hr5x(m!e zO}t7xqlEoX+O$UNl9igcwMcU`?i#wzK-uNlF=w^4IBuxOk-GuhJLaSVztMl`>p|_a zUem=*_qF8Cdh}9b?8*SUOO!Yv(+6h#50DqvMDYu zQ2^@AE4Ee2C@Cqh($_Ce&PdHoEY`~}NX-Mvq~#aoCYAuXMd|v*1x2ZeDa9G7sU^8N z`bGu@X8O5_nR!YI$@x&Fc_p?=CQ1sWd6|W!sm?IIolSA3olS||)7guj_HVb*FR`=H z2Z=yM_cT0Ty5f1)wr35E=yD;hj=s9aW(J1n63^zWdJ5KukkAME!VUnlnG!t^0SA-U zB0vOT9smH7u_P9glpYWZ000000RSKX004!POe2#7vhXp_lj9@D0j-m(BsK!nACvJP z9FzYf5F1czzzfd+000C5000yK0000000031AOHXWmy;?bK>^H@Y$ZAYN0X-|HUVyv d-X%Z*n3EqS>IE<%t~WuG4ktkdt0DjZ005#0Z8rb_ delta 1291 zcmV+m1@!vVPSsAZwE+aJpOzDoxdAPIv;|pdDpAxGm5?YZVrSXYG$OGh+mL3@Sy;2@ zLU0S5qz$b=EIt3vn?EDRvt5w~FJNq@)fCYIK>;YCWv0?8x?e5AOB7hgmE^frFhw<3 zG)vCUVo@@o4cr=Cf^iwxz>}0^qMV|wb0x#riY*k}4!lEoaidMaoqw7Xms~u5@f7fo zkO?lpamgLW4KOTwBHAOR=#k~K$y^}sR8bql=co8#d2`pU8fL1wl>n5)QZQkFJ8hEr78KOMTB|h1NA>20 zIk#@<&%Mb&Ue(Fh$Lr_&Ta153D<0V9@2DUHpNMt&56U{4U#}J@8IoZXl5t3fD@qw1 zGeRDl!6*Gq!md#4$Nx{9gybTm(TYZFL>UZ}9-X=>!?djw zaBsw3Lmx(n1H}XC_nowUqCGWl;x&#BnCqnHNaFqInR&(XM4y(z8E@K<38UF4?KI0P zcJ)u5TAYCXQ!LHhZg0;JY%#fwR5CoLbSFU&UOB@sWP|mY=wDPE6{xsm5bpLq7=L}w z1E+PFPThg6wMSDn1^XNzeE#IqfXVfp*gs!4jbh3Rj z!zYm2E})cUwxq=DgOWXbfdeRXCVz#;HiSeQW+#loNcbjN#-^u4^QZJ3#wf*Qc5(Dv zQX*3QUPL4a8!0J|Sl7CAW+2#ei1U`m8&RwlSP>D8H)utr9_NQTFWCXp7B9ZrV_z}* z81iOltludVtuu5nZ%hGW#b@97>>HtD#YfeaZ{aI<1)lk z8DS5Jf>SpCVCuh-|Z&`J=*18C!QHn~PtQG}G=|M3;SnJ#0k?y%*M$imS$ z1BFziGSI@p^?Wuu1;yg2^mQV%utJ8ZJ3KJVqK1~(LM76iY0X82(epSpNQ1daZS*o= zyUPRe{fa)mRc6jl}&Tw!>T>tdaKYki5&gk#ucl}y# zU*EP5ADi|4zMEg3UyUYtmhX$r^Lzi({R=(3a67Y@5n} zBN_pQlS?C$1g)Qz6O-a2#{sUBsw6f6)sx{QEgSTeHU7>3000C5000yK0000000031 zAOHXWnUg6cK>^K^Yb81XNt31}HUV&x-6cQ)nUfwS>ILqcNeM%f4JSbctResa003Qx BV0i!l