开票业务
This commit is contained in:
parent
5424d61de9
commit
c1e378a631
371
kaipiao.js
Normal file
371
kaipiao.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
BIN
新建 XLSX 工作表.xlsx
BIN
新建 XLSX 工作表.xlsx
Binary file not shown.
Loading…
Reference in New Issue
Block a user