chengf
2026-03-11 b88a288f4f787b509463678e3cd9ccfa3f37014b
service-job/src/main/java/com/java110/job/importData/adapt/ImportReportMainV2QueueDataAdapt.java
@@ -3,18 +3,23 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.java110.core.factory.GenerateCodeFactory;
import com.java110.dto.importData.Vtil;
import com.java110.dto.log.AssetImportLogDetailDto;
import com.java110.dto.msg.MaintenancePaymentPo;
import com.java110.dto.msg.MpFifthPaymentRecordPo;
import com.java110.dto.msg.MpPaymentRecordPo;
import com.java110.fee.api.*;
import com.java110.job.dao.IMaintenancePaymentService;
import com.java110.job.dao.IMpFifthPaymentRecordService;
import com.java110.job.dao.IMpPaymentRecordService;
import com.java110.job.importData.DefaultImportData;
import com.java110.job.importData.IImportDataAdapt;
import com.java110.po.importFee.MaintenancePayment;
import com.java110.po.importFee.MpFifthPaymentRecord;
import com.java110.utils.util.BeanConvertUtil;
import com.java110.utils.util.DateUtil;
import com.java110.utils.util.NumberUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -31,67 +36,210 @@
@Service("importReportMainV2QueueData")
public class ImportReportMainV2QueueDataAdapt extends DefaultImportData implements IImportDataAdapt {
    // 日期格式化器(处理Excel中的多种日期格式:2025/6/11、8/11/25、2025.5.13等)
    private static final SimpleDateFormat[] DATE_FORMATS = {
            new SimpleDateFormat("yyyy/MM/dd"),
            new SimpleDateFormat("MM/dd/yy"),
            new SimpleDateFormat("yyyy.MM.dd"),
            new SimpleDateFormat("yyyy-MM-dd")
    };
    @Autowired
    MaintenancePaymentApi maintenancePaymentApi;
    @Autowired
    AnnouncementTimeRangeApi announcementTimeRangeApi;
    @Autowired
    OwnersCommitteeConventionApi ownersCommitteeConventionApi;
    @Autowired
    OwnerWithdrawalInfoApi ownerWithdrawalInfoApi;
    @Autowired
    OwnerQualityGuaranteeApi ownerQualityGuaranteeApi;
    @Autowired
    MpPaymentRecordApi mpPaymentRecordApi;
    @Autowired
    MpFifthPaymentRecordApi mpFifthPaymentRecordApi;
    @Autowired
    OwnerRemarkInfoApi ownerRemarkInfoApi;
    // 注入数据库服务(SqlSessionTemplate版Service)
    @Autowired
    private IMaintenancePaymentService maintenancePaymentService;
    @Autowired
    private IMpPaymentRecordService mpPaymentRecordService;
    @Autowired
    private IMpFifthPaymentRecordService mpFifthPaymentRecordService;
    @Override
    @Transactional(rollbackFor = Exception.class) // 事务控制,确保3张表数据一致性
    public void importData(List<AssetImportLogDetailDto> assetImportLogDetailDtos) {
        for (AssetImportLogDetailDto logDetailDto : assetImportLogDetailDtos) {
            try {
                // 1. 解析Excel行数据(JSONArray对应每行的单元格列表,顺序与"荣顺苑签报单"表头一致)
                JSONArray cellArray = JSONObject.parseArray(logDetailDto.getContent());
//                if (cellArray.size() < 132) { // 荣顺苑签报单表头共132列,过滤无效行
//                    throw new RuntimeException("行数据列数不足(需132列),当前列数:" + cellArray.size());
//                }
                // 2. 校验核心必填字段(流转编号、项目名称、业委会金额等)
                JSONArray cellArray = JSONObject.parseArray(logDetailDto.getContent());
                String flowNumber = getCellValue(cellArray, 1); // 第2列:流转编号
                String projectName = getCellValue(cellArray, 4); // 第5列:项目名称
                String ownersCommitteeAmountStr = getCellValue(cellArray, 12); // 第13列:业委会金额
                if (isBlank(flowNumber) || isBlank(projectName) || isBlank(ownersCommitteeAmountStr)) {
                    throw new RuntimeException(
                            String.format("核心字段缺失:流转编号[%s]、项目名称[%s]、业委会金额[%s]",
                                    flowNumber, projectName, ownersCommitteeAmountStr)
                    );
                if (Vtil.defaultValue(getCellValue(cellArray, 1)).equals("")) {
                    continue;
                }
                AnnouncementTimeRangePo announcementTimeRangePo = null;
                OwnersCommitteeConventionPo ocoPo = null;
                OwnerWithdrawalInfoPo owiPo = null;
                OwnerQualityGuaranteePo oqgPo = null;
                OwnerRemarkInfoPo ownerRemarkInfoPo = null;
                // 3. 构建3张表的PO对象(字段映射+业务计算)
                MaintenancePaymentPo mainPo = buildMaintenancePaymentPo(cellArray);
                List<MpPaymentRecordPo> paymentRecordPos = buildMpPaymentRecordPos(cellArray, mainPo.getFlowNumber(),mainPo.getId());
                MpFifthPaymentRecordPo fifthPo = buildMpFifthPaymentRecordPo(cellArray, mainPo.getFlowNumber(),mainPo.getId());
                MaintenancePayment mainPo = buildMaintenancePaymentPoV2(cellArray);//主表
//                if (!getCellValue(cellArray, 22).isEmpty() && !getCellValue(cellArray, 23).isEmpty()) {
                    announcementTimeRangePo = buildAnnouncementTimeRangePo(cellArray, mainPo.getId());//公共收益金
//                }
//                if (!getCellValue(cellArray, 26).isEmpty() && !getCellValue(cellArray, 27).isEmpty()) {
                    ocoPo = buildOwnersCommitteeConventionPo(cellArray, mainPo.getId());
//                }
//                if (!getCellValue(cellArray, 29).isEmpty()) {
                    owiPo = buildOwnerWithdrawalInfoPo(cellArray, mainPo.getId());
//                }
//                if (!getCellValue(cellArray, 39).isEmpty()) {
                    oqgPo = buildOwnerQualityGuaranteePo(cellArray, mainPo.getId());
//                }
                List<MpPaymentRecordPo> paymentRecordPos = buildMpPaymentRecordPos(cellArray, mainPo.getFlowNumber(), mainPo.getId());
                List<MpFifthPaymentRecord> fifthPos = buildMpFifthPaymentRecordPos(cellArray, mainPo.getFlowNumber(), mainPo.getId());
//                if (!getCellValue(cellArray, 137).isEmpty()) {
                    ownerRemarkInfoPo = buildOwnerRemarkInfoPo(cellArray, mainPo.getFlowNumber(), mainPo.getId());
//                }
                // 4. 批量保存到数据库
                saveToDatabase(mainPo, paymentRecordPos, fifthPo);
                importData(mainPo, announcementTimeRangePo, ocoPo, owiPo, oqgPo, paymentRecordPos, fifthPos, ownerRemarkInfoPo);
                // 5. 更新导入日志状态(成功)- 调用父类方法
                super.updateImportLogDetailState(logDetailDto.getDetailId());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            } catch (Exception e) {
                e.printStackTrace();
                // 6. 更新导入日志状态(失败)- 调用父类异常重载方法
                super.updateImportLogDetailState(logDetailDto.getDetailId(), e);
                throw new IllegalArgumentException(e.getMessage());
            }
        }
    }
    /**
     * 构建 MaintenancePaymentPo(维修资金支取信息表)
     * 字段映射对应"荣顺苑签报单"表头顺序,包含17个自动计算字段
     */
    private void importData(MaintenancePayment mainPo, AnnouncementTimeRangePo announcementTimeRangePo, OwnersCommitteeConventionPo ocoPo, OwnerWithdrawalInfoPo owiPo, OwnerQualityGuaranteePo oqgPo,
                            List<MpPaymentRecordPo> paymentRecordPos, List<MpFifthPaymentRecord> fifthPos, OwnerRemarkInfoPo ownerRemarkInfoPo) {
        maintenancePaymentApi.saveMaintenancePayment(mainPo);
        if (announcementTimeRangePo != null) {
            announcementTimeRangeApi.saveAnnouncementTimeRangeInfo(announcementTimeRangePo);
        }
        if (ocoPo != null) {
            ownersCommitteeConventionApi.saveOwnersCommitteeConvention(ocoPo);
        }
        if (owiPo != null) {
            ownerWithdrawalInfoApi.saveOwnerWithdrawalInfo(owiPo);
        }
        if (oqgPo != null) {
            ownerQualityGuaranteeApi.saveOwnerQualityGuarantee(oqgPo);
        }
        for (MpPaymentRecordPo paymentRecordPo : paymentRecordPos) {
            mpPaymentRecordApi.saveMpPaymentRecord(paymentRecordPo);
        }
        for (MpFifthPaymentRecord fifthPo : fifthPos) {
            mpFifthPaymentRecordApi.saveMpFifthPaymentRecord(fifthPo);
        }
        if (ownerRemarkInfoPo != null) {
            ownerRemarkInfoApi.saveOwnerRemarkInfo(ownerRemarkInfoPo);
        }
    }
    private OwnerRemarkInfoPo buildOwnerRemarkInfoPo(JSONArray cellArray, String flowNumber, String id) {
        OwnerRemarkInfoPo po = new OwnerRemarkInfoPo();
        long longId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; // 避免负数
        po.setId(longId);
        po.setMpId(flowNumber);
        po.setRemarkPerson("导入内容");
        po.setRemarkContent(getCellValue(cellArray, 137));
        return po;
    }
    private OwnerQualityGuaranteePo buildOwnerQualityGuaranteePo(JSONArray cellArray, String aLong) {
        OwnerQualityGuaranteePo po = new OwnerQualityGuaranteePo();
        long longId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; // 避免负数
        po.setId(longId); // 主键ID
        po.setMpId(aLong);
            po.setQualityGuaranteePeriod2Start(Vtil.defaultValueToDate(getCellValue(cellArray, 37)));
            po.setQualityGuaranteePeriod2End(Vtil.defaultValueToDate(getCellValue(cellArray, 38)));
            po.setAcceptanceDate(Vtil.defaultValueToDate(getCellValue(cellArray, 41)));
            po.setAvailableWithdrawalDate(Vtil.defaultValueToDate(getCellValue(cellArray, 43)));
        po.setQualityGuaranteeRatio(getCellNum(cellArray, 39));
        po.setQualityGuaranteeAmount(getCellNum(cellArray, 12) * po.getQualityGuaranteeRatio());
        po.setAuditStatus(getCellValue(cellArray, 42));
        return po;
    }
    private OwnerWithdrawalInfoPo buildOwnerWithdrawalInfoPo(JSONArray cellArray, String aLong) {
        OwnerWithdrawalInfoPo po = new OwnerWithdrawalInfoPo();
        long longId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; // 避免负数
        po.setId(longId); // 主键ID
        po.setMpId(aLong);
        po.setWithdrawalEncounteredProblem(getCellValue(cellArray, 28));
        po.setShortageOrArrears(getCellNum(cellArray, 29));
        po.setRoadName(getCellValue(cellArray, 30));
        po.setLane(getCellValue(cellArray, 31));
        po.setDoor(getCellValue(cellArray, 32));
        po.setRoom(getCellValue(cellArray, 33));
        po.setDoorRoomNumber(getCellValue(cellArray, 34));
        po.setOwnerAddress(getCellValue(cellArray, 35));
        return po;
    }
    private OwnersCommitteeConventionPo buildOwnersCommitteeConventionPo(JSONArray cellArray, String mpId) {
        OwnersCommitteeConventionPo po = new OwnersCommitteeConventionPo();
        long longId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; // 避免负数
        po.setId(longId); // 主键ID
        po.setMpId(mpId);
        po.setQuota(getCellNum(cellArray, 25));
        po.setOwnersCommitteeResolution(getCellValue(cellArray, 26));
        po.setOwnersCommitteeConsultation(getCellValue(cellArray, 27));
        return po;
    }
    private AnnouncementTimeRangePo buildAnnouncementTimeRangePo(JSONArray cellArray, String mpId) {
        AnnouncementTimeRangePo po = new AnnouncementTimeRangePo();
        long longId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; // 避免负数
        po.setId(longId); // 主键ID
        po.setMpId(mpId);
        po.setPlannedAnnouncementStart(getCellValue(cellArray, 21));
        po.setPlannedAnnouncementEnd(getCellValue(cellArray, 22));
        po.setPublishedAnnouncementStart(getCellValue(cellArray, 23));
        po.setPublishedAnnouncementEnd(getCellValue(cellArray, 24));
        return po;
    }
    private MaintenancePayment buildMaintenancePaymentPoV2(JSONArray cellArray) throws ParseException {
        MaintenancePayment po = new MaintenancePayment();
        po.setId(GenerateCodeFactory.getGeneratorId("10")); // 主键ID
        po.setAuxiliaryColumn(getCellValue(cellArray, 0)); // 第1列:辅助列
        po.setFlowNumber(getCellValue(cellArray, 1)); // 第2列:流转编号
        po.setSerialNumber(parseInteger(getCellValue(cellArray, 2))); // 第3列:序号
        po.setProjectCode(getCellValue(cellArray, 3)); // 第4列:项目编码
        po.setProjectName(getCellValue(cellArray, 4)); // 第5列:项目名称
        po.setYear(parseInteger(getCellValue(cellArray, 5))); // 第6列:年份
        po.setMonth(parseInteger(getCellValue(cellArray, 6))); // 第7列:月份
        po.setDay(parseInteger(getCellValue(cellArray, 7))); // 第8列:日
        po.setDate(parseDate(getCellValue(cellArray, 8))); // 第9列:日期
        po.setProjectContent(getCellValue(cellArray, 9)); // 第10列:工程内容
        po.setManagementOfficeAmount(getCellValue(cellArray, 10)); // 第11列:管理处金额
        po.setManagementOfficeSeal(convertSealStatus(getCellValue(cellArray, 11))); // 第12列:管理处是否已盖章(√转"是",否则"否")
        po.setOwnersCommitteeAmount(getCellValue(cellArray, 12)); // 第13列:业委会金额
        po.setAuditAmount(getCellNum(cellArray, 13).toString()); // 第14列:审价金额
        po.setOwnersCommitteeSeal(convertSealStatus(getCellValue(cellArray, 14))); // 第15列:业委会是否已盖章
        po.setReportDepartment(getCellValue(cellArray, 15)); // 第16列:签报部门
        po.setFundTypeLevel1(getCellValue(cellArray, 16)); // 第17列:基金类型-一级分类
        po.setFundTypeLevel2(getCellValue(cellArray, 17)); // 第18列:基金类型-二级分类
        po.setMaintenanceType(getCellValue(cellArray, 19)); // 第19列:幢/全体
        po.setBuildingOrAll(getCellValue(cellArray, 18)); // 第20列:维修类型
        po.setPayeeName(getCellValue(cellArray, 145)); // 支付公司名称/个人名字
        po.setIdCardNumber(getCellValue(cellArray, 146)); // 个人身份证号码
        po.setBankName(getCellValue(cellArray, 147)); // 开户银行
        po.setBankAccount(getCellValue(cellArray, 148)); // 开户账号
        return po;
    }
        /**
         * 构建 MaintenancePaymentPo(维修资金支取信息表)
         * 字段映射对应"荣顺苑签报单"表头顺序,包含17个自动计算字段
         */
    private MaintenancePaymentPo buildMaintenancePaymentPo(JSONArray cellArray) throws ParseException {
        MaintenancePaymentPo po = new MaintenancePaymentPo();
        // 基础字段(直接映射Excel单元格)
@@ -118,7 +266,7 @@
        po.setBuildingOrAll(getCellValue(cellArray, 18)); // 第19列:幢/全体
        po.setMaintenanceType(getCellValue(cellArray, 19)); // 第20列:维修类型
        po.setQualityGuaranteePeriod1(getCellValue(cellArray, 20)); // 第21列:质保期(第一个)
        po.setPublicIncomeAnnouncement(getCellValue(cellArray, 21)); // 第22列:公共收益金公布情况
//        po.setPublicIncomeAnnouncement(getCellValue(cellArray, 21)); // 第22列:公共收益金公布情况
        // 公共收益金公布子字段(第23-26列)
        po.setPlannedAnnouncementStart(getCellValue(cellArray, 21)); // 拟公布-起始(年月)
        po.setPlannedAnnouncementEnd(getCellValue(cellArray, 22)); // 拟公布-止(年月)
@@ -137,104 +285,15 @@
        po.setDoorRoomNumber(getCellValue(cellArray, 34)); // 门室号
        po.setOwnerAddress(getCellValue(cellArray, 35)); // 业主地址
//        po.setWithdrawalProblem(getCellValue(cellArray, 36)); // 第38列:支取存在问题
        po.setProblemDifficulty(getCellValue(cellArray, 36)); // 第39列:问题难度
        po.setAvailableWithdrawalDate(parseDate(getCellValue(cellArray, 37))); // 第40列:可启动支取日期(年月日)
        po.setQualityGuaranteePeriod2(getCellValue(cellArray, 39) + "~" + getCellValue(cellArray, 40)); // 第41列:质保期(第二个)
        po.setQualityGuaranteeRatio(parseBigDecimal(getCellValue(cellArray, 40))); // 第42列:质保金占比
        po.setQualityGuaranteeAmount(parseBigDecimal(getCellValue(cellArray, 41))); // 第43列:质保金金额(元)
        po.setReceivedQualityGuarantee(parseBigDecimal(getCellValue(cellArray, 42))); // 第44列:已到账质保金
        po.setProblemDifficulty(getCellValue(cellArray, 36)); // 第36列:问题难度
        po.setAvailableWithdrawalDate(parseDate(getCellValue(cellArray, 43))); // 第40列:可启动支取日期(年月日)
        po.setQualityGuaranteePeriod2(getCellValue(cellArray, 37) + "~" + getCellValue(cellArray, 38)); // 第41列:质保期(第二个)
        po.setQualityGuaranteeRatio(parseBigDecimal(getCellValue(cellArray, 39))); // 第42列:质保金占比
        po.setQualityGuaranteeAmount(parseBigDecimal(getCellValue(cellArray, 40))); // 第43列:质保金金额(元)
        po.setReceivedQualityGuarantee(parseBigDecimal(getCellValue(cellArray, 44))); // 第44列:已到账质保金
//        po.setStartDate(parseDate(getCellValue(cellArray, 44))); // 第45列:起始(年月日)
//        po.setEndDate(parseDate(getCellValue(cellArray, 45))); // 第46列:终止(年月日)
        // -------------------------- 17个自动计算字段(核心业务逻辑)--------------------------
        // 1. 打印合计 = 第一次~第五次打印金额之和(第47列打印金额+第54列+第61列+第68列+第75列)
        BigDecimal print1 = parseBigDecimal(getCellValue(cellArray, 45)); // 第一次打印金额(第50列)
        BigDecimal print2 = parseBigDecimal(getCellValue(cellArray, 57)); // 第二次打印金额(第57列)
        BigDecimal print3 = parseBigDecimal(getCellValue(cellArray, 69)); // 第三次打印金额(第64列)
        BigDecimal print4 = parseBigDecimal(getCellValue(cellArray, 81)); // 第四次打印金额(第71列)
        BigDecimal print5 = parseBigDecimal(getCellValue(cellArray, 93)); // 第五次打印金额(第78列)
//        BigDecimal printTotal = add(print1, print2, print3, print4, print5);
//        po.setPrintTotal(printTotal); // 自定义字段:打印合计
        // 2. 到账合计 = 第一次~第五次到账金额之和(第51列+第58列+第65列+第72列+第79列)
        BigDecimal arrival1 = parseBigDecimal(getCellValue(cellArray, 48)); // 第一次到账金额(第52列)
        BigDecimal arrival2 = parseBigDecimal(getCellValue(cellArray, 60)); // 第二次到账金额(第59列)
        BigDecimal arrival3 = parseBigDecimal(getCellValue(cellArray, 72)); // 第三次到账金额(第66列)
        BigDecimal arrival4 = parseBigDecimal(getCellValue(cellArray, 84)); // 第四次到账金额(第73列)
        BigDecimal arrival5 = parseBigDecimal(getCellValue(cellArray, 96)); // 第五次到账金额(第80列)
//        BigDecimal arrivalTotal = add(arrival1, arrival2, arrival3, arrival4, arrival5);
//        po.setArrivalTotal(arrivalTotal); // 自定义字段:到账合计
        // 3. 维修基金到账率 = IF(到账合计<>0, 到账合计/业委会金额, 0)
        BigDecimal ownersAmount = po.getOwnersCommitteeAmount();
//        BigDecimal fundArrivalRate = (arrivalTotal.compareTo(BigDecimal.ZERO) != 0 && ownersAmount.compareTo(BigDecimal.ZERO) != 0)
//                ? arrivalTotal.divide(ownersAmount, 4, BigDecimal.ROUND_HALF_UP) // 保留4位小数
//                : BigDecimal.ZERO;
//        po.setFundArrivalRate(fundArrivalRate); // 自定义字段:维修基金到账率
        // 4. 尚缺金额 = IF(基金类型<>"维修基金", 0, 业委会金额-到账合计)
        String fundType = po.getFundTypeLevel1();
//        BigDecimal shortageAmount = ("维修基金".equals(fundType))
//                ? subtract(ownersAmount, arrivalTotal)
//                : BigDecimal.ZERO;
//        po.setFundShortageAmount(shortageAmount); // 自定义字段:尚缺金额
        // 5. 管理费 = 管理处金额 * 管理费占比(百分比转小数)
        BigDecimal managementFeeRatio = po.getManagementFeeRatio() == null ? BigDecimal.ZERO : po.getManagementFeeRatio().divide(new BigDecimal(100));
        BigDecimal managementFee = multiply(po.getManagementOfficeAmount(), managementFeeRatio);
//        po.setManagementFee(managementFee); // 自定义字段:管理费
        // 6. 应付金额A = IF(审计金额为空, 业委会金额-管理费, 审计金额-管理费)
        BigDecimal auditAmount = po.getAuditAmount() == null ? BigDecimal.ZERO : po.getAuditAmount();
        BigDecimal payableA = (auditAmount.compareTo(BigDecimal.ZERO) == 0)
                ? subtract(ownersAmount, managementFee)
                : subtract(auditAmount, managementFee);
//        po.setPayableAmountA(payableA); // 自定义字段:应付金额A
        // 7. 应付金额B = 业委会金额-管理费-质保金金额
        BigDecimal qualityGuaranteeAmount = po.getQualityGuaranteeAmount() == null ? BigDecimal.ZERO : po.getQualityGuaranteeAmount();
        BigDecimal payableB = subtract(ownersAmount, managementFee, qualityGuaranteeAmount);
//        po.setPayableAmountB(payableB); // 自定义字段:应付金额B
        // 8. 应付金额C = 实收金额-实收金额×管理费比例(实收金额取到账合计)
//        BigDecimal payableC = multiply(arrivalTotal, BigDecimal.ONE.subtract(managementFeeRatio));
//        po.setPayableAmountC(payableC); // 自定义字段:应付金额C
        // 9. 拟付合计 = 第一次~第五次拟付金额之和(第82列+第88列+第94列+第100列+第106列)
        BigDecimal plannedPay1 = parseBigDecimal(getCellValue(cellArray, 113)); // 第一次拟付金额(第83列)
        BigDecimal plannedPay2 = parseBigDecimal(getCellValue(cellArray, 119)); // 第二次拟付金额(第89列)
        BigDecimal plannedPay3 = parseBigDecimal(getCellValue(cellArray, 125)); // 第三次拟付金额(第95列)
        BigDecimal plannedPay4 = parseBigDecimal(getCellValue(cellArray, 131)); // 第四次拟付金额(第101列)
        BigDecimal plannedPay5 = parseBigDecimal(getCellValue(cellArray, 137)); // 第五次拟付金额(第107列)
        BigDecimal plannedPayTotal = add(plannedPay1, plannedPay2, plannedPay3, plannedPay4, plannedPay5);
//        po.setPlannedPayTotal(plannedPayTotal); // 自定义字段:拟付合计
        // 10. 实付合计 = 第一次~第五次实付金额之和(第85列+第91列+第97列+第103列+第109列)
        BigDecimal actualPay1 = parseBigDecimal(getCellValue(cellArray, 117)); // 第一次实付金额(第86列)
        BigDecimal actualPay2 = parseBigDecimal(getCellValue(cellArray, 123)); // 第二次实付金额(第92列)
        BigDecimal actualPay3 = parseBigDecimal(getCellValue(cellArray, 130)); // 第三次实付金额(第98列)
        BigDecimal actualPay4 = parseBigDecimal(getCellValue(cellArray, 135)); // 第四次实付金额(第104列)
        BigDecimal actualPay5 = parseBigDecimal(getCellValue(cellArray, 141)); // 第五次实付金额(第110列)
        BigDecimal actualPayTotal = add(actualPay1, actualPay2, actualPay3, actualPay4, actualPay5);
//        po.setActualPayTotal(actualPayTotal); // 自定义字段:实付合计
        // 11. 未付合计 = 审定金额-实付合计(审定金额取审计金额,无则取业委会金额)
        BigDecimal approvedAmount = (auditAmount.compareTo(BigDecimal.ZERO) == 0) ? ownersAmount : auditAmount;
        BigDecimal unpaidTotal = subtract(approvedAmount, actualPayTotal);
//        po.setUnpaidTotal(unpaidTotal); // 自定义字段:未付合计
        // 12. 垫付金额 = 复杂条件判断(基金类型、到账合计、公布情况)
//        BigDecimal advanceAmount = calculateAdvanceAmount(fundType, arrivalTotal, actualPayTotal, po.getPublicIncomeAnnouncement());
//        po.setAdvanceAmount(advanceAmount); // 自定义字段:垫付金额
        // 13. 未付合计(到账口径) = 按公布情况分支计算
//        BigDecimal unpaidByArrival = calculateUnpaidByArrival(po.getPublicIncomeAnnouncement(), ownersAmount, managementFee, actualPayTotal, arrivalTotal);
//        po.setUnpaidByArrival(unpaidByArrival); // 自定义字段:未付合计(到账口径)
        // 14-16. 待付金额A/B/C
//        po.setPendingPayA(subtract(auditAmount, managementFee)); // 待付金额A = 审价金额 - 管理费
//        po.setPendingPayB(subtract(auditAmount, qualityGuaranteeAmount, managementFee)); // 待付金额B = 审价金额 - 质保金 - 管理费
//        po.setPendingPayC(multiply(arrivalTotal, BigDecimal.ONE.subtract(managementFeeRatio))); // 待付金额C = 到账合计*(1-管理费占比)
        // 支付信息(第111-116列)
        po.setPayeeName(getCellValue(cellArray, 143)); // 支付公司名称/个人名字
@@ -254,62 +313,91 @@
    private List<MpPaymentRecordPo> buildMpPaymentRecordPos(JSONArray cellArray, String flowNumber,String mainPo) throws ParseException {
        List<MpPaymentRecordPo> pos = new ArrayList<>();
        // 第一次打印数据(第47-53列)
        pos.add(buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 1, 43, 44, 45, 46, 47, 48, 49));
        buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 1, 45, pos);
        // 第二次打印数据(第54-60列)
        pos.add(buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 2, 55, 56, 57, 58, 59, 60, 61));
        buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 2, 57, pos);
        // 第三次打印数据(第61-67列)
        pos.add(buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 3, 67, 68, 69, 70, 71, 72, 73));
        buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 3, 69, pos);
        // 第四次打印数据(第68-74列)
        pos.add(buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 4, 79, 80, 81, 82, 83, 84, 85));
        buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 4, 81, pos);
        // 第五次打印数据(第75-81列)
        pos.add(buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 5, 91, 92, 93, 94, 95, 96, 97));
        buildSinglePaymentRecord(mainPo, cellArray, flowNumber, 5, 93, pos);
        return pos;
    }
    /**
     * 构建单次打印的 MpPaymentRecordPo
     */
    private MpPaymentRecordPo buildSinglePaymentRecord(String mpId, JSONArray cellArray, String flowNumber, int printTimes,
                                                       int dateCol, int resolutionCol, int amountCol,
                                                       int arrivalDateCol, int arrivalAmountCol,
                                                       int withdrawerCol, int shortageCol) throws ParseException {
    private void buildSinglePaymentRecord(String mpId, JSONArray cellArray, String flowNumber, int printTimes,
                                                       int index, List<MpPaymentRecordPo> pos) throws ParseException {
//        if (getCellValue(cellArray, index).isEmpty()) {
//            return;
//        }
        MpPaymentRecordPo po = new MpPaymentRecordPo();
        long longId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; // 避免负数
        po.setId(longId+"");
        po.setMpId(mpId); // mp_id = 流转编号_打印次数(确保唯一)
        po.setFirstPrintDate(parseDate(getCellValue(cellArray, dateCol)));
        po.setResolutionNumber(getCellValue(cellArray, resolutionCol));
        po.setPrintAmount(parseBigDecimal(getCellValue(cellArray, amountCol)));
        po.setArrivalDate(parseDate(getCellValue(cellArray, arrivalDateCol)));
        po.setArrivalAmount(parseBigDecimal(getCellValue(cellArray, arrivalAmountCol)));
        po.setWithdrawer(getCellValue(cellArray, withdrawerCol));
        po.setShortageOrArrears(parseBigDecimal(getCellValue(cellArray, shortageCol)));
        po.setFirstPrintDate(parseDate(getCellValue(cellArray, index)));
        po.setResolutionNumber(getCellValue(cellArray, index + 1));
        po.setPrintAmount(parseBigDecimal(getCellValue(cellArray, index + 2)));
        po.setArrivalDate(parseDate(getCellValue(cellArray, index + 3)));
        po.setArrivalAmount(parseBigDecimal(getCellValue(cellArray, index + 4)));
        po.setWithdrawer(getCellValue(cellArray, index + 5));
        po.setShortageOrArrears(parseBigDecimal(getCellValue(cellArray, index + 6)));
        // 业主地址信息(复用main表的地址)
        po.setRoadName(getCellValue(cellArray, 31));
        po.setLane(getCellValue(cellArray, 32));
        po.setDoor(getCellValue(cellArray, 33));
        po.setRoom(getCellValue(cellArray, 34));
        po.setOwnerAddress(getCellValue(cellArray, 36));
        return po;
        po.setRoadName(getCellValue(cellArray, index + 7));
        po.setLane(getCellValue(cellArray, index + 8));
        po.setDoor(getCellValue(cellArray, index + 9));
        po.setRoom(getCellValue(cellArray, index + 10));
        po.setOwnerAddress(getCellValue(cellArray, index + 11));
        po.setPrintAmount(new BigDecimal(flowNumber));
        po.setSnakeCase(getCellValue(cellArray, 105));
        pos.add(po);
    }
    /**
     * 构建 MpFifthPaymentRecordPo(第五次拟付实付)
     */
    private MpFifthPaymentRecordPo buildMpFifthPaymentRecordPo(JSONArray cellArray, String flowNumber,String mainPo) throws ParseException {
        MpFifthPaymentRecordPo po = new MpFifthPaymentRecordPo();
    private List<MpFifthPaymentRecord> buildMpFifthPaymentRecordPos(JSONArray cellArray, String flowNumber,String mainPo) throws ParseException {
        List<MpFifthPaymentRecord> pos = new ArrayList<>();
        buildMpFifthPaymentRecordPo(pos, cellArray, flowNumber, mainPo, 115);
        buildMpFifthPaymentRecordPo(pos, cellArray, flowNumber, mainPo, 121);
        buildMpFifthPaymentRecordPo(pos, cellArray, flowNumber, mainPo, 127);
        buildMpFifthPaymentRecordPo(pos, cellArray, flowNumber, mainPo, 133);
        buildMpFifthPaymentRecordPo(pos, cellArray, flowNumber, mainPo, 139);
        return pos;
    }
    private void buildMpFifthPaymentRecordPo(List<MpFifthPaymentRecord> pos, JSONArray cellArray, String flowNumber, String mainPo, int index) {
//        if (getCellValue(cellArray, index).isEmpty()) {
//            return;
//        }
        MpFifthPaymentRecord po = new MpFifthPaymentRecord();
        long longId = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE; // 避免负数
        po.setId(longId+"");
        po.setMpId(mainPo); // mp_id = 流转编号_FIFTH
        // 第五次拟付实付数据(第106-111列)
        po.setFifthPlannedPaymentAmount(parseBigDecimal(getCellValue(cellArray, 137))); // 第五次拟付金额
        po.setPlannedPaymentDate(parseDate(getCellValue(cellArray, 138))); // 第五次拟付日期
        po.setCategory(getCellValue(cellArray, 139)); // 类别
        po.setReimburser(getCellValue(cellArray, 140)); // 报销人
        po.setActualPaymentAmount(parseBigDecimal(getCellValue(cellArray, 141))); // 第五次实付金额
        po.setActualPaymentDate(parseDate(getCellValue(cellArray, 142))); // 第五次实付日期
        return po;
        po.setMpId(flowNumber);
        po.setFifthPlannedPaymentAmount(Vtil.defaultValue(getCellValue(cellArray, index)));
        try {
            po.setPlannedPaymentDate(parseDate(getCellValue(cellArray, index + 1)));
            po.setActualPaymentDate(parseDate(getCellValue(cellArray, index  + 5)));
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
        po.setCategory(getCellValue(cellArray, index + 2));
        po.setReimburser(getCellValue(cellArray, index + 3));
        po.setActualPaymentAmount(getCellValue(cellArray, index + 4));
    }
    /**
@@ -351,31 +439,6 @@
        }
    }
    /**
     * 数据库保存(调用SqlSessionTemplate版Service)
     */
    private void saveToDatabase(MaintenancePaymentPo mainPo,
                                List<MpPaymentRecordPo> paymentRecordPos,
                                MpFifthPaymentRecordPo fifthPo) {
        // 1. 保存主表(单条)
        int mainFlag = maintenancePaymentService.saveMaintenancePayment(mainPo);
        if (mainFlag <= 0) {
            throw new RuntimeException("维修资金支取主表插入失败,流转编号:" + mainPo.getFlowNumber());
        }
        // 2. 批量保存5次打印记录
        int batchFlag = mpPaymentRecordService.batchSaveMpPaymentRecord(paymentRecordPos);
        if (batchFlag <= 0) {
            throw new RuntimeException("打印支取到账记录批量插入失败,流转编号:" + mainPo.getFlowNumber());
        }
        // 3. 保存第五次拟付实付记录
        int fifthFlag = mpFifthPaymentRecordService.saveMpFifthPaymentRecord(fifthPo);
        if (fifthFlag <= 0) {
            throw new RuntimeException("第五次拟付实付记录插入失败,流转编号:" + mainPo.getFlowNumber());
        }
    }
    // -------------------------- 工具方法 --------------------------
    /**
     * 获取单元格值(处理null/空字符串)
@@ -386,6 +449,15 @@
        }
        Object value = cellArray.get(index);
        return value == null ? "" : value.toString().trim();
    }
    private Double getCellNum(JSONArray cellArray, int index) {
        if (index < 0 || index >= cellArray.size()) {
            return 0.0;
        }
        Object value = cellArray.get(index);
        return value == null ? 0 : Double.parseDouble((String) value);
    }
    /**
@@ -425,40 +497,81 @@
        }
    }
    private String parseDateToString(String str) throws ParseException{
        return parseDate(str) == null ? "" : str;
    }
    /**
     * 解析日期(支持多种格式,空值返回null)
     */
    // 扩展支持的日期格式:覆盖横线/斜杠分隔、年月日/月日年等常见格式
    private static final List<SimpleDateFormat> DATE_FORMATS = new ArrayList<SimpleDateFormat>() {{
        add(new SimpleDateFormat("yyyy-MM-dd"));      // 2025-12-12
        add(new SimpleDateFormat("yyyy/MM/dd"));      // 2025/12/12
        add(new SimpleDateFormat("yyyyMMdd"));        // 20251212
        add(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); // 带时分秒的格式(兼容)
        add(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"));
        add(new SimpleDateFormat("MM/dd/yyyy"));      // 兼容国外格式(可选)
    }};
    /**
     * 解析日期:支持Excel日期序列号、2025-12-12、2025/12/12等格式
     * @param str 待解析的日期字符串(或Excel序列号)
     * @return 解析后的Date对象,空字符串返回null
     * @throws ParseException 不支持的格式抛出异常
     */
    private Date parseDate(String str) throws ParseException {
        if (isBlank(str)) {
        // 空值处理
        if (StringUtils.isBlank(str)) {
            return null;
        }
        String trimStr = str.trim();
        // 手动解析Excel日期序列号(1900-01-01为基准,注意Excel的1900闰年bug)
        // 第一步:尝试解析Excel日期序列号(数字格式)
        try {
            double excelDateNum = Double.parseDouble(str.trim());
            // Excel 1900日期系统:序列号1=1900-01-01,且错误认为1900是闰年(多算1天)
            Calendar cal = Calendar.getInstance();
            cal.set(1900, 0, 1); // 1900-01-01
            cal.add(Calendar.DAY_OF_YEAR, (int) excelDateNum - 2); // 修正闰年bug(减2)
            return cal.getTime();
            double excelDateNum = Double.parseDouble(trimStr);
            return convertExcelSerialToDate(excelDateNum);
        } catch (NumberFormatException e) {
            // 不是数字,继续解析文本格式
            // 不是数字,继续解析文本日期格式
        }
        // 文本格式解析(同之前逻辑)
        // 第二步:尝试解析文本格式日期(覆盖-和/分隔符)
        for (SimpleDateFormat format : DATE_FORMATS) {
            try {
                format.setLenient(false);
                return format.parse(str.trim());
                format.setLenient(false); // 严格校验,避免2025-13-32这类无效日期
                return format.parse(trimStr);
            } catch (ParseException e) {
                // 该格式解析失败,尝试下一个
                continue;
            }
        }
        // 所有格式都不匹配,抛异常
        throw new ParseException("不支持的日期格式:" + str, 0);
    }
    /**
     * 转换Excel日期序列号为Date(修正1900闰年bug)
     * Excel序列号规则:1=1900-01-01(错误认为1900是闰年,多算1天)
     * @param excelSerial Excel日期序列号(如45735)
     * @return 对应的Date对象
     */
    private Date convertExcelSerialToDate(double excelSerial) {
        // 初始化Calendar:时区设为GMT,避免本地时区偏移
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        // Excel 1900日期系统基准:序列号1对应1900-01-01
        cal.set(1900, Calendar.JANUARY, 1, 0, 0, 0);
        cal.set(Calendar.MILLISECOND, 0);
        // 修正Excel的1900闰年bug:Excel认为1900是闰年,实际不是,需减2天
        // 注:如果序列号<60(对应1900-02-28),减1天即可;通用场景减2天兼容所有情况
        int daysToAdd = (int) excelSerial - 2;
        cal.add(Calendar.DAY_OF_YEAR, daysToAdd);
        return cal.getTime();
    }
    /**
     * 转换盖章状态(√→是,其他→否)
     */
    private String convertSealStatus(String str) {