wujianwei
2026-03-31 3c8ed575db70e0b995c7ee541ff3753ee8b57d87
tms/src/main/java/com/ruoyi/tms/service/impl/TmsReceivableFeeServiceImpl.java
@@ -1,15 +1,29 @@
package com.ruoyi.tms.service.impl;
import java.io.File;
import java.io.FileOutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.SystemDataNoEnum;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.UtilException;
import com.ruoyi.common.utils.file.DownloadExportUtil;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.config.RuoYiConfig;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFColor;
import javax.annotation.Resource;
import com.ruoyi.common.utils.SecurityUtils;
@@ -18,11 +32,21 @@
import com.ruoyi.tms.domain.*;
import com.ruoyi.tms.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.scheduling.annotation.Async;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.web.client.RestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.utils.PageUtils;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.annotation.DataSource;
@@ -57,7 +81,14 @@
    private TmsArBillMapper tmsArBillMapper;
    @Resource
    private TmsArBillItemMapper tmsArBillItemMapper;
    @Autowired
    private RestTemplate restTemplate;
    @Value("${custom.cwxtApi.url}")
    private String url;
    @Autowired
    private RedisCache redisCache;
    /**
     * 查询应收费用
     *
@@ -136,6 +167,468 @@
            PageUtils.startPage(pageNum, Constants.EXPORT_PATE_SIZE);
            return selectTmsReceivableFeeList(tmsReceivableFee);
        });
    }
    /**
     * 导出对账单一式多联格式
     *
     * @param tmsReceivableFee 应收费用查询条件
     * @param exportKey 导出功能的唯一标识
     */
    @DataSource(DataSourceType.SLAVE)
    @Async
    @Override
    public void exportArBillFormat(TmsReceivableFee tmsReceivableFee, String exportKey) {
        String fileName = ExcelUtil.encodeFileName("对账单");
        // 设置当前任务为"下载中"状态
        DownloadExportUtil.deleteDownloadFile(redisCache, exportKey, "0");
        try {
            // 执行导出并获取文件名
            fileName = exportArBillData(fileName, tmsReceivableFee);
            // 设置下载完成状态
            DownloadExportUtil.setDownloadFile(redisCache, exportKey, fileName);
            logger.info("Export completed for key: {}, file: {}", exportKey, fileName);
        } catch (Exception e) {
            logger.error("Export failed for key: {}, error: {}", exportKey, e.getMessage(), e);
            DownloadExportUtil.deleteDownloadFile(redisCache, exportKey, "1"); // 设置失败状态
            throw e;
        }
    }
    /**
     * 导出对账单一式多联数据
     *
     * @param fileName 文件名
     * @param tmsReceivableFee 应收费用查询条件
     * @return 导出后的文件名
     */
    protected String exportArBillData(String fileName, TmsReceivableFee tmsReceivableFee) {
        try (SXSSFWorkbook workbook = new SXSSFWorkbook(1000)) {
            // 创建工作表
            SXSSFSheet sheet = workbook.createSheet("对账单");
            // 设置列宽
            sheet.setColumnWidth(0, 5000);  // 序号
            sheet.setColumnWidth(1, 5000);  // 装货日期
            sheet.setColumnWidth(2, 8000);  // 装货点
            sheet.setColumnWidth(3, 8000);  // 卸货点
            sheet.setColumnWidth(4, 5000);  // 车牌
            sheet.setColumnWidth(5, 5000);  // 型号
            sheet.setColumnWidth(6, 5000);  // 运费
            sheet.setColumnWidth(7, 5000);  // 无缝费
            sheet.setColumnWidth(8, 5000);  // 香港清关费
            sheet.setColumnWidth(9, 10000); // 备注
            // 创建样式
            Map<String, CellStyle> styles = createArBillStyles(workbook);
            // 标题区域
            createTitleArea(sheet, styles, tmsReceivableFee);
            // 表头
            createTableHeader(sheet, styles);
            // 数据区域
            int startRow = 4;
            List<TmsReceivableFee> feeList = selectTmsReceivableFeeList(tmsReceivableFee);
            BigDecimal totalFreight = BigDecimal.ZERO;
            BigDecimal totalSeamless = BigDecimal.ZERO;
            BigDecimal totalCustoms = BigDecimal.ZERO;
            for (int i = 0; i < feeList.size(); i++) {
                TmsReceivableFee fee = feeList.get(i);
                Row row = sheet.createRow(startRow + i);
                // 序号
                Cell cell0 = row.createCell(0);
                cell0.setCellValue(i + 1);
                cell0.setCellStyle(styles.get("data"));
                // 装货日期
                Cell cell1 = row.createCell(1);
                if (fee.getDispatchConfirmTime() != null) {
                    cell1.setCellValue(DateUtils.parseDateToStr("yyyy-MM-dd", fee.getDispatchConfirmTime()));
                }
                cell1.setCellStyle(styles.get("data"));
                // 装货点
                Cell cell2 = row.createCell(2);
                // 这里需要根据实际数据结构获取装货点信息
                cell2.setCellValue("" + (fee.getProjectName() != null ? fee.getProjectName() : ""));
                cell2.setCellStyle(styles.get("data"));
                // 卸货点
                Cell cell3 = row.createCell(3);
                // 这里需要根据实际数据结构获取卸货点信息
                cell3.setCellValue("" + (fee.getCustomerName() != null ? fee.getCustomerName() : ""));
                cell3.setCellStyle(styles.get("data"));
                // 车牌
                Cell cell4 = row.createCell(4);
                // 这里需要根据实际数据结构获取车牌号
                cell4.setCellValue("");
                cell4.setCellStyle(styles.get("data"));
                // 型号
                Cell cell5 = row.createCell(5);
                // 这里需要根据实际数据结构获取型号
                cell5.setCellValue("");
                cell5.setCellStyle(styles.get("data"));
                // 运费
                Cell cell6 = row.createCell(6);
                BigDecimal freight = fee.getReceivableRMBAmount();
                cell6.setCellValue(freight != null ? freight.doubleValue() : 0);
                cell6.setCellStyle(styles.get("data"));
                totalFreight = totalFreight.add(freight != null ? freight : BigDecimal.ZERO);
                // 无缝费
                Cell cell7 = row.createCell(7);
                BigDecimal seamless = BigDecimal.ZERO; // 这里需要根据实际数据结构获取无缝费
                cell7.setCellValue(seamless.doubleValue());
                cell7.setCellStyle(styles.get("data"));
                totalSeamless = totalSeamless.add(seamless);
                // 香港清关费
                Cell cell8 = row.createCell(8);
                BigDecimal customs = fee.getReceivableHKBAmount();
                cell8.setCellValue(customs != null ? customs.doubleValue() : 0);
                cell8.setCellStyle(styles.get("data"));
                totalCustoms = totalCustoms.add(customs != null ? customs : BigDecimal.ZERO);
                // 备注
                Cell cell9 = row.createCell(9);
                cell9.setCellValue("" + (fee.getDispatchNo() != null ? fee.getDispatchNo() : ""));
                cell9.setCellStyle(styles.get("data"));
            }
            // 小计行
            int subTotalRow = startRow + feeList.size();
            Row subTotal = sheet.createRow(subTotalRow);
            Cell subTotalCell = subTotal.createCell(2);
            subTotalCell.setCellValue("小计");
            subTotalCell.setCellStyle(styles.get("total"));
            sheet.addMergedRegion(new CellRangeAddress(subTotalRow, subTotalRow, 2, 5));
            Cell subTotalFreight = subTotal.createCell(6);
            subTotalFreight.setCellValue(totalFreight.doubleValue());
            subTotalFreight.setCellStyle(styles.get("total"));
            Cell subTotalSeamless = subTotal.createCell(7);
            subTotalSeamless.setCellValue(totalSeamless.doubleValue());
            subTotalSeamless.setCellStyle(styles.get("total"));
            Cell subTotalCustoms = subTotal.createCell(8);
            subTotalCustoms.setCellValue(totalCustoms.doubleValue());
            subTotalCustoms.setCellStyle(styles.get("total"));
            // 合计行
            int totalRow = subTotalRow + 1;
            Row total = sheet.createRow(totalRow);
            Cell totalCell = total.createCell(2);
            totalCell.setCellValue("合计(CNB)");
            totalCell.setCellStyle(styles.get("total"));
            sheet.addMergedRegion(new CellRangeAddress(totalRow, totalRow, 2, 5));
            Cell totalFreightCell = total.createCell(6);
            totalFreightCell.setCellValue(totalFreight.doubleValue());
            totalFreightCell.setCellStyle(styles.get("total"));
            Cell totalSeamlessCell = total.createCell(7);
            totalSeamlessCell.setCellValue(totalSeamless.doubleValue());
            totalSeamlessCell.setCellStyle(styles.get("total"));
            Cell totalCustomsCell = total.createCell(8);
            totalCustomsCell.setCellValue(totalCustoms.doubleValue());
            totalCustomsCell.setCellStyle(styles.get("total"));
            // 备注说明
            createNotesArea(sheet, styles, totalRow + 1);
            // 账户信息
            createAccountInfo(sheet, styles, totalRow + 4);
            // 签字盖章区域
            createSignatureArea(sheet, styles, totalRow + 8);
            // 保存文件
            String path = RuoYiConfig.getDownloadPath() + fileName;
            File file = new File(path);
            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            try (FileOutputStream fos = new FileOutputStream(file)) {
                workbook.write(fos);
            }
            return fileName;
        } catch (Exception e) {
            logger.error("Export AR bill format failed: {}", e.getMessage(), e);
            throw new UtilException("Export failed!");
        }
    }
    /**
     * 创建对账单一式多联样式
     *
     * @param workbook 工作簿
     * @return 样式映射
     */
    private Map<String, CellStyle> createArBillStyles(SXSSFWorkbook workbook) {
        Map<String, CellStyle> styles = new HashMap<>();
        // 标题样式
        CellStyle titleStyle = workbook.createCellStyle();
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        Font titleFont = workbook.createFont();
        titleFont.setFontName("Arial");
        titleFont.setFontHeightInPoints((short) 16);
        titleFont.setBold(true);
        titleStyle.setFont(titleFont);
        styles.put("title", titleStyle);
        // 表头样式
        CellStyle headerStyle = workbook.createCellStyle();
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        Font headerFont = workbook.createFont();
        headerFont.setFontName("Arial");
        headerFont.setFontHeightInPoints((short) 10);
        headerFont.setBold(true);
        headerStyle.setFont(headerFont);
        setDefaultBorders(headerStyle);
        styles.put("header", headerStyle);
        // 数据样式
        CellStyle dataStyle = workbook.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);
        dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        Font dataFont = workbook.createFont();
        dataFont.setFontName("Arial");
        dataFont.setFontHeightInPoints((short) 10);
        dataStyle.setFont(dataFont);
        setDefaultBorders(dataStyle);
        styles.put("data", dataStyle);
        // 合计样式
        CellStyle totalStyle = workbook.createCellStyle();
        totalStyle.setAlignment(HorizontalAlignment.CENTER);
        totalStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        totalStyle.setFillForegroundColor(IndexedColors.GREY_125_PERCENT.getIndex());
        totalStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        Font totalFont = workbook.createFont();
        totalFont.setFontName("Arial");
        totalFont.setFontHeightInPoints((short) 10);
        totalFont.setBold(true);
        totalStyle.setFont(totalFont);
        setDefaultBorders(totalStyle);
        styles.put("total", totalStyle);
        // 普通文本样式
        CellStyle textStyle = workbook.createCellStyle();
        textStyle.setAlignment(HorizontalAlignment.LEFT);
        textStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        Font textFont = workbook.createFont();
        textFont.setFontName("Arial");
        textFont.setFontHeightInPoints((short) 10);
        textStyle.setFont(textFont);
        styles.put("text", textStyle);
        return styles;
    }
    /**
     * 设置默认边框
     *
     * @param style 单元格样式
     */
    private void setDefaultBorders(CellStyle style) {
        style.setBorderRight(BorderStyle.THIN);
        style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderLeft(BorderStyle.THIN);
        style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderTop(BorderStyle.THIN);
        style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
        style.setBorderBottom(BorderStyle.THIN);
        style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
    }
    /**
     * 创建标题区域
     *
     * @param sheet 工作表
     * @param styles 样式映射
     * @param tmsReceivableFee 应收费用查询条件
     */
    private void createTitleArea(SXSSFSheet sheet, Map<String, CellStyle> styles, TmsReceivableFee tmsReceivableFee) {
        // 标题行
        Row titleRow = sheet.createRow(0);
        titleRow.setHeightInPoints(30);
        Cell titleCell = titleRow.createCell(0);
        titleCell.setCellValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
        titleCell.setCellStyle(styles.get("title"));
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 9));
        // 对账单行
        Row billRow = sheet.createRow(1);
        billRow.setHeightInPoints(20);
        Cell toCell = billRow.createCell(0);
        toCell.setCellValue("TO: XXXXXXXXXXXXXXXXXXXXXXXXXXX");
        toCell.setCellStyle(styles.get("text"));
        Cell billTitleCell = billRow.createCell(3);
        billTitleCell.setCellValue("2025年06月对账单");
        billTitleCell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 3, 6));
        // 公司信息行
        Row companyRow = sheet.createRow(2);
        companyRow.setHeightInPoints(20);
        Cell fromCell = companyRow.createCell(0);
        fromCell.setCellValue("FROM:珠海市汇畅交通投资有限公司");
        fromCell.setCellStyle(styles.get("text"));
    }
    /**
     * 创建表格表头
     *
     * @param sheet 工作表
     * @param styles 样式映射
     */
    private void createTableHeader(SXSSFSheet sheet, Map<String, CellStyle> styles) {
        Row headerRow = sheet.createRow(3);
        headerRow.setHeightInPoints(25);
        String[] headers = {"序号", "装货日期", "装货点", "卸货点", "车牌", "型号", "运费(人民币)", "无缝费(人民币)", "香港清关费(人民币)", "备注"};
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(styles.get("header"));
        }
    }
    /**
     * 创建备注说明区域
     *
     * @param sheet 工作表
     * @param styles 样式映射
     * @param startRow 起始行
     */
    private void createNotesArea(SXSSFSheet sheet, Map<String, CellStyle> styles, int startRow) {
        Row note1Row = sheet.createRow(startRow);
        Cell note1Cell = note1Row.createCell(0);
        note1Cell.setCellValue("1.依据合同相关规定,贵司需向我司支付¥(大写金额:)的费用,请贵司及时安排支付。");
        note1Cell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(startRow, startRow, 0, 9));
        Row note2Row = sheet.createRow(startRow + 1);
        Cell note2Cell = note2Row.createCell(0);
        note2Cell.setCellValue("2.传真件与原件起同等法律效力。");
        note2Cell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(startRow + 1, startRow + 1, 0, 9));
    }
    /**
     * 创建账户信息区域
     *
     * @param sheet 工作表
     * @param styles 样式映射
     * @param startRow 起始行
     */
    private void createAccountInfo(SXSSFSheet sheet, Map<String, CellStyle> styles, int startRow) {
        Row accountTitleRow = sheet.createRow(startRow);
        Cell accountTitleCell = accountTitleRow.createCell(0);
        accountTitleCell.setCellValue("烦请核对确认并转至我司如下帐号:");
        accountTitleCell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(startRow, startRow, 0, 9));
        Row bankRow = sheet.createRow(startRow + 1);
        Cell bankCell = bankRow.createCell(0);
        bankCell.setCellValue("开户银行:中国农业发展银行珠海市分行");
        bankCell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(startRow + 1, startRow + 1, 0, 9));
        Row accountRow = sheet.createRow(startRow + 2);
        Cell accountCell = accountRow.createCell(0);
        accountCell.setCellValue("账号:20344990100000422001");
        accountCell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(startRow + 2, startRow + 2, 0, 9));
        Row nameRow = sheet.createRow(startRow + 3);
        Cell nameCell = nameRow.createCell(0);
        nameCell.setCellValue("户名:珠海市汇畅交通投资有限公司");
        nameCell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(startRow + 3, startRow + 3, 0, 9));
    }
    /**
     * 创建签字盖章区域
     *
     * @param sheet 工作表
     * @param styles 样式映射
     * @param startRow 起始行
     */
    private void createSignatureArea(SXSSFSheet sheet, Map<String, CellStyle> styles, int startRow) {
        Row payerRow = sheet.createRow(startRow);
        Cell payerCell = payerRow.createCell(0);
        payerCell.setCellValue("付款单位(甲方):XXXXXXXXXXXXXX");
        payerCell.setCellStyle(styles.get("text"));
        Cell payeeCell = payerRow.createCell(6);
        payeeCell.setCellValue("收款单位(乙方):珠海市汇畅交通投资有限公司");
        payeeCell.setCellStyle(styles.get("text"));
        Row creatorRow = sheet.createRow(startRow + 1);
        Cell creatorCell = creatorRow.createCell(0);
        creatorCell.setCellValue("制表人员:");
        creatorCell.setCellStyle(styles.get("text"));
        Cell creatorCell2 = creatorRow.createCell(6);
        creatorCell2.setCellValue("制表人员:");
        creatorCell2.setCellStyle(styles.get("text"));
        Row checkerRow = sheet.createRow(startRow + 2);
        Cell checkerCell = checkerRow.createCell(0);
        checkerCell.setCellValue("审核人员:");
        checkerCell.setCellStyle(styles.get("text"));
        Cell checkerCell2 = checkerRow.createCell(6);
        checkerCell2.setCellValue("审核人员:");
        checkerCell2.setCellStyle(styles.get("text"));
        Row reviewerRow = sheet.createRow(startRow + 3);
        Cell reviewerCell = reviewerRow.createCell(0);
        reviewerCell.setCellValue("复核人员:");
        reviewerCell.setCellStyle(styles.get("text"));
        Cell reviewerCell2 = reviewerRow.createCell(6);
        reviewerCell2.setCellValue("复核人员:");
        reviewerCell2.setCellStyle(styles.get("text"));
        Row stampRow = sheet.createRow(startRow + 4);
        Cell stampCell = stampRow.createCell(0);
        stampCell.setCellValue("盖章:");
        stampCell.setCellStyle(styles.get("text"));
        Cell stampCell2 = stampRow.createCell(6);
        stampCell2.setCellValue("盖章:");
        stampCell2.setCellStyle(styles.get("text"));
        Row dateRow = sheet.createRow(startRow + 5);
        Cell dateCell = dateRow.createCell(0);
        dateCell.setCellValue("日期:2026年  月  日");
        dateCell.setCellStyle(styles.get("text"));
        Cell dateCell2 = dateRow.createCell(6);
        dateCell2.setCellValue("日期:2026年  月  日");
        dateCell2.setCellStyle(styles.get("text"));
    }
