wujianwei
2026-04-01 8f30b77a480f8921bffe2bf176bf3479f5bcbedc
tms/src/main/java/com/ruoyi/tms/service/impl/TmsArBillServiceImpl.java
@@ -6,6 +6,8 @@
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.*;
@@ -35,6 +37,16 @@
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;
/**
 * 应收账单Service业务层处理
@@ -67,6 +79,12 @@
    @Resource
    private TmsDispatchOrderMapper tmsDispatchOrderMapper;
    @Autowired
    private RestTemplate restTemplate;
    @Value("${custom.cwxtApi.url}")
    private String url;
    /**
     * 查询应收账单
@@ -241,6 +259,197 @@
    }
    /**
     * 手动推送应收账单到外部系统
     *
     * @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));
    }
    /**
     * 更新推送状态
     *
     * @param id 应收账单ID
     * @param pushStatus 推送状态
     * @param pushFailReason 推送失败原因
     * @return 结果
     */
    @Override
    public int updatePushStatus(Integer id, Integer pushStatus, String pushFailReason) {
        TmsArBill tmsArBill = new TmsArBill();
        tmsArBill.setId(id);
        tmsArBill.setPushStatus(pushStatus);
        tmsArBill.setPushTime(DateUtils.getNowDate());
        tmsArBill.setPushFailReason(pushFailReason);
        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", "");
            // 构建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());
            // 更新推送状态为成功
            tmsArBill.setPushStatus(2);
            tmsArBill.setPushTime(DateUtils.getNowDate());
            tmsArBill.setPushFailReason(null);
            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());
            tmsArBill.setPushFailReason(e.getMessage());
            tmsArBillMapper.updateTmsArBill(tmsArBill);
        }
    }
    /**
     * 导出对账单一式多联格式
     *
     * @param tmsArBill 应收账单
@@ -376,15 +585,15 @@
                        
                        // 装货点
                        Cell cell2 = row.createCell(2);
                        if (dispatchOrder != null && dispatchOrder.getShipperAddress() != null) {
                            cell2.setCellValue(dispatchOrder.getShipperAddress());
                        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());
                        if (dispatchOrder != null && dispatchOrder.getReceiverRegionLabel() != null) {
                            cell3.setCellValue(dispatchOrder.getReceiverRegionLabel());
                            cell3.setCellStyle(styles.get("data"));
                        }
@@ -398,7 +607,10 @@
                        // 型号
                        Cell cell5 = row.createCell(5);
                        if (dispatchOrder != null && dispatchOrder.getActualVehicleType() != null) {
                            cell5.setCellValue(dispatchOrder.getActualVehicleType());
                            // 使用字典转换车辆型号
                            String vehicleType = dispatchOrder.getActualVehicleType();
                            String vehicleTypeLabel = DictUtils.getDictLabel("vehicle_type", vehicleType);
                            cell5.setCellValue(StringUtils.isNotEmpty(vehicleTypeLabel) ? vehicleTypeLabel : vehicleType);
                            cell5.setCellStyle(styles.get("data"));
                        }
@@ -443,10 +655,10 @@
            // 小计行
            int subTotalRow = startRow + rowIndex;
            Row subTotal = sheet.createRow(subTotalRow);
            Cell subTotalCell = subTotal.createCell(2);
            Cell subTotalCell = subTotal.createCell(0);
            subTotalCell.setCellValue("小计");
            subTotalCell.setCellStyle(styles.get("total"));
            sheet.addMergedRegion(new CellRangeAddress(subTotalRow, subTotalRow, 2, 5));
            sheet.addMergedRegion(new CellRangeAddress(subTotalRow, subTotalRow, 0, 5));
            
            // 填充费用小计
            for (int j = 0; j < feeNameList.size(); j++) {
@@ -459,21 +671,28 @@
            // 合计行
            int totalRow = subTotalRow + 1;
            Row total = sheet.createRow(totalRow);
            Cell totalCell = total.createCell(2);
            totalCell.setCellValue("合计(CNB)");
            Cell totalCell = total.createCell(0);
            totalCell.setCellValue("合计(RMB)");
            totalCell.setCellStyle(styles.get("total"));
            sheet.addMergedRegion(new CellRangeAddress(totalRow, totalRow, 2, 5));
            sheet.addMergedRegion(new CellRangeAddress(totalRow, totalRow, 0, 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());
                cell.setCellStyle(styles.get("total"));
            // 计算所有费用的合计
            BigDecimal grandTotal = BigDecimal.ZERO;
            for (BigDecimal amount : feeTotals.values()) {
                grandTotal = grandTotal.add(amount);
            }
            // 填充费用合计(所有费用的总和)
            Cell totalAmountCell = total.createCell(baseColumns);
            totalAmountCell.setCellValue(grandTotal.doubleValue());
            totalAmountCell.setCellStyle(styles.get("total"));
            // 合并费用列单元格
            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);
@@ -529,8 +748,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 +777,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);
@@ -598,22 +813,22 @@
        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: "+tmsArBill.getCustomerName());
        toCell.setCellStyle(styles.get("text"));
        Cell billTitleCell = billRow.createCell(3);
        Cell billTitleCell = billRow.createCell(0);
        billTitleCell.setCellValue(tmsArBill.getBillName());
        billTitleCell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 3, 6));
        billTitleCell.setCellStyle(styles.get("title"));
        sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 9));
        // 公司信息行
        Row companyRow = sheet.createRow(2);
        companyRow.setHeightInPoints(20);
        Cell fromCell = companyRow.createCell(0);
        Cell toCell = companyRow.createCell(0);
        toCell.setCellValue("TO: "+tmsArBill.getCustomerName());
        toCell.setCellStyle(styles.get("text"));
        Cell fromCell = companyRow.createCell(5);
        fromCell.setCellValue("FROM:珠海市汇畅交通投资有限公司");
        fromCell.setCellStyle(styles.get("text"));
    }
@@ -653,16 +868,79 @@
    }
    /**
     * 将金额转换为中文大写
     *
     * @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) {
    private void createNotesArea(SXSSFSheet sheet, Map<String, CellStyle> styles, int startRow, BigDecimal grandTotal) {
        Row note1Row = sheet.createRow(startRow);
        Cell note1Cell = note1Row.createCell(0);
        note1Cell.setCellValue("1.依据合同相关规定,贵司需向我司支付¥(大写金额:)的费用,请贵司及时安排支付。");
        String chineseAmount = convertToChineseUppercase(grandTotal);
        note1Cell.setCellValue("1.依据合同相关规定,贵司需向我司支付¥" + grandTotal.setScale(2, RoundingMode.HALF_UP) + "(大写金额:" + chineseAmount + ")的费用,请贵司及时安排支付。");
        note1Cell.setCellStyle(styles.get("text"));
        sheet.addMergedRegion(new CellRangeAddress(startRow, startRow, 0, 9));
@@ -770,3 +1048,4 @@
        dateCell2.setCellStyle(styles.get("text"));
    }
}