sen
2 天以前 7ed2a032d0724e68aec8af940f2ce0023a9f0eb7
tms/src/main/java/com/ruoyi/tms/service/impl/TmsArBillServiceImpl.java
@@ -1,11 +1,11 @@
package com.ruoyi.tms.service.impl;
import java.util.List;
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.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.tms.domain.*;
@@ -21,6 +21,7 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
@@ -35,6 +36,17 @@
import com.ruoyi.tms.service.ITmsArBillService;
import com.ruoyi.common.core.text.Convert;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
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.web.client.RestTemplate;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
/**
 * 应收账单Service业务层处理
@@ -67,6 +79,12 @@
    @Resource
    private TmsDispatchOrderMapper tmsDispatchOrderMapper;
    @Autowired
    private RestTemplate restTemplate;
    @Value("${custom.cwxtApi.url}")
    private String url;
    /**
     * 查询应收账单
@@ -241,6 +259,268 @@
    }
    /**
     * 手动推送应收账单到外部系统
     *
     * @param id 应收账单ID
     * @return 结果
     */
    @Override
    public void manualPushToExternalSystem(Integer id) {
        TmsArBill tmsArBill = tmsArBillMapper.selectTmsArBillById(id);
        if (tmsArBill == null) {
            throw new RuntimeException("应收账单不存在");
        }
        // 查询关联的应收费用列表
        List<TmsReceivableFee> tmsReceivableFees = tmsReceivableFeeMapper.selectList(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<TmsReceivableFee>()
                .eq(TmsReceivableFee::getBillRelationId, id)
        );
        // 为每个应收费用加载明细
        for (TmsReceivableFee fee : tmsReceivableFees) {
            List<TmsReceivableFeeItem> items = tmsReceivableFeeItemMapper.selectTmsReceivableFeeItemList(new TmsReceivableFeeItem() {
                {
                    setHeadId(fee.getId());
                }
            });
            fee.setItems(items);
        }
        // 异步推送
        AsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        executor.execute(() -> pushToExternalSystem(tmsArBill, tmsReceivableFees));
    }
    @Override
    public void cancelPushToExternalSystem(Integer id) {
        TmsArBill tmsArBill = tmsArBillMapper.selectTmsArBillById(id);
        if (tmsArBill == null) {
            throw new RuntimeException("应收账单不存在");
        }
        // 异步推送作废请求
        AsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        executor.execute(() -> pushCancelToExternalSystem(tmsArBill));
    }
    /**
     * 向外部系统推送应收数据作废
     * @param tmsArBill 应收账单
     */
    @Async
    protected void pushCancelToExternalSystem(TmsArBill tmsArBill) {
        java.util.Map<String, Object> requestBody = new java.util.HashMap<>();
        try {
    ;
            // 构建请求体
            String apiUrl = url+"/cancelBill";
            // 构建请求体,只需要sourceSystemId
            requestBody.put("sourceSystemId", tmsArBill.getSourceSystemId());
            // 设置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());
            // 更新推送状态为成功
            tmsArBill.setStatus(3); // 设置账单状态为作废
            tmsArBillMapper.updateTmsArBill(tmsArBill);
            // 重置关联的应收费用状态为待确认
            tmsReceivableFeeMapper.update(new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<TmsReceivableFee>()
                    .set(TmsReceivableFee::getStatus, 0)
                    .set(TmsReceivableFee::getBillRelationId, null)
                    .set(TmsReceivableFee::getBillRelationNo, null)
                    .eq(TmsReceivableFee::getBillRelationId, tmsArBill.getId())
            );
            logger.info("重置应收费用状态成功,账单ID: {}", tmsArBill.getId());
        } catch (Exception e) {
            logger.error("推送应收数据作废到外部系统失败,账单ID: {}, 客户: {}",
                tmsArBill.getId(), tmsArBill.getCustomerName(), e);
            logger.debug("推送失败的请求数据: {}", JSON.toJSONString(requestBody));
            // 更新推送状态为失败
            tmsArBill.setPushStatus(3);
            tmsArBill.setPushTime(DateUtils.getNowDate());
            tmsArBillMapper.updateTmsArBill(tmsArBill);
        }
    }
    /**
     * 更新推送状态
     *
     * @param id 应收账单ID
     * @param pushStatus 推送状态
     * @return 结果
     */
    @Override
    public int updatePushStatus(Integer id, Integer pushStatus) {
        TmsArBill tmsArBill = new TmsArBill();
        tmsArBill.setId(id);
        tmsArBill.setPushStatus(pushStatus);
        tmsArBill.setPushTime(DateUtils.getNowDate());
        return tmsArBillMapper.updateTmsArBill(tmsArBill);
    }
    /**
     * 向外部系统推送数据
     * @param tmsArBill 应收账单
     * @param tmsReceivableFees 应收费用列表
     */
    @Async
    protected void pushToExternalSystem(TmsArBill tmsArBill, List<TmsReceivableFee> tmsReceivableFees) {
        java.util.Map<String, Object> requestBody = new java.util.HashMap<>();
        try {
            // 更新推送状态为推送中
            tmsArBill.setPushStatus(1);
            tmsArBill.setPushTime(DateUtils.getNowDate());
            tmsArBillMapper.updateTmsArBill(tmsArBill);
            // 构建请求体
            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", "");
            billMap.put("sourceSystemId", tmsArBill.getId());
            // 构建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());
            // 解析响应,获取sourceSystemId
            try {
                JSONObject result = JSONObject.parseObject(response.getBody());
                String sourceSystemId = result.getString("sourceSystemId");
                if (sourceSystemId != null) {
                    tmsArBill.setSourceSystemId(Integer.parseInt(sourceSystemId));
                }
            } catch (Exception e) {
                logger.error("解析外部系统响应失败: {}", e.getMessage());
            }
            // 更新推送状态为成功
            tmsArBill.setPushStatus(2);
            tmsArBill.setPushTime(DateUtils.getNowDate());
            tmsArBillMapper.updateTmsArBill(tmsArBill);
        } catch (Exception e) {
            logger.error("推送数据到外部系统失败,账单ID: {}, 客户: {}",
                tmsArBill.getId(), tmsArBill.getCustomerName(), e);
            logger.debug("推送失败的请求数据: {}", JSON.toJSONString(requestBody));
            // 更新推送状态为失败
            tmsArBill.setPushStatus(3);
            tmsArBill.setPushTime(DateUtils.getNowDate());
            tmsArBillMapper.updateTmsArBill(tmsArBill);
        }
    }
    /**
     * 导出对账单一式多联格式
     *
     * @param tmsArBill 应收账单
@@ -291,8 +571,8 @@
            String rateStr = sysConfigService.selectConfigByKey("sys.hk.rmb.rate");
            BigDecimal exchangeRate = new BigDecimal(rateStr);
            
            // 收集所有费用名称,用于动态生成列
            Set<String> feeNames = new HashSet<>();
            // 收集所有费用名称和对应的货币,用于动态生成列
            Map<String, String> feeCurrencyMap = new HashMap<>();
                for (TmsArBillItem item : bill.getItems()) {
                    // 应收费用ID
@@ -301,15 +581,33 @@
                    List<TmsReceivableFeeItem> tmsReceivableFeeItems = tmsReceivableFeeItemMapper.selectTmsReceivableFeeItemList(new TmsReceivableFeeItem() {{
                        setHeadId(arFeeId);
                    }});
                    // 从应收费用明细中收集费用名称
                    // 从应收费用明细中收集费用名称和货币
                    for (TmsReceivableFeeItem feeItem : tmsReceivableFeeItems) {
                        feeNames.add(feeItem.getFeeName());
                        feeCurrencyMap.put(feeItem.getFeeName(), feeItem.getCurrency());
                    }
                }
            
            // 将费用名称转换为列表,保持顺序
            List<String> feeNameList = new ArrayList<>(feeNames);
            // 将费用名称转换为列表,并按要求排序:运费放在最前面,杂费放在最后面
            List<String> feeNameList = new ArrayList<>();
            List<String> otherFees = new ArrayList<>();
            for (String feeName : feeCurrencyMap.keySet()) {
                if ("运费".equals(feeName)) {
                    feeNameList.add(feeName);
                } else if ("杂费".equals(feeName)) {
                    otherFees.add(feeName);
                } else {
                    otherFees.add(feeName);
                }
            }
            // 添加其他费用
            feeNameList.addAll(otherFees.stream().filter(fee -> !"杂费".equals(fee)).collect(Collectors.toList()));
            // 添加杂费到最后
            if (feeCurrencyMap.containsKey("杂费")) {
                feeNameList.add("杂费");
            }
            
            // 基础列数(序号、装货日期、装货点、卸货点、车牌、型号)
            int baseColumns = 6;
@@ -335,10 +633,10 @@
            createTitleArea(sheet, styles,bill);
            
            // 表头
            createDynamicTableHeader(sheet, styles, feeNameList, baseColumns, remarkColumn);
            createDynamicTableHeader(sheet, styles, feeNameList, feeCurrencyMap, baseColumns, remarkColumn);
            
            // 数据区域
            int startRow = 4;
            int startRow = 5;
            // 费用合计映射
            Map<String, BigDecimal> feeTotals = new HashMap<>();
            for (String feeName : feeNameList) {
@@ -364,43 +662,41 @@
                        }
                        // 装货日期
                        Cell cell1 = row.createCell(1);
                        if (dispatchOrder != null) {
                          TmsTrip tmsTrip = tmsTripMapper.selectTmsTripByTripType(3,dispatchOrder.getId() );
                          if (tmsTrip!=null) {
                              cell1.setCellValue(DateUtils.parseDateToStr("yyyy-MM-dd", tmsTrip.getTripTime()));
                              cell1.setCellStyle(styles.get("data"));
                          }
                        if (dispatchOrder != null && dispatchOrder.getOrderTime() !=null) {
                            cell1.setCellValue(DateUtils.parseDateToStr("yyyy-MM-dd", dispatchOrder.getOrderTime()));
                        }
                        cell1.setCellStyle(styles.get("data"));
                        // 装货点
                        Cell cell2 = row.createCell(2);
                        if (dispatchOrder != null && dispatchOrder.getShipperAddress() != null) {
                            cell2.setCellValue(dispatchOrder.getShipperAddress());
                            cell2.setCellStyle(styles.get("data"));
                        if (dispatchOrder != null && dispatchOrder.getShipperRegionLabel() != null) {
                            cell2.setCellValue(dispatchOrder.getShipperRegionLabel());
                        }
                        cell2.setCellStyle(styles.get("data"));
                        // 卸货点
                        Cell cell3 = row.createCell(3);
                        if (dispatchOrder != null && dispatchOrder.getReceiverAddress() != null) {
                            cell3.setCellValue(dispatchOrder.getReceiverAddress());
                            cell3.setCellStyle(styles.get("data"));
                        if (dispatchOrder != null && dispatchOrder.getReceiverRegionLabel() != null) {
                            cell3.setCellValue(dispatchOrder.getReceiverRegionLabel());
                        }
                        cell3.setCellStyle(styles.get("data"));
                        // 车牌
                        Cell cell4 = row.createCell(4);
                        if (dispatchOrder != null && dispatchOrder.getLicensePlate() != null) {
                            cell4.setCellValue(dispatchOrder.getLicensePlate());
                            cell4.setCellStyle(styles.get("data"));
                        }
                        cell4.setCellStyle(styles.get("data"));
                        // 型号
                        Cell cell5 = row.createCell(5);
                        if (dispatchOrder != null && dispatchOrder.getActualVehicleType() != null) {
                            cell5.setCellValue(dispatchOrder.getActualVehicleType());
                            cell5.setCellStyle(styles.get("data"));
                            // 使用字典转换车辆型号
                            String vehicleType = dispatchOrder.getActualVehicleType();
                            String vehicleTypeLabel = DictUtils.getDictLabel("vehicle_type", vehicleType);
                            cell5.setCellValue(StringUtils.isNotEmpty(vehicleTypeLabel) ? vehicleTypeLabel : vehicleType);
                        }
                        cell5.setCellStyle(styles.get("data"));
                        // 构建费用名称到金额的映射(港币转人民币)
                        Map<String, BigDecimal> feeMap = new HashMap<>();
@@ -410,12 +706,8 @@
                        }});
                        // 处理应收费用明细
                        for (TmsReceivableFeeItem feeItem : tmsReceivableFeeItems) {
                            BigDecimal amount = feeItem.getRegisterAmount();
                            // 如果是港币,转换为人民币
                            if ("HKD".equals(feeItem.getCurrency()) || "港币".equals(feeItem.getCurrency())) {
                                amount = amount.multiply(exchangeRate).setScale(2, RoundingMode.HALF_UP);
                            }
                            feeMap.put(feeItem.getFeeName(), amount);
                            // 保持原始金额,不进行币种转换
                            feeMap.put(feeItem.getFeeName(), feeItem.getRegisterAmount());
                        }
                        
                        // 填充费用列
@@ -430,9 +722,9 @@
                            feeTotals.put(feeName, feeTotals.get(feeName).add(feeAmount));
                        }
                        
                        // 备注
                        // 备注 - 为空
                        Cell remarkCell = row.createCell(remarkColumn);
                        remarkCell.setCellValue("" + (fee.getDispatchNo() != null ? fee.getDispatchNo() : ""));
                        remarkCell.setCellValue("");
                        remarkCell.setCellStyle(styles.get("data"));
                        
                        rowIndex++;
@@ -443,43 +735,70 @@
            // 小计行
            int subTotalRow = startRow + rowIndex;
            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));
            // 为所有列创建单元格
            for (int i = 0; i <= remarkColumn; i++) {
                Cell cell = subTotal.createCell(i);
                if (i == 0) {
                    cell.setCellValue("小计");
                }
                cell.setCellStyle(styles.get("total"));
            }
            sheet.addMergedRegion(new CellRangeAddress(subTotalRow, subTotalRow, 0, 5));
            
            // 填充费用小计
            for (int j = 0; j < feeNameList.size(); j++) {
                String feeName = feeNameList.get(j);
                Cell cell = subTotal.createCell(baseColumns + j);
                Cell cell = subTotal.getCell(baseColumns + j);
                cell.setCellValue(feeTotals.get(feeName).doubleValue());
                cell.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));
            // 填充费用合计
            for (int j = 0; j < feeNameList.size(); j++) {
                String feeName = feeNameList.get(j);
                Cell cell = total.createCell(baseColumns + j);
                cell.setCellValue(feeTotals.get(feeName).doubleValue());
            // 为所有列创建单元格
            for (int i = 0; i <= remarkColumn; i++) {
                Cell cell = total.createCell(i);
                if (i == 0) {
                    cell.setCellValue("合计(RMB)");
                }
                // 合计行的备注设置汇率
                if (i == remarkColumn) {
                    cell.setCellValue("汇率: " + exchangeRate);
                }
                cell.setCellStyle(styles.get("total"));
            }
            sheet.addMergedRegion(new CellRangeAddress(totalRow, totalRow, 0, 5));
            // 计算所有费用的合计(转换为人民币)
            BigDecimal grandTotal = BigDecimal.ZERO;
            for (String feeName : feeTotals.keySet()) {
                BigDecimal amount = feeTotals.get(feeName);
                // 获取该费用的币种
                String currency = feeCurrencyMap.get(feeName);
                // 如果是港币,转换为人民币
                if ("HKD".equals(currency) || "港币".equals(currency)) {
                    amount = amount.multiply(exchangeRate).setScale(2, RoundingMode.HALF_UP);
                }
                grandTotal = grandTotal.add(amount);
            }
            // 填充费用合计(所有费用的总和)
            for (int j = 0; j < feeNameList.size(); j++) {
                Cell cell = total.getCell(baseColumns + j);
                if (j == 0) {
                    cell.setCellValue(grandTotal.doubleValue());
                }
            }
            // 合并费用列单元格
            if (feeNameList.size() > 1) {
                sheet.addMergedRegion(new CellRangeAddress(totalRow, totalRow, baseColumns, baseColumns + feeNameList.size() - 1));
            }
            
            // 备注说明区域
            createNotesArea(sheet, styles, totalRow + 1);
            createNotesArea(sheet, styles, totalRow + 1, grandTotal);
            
            // 账户信息区域
            createAccountInfo(sheet, styles, totalRow + 4);
            // 签字盖章区域
            createSignatureArea(sheet, styles, totalRow + 8,bill);
            // 账户信息和签字盖章区域(合并成一个大格)
            createAccountAndSignatureArea(sheet, styles, totalRow + 4, bill);
            // 保存文件
            String path = RuoYiConfig.getDownloadPath() + fileName;
@@ -514,6 +833,10 @@
        CellStyle titleStyle = workbook.createCellStyle();
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        titleStyle.setBorderTop(BorderStyle.THIN);
        titleStyle.setBorderBottom(BorderStyle.THIN);
        titleStyle.setBorderLeft(BorderStyle.THIN);
        titleStyle.setBorderRight(BorderStyle.THIN);
        Font titleFont = workbook.createFont();
        titleFont.setFontName("微软雅黑");
        titleFont.setFontHeightInPoints((short) 16);
@@ -529,8 +852,6 @@
        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setBorderRight(BorderStyle.THIN);
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        Font headerFont = workbook.createFont();
        headerFont.setFontName("微软雅黑");
        headerFont.setFontHeightInPoints((short) 11);
@@ -560,8 +881,6 @@
        totalStyle.setBorderBottom(BorderStyle.THIN);
        totalStyle.setBorderLeft(BorderStyle.THIN);
        totalStyle.setBorderRight(BorderStyle.THIN);
        totalStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        totalStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        Font totalFont = workbook.createFont();
        totalFont.setFontName("微软雅黑");
        totalFont.setFontHeightInPoints((short) 11);
@@ -573,11 +892,29 @@
        CellStyle textStyle = workbook.createCellStyle();
        textStyle.setAlignment(HorizontalAlignment.LEFT);
        textStyle.setVerticalAlignment(VerticalAlignment.TOP);
        textStyle.setBorderTop(BorderStyle.THIN);
        textStyle.setBorderBottom(BorderStyle.THIN);
        textStyle.setBorderLeft(BorderStyle.THIN);
        textStyle.setBorderRight(BorderStyle.THIN);
        Font textFont = workbook.createFont();
        textFont.setFontName("微软雅黑");
        textFont.setFontHeightInPoints((short) 11);
        textStyle.setFont(textFont);
        styles.put("text", textStyle);
        // 文本样式
        CellStyle textStyle2 = workbook.createCellStyle();
        textStyle2.setAlignment(HorizontalAlignment.LEFT);
        textStyle2.setVerticalAlignment(VerticalAlignment.TOP);
        textStyle2.setBorderTop(BorderStyle.THIN);
        textStyle2.setBorderBottom(BorderStyle.THIN);
        textStyle2.setBorderLeft(BorderStyle.THIN);
        Font textFont2 = workbook.createFont();
        textFont2.setFontName("微软雅黑");
        textFont2.setFontHeightInPoints((short) 11);
        textStyle2.setFont(textFont2);
        styles.put("text2", textStyle2);
        
        return styles;
    }
@@ -587,35 +924,63 @@
     *
     * @param sheet 工作表
     * @param styles 样式映射
     * @param tmsReceivableFee 应收费用查询条件
     */
    private void createTitleArea(SXSSFSheet sheet, Map<String, CellStyle> styles, TmsArBill tmsArBill) {
        // 标题行
        // 标题行(无边框,居中)
        Row titleRow = sheet.createRow(0);
        titleRow.setHeightInPoints(30);
        Cell titleCell = titleRow.createCell(0);
        titleCell.setCellValue(tmsArBill.getCustomerName());
        titleCell.setCellStyle(styles.get("title"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = titleRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("珠海市汇畅交通投资有限公司");
                // 居中显示
                CellStyle centerStyle = sheet.getWorkbook().createCellStyle();
                centerStyle.setAlignment(HorizontalAlignment.CENTER);
                centerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
                cell.setCellStyle(centerStyle);
            }
            // 不设置边框样式
        }
        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 9));
        // 对账单行
        // 对账单行 - 居中显示(无边框)
        Row billRow = sheet.createRow(1);
        billRow.setHeightInPoints(20);
        Cell toCell = billRow.createCell(0);
        toCell.setCellValue("TO: "+tmsArBill.getCustomerName());
        toCell.setCellStyle(styles.get("text"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = billRow.createCell(i);
            if (i == 0) {
                cell.setCellValue(tmsArBill.getBillName());
                // 居中显示
                CellStyle centerStyle = sheet.getWorkbook().createCellStyle();
                centerStyle.setAlignment(HorizontalAlignment.CENTER);
                centerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
                cell.setCellStyle(centerStyle);
            }
            // 不设置边框样式
        }
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 9));
        Cell billTitleCell = billRow.createCell(3);
        billTitleCell.setCellValue(tmsArBill.getBillName());
        billTitleCell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 3, 6));
        // FROM行(在TO上面,无边框)
        Row fromRow = sheet.createRow(2);
        fromRow.setHeightInPoints(20);
        for (int i = 0; i <= 9; i++) {
            Cell cell = fromRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("FROM:珠海市汇畅交通投资有限公司");
            }
            // 不设置边框样式
        }
        // 公司信息行
        Row companyRow = sheet.createRow(2);
        companyRow.setHeightInPoints(20);
        Cell fromCell = companyRow.createCell(0);
        fromCell.setCellValue("FROM:珠海市汇畅交通投资有限公司");
        fromCell.setCellStyle(styles.get("text"));
        // TO行(无边框)
        Row toRow = sheet.createRow(3);
        toRow.setHeightInPoints(20);
        for (int i = 0; i <= 9; i++) {
            Cell cell = toRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("TO: "+tmsArBill.getCustomerName());
            }
            // 不设置边框样式
        }
    }
    /**
@@ -624,11 +989,12 @@
     * @param sheet 工作表
     * @param styles 样式映射
     * @param feeNameList 费用名称列表
     * @param feeCurrencyMap 费用名称到货币的映射
     * @param baseColumns 基础列数
     * @param remarkColumn 备注列位置
     */
    private void createDynamicTableHeader(SXSSFSheet sheet, Map<String, CellStyle> styles, List<String> feeNameList, int baseColumns, int remarkColumn) {
        Row headerRow = sheet.createRow(3);
    private void createDynamicTableHeader(SXSSFSheet sheet, Map<String, CellStyle> styles, List<String> feeNameList, Map<String, String> feeCurrencyMap, int baseColumns, int remarkColumn) {
        Row headerRow = sheet.createRow(4);
        headerRow.setHeightInPoints(25);
        
        // 基础列
@@ -642,7 +1008,10 @@
        // 费用列
        for (int i = 0; i < feeNameList.size(); i++) {
            Cell cell = headerRow.createCell(baseColumns + i);
            cell.setCellValue(feeNameList.get(i) + "(人民币)");
            String feeName = feeNameList.get(i);
            String currency = feeCurrencyMap.get(feeName);
            cell.setCellValue(feeName + "(" + currency + ")");
            cell.setCellStyle(styles.get("header"));
        }
        
@@ -653,57 +1022,195 @@
    }
    /**
     * 将金额转换为中文大写
     *
     * @param amount 金额
     * @return 中文大写金额
     */
    private String convertToChineseUppercase(BigDecimal amount) {
        if (amount == null) {
            return "零元整";
        }
        String[] digits = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
        String[] units = {"", "拾", "佰", "仟"};
        String[] bigUnits = {"", "万", "亿"};
        String amountStr = amount.setScale(2, RoundingMode.HALF_UP).toString();
        String integerPart = amountStr.split("\\.")[0];
        String decimalPart = amountStr.split("\\.")[1];
        StringBuilder result = new StringBuilder();
        // 处理整数部分
        int length = integerPart.length();
        for (int i = 0; i < length; i++) {
            int digit = Integer.parseInt(String.valueOf(integerPart.charAt(i)));
            int unitIndex = (length - i - 1) % 4;
            int bigUnitIndex = (length - i - 1) / 4;
            if (digit != 0) {
                result.append(digits[digit]).append(units[unitIndex]).append(bigUnits[bigUnitIndex]);
            } else {
                // 避免连续的零
                if (i > 0 && Integer.parseInt(String.valueOf(integerPart.charAt(i - 1))) != 0) {
                    result.append(digits[digit]);
                }
                // 只在非末尾位置添加大单位
                if (unitIndex == 0 && bigUnitIndex > 0 && i < length - 1) {
                    result.append(bigUnits[bigUnitIndex]);
                }
            }
        }
        result.append("元");
        // 处理小数部分
        int jiao = Integer.parseInt(String.valueOf(decimalPart.charAt(0)));
        int fen = Integer.parseInt(String.valueOf(decimalPart.charAt(1)));
        if (jiao == 0 && fen == 0) {
            result.append("整");
        } else if (jiao == 0) {
            result.append("零").append(digits[fen]).append("分");
        } else if (fen == 0) {
            result.append(digits[jiao]).append("角整");
        } else {
            result.append(digits[jiao]).append("角").append(digits[fen]).append("分");
        }
        return result.toString();
    }
    /**
     * 创建备注说明区域
     *
     * @param sheet 工作表
     * @param styles 样式映射
     * @param startRow 起始行
     * @param grandTotal 总金额
     */
    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));
    private void createNotesArea(SXSSFSheet sheet, Map<String, CellStyle> styles, int startRow, BigDecimal grandTotal) {
        // 创建无边界样式
        CellStyle noBorderStyle = sheet.getWorkbook().createCellStyle();
        // 创建两行
        for (int rowIdx = 0; rowIdx < 2; rowIdx++) {
            Row row = sheet.createRow(startRow + rowIdx);
            for (int i = 0; i <= 9; i++) {
                Cell cell = row.createCell(i);
                if (i == 0) {
                    // 第一列设置带边框的样式
                    cell.setCellStyle(styles.get("text2"));
                } else {
                    // 其他列设置无边界样式
                    cell.setCellStyle(noBorderStyle);
                }
            }
        }
        // 第一行备注
        Row firstRow = sheet.getRow(startRow);
        Cell cell1 = firstRow.getCell(0);
        String chineseAmount = convertToChineseUppercase(grandTotal);
        cell1.setCellValue("1.依据合同相关规定,贵司需向我司支付¥" + grandTotal.setScale(2, RoundingMode.HALF_UP) + "(大写金额:" + chineseAmount + ")的费用,请贵司及时安排支付。");
        // 第二行备注
        Row secondRow = sheet.getRow(startRow + 1);
        Cell cell2 = secondRow.getCell(0);
        cell2.setCellValue("2.传真件与原件起同等法律效力。");
    }
    /**
     * 创建账户信息区域
     * 创建账户信息和签字盖章区域
     *
     * @param sheet 工作表
     * @param styles 样式映射
     * @param sheet    工作表
     * @param styles   样式映射
     * @param startRow 起始行
     * @param bill
     */
    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));
    private void createAccountAndSignatureArea(SXSSFSheet sheet, Map<String, CellStyle> styles, int startRow, TmsArBill bill) {
        // 计算结束行(账户信息4行 + 签字盖章6行)
        int endRow = startRow + 9;
        // 创建所有行,设置行高
        for (int rowIdx = 0; rowIdx <= 9; rowIdx++) {
            Row row = sheet.createRow(startRow + rowIdx);
            row.setHeightInPoints(20);
            for (int i = 0; i <= 9; i++) {
                Cell cell = row.createCell(i);
                // 不设置边框样式,只显示数据
            }
        }
        // 账户信息区域(左上角)
        Row infoRow1 = sheet.getRow(startRow);
        Cell infoCell1 = infoRow1.getCell(0);
        infoCell1.setCellValue("烦请核对确认并转至我司如下帐号:");
        Row infoRow2 = sheet.getRow(startRow + 1);
        Cell infoCell2 = infoRow2.getCell(0);
        infoCell2.setCellValue("开户银行:中国农业发展银行珠海市分行");
        Row infoRow3 = sheet.getRow(startRow + 2);
        Cell infoCell3 = infoRow3.getCell(0);
        infoCell3.setCellValue("账号:20344990100000422001");
        Row infoRow4 = sheet.getRow(startRow + 3);
        Cell infoCell4 = infoRow4.getCell(0);
        infoCell4.setCellValue("户名:珠海市汇畅交通投资有限公司");
        // 付款单位(左下角)
        Row payerRow = sheet.getRow(startRow + 4);
        Cell payerCell = payerRow.getCell(0);
        payerCell.setCellValue("付款单位(甲方):" + bill.getCustomerName());
        // 收款单位(右下角)
        Row payeeRow = sheet.getRow(startRow + 4);
        Cell payeeCell = payeeRow.getCell(6);
        payeeCell.setCellValue("收款单位(乙方):珠海市汇畅交通投资有限公司");
        // 制表人员
        Row creatorRow = sheet.getRow(startRow + 5);
        Cell creatorCell1 = creatorRow.getCell(0);
        creatorCell1.setCellValue("制表人员:");
        Cell creatorCell2 = creatorRow.getCell(6);
        creatorCell2.setCellValue("制表人员:");
        // 审核人员
        Row checkerRow = sheet.getRow(startRow + 6);
        Cell checkerCell1 = checkerRow.getCell(0);
        checkerCell1.setCellValue("审核人员:");
        Cell checkerCell2 = checkerRow.getCell(6);
        checkerCell2.setCellValue("审核人员:");
        // 复核人员
        Row reviewerRow = sheet.getRow(startRow + 7);
        Cell reviewerCell1 = reviewerRow.getCell(0);
        reviewerCell1.setCellValue("复核人员:");
        Cell reviewerCell2 = reviewerRow.getCell(6);
        reviewerCell2.setCellValue("复核人员:");
        // 盖章
        Row stampRow = sheet.getRow(startRow + 8);
        Cell stampCell1 = stampRow.getCell(0);
        stampCell1.setCellValue("盖章:");
        Cell stampCell2 = stampRow.getCell(6);
        stampCell2.setCellValue("盖章:");
        // 日期
        Row dateRow = sheet.getRow(startRow + 9);
        Cell dateCell1 = dateRow.getCell(0);
        dateCell1.setCellValue("日期:2026年  月  日");
        Cell dateCell2 = dateRow.getCell(6);
        dateCell2.setCellValue("日期:2026年  月  日");
        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));
    }
    /**
@@ -715,58 +1222,77 @@
     * @param bill
     */
    private void createSignatureArea(SXSSFSheet sheet, Map<String, CellStyle> styles, int startRow, TmsArBill bill) {
        // 付款单位(甲方)和收款单位(乙方)行
        Row payerRow = sheet.createRow(startRow);
        Cell payerCell = payerRow.createCell(0);
        payerCell.setCellValue("付款单位(甲方):"+bill.getCustomerName());
        payerCell.setCellStyle(styles.get("text"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = payerRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("付款单位(甲方):"+bill.getCustomerName());
            } else if (i == 6) {
                cell.setCellValue("收款单位(乙方):珠海市汇畅交通投资有限公司");
            }
            cell.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"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = creatorRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("制表人员:");
            } else if (i == 6) {
                cell.setCellValue("制表人员:");
            }
            cell.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"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = checkerRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("审核人员:");
            } else if (i == 6) {
                cell.setCellValue("审核人员:");
            }
            cell.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"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = reviewerRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("复核人员:");
            } else if (i == 6) {
                cell.setCellValue("复核人员:");
            }
            cell.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"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = stampRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("盖章:");
            } else if (i == 6) {
                cell.setCellValue("盖章:");
            }
            cell.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"));
        for (int i = 0; i <= 9; i++) {
            Cell cell = dateRow.createCell(i);
            if (i == 0) {
                cell.setCellValue("日期:2026年  月  日");
            } else if (i == 6) {
                cell.setCellValue("日期:2026年  月  日");
            }
            cell.setCellStyle(styles.get("text"));
        }
    }
}