@@ -257,7 +750,10 @@
        tmsArBill.setSettleAmount(BigDecimal.ZERO);
        tmsReceivableFees.forEach(item ->{
            List<TmsReceivableFeeItem> tmsReceivableFeeItems = tmsReceivableFeeItemMapper.selectTmsReceivableFeeItemList(new TmsReceivableFeeItem() {{
                setHeadId(item.getId());
            }});
            item.setItems(tmsReceivableFeeItems);
            BigDecimal rmbAmount = item.getReceivableHKBAmount()
                    .multiply(exchangeRate)
                    .setScale(2, RoundingMode.HALF_UP);
@@ -308,8 +804,138 @@
            return billItem;
        }).collect(Collectors.toList());
        tmsArBillItemMapper.insertTmsArBillItemBatch(rmb);
        // 向外部系统推送数据
        AsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        executor.execute(() -> pushToExternalSystem(tmsArBill, tmsReceivableFees));
        return AjaxResult.success();
    }
    /**
     * 向外部系统推送数据
     * @param tmsArBill 应收账单
     * @param tmsReceivableFees 应收费用列表
     */
    @Async
    protected void pushToExternalSystem(TmsArBill tmsArBill, List<TmsReceivableFee> tmsReceivableFees) {
        java.util.Map<String, Object> requestBody = new java.util.HashMap<>();
        try {
            // 构建请求体
            String apiUrl = url+"/addBill";
            // 构建bill部分
            java.util.Map<String, Object> billMap = new java.util.HashMap<>();
            billMap.put("billName", tmsArBill.getBillName());
            billMap.put("customerName", tmsArBill.getCustomerName());
            billMap.put("payee", "");
            billMap.put("responsiblePerson", "");
            billMap.put("responsibleLeader", "");
            billMap.put("settlementMethod", "");
            billMap.put("businessType", "");
            billMap.put("promotionRequirement", "");
            billMap.put("isInternalSettlement", "0");
            billMap.put("internalSettlementUnit", "");
            billMap.put("documentCount", tmsReceivableFees.size());
            billMap.put("totalAmount", tmsArBill.getSettleAmount());
            billMap.put("currency", "RMB");
            billMap.put("discountAmount", 0.00);
            billMap.put("receivedAmount", 0.00);
            billMap.put("pendingAmount", tmsArBill.getSettleAmount());
            billMap.put("exchangeRate", tmsArBill.getSettleRate());
            billMap.put("cnyAmount", tmsArBill.getSettleAmount());
            billMap.put("periodType", "");
            billMap.put("businessStartDate", "");
            billMap.put("businessEndDate", "");
            billMap.put("billingStartDate", "");
            billMap.put("billingEndDate", "");
            billMap.put("billGenerateDate", "");
            billMap.put("billSendDate", "");
            billMap.put("billDueDate", "");
            billMap.put("settlementCategory", "");
            billMap.put("settlementPeriod", "");
            billMap.put("status", "0");
            billMap.put("remark", "");
            // 构建fees部分
            List<java.util.Map<String, Object>> feesList = new java.util.ArrayList<>();
            for (int i = 0; i < tmsReceivableFees.size(); i++) {
                TmsReceivableFee fee = tmsReceivableFees.get(i);
                java.util.Map<String, Object> feeMap = new java.util.HashMap<>();
                feeMap.put("serialNumber", String.format("%03d", i + 1));
                feeMap.put("relatedBillNo", "");
                feeMap.put("sourceSystem", "TMS");
                feeMap.put("businessSector", "0");
                feeMap.put("documentType", "0");
                feeMap.put("documentNo", fee.getDispatchNo() != null ? fee.getDispatchNo() : "");
                feeMap.put("isInternalSettlement", "0");
                feeMap.put("internalSettlementUnit", "");
                feeMap.put("customerName", tmsArBill.getCustomerName());
                feeMap.put("projectName", fee.getProjectName() != null ? fee.getProjectName() : "");
                feeMap.put("businessTime", fee.getDispatchConfirmTime());
                feeMap.put("receivableConfirmTime", fee.getDispatchConfirmTime());
                feeMap.put("receivableAmount", fee.getReceivableRMBAmount().add(fee.getReceivableHKBAmount()));
                // 构建receivableAmountStr
                BigDecimal rmbAmount = fee.getReceivableRMBAmount();
                BigDecimal hkbAmount = fee.getReceivableHKBAmount();
                StringBuilder amountStr = new StringBuilder();
                if (rmbAmount.compareTo(BigDecimal.ZERO) > 0) {
                    amountStr.append(rmbAmount).append("人民币");
                }
                if (hkbAmount.compareTo(BigDecimal.ZERO) > 0) {
                    if (amountStr.length() > 0) {
                        amountStr.append(" ");
                    }
                    amountStr.append(hkbAmount).append("港币");
                }
                feeMap.put("receivableAmountStr", amountStr.toString());
                feeMap.put("status", "1");
                feeMap.put("remark", "");
                // 构建feeDetails部分
                List<java.util.Map<String, Object>> feeDetailsList = new java.util.ArrayList<>();
                List<TmsReceivableFeeItem> items = fee.getItems();
                for (int j = 0; j < items.size(); j++) {
                    TmsReceivableFeeItem item = items.get(j);
                    java.util.Map<String, Object> feeDetailMap = new java.util.HashMap<>();
                    feeDetailMap.put("serialNumber", String.format("%03d", j + 1));
                    feeDetailMap.put("feeType", item.getFeeType());
                    feeDetailMap.put("feeName", item.getFeeName());
                    feeDetailMap.put("billingUnit", "次");
                    feeDetailMap.put("unitPrice", item.getRegisterAmount());
                    feeDetailMap.put("billingQuantity", item.getRegisterAmount());
                    feeDetailMap.put("billingAmount", item.getRegisterAmount());
                    feeDetailMap.put("actualAmount", item.getRegisterAmount());
                    feeDetailMap.put("currency", item.getCurrency());
                    feeDetailMap.put("feeRegTime", item.getRegisterTime());
                    feeDetailMap.put("remark", "");
                    feeDetailsList.add(feeDetailMap);
                }
                feeMap.put("feeDetails", feeDetailsList);
                feesList.add(feeMap);
            }
            // 构建完整请求体
            requestBody.put("bill", billMap);
            requestBody.put("fees", feesList);
            // 设置HTTP头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            HttpEntity<String> entity = new HttpEntity<>(JSON.toJSONString(requestBody), headers);
            // 发送API请求
            ResponseEntity<String> response = restTemplate.exchange(apiUrl, HttpMethod.POST, entity, String.class);
            logger.info("推送数据到外部系统成功,响应: {}", response.getBody());
        } catch (Exception e) {
            logger.error("推送数据到外部系统失败,账单ID: {}, 客户: {}",
                tmsArBill.getId(), tmsArBill.getCustomerName(), e);
            logger.debug("推送失败的请求数据: {}", JSON.toJSONString(requestBody));
            // 推送失败不影响主流程,记录日志即可
        }
    }
    /**
     * 删除应收费用信息