yxh
3 天以前 68b49efb4da346edb42a5d8ebb39af47207402bd
修改了报销校验
已修改27个文件
1623 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonatebaseinfoController.java 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonatecompletioninfoController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonationwitnessController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonorpaymentController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceExternalpersonController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceFundController.java 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceReimbursementController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceRelativesconfirmationController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/MergeFilesToPDFUtils.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/domain/ServiceFunddetail.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/mapper/ServiceDonationwitnessMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/IServiceFundService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/DingTalkServiceImpl.java 290 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceDonatebaseinfoServiceImpl.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceDonatecompletioninfoServiceImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceDonationwitnessServiceImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceEthicalreviewopinionsServiceImpl.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceExternalpersonServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceFundServiceImpl.java 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceFunddetailServiceImpl.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceReimbursementServiceImpl.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceRelativesconfirmationServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/resources/mapper/project/ServiceDonationwitnessMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/resources/mapper/project/ServiceExternalpersonMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-project/src/main/resources/mapper/project/ServiceFunddetailMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonatebaseinfoController.java
@@ -262,18 +262,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会导致生成的 doc 内容不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -292,44 +290,56 @@
            throw new ServiceException("下载失败,用户信息出错", HttpStatus.NO_CONTENT);
        }
        // 以下字段在数据库中均可为空,统一取空串兜底,避免拼装模板时抛空指针异常
        String diseasetype = serviceDonatebaseinfo.getDiseasetype() == null ? "" : serviceDonatebaseinfo.getDiseasetype();
        String infectious = serviceDonatebaseinfo.getInfectious() == null ? "" : serviceDonatebaseinfo.getInfectious();
        String infosources = serviceDonatebaseinfo.getInfosources() == null ? "" : serviceDonatebaseinfo.getInfosources();
        String selfwill = serviceDonatebaseinfo.getSelfwill() == null ? "" : serviceDonatebaseinfo.getSelfwill();
        String othercases = serviceDonatebaseinfo.getOthercases() == null ? "" : serviceDonatebaseinfo.getOthercases();
        String kinship = serviceDonatebaseinfo.getKinship() == null ? "" : serviceDonatebaseinfo.getKinship();
        String patientstate = serviceDonatebaseinfo.getPatientstate() == null ? "" : serviceDonatebaseinfo.getPatientstate();
        dataMap.put("JXBH", serviceDonatebaseinfo.getDonorno() == null ? "" : serviceDonatebaseinfo.getDonorno());
        dataMap.put("SZYLJG", serviceDonatebaseinfo.getTreatmenthospitalname() == null ? "" : serviceDonatebaseinfo.getTreatmenthospitalname());
        dataMap.put("KS", serviceDonatebaseinfo.getTreatmentdeptname() == null ? "" : serviceDonatebaseinfo.getTreatmentdeptname());
        dataMap.put("XM", serviceDonatebaseinfo.getName() == null ? "" : serviceDonatebaseinfo.getName());
        if (serviceDonatebaseinfo.getSex().equals("0")) {
        if ("0".equals(serviceDonatebaseinfo.getSex())) {
            dataMap.put("XB", "未知的性别");
        } else if (serviceDonatebaseinfo.getSex().equals("1")) {
        } else if ("1".equals(serviceDonatebaseinfo.getSex())) {
            dataMap.put("XB", "男");
        } else if (serviceDonatebaseinfo.getSex().equals("2")) {
        } else if ("2".equals(serviceDonatebaseinfo.getSex())) {
            dataMap.put("XB", "女");
        }
        Date dt = serviceDonatebaseinfo.getBirthday();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = formatter.format(dt);
        String time = date.substring(0, 10);
        dataMap.put("CSRQ", serviceDonatebaseinfo.getBirthday() == null ? "" : time);
        String csrq = "";
        if (serviceDonatebaseinfo.getBirthday() != null) {
            csrq = formatter.format(serviceDonatebaseinfo.getBirthday()).substring(0, 10);
        }
        dataMap.put("CSRQ", csrq);
        dataMap.put("GJ", serviceDonatebaseinfo.getNationality() == null ? "" : serviceDonatebaseinfo.getNationality());
        if (serviceDonatebaseinfo.getIdcardtype() == 1) {
        // idcardtype 为包装类型 Long,数据库中可为 NULL;先取值再判断,避免自动拆箱抛空指针
        Long idcardtype = serviceDonatebaseinfo.getIdcardtype();
        if (idcardtype != null && idcardtype == 1) {
            dataMap.put("ZJLX", "身份证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 2) {
        } else if (idcardtype != null && idcardtype == 2) {
            dataMap.put("ZJLX", "军人证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 3) {
        } else if (idcardtype != null && idcardtype == 3) {
            dataMap.put("ZJLX", "护照");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 4) {
        } else if (idcardtype != null && idcardtype == 4) {
            dataMap.put("ZJLX", "户口本");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 5) {
        } else if (idcardtype != null && idcardtype == 5) {
            dataMap.put("ZJLX", "外国人永久居住证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 6) {
        } else if (idcardtype != null && idcardtype == 6) {
            dataMap.put("ZJLX", "武警证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 10) {
        } else if (idcardtype != null && idcardtype == 10) {
            dataMap.put("ZJLX", "学生证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 11) {
        } else if (idcardtype != null && idcardtype == 11) {
            dataMap.put("ZJLX", "士兵证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 16) {
        } else if (idcardtype != null && idcardtype == 16) {
            dataMap.put("ZJLX", "港澳居民来往内地通行证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 17) {
        } else if (idcardtype != null && idcardtype == 17) {
            dataMap.put("ZJLX", "台湾居民来往大陆通行证");
        } else if (serviceDonatebaseinfo.getIdcardtype() == 18) {
        } else if (idcardtype != null && idcardtype == 18) {
            dataMap.put("ZJLX", "其他证件类型");
        }
        dataMap.put("ZJHM", serviceDonatebaseinfo.getIdcardno() == null ? "" : serviceDonatebaseinfo.getIdcardno());
@@ -346,28 +356,28 @@
        dataMap.put("ZYH", serviceDonatebaseinfo.getInpatientno() == null ? "" : serviceDonatebaseinfo.getInpatientno());
        if (serviceDonatebaseinfo.getBloodtype().equals("1")) {
        if ("1".equals(serviceDonatebaseinfo.getBloodtype())) {
            dataMap.put("XXA", (char) 8730);
            dataMap.put("XXB", "□");
            dataMap.put("XXO", "□");
            dataMap.put("XXAB", "□");
        } else if (serviceDonatebaseinfo.getBloodtype().equals("2")) {
        } else if ("2".equals(serviceDonatebaseinfo.getBloodtype())) {
            dataMap.put("XXA", "□");
            dataMap.put("XXB", (char) 8730);
            dataMap.put("XXO", "□");
            dataMap.put("XXAB", "□");
        } else if (serviceDonatebaseinfo.getBloodtype().equals("3")) {
        } else if ("3".equals(serviceDonatebaseinfo.getBloodtype())) {
            dataMap.put("XXA", "□");
            dataMap.put("XXB", "□");
            dataMap.put("XXO", (char) 8730);
            dataMap.put("XXAB", "□");
        } else if (serviceDonatebaseinfo.getBloodtype().equals("4")) {
        } else if ("4".equals(serviceDonatebaseinfo.getBloodtype())) {
            dataMap.put("XXA", "□");
            dataMap.put("XXB", "□");
            dataMap.put("XXO", "□");
            dataMap.put("XXAB", (char) 8730);
        }
        if (serviceDonatebaseinfo.getRhyin().equals("1")) {
        if ("1".equals(serviceDonatebaseinfo.getRhyin())) {
            dataMap.put("YX", (char) 8730);
        } else {
            dataMap.put("YX", "□");
@@ -376,22 +386,22 @@
        dataMap.put("JBZD", serviceDonatebaseinfo.getDiagnosisname() == null ? "" : serviceDonatebaseinfo.getDiagnosisname());
        //疾病类型
        if (serviceDonatebaseinfo.getDiseasetype().contains("0")) {
        if (diseasetype.contains("0")) {
            dataMap.put("NXGYW", (char) 8730);
            if (serviceDonatebaseinfo.getDiseasetype().contains("1")) {
            if (diseasetype.contains("1")) {
                dataMap.put("NWS", (char) 8730);
                if (serviceDonatebaseinfo.getDiseasetype().contains("2")) {
                if (diseasetype.contains("2")) {
                    dataMap.put("QXQY", (char) 8730);
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -399,16 +409,16 @@
                    }
                } else {
                    dataMap.put("QXQY", "□");
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -417,18 +427,18 @@
                }
            } else {
                dataMap.put("NWS", "□");
                if (serviceDonatebaseinfo.getDiseasetype().contains("2")) {
                if (diseasetype.contains("2")) {
                    dataMap.put("QXQY", (char) 8730);
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -436,16 +446,16 @@
                    }
                } else {
                    dataMap.put("QXQY", "□");
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -455,20 +465,20 @@
            }
        } else {
            dataMap.put("NXGYW", "□");
            if (serviceDonatebaseinfo.getDiseasetype().contains("1")) {
            if (diseasetype.contains("1")) {
                dataMap.put("NWS", (char) 8730);
                if (serviceDonatebaseinfo.getDiseasetype().contains("2")) {
                if (diseasetype.contains("2")) {
                    dataMap.put("QXQY", (char) 8730);
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -476,16 +486,16 @@
                    }
                } else {
                    dataMap.put("QXQY", "□");
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -494,18 +504,18 @@
                }
            } else {
                dataMap.put("NWS", "□");
                if (serviceDonatebaseinfo.getDiseasetype().contains("2")) {
                if (diseasetype.contains("2")) {
                    dataMap.put("QXQY", (char) 8730);
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -513,16 +523,16 @@
                    }
                } else {
                    dataMap.put("QXQY", "□");
                    if (serviceDonatebaseinfo.getDiseasetype().contains("3")) {
                    if (diseasetype.contains("3")) {
                        dataMap.put("NZL", (char) 8730);
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
                        }
                    } else {
                        dataMap.put("NZL", "□");
                        if (serviceDonatebaseinfo.getDiseasetype().contains("4")) {
                        if (diseasetype.contains("4")) {
                            dataMap.put("XXGJB", (char) 8730);
                        } else {
                            dataMap.put("XXGJB", "□");
@@ -536,20 +546,20 @@
        //传染病情况
        if (serviceDonatebaseinfo.getInfectious().contains("1")) {
        if (infectious.contains("1")) {
            dataMap.put("YG", (char) 8730);
            if (serviceDonatebaseinfo.getInfectious().contains("2")) {
            if (infectious.contains("2")) {
                dataMap.put("BG", (char) 8730);
                if (serviceDonatebaseinfo.getInfectious().contains("3")) {
                if (infectious.contains("3")) {
                    dataMap.put("MD", (char) 8730);
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
                    }
                } else {
                    dataMap.put("MD", "□");
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
@@ -557,16 +567,16 @@
                }
            } else {
                dataMap.put("BG", "□");
                if (serviceDonatebaseinfo.getInfectious().contains("3")) {
                if (infectious.contains("3")) {
                    dataMap.put("MD", (char) 8730);
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
                    }
                } else {
                    dataMap.put("MD", "□");
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
@@ -575,18 +585,18 @@
            }
        } else {
            dataMap.put("YG", "□");
            if (serviceDonatebaseinfo.getInfectious().contains("2")) {
            if (infectious.contains("2")) {
                dataMap.put("BG", (char) 8730);
                if (serviceDonatebaseinfo.getInfectious().contains("3")) {
                if (infectious.contains("3")) {
                    dataMap.put("MD", (char) 8730);
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
                    }
                } else {
                    dataMap.put("MD", "□");
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
@@ -594,16 +604,16 @@
                }
            } else {
                dataMap.put("BG", "□");
                if (serviceDonatebaseinfo.getInfectious().contains("3")) {
                if (infectious.contains("3")) {
                    dataMap.put("MD", (char) 8730);
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
                    }
                } else {
                    dataMap.put("MD", "□");
                    if (serviceDonatebaseinfo.getInfectious().contains("4")) {
                    if (infectious.contains("4")) {
                        dataMap.put("AZB", (char) 8730);
                    } else {
                        dataMap.put("AZB", "□");
@@ -613,16 +623,16 @@
        }
        dataMap.put("CRBQT", serviceDonatebaseinfo.getInfectiousOther() == null ? "" : serviceDonatebaseinfo.getInfectiousOther());
        //病人状况
        if (serviceDonatebaseinfo.getPatientstate().contains("0")) {
        if (patientstate.contains("0")) {
            dataMap.put("SDHM", (char) 8730);
            if (serviceDonatebaseinfo.getPatientstate().contains("1")) {
            if (patientstate.contains("1")) {
                dataMap.put("WZZHX", (char) 8730);
            } else {
                dataMap.put("WZZHX", "□");
            }
        } else {
            dataMap.put("SDHM", "□");
            if (serviceDonatebaseinfo.getPatientstate().contains("1")) {
            if (patientstate.contains("1")) {
                dataMap.put("WZZHX", (char) 8730);
            } else {
                dataMap.put("WZZHX", "□");
@@ -630,20 +640,20 @@
        }
        //其他情况
        if (serviceDonatebaseinfo.getOthercases().contains("0")) {
        if (othercases.contains("0")) {
            dataMap.put("JTSG", (char) 8730);
            if (serviceDonatebaseinfo.getOthercases().contains("1")) {
            if (othercases.contains("1")) {
                dataMap.put("XSAJ", (char) 8730);
                if (serviceDonatebaseinfo.getOthercases().contains("2")) {
                if (othercases.contains("2")) {
                    dataMap.put("GSSG", (char) 8730);
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
                    }
                } else {
                    dataMap.put("GSSG", "□");
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
@@ -651,16 +661,16 @@
                }
            } else {
                dataMap.put("XSAJ", "□");
                if (serviceDonatebaseinfo.getOthercases().contains("2")) {
                if (othercases.contains("2")) {
                    dataMap.put("GSSG", (char) 8730);
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
                    }
                } else {
                    dataMap.put("GSSG", "□");
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
@@ -669,18 +679,18 @@
            }
        } else {
            dataMap.put("JTSG", "□");
            if (serviceDonatebaseinfo.getOthercases().contains("1")) {
            if (othercases.contains("1")) {
                dataMap.put("XSAJ", (char) 8730);
                if (serviceDonatebaseinfo.getOthercases().contains("2")) {
                if (othercases.contains("2")) {
                    dataMap.put("GSSG", (char) 8730);
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
                    }
                } else {
                    dataMap.put("GSSG", "□");
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
@@ -688,16 +698,16 @@
                }
            } else {
                dataMap.put("XSAJ", "□");
                if (serviceDonatebaseinfo.getOthercases().contains("2")) {
                if (othercases.contains("2")) {
                    dataMap.put("GSSG", (char) 8730);
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
                    }
                } else {
                    dataMap.put("GSSG", "□");
                    if (serviceDonatebaseinfo.getOthercases().contains("3")) {
                    if (othercases.contains("3")) {
                        dataMap.put("QTYWSG", (char) 8730);
                    } else {
                        dataMap.put("QTYWSG", "□");
@@ -707,20 +717,20 @@
        }
        //情亲属关系情况
        if (serviceDonatebaseinfo.getKinship().contains("0")) {
        if (kinship.contains("0")) {
            dataMap.put("PE", (char) 8730);
            if (serviceDonatebaseinfo.getKinship().contains("1")) {
            if (kinship.contains("1")) {
                dataMap.put("ZN", (char) 8730);
                if (serviceDonatebaseinfo.getKinship().contains("2")) {
                if (kinship.contains("2")) {
                    dataMap.put("FQ", (char) 8730);
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
                    }
                } else {
                    dataMap.put("FQ", "□");
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
@@ -728,16 +738,16 @@
                }
            } else {
                dataMap.put("ZN", "□");
                if (serviceDonatebaseinfo.getKinship().contains("2")) {
                if (kinship.contains("2")) {
                    dataMap.put("FQ", (char) 8730);
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
                    }
                } else {
                    dataMap.put("FQ", "□");
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
@@ -746,18 +756,18 @@
            }
        } else {
            dataMap.put("PE", "□");
            if (serviceDonatebaseinfo.getKinship().contains("1")) {
            if (kinship.contains("1")) {
                dataMap.put("ZN", (char) 8730);
                if (serviceDonatebaseinfo.getKinship().contains("2")) {
                if (kinship.contains("2")) {
                    dataMap.put("FQ", (char) 8730);
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
                    }
                } else {
                    dataMap.put("FQ", "□");
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
@@ -765,16 +775,16 @@
                }
            } else {
                dataMap.put("ZN", "□");
                if (serviceDonatebaseinfo.getKinship().contains("2")) {
                if (kinship.contains("2")) {
                    dataMap.put("FQ", (char) 8730);
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
                    }
                } else {
                    dataMap.put("FQ", "□");
                    if (serviceDonatebaseinfo.getKinship().contains("3")) {
                    if (kinship.contains("3")) {
                        dataMap.put("MQ", (char) 8730);
                    } else {
                        dataMap.put("MQ", "□");
@@ -785,20 +795,20 @@
        dataMap.put("QSQT", serviceDonatebaseinfo.getKinshipOther() == null ? "" : serviceDonatebaseinfo.getKinshipOther());
        //本人捐献
        if (serviceDonatebaseinfo.getSelfwill().contains("0")) {
        if (selfwill.contains("0")) {
            dataMap.put("ZYDJ", (char) 8730);
            if (serviceDonatebaseinfo.getSelfwill().contains("1")) {
            if (selfwill.contains("1")) {
                dataMap.put("SMTY", (char) 8730);
                if (serviceDonatebaseinfo.getSelfwill().contains("2")) {
                if (selfwill.contains("2")) {
                    dataMap.put("KTTY", (char) 8730);
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
                    }
                } else {
                    dataMap.put("KTTY", "□");
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
@@ -806,16 +816,16 @@
                }
            } else {
                dataMap.put("SMTY", "□");
                if (serviceDonatebaseinfo.getSelfwill().contains("2")) {
                if (selfwill.contains("2")) {
                    dataMap.put("KTTY", (char) 8730);
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
                    }
                } else {
                    dataMap.put("KTTY", "□");
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
@@ -824,18 +834,18 @@
            }
        } else {
            dataMap.put("ZYDJ", "□");
            if (serviceDonatebaseinfo.getSelfwill().contains("1")) {
            if (selfwill.contains("1")) {
                dataMap.put("SMTY", (char) 8730);
                if (serviceDonatebaseinfo.getSelfwill().contains("2")) {
                if (selfwill.contains("2")) {
                    dataMap.put("KTTY", (char) 8730);
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
                    }
                } else {
                    dataMap.put("KTTY", "□");
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
@@ -843,16 +853,16 @@
                }
            } else {
                dataMap.put("SMTY", "□");
                if (serviceDonatebaseinfo.getSelfwill().contains("2")) {
                if (selfwill.contains("2")) {
                    dataMap.put("KTTY", (char) 8730);
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
                    }
                } else {
                    dataMap.put("KTTY", "□");
                    if (serviceDonatebaseinfo.getSelfwill().contains("3")) {
                    if (selfwill.contains("3")) {
                        dataMap.put("WBSBTY", (char) 8730);
                    } else {
                        dataMap.put("WBSBTY", "□");
@@ -883,25 +893,25 @@
            dataMap.put("YJXZGX", "受委托人");
        }
        //信息来源
        if (serviceDonatebaseinfo.getInfosources().contains("红十字会")) {
        if (infosources.contains("红十字会")) {
            dataMap.put("HSZH2", (char) 8730);  // 勾选符号
        } else {
            dataMap.put("HSZH2", "□");  // 方框符号
        }
        if (serviceDonatebaseinfo.getInfosources().contains("医疗机构")) {
        if (infosources.contains("医疗机构")) {
            dataMap.put("YLJG", (char) 8730);
            if (serviceDonatebaseinfo.getInfosources().contains("家属")) {
            if (infosources.contains("家属")) {
                dataMap.put("QS", (char) 8730);
                if (serviceDonatebaseinfo.getInfosources().contains("交通")) {
                if (infosources.contains("交通")) {
                    dataMap.put("JT", (char) 8730);
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
                    }
                } else {
                    dataMap.put("JT", "□");
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
@@ -909,16 +919,16 @@
                }
            } else {
                dataMap.put("QS", "□");
                if (serviceDonatebaseinfo.getInfosources().contains("交通")) {
                if (infosources.contains("交通")) {
                    dataMap.put("JT", (char) 8730);
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
                    }
                } else {
                    dataMap.put("JT", "□");
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
@@ -927,18 +937,18 @@
            }
        } else {
            dataMap.put("YLJG", "□");
            if (serviceDonatebaseinfo.getInfosources().contains("家属")) {
            if (infosources.contains("家属")) {
                dataMap.put("QS", (char) 8730);
                if (serviceDonatebaseinfo.getInfosources().contains("交通")) {
                if (infosources.contains("交通")) {
                    dataMap.put("JT", (char) 8730);
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
                    }
                } else {
                    dataMap.put("JT", "□");
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
@@ -946,16 +956,16 @@
                }
            } else {
                dataMap.put("QS", "□");
                if (serviceDonatebaseinfo.getInfosources().contains("交通")) {
                if (infosources.contains("交通")) {
                    dataMap.put("JT", (char) 8730);
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
                    }
                } else {
                    dataMap.put("JT", "□");
                    if (serviceDonatebaseinfo.getInfosources().contains("公安")) {
                    if (infosources.contains("公安")) {
                        dataMap.put("GA", (char) 8730);
                    } else {
                        dataMap.put("GA", "□");
@@ -967,10 +977,11 @@
        dataMap.put("HSZH", serviceDonatebaseinfo.getRedorganname() == null ? "" : serviceDonatebaseinfo.getRedorganname());
        dataMap.put("LXR", serviceDonatebaseinfo.getContactperson() == null ? "" : serviceDonatebaseinfo.getContactperson());
        Date dt1 = serviceDonatebaseinfo.getContacttime();
        String date1 = formatter.format(dt1);
        String time1 = date1.substring(0, 16);
        dataMap.put("LXSJ", serviceDonatebaseinfo.getContacttime() == null ? "" : time1);
        String lxsj = "";
        if (serviceDonatebaseinfo.getContacttime() != null) {
            lxsj = formatter.format(serviceDonatebaseinfo.getContacttime()).substring(0, 16);
        }
        dataMap.put("LXSJ", lxsj);
        dataMap.put("HQZZ", serviceDonatebaseinfo.getAcquisitiontissuename() == null ? "" : serviceDonatebaseinfo.getAcquisitiontissuename());
        dataMap.put("BGR", serviceDonatebaseinfo.getReportername() == null ? "" : serviceDonatebaseinfo.getReportername());
        dataMap.put("BGRDH", serviceDonatebaseinfo.getReporterphone() == null ? "" : serviceDonatebaseinfo.getReporterphone());
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonatecompletioninfoController.java
@@ -3,11 +3,13 @@
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.OrganEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.bean.DtoConversionUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -221,18 +223,16 @@
        String name = "人体器官捐献完成结果登记表_" + dataMap.get("XM") + "_" + newTime;
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonationwitnessController.java
@@ -6,7 +6,9 @@
import com.ruoyi.common.annotation.NotRepeatCommit;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.enums.OrganEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.DtoConversionUtils;
@@ -210,18 +212,16 @@
        String name = "人体器官捐献获取见证登记表_" + dataMap.get("XM") + "_" + newTime;
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceDonorpaymentController.java
@@ -3,10 +3,12 @@
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.project.domain.ServiceDonorpayment;
import com.ruoyi.project.service.IServiceDonorpaymentService;
@@ -131,19 +133,16 @@
        String name = "浙江省接收器官结算表_"+ newTime;
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            logger.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceExternalpersonController.java
@@ -96,7 +96,8 @@
        {
            return AjaxResult.error("新增人员需要确定人员类型!");
        }
        if (!"org".equals(usertype)){
        // UserType 自 2026-09-27 起为多值字段(逗号分隔),单位判断改为"包含 org"
        if (!hasUserType(usertype, "org")){
            String idcardno = serviceExternalperson.getIdcardno();
            List<ServiceExternalperson> existpsersons = serviceExternalpersonService.getInfoByIDCardNo(idcardno);
            if (existpsersons.size() > 0) {
@@ -148,4 +149,23 @@
    public AjaxResult getInfoByIDCardNo(@PathVariable("idcardno") String idcardno) {
        return AjaxResult.success(serviceExternalpersonService.getInfoByIDCardNo(idcardno));
    }
    /**
     * 判断人员类型(多值,逗号分隔)中是否包含指定类型
     *
     * @param usertype 人员类型,如 "zzry,performance";兼容改造前的单值
     * @param code     字典 sys_UserType 的取值,如 "org"
     * @return 是否包含
     */
    private static boolean hasUserType(String usertype, String code) {
        if (usertype == null || usertype.isEmpty()) {
            return false;
        }
        for (String item : usertype.split(",")) {
            if (code.equals(item.trim())) {
                return true;
            }
        }
        return false;
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceFundController.java
@@ -322,6 +322,23 @@
        return AjaxResult.success(serviceFundService.addOrUpdateNew(serviceFundVO));
    }
    /**
     * 专家劳务费:保存前的费用控制校验(干跑,不落库)
     *
     * <p>供专家劳务费填报页在点“保存”前调用,把 9 条规则的校验结果
     * (errors 阻断 / warnings 提示)返回前端展示;真正的落库拦截在 addOrUpdateNew 中。
     */
    @ApiOperation("专家劳务费保存前校验")
    @PostMapping("/validateExpertFee")
    public AjaxResult validateExpertFee(@RequestBody ServiceFundVO serviceFundVO) {
        ExpertFeeCheckResult result = serviceFundService.validateExpertFee(serviceFundVO);
        AjaxResult ajaxResult = AjaxResult.success();
        ajaxResult.put("errors", result.getErrors());
        ajaxResult.put("warnings", result.getWarnings());
        ajaxResult.put("blocked", result.hasError());
        return ajaxResult;
    }
    /**
     * 审核费用
@@ -475,18 +492,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -659,18 +674,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -756,18 +769,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -858,20 +869,16 @@
        }
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            log.error("FileNotFoundException报错信息是:{}", e1.getMessage());
//            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            log.error("TemplateException报错信自是:{}", e.getMessage());
            e.getMessage();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -1083,18 +1090,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -1152,18 +1157,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -1652,18 +1655,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
@@ -1741,18 +1742,16 @@
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceReimbursementController.java
@@ -14,6 +14,7 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.project.domain.*;
import com.ruoyi.project.common.FundNotifyMessageBuilder;
import com.ruoyi.project.domain.dto.ServiceReimbursementDto;
import com.ruoyi.project.domain.vo.CheckFundVO;
import com.ruoyi.project.domain.vo.DingTalkReqVo;
@@ -346,17 +347,27 @@
                    //陈慕华信息
                    sysUser = sysUserService.selectUserByUserName("047");
                }
                ConcurrentHashMap map = new ConcurrentHashMap();
                ArrayList<ConcurrentHashMap<String, Object>> contentList = new ArrayList<>();
                map.put("提交人:", loginUser.getUser().getUserName());
                map.put("提交时间:", new Date());
                map.put("内容:", serviceReimbursement.getReason());
                contentList.add(map);
                DingTalkReqVo dingTalkReqVo = new DingTalkReqVo();
                dingTalkReqVo.setTitle("差旅审批信息");
                dingTalkReqVo.setNumber(sysUser != null ? sysUser.getPhonenumber() : "");
                dingTalkReqVo.setContents(contentList);
                dingTalkService.sendNotification(dingTalkReqVo);
                //差旅费上报提交:单据信息 + 提交人/提交时间/内容
                //钉钉属于旁路通知,任何失败都不得影响"上报"主流程(否则前端会提示失败但数据已更新)
                try {
                    List<ConcurrentHashMap<String, Object>> contentList = FundNotifyMessageBuilder.newRows();
                    FundNotifyMessageBuilder.addBillInfo(contentList, "0", serviceReimbursement.getUsername(),
                            serviceReimbursement.getAmountrequested(), null,
                            FundNotifyMessageBuilder.billNo(serviceReimbursement.getBh(),
                                    serviceReimbursement.getId()));
                    FundNotifyMessageBuilder.addTravelInfo(contentList, serviceReimbursement.getTravelers(),
                            serviceReimbursement.getReason());
                    FundNotifyMessageBuilder.addSubmitInfo(contentList, loginUser.getUser().getNickName(),
                            new Date(), serviceReimbursement.getReason());
                    DingTalkReqVo dingTalkReqVo = new DingTalkReqVo();
                    dingTalkReqVo.setTitle(FundNotifyMessageBuilder.TITLE);
                    dingTalkReqVo.setNumber(sysUser != null ? sysUser.getPhonenumber() : "");
                    dingTalkReqVo.setContents(contentList);
                    dingTalkService.sendNotification(dingTalkReqVo);
                } catch (Throwable t) {
                    log.error("差旅上报钉钉通知失败,已忽略(不影响上报主流程),id={}",
                            serviceReimbursement.getId(), t);
                }
            }
            serviceReimbursementService.updateById(serviceReimbursement);
@@ -467,18 +478,16 @@
            System.out.println("目录已存在");
        }
        Writer out = null;
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
ruoyi-admin/src/main/java/com/ruoyi/web/controller/project/ServiceRelativesconfirmationController.java
@@ -189,13 +189,16 @@
        String name = "人体器官捐献亲属确认登记表_" + dataMap.get("XM") + "_" + newTime;
        //输出文档路径及名称
        File outFile = new File(RuoYiConfig.getProfile() + "/download/wordtemplate/" + name + ".doc");
        Writer out = null;
        if (t == null) {
            throw new ServiceException("模板加载失败,请检查模板文件是否存在", HttpStatus.NO_CONTENT);
        }
        try {
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"));
        // Writer 必须关闭/刷新:BufferedWriter 带缓冲,不关闭会造成文件句柄泄漏且内容可能不完整
        try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"))) {
            t.process(dataMap, out);
        } catch (TemplateException e) {
            e.printStackTrace();
        } catch (TemplateException | IOException e) {
            log.error("生成模板文档失败, name={}", name, e);
            throw new ServiceException("生成文档失败", HttpStatus.ERROR);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("downloadUrl", "/profile/download/wordtemplate/" + name + ".doc");
ruoyi-admin/src/main/resources/application.yml
@@ -53,8 +53,9 @@
    # 国际化资源文件路径
    basename: i18n/messages
  profiles:
#    active: srm
    active: druid
    active: srm
#    active: druid
  # 文件上传
  servlet:
    multipart:
ruoyi-common/src/main/java/com/ruoyi/common/utils/MergeFilesToPDFUtils.java
@@ -76,11 +76,16 @@
     * @param outputPath
     */
    public Boolean wordtoPdf(String inputPath, String outputPath) {
        // Spire.Doc 的 Document 持有文件句柄,必须显式关闭,否则句柄要等 finalize 才释放,且会占用源文件
        Document doc = new Document();
        //加载Word
        doc.loadFromFile(inputPath);
        //保存为PDF格式
        doc.saveToFile(outputPath, FileFormat.PDF);
        try {
            //加载Word
            doc.loadFromFile(inputPath);
            //保存为PDF格式
            doc.saveToFile(outputPath, FileFormat.PDF);
        } finally {
            doc.close();
        }
        return true;
    }
@@ -91,18 +96,25 @@
        com.itextpdf.text.Document document = new com.itextpdf.text.Document();
        log.info("fileName的全路径:{}", archived + "/" + fileName);
        PdfCopy copy = new PdfCopy(document, new FileOutputStream(archived + "/" + fileName));
        document.open();
        for (String pdfFile : pdfFiles) {
            PdfReader reader = new PdfReader(pdfFile);
            int totalPages = reader.getNumberOfPages();
            for (int page = 1; page <= totalPages; page++) {
                PdfImportedPage importedPage = copy.getImportedPage(reader, page);
                copy.addPage(importedPage);
        // 合并过程中任何一步抛异常都必须关闭 document:document.close() 会级联关闭 PdfWriter 及其输出流,
        // 否则输出流句柄会被长时间占用(依赖 finalize 才能释放),且异常时文件内容不完整
        try {
            document.open();
            for (String pdfFile : pdfFiles) {
                PdfReader reader = new PdfReader(pdfFile);
                try {
                    int totalPages = reader.getNumberOfPages();
                    for (int page = 1; page <= totalPages; page++) {
                        PdfImportedPage importedPage = copy.getImportedPage(reader, page);
                        copy.addPage(importedPage);
                    }
                } finally {
                    reader.close();
                }
            }
            reader.close();
        } finally {
            document.close();
        }
        document.close();
    }
}
ruoyi-project/src/main/java/com/ruoyi/project/domain/ServiceFunddetail.java
@@ -77,6 +77,13 @@
    private String unituserno;
    /**
     * 单位标识:1=单位(受益人选择单位时不需要身份证号),0=个人
     */
    @ApiModelProperty("单位标识:1=单位,0=个人")
    @Excel(name = "单位标识", readConverterExp = "1=单位,0=个人")
    private Integer isunit;
    /**
     * 职称,见字典sys_professionaltitle
     */
    @ApiModelProperty("职称")
ruoyi-project/src/main/java/com/ruoyi/project/mapper/ServiceDonationwitnessMapper.java
@@ -12,6 +12,7 @@
import com.ruoyi.project.domain.vo.DonationWitnessVO;
import com.ruoyi.project.domain.vo.TimeVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * 捐献见证Mapper接口
@@ -30,6 +31,18 @@
     */
    public List<ServiceDonationwitness> selectServiceDonationwitnessList(ServiceDonationwitness serviceDonationwitness);
    /**
     * 一次性查出全部「捐献基础ID -> 死亡时间」对应关系。
     *
     * 用于列表页计算年龄:原实现在记录循环里按 InfoID 逐条查询见证表(N+1),
     * 首页/列表有多少条记录就会发多少次 SQL。改为一次查询后在内存中匹配。
     * 只取 InfoID 与 DeathTime 两列,避免拉取整行宽表数据。
     *
     * @param infoIds 需要查询的捐献基础ID集合;为空时查全部
     * @return 仅含 infoid 与 deathtime 的列表
     */
    List<ServiceDonationwitness> selectDeathTimeByInfoIds(@Param("infoIds") Collection<Long> infoIds);
    List<ServiceDonationwitness> getWitnessList();
    List<DonationWitnessVO> selectVOList(DonationWitnessVO donationWitnessVO);
ruoyi-project/src/main/java/com/ruoyi/project/service/IServiceFundService.java
@@ -89,5 +89,15 @@
    public Boolean updateFundTaxIdById(Long id, Long fundTaxIds);
    /**
     * 专家劳务费:9 条费用控制规则的表单校验(不落库)。
     *
     * <p>供填报保存前调用,返回 errors(阻断性,不能保存)与 warnings(仅提示);
     * 真正的落库拦截在 {@link #addOrUpdateNew} 内。
     *
     * @param serviceFundVO 本次提交的表单
     * @return 校验结果
     */
    ExpertFeeCheckResult validateExpertFee(ServiceFundVO serviceFundVO);
}
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/DingTalkServiceImpl.java
@@ -18,7 +18,7 @@
import com.ruoyi.project.service.DingTalkService;
import com.ruoyi.system.mapper.SysUserMapper;
import com.taobao.api.ApiException;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -38,7 +38,7 @@
 * @date 2023-05-23
 */
@Service
@Log4j
@Slf4j
public class DingTalkServiceImpl implements DingTalkService {
@@ -51,119 +51,195 @@
    @Value("${agentId}")
    private Long agentId;
    /**
     * access_token 缓存。
     * 钉钉 token 有效期 7200 秒,这里按 6000 秒保守刷新;
     * 原实现每次发消息都调一次 /gettoken,钉钉对取 token 有频次限制,审核高峰期容易整体失败。
     */
    private volatile String cachedAccessToken;
    /** access_token 过期时间(epoch millis) */
    private volatile long accessTokenExpireAt;
    private static final long ACCESS_TOKEN_TTL_MILLIS = 6000L * 1000L;
    /**
     * 钉钉接口超时(毫秒)。
     * SDK 未显式设置超时时会无限等待,钉钉网络不通将占满容器线程、拖死审批等调用方,
     * 因此这里对所有出站请求统一设置连接/读取超时。
     */
    private static final int CONNECT_TIMEOUT_MS = 3000;
    private static final int READ_TIMEOUT_MS = 5000;
    @Autowired
    private SysUserMapper sysUserMapper;
    /**
     * 创建带超时的钉钉客户端
     */
    private DefaultDingTalkClient newClient(String url) {
        DefaultDingTalkClient client = new DefaultDingTalkClient(url);
        client.setConnectTimeout(CONNECT_TIMEOUT_MS);
        client.setReadTimeout(READ_TIMEOUT_MS);
        return client;
    }
    /**
     * 发送钉钉通知(对外唯一入口)。
     *
     * <p><b>本方法保证不向外抛任何异常</b>:钉钉属于旁路通知,任何失败都只记日志并返回 false,
     * 绝不能影响审批、上报等业务主流程。
     */
    @Override
    public Boolean sendNotification(DingTalkReqVo dingTalkReqVo) {
        String body = null;
        try {
            return doSendNotification(dingTalkReqVo);
        } catch (Throwable t) {
            log.error("钉钉通知发送发生未预期异常,已忽略(不影响业务主流程)", t);
            return false;
        }
    }
    private Boolean doSendNotification(DingTalkReqVo dingTalkReqVo) {
        List<String> userIdlist = new ArrayList();
        Boolean result = false;
        log.info("发送钉钉通知");
        if (dingTalkReqVo == null) {
            log.warn("钉钉通知入参为空,跳过发送");
            return false;
        }
        log.info("发送钉钉通知,title={}", dingTalkReqVo.getTitle());
        String accessToken = null;
        try {
            accessToken = getAccessToken();
        } catch (ApiException e) {
            e.printStackTrace();
        } catch (Exception e) {
            log.error("钉钉获取 access_token 失败,无法发送通知", e);
        }
        if (StringUtils.isNotEmpty(dingTalkReqVo.getNumber())) {
            DingTalkClient clientT = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getbymobile");
            OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest();
            req.setMobile(dingTalkReqVo.getNumber());
            OapiV2UserGetbymobileResponse rsp = null;
            try {
                rsp = clientT.execute(req, accessToken);
                body = rsp.getBody();
                String jsonObject = JSONObject.parseObject(body).getJSONObject("result").get("userid").toString();
                userIdlist.add(jsonObject);
        if (StringUtils.isEmpty(accessToken)) {
            log.error("钉钉 access_token 为空,无法发送通知");
            return false;
        }
            } catch (ApiException e) {
                e.printStackTrace();
        // ---- 解析收件人:优先手机号,其次部门 ----
        if (StringUtils.isNotEmpty(dingTalkReqVo.getNumber())) {
            try {
                DingTalkClient clientT = newClient("https://oapi.dingtalk.com/topapi/v2/user/getbymobile");
                OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest();
                req.setMobile(dingTalkReqVo.getNumber());
                OapiV2UserGetbymobileResponse rsp = clientT.execute(req, accessToken);
                String body = rsp == null ? null : rsp.getBody();
                if (StringUtils.isEmpty(body)) {
                    log.error("钉钉按手机号查用户失败,mobile={},响应为空", dingTalkReqVo.getNumber());
                    return false;
                }
                JSONObject resultObj = JSONObject.parseObject(body).getJSONObject("result");
                if (resultObj == null || resultObj.get("userid") == null) {
                    log.error("钉钉按手机号查用户失败,mobile={},响应={}", dingTalkReqVo.getNumber(), body);
                    return false;
                }
                userIdlist.add(resultObj.get("userid").toString());
            } catch (Exception e) {
                log.error("钉钉按手机号查用户异常,mobile={}", dingTalkReqVo.getNumber(), e);
                return false;
            }
        } else if (dingTalkReqVo.getDeptId() != null) {
            try {
                DingTalkClient client3 = newClient("https://oapi.dingtalk.com/topapi/user/listid");
                OapiUserListidRequest request2 = new OapiUserListidRequest();
                request2.setDeptId(dingTalkReqVo.getDeptId()); // 设置要获取的部门 ID,这里以根部门为例675909059L
                // 设置部门 ID,如果获取所有员工手机号,可以设置为根部门的 ID
                OapiUserListidResponse response2 = client3.execute(request2, accessToken);
                String body = response2 == null ? null : response2.getBody();
                if (StringUtils.isNotEmpty(body)) {
                    // 解析为JSONObject
                    JSONObject jsonObject = JSONObject.parseObject(body);
                    // 提取出JSONArray
                    JSONArray jsonArray = new JSONArray(jsonObject.getJSONObject("result").getJSONArray("userid_list"));
                    // 将JSONArray转为List列表
                    String str = JSONObject.toJSONString(jsonArray);
                    userIdlist = JSONObject.parseObject(str, List.class);
                }
            } catch (Exception e) {
                log.error("钉钉按部门查用户异常,deptId={}", dingTalkReqVo.getDeptId(), e);
                return false;
            }
        } else {
            DingTalkClient client3 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/listid");
            // 既无手机号也无部门:原实现会走 listid(null) 被钉钉拒绝且异常被吞掉,表现为"静默发不出去"
            log.warn("钉钉通知未提供收件人(手机号与部门均为空),跳过发送");
            return false;
        }
            OapiUserListidRequest request2 = new OapiUserListidRequest();
            request2.setDeptId(dingTalkReqVo.getDeptId()); // 设置要获取的部门 ID,这里以根部门为例675909059L
            // 设置部门 ID,如果获取所有员工手机号,可以设置为根部门的 ID
            OapiUserListidResponse response2 = null;
            try {
                response2 = client3.execute(request2, accessToken);
            } catch (ApiException e) {
                e.printStackTrace();
            }
            body = response2.getBody();
            // 解析为JSONObject
            JSONObject jsonObject = JSONObject.parseObject(body);
            if (jsonObject != null) {
                // 提取出JSONArray
                JSONArray jsonArray = new JSONArray(jsonObject.getJSONObject("result").getJSONArray("userid_list"));
                // 将JSONArray转为List列表
                String str = JSONObject.toJSONString(jsonArray);
                userIdlist = JSONObject.parseObject(str, List.class);
            }
        if (CollectionUtils.isEmpty(userIdlist)) {
            log.warn("钉钉通知未解析到任何收件人,跳过发送");
            return false;
        }
        //userid数组
        for (String urid : userIdlist) {
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
            OapiMessageCorpconversationAsyncsendV2Request request = new OapiMessageCorpconversationAsyncsendV2Request();
            request.setUseridList(urid);
            request.setAgentId(agentId);
            request.setToAllUser(false);
            //发送内容处理
            List<ConcurrentHashMap<String, Object>> contents = dingTalkReqVo.getContents();
            ArrayList<OapiMessageCorpconversationAsyncsendV2Request.Form> objects = new ArrayList<>();
            for (int i = 0; i < contents.size(); i++) {
                ConcurrentHashMap<String, Object> map = contents.get(i);
        // ---- 组装消息体(所有收件人共用) ----
        // contents 中每行一个单键 map,用 List 顺序保证钉钉表单行序
        List<OapiMessageCorpconversationAsyncsendV2Request.Form> objects = new ArrayList<>();
        List<ConcurrentHashMap<String, Object>> contents = dingTalkReqVo.getContents();
        if (contents != null) {
            for (ConcurrentHashMap<String, Object> map : contents) {
                if (map == null) {
                    continue;
                }
                for (Map.Entry<String, Object> entry : map.entrySet()) {
                    Object value = entry.getValue();
                    if (value == null) {
                        continue;
                    }
                    OapiMessageCorpconversationAsyncsendV2Request.Form formSeverityDesc = new OapiMessageCorpconversationAsyncsendV2Request.Form();
                    formSeverityDesc.setKey(entry.getKey());
                    if (entry.getKey().contains("审批时间")) {
                        Date date = (Date) entry.getValue();
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                        String format = simpleDateFormat.format(date);
                        formSeverityDesc.setValue(format);
                    if (entry.getKey().contains("审批时间") && value instanceof Date) {
                        // 24 小时制:原实现用 hh(12 小时制且无 AM/PM),下午的时间会显示成 01:xx
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        formSeverityDesc.setValue(simpleDateFormat.format((Date) value));
                    } else {
                        if (entry.getValue() != null) {
                            formSeverityDesc.setValue(entry.getValue().toString());
                        }
                        formSeverityDesc.setValue(value.toString());
                    }
                    objects.add(formSeverityDesc);
                }
            }
        }
            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
            msg.setOa(new OapiMessageCorpconversationAsyncsendV2Request.OA());
            msg.getOa().setMessageUrl(dingTalkReqVo.getUrl());
            msg.getOa().setPcMessageUrl(dingTalkReqVo.getUrl());
            msg.getOa().setHead(new OapiMessageCorpconversationAsyncsendV2Request.Head());
            //   msg.getOa().getHead().setText("你在说啥呢绒顶替苛夺阿斯蒂芬阿斯蒂芬");
            msg.getOa().getHead().setBgcolor("00409eff");
            msg.getOa().setBody(new OapiMessageCorpconversationAsyncsendV2Request.Body());
            msg.getOa().getBody().setTitle(dingTalkReqVo.getTitle());
            // msg.getOa().getBody().setContent("待处理:" + "123" + "条");
            msg.getOa().getBody().setForm(objects);
            msg.setMsgtype("oa");
            request.setMsg(msg);
            //  log.info("获取发送通知消息体和获取发送通知人完成");
        // ---- 逐人发送;全部成功才算成功(原实现 result 写在循环内,只反映最后一个收件人) ----
        boolean allSuccess = true;
        for (String urid : userIdlist) {
            OapiMessageCorpconversationAsyncsendV2Response response = null;
            try {
                DingTalkClient client = newClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
                OapiMessageCorpconversationAsyncsendV2Request request = new OapiMessageCorpconversationAsyncsendV2Request();
                request.setUseridList(urid);
                request.setAgentId(agentId);
                request.setToAllUser(false);
                OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
                msg.setOa(new OapiMessageCorpconversationAsyncsendV2Request.OA());
                msg.getOa().setMessageUrl(dingTalkReqVo.getUrl());
                msg.getOa().setPcMessageUrl(dingTalkReqVo.getUrl());
                msg.getOa().setHead(new OapiMessageCorpconversationAsyncsendV2Request.Head());
                msg.getOa().getHead().setBgcolor("00409eff");
                msg.getOa().setBody(new OapiMessageCorpconversationAsyncsendV2Request.Body());
                msg.getOa().getBody().setTitle(dingTalkReqVo.getTitle());
                msg.getOa().getBody().setForm(objects);
                msg.setMsgtype("oa");
                request.setMsg(msg);
                response = client.execute(request, accessToken);
            } catch (ApiException e) {
                e.getErrMsg();
            } catch (Exception e) {
                log.error("钉钉消息发送异常,收件人={}", urid, e);
            }
            //  log.info("消息任务ID"+response.getTaskId());
            System.out.println(response.getTaskId());
            result = response.isSuccess();
            if (response == null || !response.isSuccess()) {
                allSuccess = false;
                log.error("钉钉消息发送失败,收件人={},响应={}", urid,
                        response == null ? "null" : response.getBody());
                // 失败可能是 access_token 失效,清掉缓存让下次重新获取
                clearAccessToken();
            } else {
                log.info("钉钉消息发送成功,收件人={},taskId={}", urid, response.getTaskId());
            }
        }
        return result;
        return allSuccess;
    }
    /**
@@ -193,7 +269,7 @@
            //根据传过来的部门ID,去获取该部门下的用户信息
            if (begin != 1) DingUserInfoList(deptID);
            //获取子部门的dept_id
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/listsub");
            DingTalkClient client = newClient("https://oapi.dingtalk.com/topapi/v2/department/listsub");
            OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest();
            req.setDeptId(deptID);
            req.setLanguage("zh_CN");
@@ -218,7 +294,7 @@
    public void DingUserInfoList(Long deptId) {
        DingTalkClient client = null;
        try {
            client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/list?access_token=" + getAccessToken());
            client = newClient("https://oapi.dingtalk.com/topapi/v2/user/list?access_token=" + getAccessToken());
            OapiV2UserListRequest req = new OapiV2UserListRequest();
            req.setDeptId(deptId);
            req.setCursor(0L);
@@ -248,19 +324,43 @@
    /**
     * 获取 access_token
     * 获取 access_token(带缓存,失效自动重取)
     */
    private String getAccessToken() throws ApiException {
        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
        OapiGettokenRequest request = new OapiGettokenRequest();
        //Appkey
        request.setAppkey(dingAppid);
        //Appsecret
        request.setAppsecret(dingAppSecret);
        /*请求方式*/
        request.setHttpMethod("GET");
        OapiGettokenResponse response = client.execute(request);
        return response.getAccessToken();
        String token = cachedAccessToken;
        if (StringUtils.isNotEmpty(token) && System.currentTimeMillis() < accessTokenExpireAt) {
            return token;
        }
        synchronized (this) {
            if (StringUtils.isNotEmpty(cachedAccessToken)
                    && System.currentTimeMillis() < accessTokenExpireAt) {
                return cachedAccessToken;
            }
            DefaultDingTalkClient client = newClient("https://oapi.dingtalk.com/gettoken");
            OapiGettokenRequest request = new OapiGettokenRequest();
            //Appkey
            request.setAppkey(dingAppid);
            //Appsecret
            request.setAppsecret(dingAppSecret);
            /*请求方式*/
            request.setHttpMethod("GET");
            OapiGettokenResponse response = client.execute(request);
            String newToken = response == null ? null : response.getAccessToken();
            if (StringUtils.isNotEmpty(newToken)) {
                cachedAccessToken = newToken;
                accessTokenExpireAt = System.currentTimeMillis() + ACCESS_TOKEN_TTL_MILLIS;
            }
            return newToken;
        }
    }
    /**
     * 清空 access_token 缓存。
     * 发送失败有可能是 token 失效(钉钉 errcode 40014/42001),清掉后下次发送会重新获取。
     */
    private void clearAccessToken() {
        cachedAccessToken = null;
        accessTokenExpireAt = 0L;
    }
}
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceDonatebaseinfoServiceImpl.java
@@ -236,6 +236,23 @@
            serviceDonatebaseinfo.setDeptid(user.getDeptId());
        }
        List<ServiceDonatebaseinfo> serviceDonatebaseinfos = serviceDonatebaseinfoMapper.selectServiceDonatebaseinfoList(serviceDonatebaseinfo);
        // 一次性取出各捐献基础对应的死亡时间,避免在循环里按 InfoID 逐条查见证表(N+1)
        List<Long> infoIds = new ArrayList<>();
        for (ServiceDonatebaseinfo item : serviceDonatebaseinfos) {
            if (item.getId() != null) {
                infoIds.add(item.getId());
            }
        }
        Map<Long, Date> deathTimeMap = new HashMap<>();
        if (!infoIds.isEmpty()) {
            for (ServiceDonationwitness witness : serviceDonationwitnessMapper.selectDeathTimeByInfoIds(infoIds)) {
                if (witness.getInfoid() != null && !deathTimeMap.containsKey(witness.getInfoid())) {
                    deathTimeMap.put(witness.getInfoid(), witness.getDeathtime());
                }
            }
        }
        for (ServiceDonatebaseinfo serviceDonatebaseinfo1 : serviceDonatebaseinfos) {
            //先给个年龄默认值(因为age是数字类型,不能放横杠,所以就所横扛放到单位上)
            serviceDonatebaseinfo1.setAgeunit("-");
@@ -243,22 +260,17 @@
            serviceDonatebaseinfo1.setAge2(null);
            //获取死亡时间,如果获取见证里有死亡时间,则进行计算他的年龄
            ServiceDonationwitness sdw = new ServiceDonationwitness();
            sdw.setInfoid(serviceDonatebaseinfo1.getId());
            List<ServiceDonationwitness> serviceDonationwitnessList = serviceDonationwitnessMapper.selectServiceDonationwitnessList(sdw);
            if (!CollectionUtils.isEmpty(serviceDonationwitnessList) && serviceDonatebaseinfo1.getBirthday() != null) {
                Date deathtime = serviceDonationwitnessList.get(0).getDeathtime();
                if (ObjectUtils.isNotEmpty(deathtime)) {
                    Map<String, String> map = CalculateDateUtils.calculateAge(serviceDonatebaseinfo1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                    if (Objects.isNull(map)) {
                        log.error("ServiceDonationwitness出生日期:{},ServiceDonationwitness死亡日期:{}", serviceDonatebaseinfo1.getBirthday(), deathtime);
                        continue;
                    }
                    serviceDonatebaseinfo1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                    serviceDonatebaseinfo1.setAgeunit(map.get("ageUnit"));
                    serviceDonatebaseinfo1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                    serviceDonatebaseinfo1.setAgeunit2(map.get("ageUnit2"));
            Date deathtime = deathTimeMap.get(serviceDonatebaseinfo1.getId());
            if (ObjectUtils.isNotEmpty(deathtime) && serviceDonatebaseinfo1.getBirthday() != null) {
                Map<String, String> map = CalculateDateUtils.calculateAge(serviceDonatebaseinfo1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                if (Objects.isNull(map)) {
                    log.error("ServiceDonationwitness出生日期:{},ServiceDonationwitness死亡日期:{}", serviceDonatebaseinfo1.getBirthday(), deathtime);
                    continue;
                }
                serviceDonatebaseinfo1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                serviceDonatebaseinfo1.setAgeunit(map.get("ageUnit"));
                serviceDonatebaseinfo1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                serviceDonatebaseinfo1.setAgeunit2(map.get("ageUnit2"));
            }
        }
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceDonatecompletioninfoServiceImpl.java
@@ -25,7 +25,9 @@
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -132,6 +134,22 @@
        }
        List<DonationCompletionVO> donationCompletionVOS = serviceDonatecompletioninfoMapper.selectVOList(donationCompletionVO);
        // 一次性取出各捐献基础对应的死亡时间,避免在循环里按 InfoID 逐条查见证表(N+1)
        List<Long> infoIds = new ArrayList<>();
        for (DonationCompletionVO item : donationCompletionVOS) {
            if (item.getInfoid() != null) {
                infoIds.add(item.getInfoid());
            }
        }
        Map<Long, Date> deathTimeMap = new HashMap<>();
        if (!infoIds.isEmpty()) {
            for (ServiceDonationwitness witness : serviceDonationwitnessMapper.selectDeathTimeByInfoIds(infoIds)) {
                if (witness.getInfoid() != null && !deathTimeMap.containsKey(witness.getInfoid())) {
                    deathTimeMap.put(witness.getInfoid(), witness.getDeathtime());
                }
            }
        }
        for (DonationCompletionVO donationCompletionVO1 : donationCompletionVOS) {
//            if (donationCompletionVO1.getBirthday() != null) {
//                Map<String, String> map = CalculateDateUtils.calculateAge(donationCompletionVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), LocalDate.now());
@@ -146,23 +164,18 @@
            donationCompletionVO1.setAge(null);
            donationCompletionVO1.setAge2(null);
            //获取死亡时间,如果获取见证里有死亡时间,则进行计算他的年龄
            ServiceDonationwitness sdw = new ServiceDonationwitness();
            sdw.setInfoid(donationCompletionVO1.getInfoid());
            List<ServiceDonationwitness> serviceDonationwitnessList = serviceDonationwitnessMapper.selectServiceDonationwitnessList(sdw);
            if (!org.springframework.util.CollectionUtils.isEmpty(serviceDonationwitnessList) && donationCompletionVO1.getBirthday() != null) {
                Date deathtime = serviceDonationwitnessList.get(0).getDeathtime();
                if (ObjectUtils.isNotEmpty(deathtime)) {
                    Map<String, String> map = CalculateDateUtils.calculateAge(donationCompletionVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                    if (ObjectUtils.isEmpty(map)) {
                        log.error("DonationCompletionVO出生日期:{},DonationCompletionVO死亡日期:{}", donationCompletionVO1.getBirthday(), deathtime);
                        continue;
                    }
                    donationCompletionVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                    donationCompletionVO1.setAgeunit(map.get("ageUnit"));
                    donationCompletionVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                    donationCompletionVO1.setAgeunit2(map.get("ageUnit2"));
            Date deathtime = deathTimeMap.get(donationCompletionVO1.getInfoid());
            if (ObjectUtils.isNotEmpty(deathtime) && donationCompletionVO1.getBirthday() != null) {
                Map<String, String> map = CalculateDateUtils.calculateAge(donationCompletionVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                if (ObjectUtils.isEmpty(map)) {
                    log.error("DonationCompletionVO出生日期:{},DonationCompletionVO死亡日期:{}", donationCompletionVO1.getBirthday(), deathtime);
                    continue;
                }
                donationCompletionVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                donationCompletionVO1.setAgeunit(map.get("ageUnit"));
                donationCompletionVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                donationCompletionVO1.setAgeunit2(map.get("ageUnit2"));
            }
        }
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceDonationwitnessServiceImpl.java
@@ -26,6 +26,7 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.ruoyi.common.utils.StringUtils;
@@ -95,28 +96,40 @@
            donationWitnessVO.setReporterno(user.getUserName());
        }
        List<DonationWitnessVO> donationWitnessVOS = serviceDonationwitnessMapper.selectVOList(donationWitnessVO);
        // 一次性取出各捐献基础对应的死亡时间,避免在循环里按 InfoID 逐条查见证表(N+1)
        List<Long> infoIds = new ArrayList<>();
        for (DonationWitnessVO item : donationWitnessVOS) {
            if (item.getInfoid() != null) {
                infoIds.add(item.getInfoid());
            }
        }
        Map<Long, Date> deathTimeMap = new HashMap<>();
        if (!infoIds.isEmpty()) {
            for (ServiceDonationwitness witness : serviceDonationwitnessMapper.selectDeathTimeByInfoIds(infoIds)) {
                if (witness.getInfoid() != null && !deathTimeMap.containsKey(witness.getInfoid())) {
                    deathTimeMap.put(witness.getInfoid(), witness.getDeathtime());
                }
            }
        }
        for (DonationWitnessVO donationWitnessVO1 : donationWitnessVOS) {
            //先给个年龄默认值(因为age是数字类型,不能放横杠,所以就所横扛放到单位上)
            donationWitnessVO1.setAgeunit("-");
            //获取死亡时间,如果获取见证里有死亡时间,则进行计算他的年龄
            ServiceDonationwitness sdw = new ServiceDonationwitness();
            sdw.setInfoid(donationWitnessVO1.getInfoid());
            List<ServiceDonationwitness> serviceDonationwitnessList = serviceDonationwitnessMapper.selectServiceDonationwitnessList(sdw);
            if (!CollectionUtils.isEmpty(serviceDonationwitnessList) && donationWitnessVO1.getBirthday() != null) {
                Date deathtime = serviceDonationwitnessList.get(0).getDeathtime();
                if (ObjectUtils.isNotEmpty(deathtime)) {
                    Map<String, String> map = CalculateDateUtils.calculateAge(donationWitnessVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                    if (ObjectUtils.isEmpty(map)) {
                        log.error("DonationWitnessVO出生日期:{},DonationWitnessVO死亡日期:{}", donationWitnessVO1.getBirthday(), deathtime);
                        continue;
                    }
                    donationWitnessVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                    donationWitnessVO1.setAgeunit(map.get("ageUnit"));
                    donationWitnessVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                    donationWitnessVO1.setAgeunit2(map.get("ageUnit2"));
            Date deathtime = deathTimeMap.get(donationWitnessVO1.getInfoid());
            if (ObjectUtils.isNotEmpty(deathtime) && donationWitnessVO1.getBirthday() != null) {
                Map<String, String> map = CalculateDateUtils.calculateAge(donationWitnessVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                if (ObjectUtils.isEmpty(map)) {
                    log.error("DonationWitnessVO出生日期:{},DonationWitnessVO死亡日期:{}", donationWitnessVO1.getBirthday(), deathtime);
                    continue;
                }
                donationWitnessVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                donationWitnessVO1.setAgeunit(map.get("ageUnit"));
                donationWitnessVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                donationWitnessVO1.setAgeunit2(map.get("ageUnit2"));
            }
        }
        return donationWitnessVOS;
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceEthicalreviewopinionsServiceImpl.java
@@ -23,6 +23,7 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.ruoyi.common.utils.StringUtils;
@@ -99,28 +100,39 @@
        }
        List<EthicalReviewVO> ethicalReviewVOS = serviceEthicalreviewopinionsMapper.selectVOList(ethicalReviewVO);
        // 一次性取出各捐献基础对应的死亡时间,避免在循环里按捐献基础ID逐条查见证表(N+1)
        List<Long> infoIds = new ArrayList<>();
        for (EthicalReviewVO item : ethicalReviewVOS) {
            if (item.getId() != null) {
                infoIds.add(item.getId());
            }
        }
        Map<Long, Date> deathTimeMap = new HashMap<>();
        if (!infoIds.isEmpty()) {
            for (ServiceDonationwitness witness : serviceDonationwitnessMapper.selectDeathTimeByInfoIds(infoIds)) {
                if (witness.getInfoid() != null && !deathTimeMap.containsKey(witness.getInfoid())) {
                    deathTimeMap.put(witness.getInfoid(), witness.getDeathtime());
                }
            }
        }
        for (EthicalReviewVO ethicalReviewVO1 : ethicalReviewVOS) {
            //先给个年龄默认值(因为age是数字类型,不能放横杠,所以就所横扛放到单位上)
            ethicalReviewVO1.setAgeunit("-");
            ethicalReviewVO1.setAge(null);
            ethicalReviewVO1.setAge2(null);
            //获取死亡时间,如果获取见证里有死亡时间,则进行计算他的年龄
            ServiceDonationwitness sdw = new ServiceDonationwitness();
            sdw.setInfoid(ethicalReviewVO1.getId());
            List<ServiceDonationwitness> serviceDonationwitnessList = serviceDonationwitnessMapper.selectServiceDonationwitnessList(sdw);
            if (!CollectionUtils.isEmpty(serviceDonationwitnessList) && ethicalReviewVO1.getBirthday() != null) {
                Date deathtime = serviceDonationwitnessList.get(0).getDeathtime();
                if (ObjectUtils.isNotEmpty(deathtime)) {
                    Map<String, String> map = CalculateDateUtils.calculateAge(ethicalReviewVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                    if (ObjectUtils.isEmpty(map)) {
                        log.error("EthicalReviewVO出生日期:{},EthicalReviewVO死亡日期:{}", ethicalReviewVO1.getBirthday(), deathtime);
                        continue;
                    }
                    ethicalReviewVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                    ethicalReviewVO1.setAgeunit(map.get("ageUnit"));
                    ethicalReviewVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                    ethicalReviewVO1.setAgeunit2(map.get("ageUnit2"));
            Date deathtime = deathTimeMap.get(ethicalReviewVO1.getId());
            if (ObjectUtils.isNotEmpty(deathtime) && ethicalReviewVO1.getBirthday() != null) {
                Map<String, String> map = CalculateDateUtils.calculateAge(ethicalReviewVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                if (ObjectUtils.isEmpty(map)) {
                    log.error("EthicalReviewVO出生日期:{},EthicalReviewVO死亡日期:{}", ethicalReviewVO1.getBirthday(), deathtime);
                    continue;
                }
                ethicalReviewVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                ethicalReviewVO1.setAgeunit(map.get("ageUnit"));
                ethicalReviewVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                ethicalReviewVO1.setAgeunit2(map.get("ageUnit2"));
            }
        }
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceExternalpersonServiceImpl.java
@@ -49,7 +49,8 @@
            wrappers.like(ServiceExternalperson::getUsername, serviceExternalperson.getUsername());
        }
        if (StringUtils.isNotBlank(serviceExternalperson.getUsertype())) {
            wrappers.eq(ServiceExternalperson::getUsertype, serviceExternalperson.getUsertype());
            // UserType 自 2026-09-27 起为多值字段(逗号分隔),按"包含"匹配
            wrappers.apply("FIND_IN_SET({0}, UserType)", serviceExternalperson.getUsertype());
        }
        if (StringUtils.isNotBlank(serviceExternalperson.getUnitname())) {
            wrappers.like(ServiceExternalperson::getUnitname, serviceExternalperson.getUnitname());
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceFundServiceImpl.java
@@ -16,11 +16,13 @@
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.ApplyTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.HttpClientKit;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.DtoConversionUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.project.common.FundNotifyMessageBuilder;
import com.ruoyi.project.common.IdGeneratorUtils;
import com.ruoyi.project.domain.*;
import com.ruoyi.project.domain.vo.*;
@@ -28,6 +30,7 @@
import com.ruoyi.project.service.*;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.mapper.SysUserRoleMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
@@ -61,6 +64,10 @@
public class ServiceFundServiceImpl extends ServiceImpl<ServiceFundMapper, ServiceFund> implements IServiceFundService {
    @Autowired
    ServiceFundMapper serviceFundMapper;
    /** 专家劳务费 9 条费用控制规则校验(同一案例累计) */
    @Autowired
    private ExpertFeeValidateService expertFeeValidateService;
    @Autowired
    private DingTalkService dingTalkService;
@@ -137,6 +144,35 @@
    @Value("${spring.profiles.active}")
    private String active;
    @Autowired
    private ISysConfigService sysConfigService;
    /**
     * 审批单是否上传医院协友(致远)系统:0=不上传(测试环境),1=上传(正式环境)。
     *
     * <p>2026-10-09 新增:原来用 {@code spring.profiles.active == druid} 判断是否推送,
     * 测试环境与正式环境无法通过参数区分。改为系统参数控制。
     */
    private static final String CFG_UPLOAD_OA = "sys.approval.upload.oa";
    /** 参数缺省值:上传(保持正式环境原有行为) */
    private static final boolean DEFAULT_UPLOAD_OA = true;
    /** 审批单是否需要上传医院协友(致远)系统 */
    private boolean isUploadOaEnabled() {
        String value = null;
        try {
            value = sysConfigService.selectConfigByKey(CFG_UPLOAD_OA);
        } catch (Exception e) {
            log.warn("读取参数 {} 失败,按默认值 {} 处理", CFG_UPLOAD_OA, DEFAULT_UPLOAD_OA, e);
        }
        if (value == null || value.trim().isEmpty()) {
            return DEFAULT_UPLOAD_OA;
        }
        String v = value.trim();
        return "1".equals(v) || "true".equalsIgnoreCase(v) || "y".equalsIgnoreCase(v) || "on".equalsIgnoreCase(v);
    }
    /**
     * 致远系统流程退回
     *
@@ -199,6 +235,12 @@
        String phone = null;
        String applytype = null;
        Long flowlevel = null;
        //钉钉通知用的单据信息
        Double amount = null;
        String donorName = null;
        String billNo = null;
        String travelers = null;
        String reason = null;
        if (serviceProcessVO.getApplytype().equals("0")) {
            ServiceReimbursement serviceReimbursement = new ServiceReimbursement();
            serviceReimbursement.setId(StringUtils.isNotEmpty(serviceProcessVO.getBackCode()) ? Long.valueOf(serviceProcessVO.getBackCode()) : null);
@@ -213,6 +255,11 @@
            phone = serviceReimbursementList.get(0).getPhone();
            flowlevel = serviceReimbursementList.get(0).getFlowlevel();
            applytype = "0";
            amount = serviceReimbursementList.get(0).getAmountrequested();
            billNo = FundNotifyMessageBuilder.billNo(serviceReimbursementList.get(0).getBh(),
                    serviceReimbursementList.get(0).getId());
            travelers = serviceReimbursementList.get(0).getTravelers();
            reason = serviceReimbursementList.get(0).getReason();
        } else {
            ServiceFund serviceFund = new ServiceFund();
            serviceFund.setId(StringUtils.isNotEmpty(serviceProcessVO.getBackCode()) ? Long.valueOf(serviceProcessVO.getBackCode()) : null);
@@ -226,6 +273,10 @@
            phone = serviceFunds.get(0).getPhone();
            flowlevel = serviceFunds.get(0).getFlowlevel();
            applytype = serviceFunds.get(0).getApplytype();
            amount = serviceFunds.get(0).getPretaxcost();
            donorName = serviceFunds.get(0).getDonorname();
            billNo = FundNotifyMessageBuilder.billNo(serviceFunds.get(0).getBh(),
                    serviceFunds.get(0).getId());
        }
//        //将操作保存到fundflow表中
@@ -248,7 +299,16 @@
        SysUser sysUser = sysUserService.selectUserByUserName(userNo);
        sendMeg(serviceProcessVO, phone);
        //钉钉通知内容:单据信息(类型/报销人/金额/案例/编号)+ 审批人/审批时间/意见
        List<ConcurrentHashMap<String, Object>> notifyRows = FundNotifyMessageBuilder.newRows();
        FundNotifyMessageBuilder.addBillInfo(notifyRows, applytype, userName, amount, donorName, billNo);
        FundNotifyMessageBuilder.addTravelInfo(notifyRows, travelers, reason);
        FundNotifyMessageBuilder.addAuditResult(notifyRows, serviceProcessVO.getCheckusername(),
                serviceProcessVO.getCheckTime(),
                FundNotifyMessageBuilder.opinion(serviceProcessVO.getFlowconclusion(),
                        serviceProcessVO.getFlowcontent()));
        sendMeg(serviceProcessVO, phone, notifyRows);
        //发送信息保存
        ServiceSystemmessage serviceSystemmessage = new ServiceSystemmessage();
        serviceSystemmessage.setFundtype(1);
@@ -260,7 +320,7 @@
        serviceSystemmessage.setUpdateBy(sysUser.getNickName());
        serviceSystemmessage.setIsread(0);
        serviceSystemmessage.setMessagetype(1);
        serviceSystemmessage.setMessagetitle("财务系统信息");
        serviceSystemmessage.setMessagetitle("OPO系统审批");
        serviceSystemmessage.setMessagecontent(serviceProcessVO.getFlowcontent());
        iServiceSystemmessageService.save(serviceSystemmessage);
@@ -374,6 +434,7 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> checkFund(CheckFundVO checkFundVO, LoginUser loginUser) {
        ServiceFund serviceFund = getById(checkFundVO.getFundid());
        Map<String, String> map = new HashMap<>();
@@ -381,6 +442,19 @@
        if (serviceFund == null) {
            map.put(HttpStatus.NO_CONTENT + "", "费用编号不正确");
            return map;
        }
        // 专家劳务费:审核“通过”前校验 9 条费用控制规则(同一案例累计);不通过则不阻断
        if (checkFundVO.getFlowconclusion() != null && checkFundVO.getFlowconclusion() == 1) {
            ExpertFeeCheckResult expertFeeResult = expertFeeValidateService.validateBeforeCheck(checkFundVO.getFundid());
            if (expertFeeResult.hasError()) {
                map.put(HttpStatus.ERROR + "", expertFeeResult.errorMessage());
                return map;
            }
            if (expertFeeResult.hasWarning()) {
                // 规则 5、6 仅提示,不阻断审核
                log.warn("专家劳务费审核校验提示:fundId={},{}", checkFundVO.getFundid(), expertFeeResult.warningMessage());
            }
        }
        Integer TotalLevel = 0;
@@ -409,6 +483,11 @@
        Integer RecordStatus = serviceFund.getRecordstatus();
        Integer FlowLevel = serviceFund.getFlowlevel().intValue();
        Integer OriginalFlowLevel = FlowLevel;
        //审批结论必填:原实现直接 == 1 拆箱,接口被直调传 null 会 NPE 500
        if (checkFundVO.getFlowconclusion() == null) {
            map.put(HttpStatus.ERROR + "", "审批结论不能为空");
            return map;
        }
        if (checkFundVO.getFlowconclusion() == 1) {
            CheckFlag = 1;
            if (TotalLevel == 0) {
@@ -437,8 +516,9 @@
                if (newSwitch == 1) {
                    aBoolean1 = sendData(serviceFund);
                    if (!aBoolean1) {
                        map.put("500", "发送失败");
                        return map;
                        // 推送医院协友系统失败:整个审批不生效(事务回滚),允许下次重新审批
                        log.error("费用审批单上传医院协友系统失败,审批不生效,fundId={},请重新审批", serviceFund.getId());
                        throw new ServiceException("医院协友系统接收失败,本次审批未生效,请稍后重新审批");
                    }
                }
                //保存审批流程表
@@ -481,21 +561,22 @@
            }
            SysUser sysUser = sysUserService.selectUserByUserName(serviceFund.getUserno());
            ArrayList<ConcurrentHashMap<String, Object>> contentList = new ArrayList<>();
            ConcurrentHashMap cmap = new ConcurrentHashMap();
            cmap.put("审批人:", loginUser.getUser().getUserName());
            cmap.put("审批时间:", new Date());
            cmap.put("意见:", "不通过");
            if (StringUtils.isNotBlank(checkFundVO.getFlowcontent())) cmap.put("意见:", checkFundVO.getFlowcontent());
            contentList.add(cmap);
            try{
            DingTalkReqVo dingTalkReqVo = new DingTalkReqVo();
            dingTalkReqVo.setTitle("财务系统驳回信息");
            dingTalkReqVo.setNumber(sysUser.getPhonenumber());
            dingTalkReqVo.setContents(contentList);
            dingTalkService.sendNotification(dingTalkReqVo);
            }catch (Exception e){
                log.error("费用---财务系统驳回信息报错了", e);
            //审批未通过:单据信息 + 审批人/审批时间/意见(意见 = "审批未通过。" + 驳回意见)
            List<ConcurrentHashMap<String, Object>> contentList = FundNotifyMessageBuilder.newRows();
            FundNotifyMessageBuilder.addBillInfo(contentList, serviceFund.getApplytype(),
                    serviceFund.getUsername(), serviceFund.getPretaxcost(), serviceFund.getDonorname(),
                    FundNotifyMessageBuilder.billNo(serviceFund.getBh(), serviceFund.getId()));
            FundNotifyMessageBuilder.addAuditResult(contentList, loginUser.getUser().getNickName(), new Date(),
                    FundNotifyMessageBuilder.opinion(FundNotifyMessageBuilder.CONCLUSION_REJECT,
                            checkFundVO.getFlowcontent()));
            try {
                DingTalkReqVo dingTalkReqVo = new DingTalkReqVo();
                dingTalkReqVo.setTitle(FundNotifyMessageBuilder.TITLE);
                dingTalkReqVo.setNumber(sysUser.getPhonenumber());
                dingTalkReqVo.setContents(contentList);
                dingTalkService.sendNotification(dingTalkReqVo);
            } catch (Exception e) {
                log.error("费用---审批未通过钉钉通知失败,fundId={}", serviceFund.getId(), e);
            }
        }
@@ -538,8 +619,9 @@
                //搞一个开关,别影响老流程
                aBoolean1 = sendData(serviceFund);
                if (!aBoolean1) {
                    map.put("500", "发送失败");
                    return map;
                    // 推送医院协友系统失败:整个审批不生效(事务回滚),允许下次重新审批
                    log.error("费用审批单上传医院协友系统失败,审批不生效,fundId={},请重新审批", serviceFund.getId());
                    throw new ServiceException("医院协友系统接收失败,本次审批未生效,请稍后重新审批");
                }
            }
        }
@@ -603,7 +685,30 @@
    }
    /**
     * 审批单上传医院协友(致远)系统。
     *
     * <p>2026-10-09:外层包一层兜底,保证**任何异常都不会向外抛**(原来 strRes 为 null 时
     * 会在 parseObject 后取 code 触发 NPE,把整个审批请求打成 500)。
     * 返回 false 表示推送失败,调用方按"审批不生效、允许下次重新审批"处理。
     */
    public Boolean sendData(ServiceFund serviceFund) {
        if (!isUploadOaEnabled()) {
            // 上传开关关闭(如测试环境):跳过上传并视为成功,绝不能因此阻断审批
            log.info("审批单上传医院协友系统已关闭(sys.approval.upload.oa != 1),跳过上传并视为成功,fundId={}",
                    serviceFund == null ? null : serviceFund.getId());
            return true;
        }
        try {
            return doSendData(serviceFund);
        } catch (Throwable t) {
            log.error("费用审批单上传医院协友系统异常,审批不生效,fundId={}",
                    serviceFund == null ? null : serviceFund.getId(), t);
            return false;
        }
    }
    private Boolean doSendData(ServiceFund serviceFund) {
        // fund和funddetail的数据获取
        addFundSharedInfo(serviceFund.getId());
        List<ServiceFundShared> fundShareInfos = serviceFundSharedMapper.getFundShareInfoById(serviceFund.getId());
@@ -644,7 +749,7 @@
        map.put("data", data);
        String strRes = null;
        if (!active.equals("druid")) {
        if (isUploadOaEnabled()) {
            //如果是本地环境,就不调这个了
            String token = getToken();
            JSONObject json1 = JSONObject.parseObject(token);
@@ -684,9 +789,28 @@
            }
        }
        log.error("------------------strRes的值为:{}", strRes);
        JSONObject jsonObject = JSONObject.parseObject(strRes);
        if (strRes == null || strRes.trim().isEmpty()) {
            // 医院协友系统无返回(超时/异常/未配置):推送失败,由调用方回滚整个审批
            log.error("费用审批单上传医院协友系统失败:接口无返回内容,fundId={}", serviceFund.getId());
            return false;
        }
        JSONObject jsonObject = null;
        try {
            jsonObject = JSONObject.parseObject(strRes);
        } catch (Exception e) {
            log.error("费用审批单上传医院协友系统失败:返回内容无法解析,fundId={},strRes={}",
                    serviceFund.getId(), strRes, e);
            return false;
        }
        if (jsonObject == null) {
            log.error("费用审批单上传医院协友系统失败:返回内容为空,fundId={},strRes={}",
                    serviceFund.getId(), strRes);
            return false;
        }
        String code = jsonObject.getString("code");
        if (!code.equals("0")) {
        if (code == null || !"0".equals(code)) {
            log.error("费用审批单上传医院协友系统失败:code={},fundId={},strRes={}",
                    code, serviceFund.getId(), strRes);
            return false;
        }
@@ -885,7 +1009,7 @@
            serviceFunddetailShareds.get(i).setId(null);
            //数据发送给财务
            try {
                if (!active.equals("druid")) uploadOAFileAndUpdateDb(serviceFunddetailShareds.get(i));
                if (isUploadOaEnabled()) uploadOAFileAndUpdateDb(serviceFunddetailShareds.get(i));
            } catch (Exception e) {
                e.printStackTrace();
                log.error("Exception中入参的ID为:{},异常信息为 : {}", id, e.getMessage());
@@ -1421,8 +1545,23 @@
    }
    @Override
    public ExpertFeeCheckResult validateExpertFee(ServiceFundVO serviceFundVO) {
        return expertFeeValidateService.validateBeforeSave(serviceFundVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addOrUpdateNew(ServiceFundVO serviceFundVO) {
        // 专家劳务费:保存前校验 9 条费用控制规则(同一案例累计)
        ExpertFeeCheckResult expertFeeResult = expertFeeValidateService.validateBeforeSave(serviceFundVO);
        if (expertFeeResult.hasError()) {
            throw new BaseException(expertFeeResult.errorMessage());
        }
        if (expertFeeResult.hasWarning()) {
            // 规则 5、6 仅提示,不阻断保存;前端单独取用提示文案
            log.warn("专家劳务费保存校验提示:{}", expertFeeResult.warningMessage());
        }
        Long id = null;
        //将数据保存到fund表
        serviceFundVO.setUploadStates(0);
@@ -1484,10 +1623,9 @@
            return null;
        }
        //总税前
        Double pretaxcosts = 0.00;
        //总税后
        Double taxedcosts = 0.00;
        //总税前、总税后(金额累加用 BigDecimal,避免浮点尾差)
        BigDecimal pretaxcosts = BigDecimal.ZERO;
        BigDecimal taxedcosts = BigDecimal.ZERO;
        for (ServiceFunddetailVO serviceFunddetailVO : serviceFunddetails) {
            serviceFunddetailVO.setFundid(id);
@@ -1496,13 +1634,18 @@
            if (!performance.isEmpty()) {
                Double amount = performance.get("amount");
                Double taxedamount = performance.get("taxedamount");
                pretaxcosts = pretaxcosts + amount;
                taxedcosts = taxedcosts + taxedamount;
                if (amount != null) {
                    pretaxcosts = pretaxcosts.add(BigDecimal.valueOf(amount));
                }
                if (taxedamount != null) {
                    taxedcosts = taxedcosts.add(BigDecimal.valueOf(taxedamount));
                }
            }
            serviceFundVO.setPretaxcost(pretaxcosts);
            serviceFundVO.setTaxedcost(taxedcosts);
            updateById(serviceFundVO);
        }
        // 合计在循环结束后统一回写,原来写在循环内会每行都 UPDATE 一次,且中间值被反复覆盖
        serviceFundVO.setPretaxcost(pretaxcosts.doubleValue());
        serviceFundVO.setTaxedcost(taxedcosts.doubleValue());
        updateById(serviceFundVO);
        Map map = new HashMap();
        map.put("fundId", id);
        return map;
@@ -1700,7 +1843,7 @@
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("userName", "opo");
        map.put("password", "4126407a-9821-4874-be41-6568abd6dbe5");
        map.put("loginName", "OPO系统");
        map.put("loginName", "OPO系统审批");
        JSONObject jsonObj = new JSONObject(map);
@@ -1819,7 +1962,8 @@
    }
    public void sendMeg(ServiceProcessVO serviceProcessVO, String phone) {
    public void sendMeg(ServiceProcessVO serviceProcessVO, String phone,
                        List<ConcurrentHashMap<String, Object>> contentList) {
        //记录操作记录
        ServiceFundflow serviceFundflow = new ServiceFundflow();
@@ -1838,20 +1982,14 @@
        serviceFundflow.setFundid(StringUtils.isNotEmpty(serviceProcessVO.getBackCode()) ? Long.valueOf(serviceProcessVO.getBackCode()) : null);
        fundflowService.save(serviceFundflow);
        try {
            ArrayList<ConcurrentHashMap<String, Object>> contentList = new ArrayList<>();
            ConcurrentHashMap map = new ConcurrentHashMap();
            map.put("审批人:", serviceProcessVO.getCheckusername());
            map.put("审批时间:", serviceProcessVO.getCheckTime());
            map.put("意见:", serviceProcessVO.getFlowcontent());
            contentList.add(map);
            DingTalkReqVo dingTalkReqVo = new DingTalkReqVo();
            dingTalkReqVo.setTitle("财务系统信息");
            dingTalkReqVo.setTitle(FundNotifyMessageBuilder.TITLE);
            dingTalkReqVo.setNumber(phone);
            dingTalkReqVo.setContents(contentList);
            log.info("钉钉发送消息:{}", dingTalkReqVo);
            dingTalkService.sendNotification(dingTalkReqVo);
        } catch (Exception e) {
            log.error("财务系统信息---钉钉消息发送失败,不影响主流程", e);
            log.error("报销审批钉钉消息发送失败,不影响主流程", e);
        }
    }
}
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceFunddetailServiceImpl.java
@@ -136,21 +136,30 @@
     */
    @Override
    public Double getTaxByBeneFiciaryNo(TaxMoneyVO taxMoneyVo) {
        Double sum = 0.00;
        Double totalScore = 0.00;
        // 金额累加必须用 BigDecimal:double 累加会引入浮点尾差,导致算出的个税出现分以下误差
        BigDecimal sum = BigDecimal.ZERO;
        BigDecimal totalScore = BigDecimal.ZERO;
        List<ServiceFunddetail> taxationByBeneFiciaryNo = serviceFunddetailMapper.getTaxBeforeByBeneFiciaryNo(taxMoneyVo);
        if (CollectionUtils.isNotEmpty(taxationByBeneFiciaryNo)) {
            //计算申请金额总和,不包括当前传进来的
            sum = taxationByBeneFiciaryNo.stream().mapToDouble(ServiceFunddetail::getAmount).sum();
            sum = taxationByBeneFiciaryNo.stream()
                    .map(ServiceFunddetail::getAmount)
                    .filter(java.util.Objects::nonNull)
                    .map(BigDecimal::valueOf)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            //不加本次薪酬的个税
            totalScore = taxationByBeneFiciaryNo.stream().mapToDouble(ServiceFunddetail::getTaxamount).sum();
            totalScore = taxationByBeneFiciaryNo.stream()
                    .map(ServiceFunddetail::getTaxamount)
                    .filter(java.util.Objects::nonNull)
                    .map(BigDecimal::valueOf)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        sum = sum + new Double(taxMoneyVo.getMoney());
        sum = sum.add(new BigDecimal(taxMoneyVo.getMoney()));
        //加上本次薪酬的总个税
        String taxation = TaxtUtils.getTaxation(new BigDecimal(sum));
        String taxation = TaxtUtils.getTaxation(sum);
        Double tax = Double.valueOf(taxation) - totalScore;
        Double tax = new BigDecimal(taxation).subtract(totalScore).doubleValue();
        return tax;
    }
@@ -163,21 +172,30 @@
    @Override
    public TaxedMoneyVO getTaxBeforeByAfterMoney(TaxMoneyVO taxMoneyVo) {
        TaxedMoneyVO taxedMoneyVO = new TaxedMoneyVO();
        Double sum = 0.00;
        Double totalScore = 0.00;
        // 金额累加必须用 BigDecimal,避免浮点尾差影响税前/税额结果
        BigDecimal sum = BigDecimal.ZERO;
        BigDecimal totalScore = BigDecimal.ZERO;
        List<ServiceFunddetail> taxationByBeneFiciaryNo = serviceFunddetailMapper.getTaxBeforeByBeneFiciaryNo(taxMoneyVo);
        if (CollectionUtils.isNotEmpty(taxationByBeneFiciaryNo)) {
            //计算出税后总金额(不包含本次)
            sum = taxationByBeneFiciaryNo.stream().mapToDouble(ServiceFunddetail::getTaxedamount).sum();
            sum = taxationByBeneFiciaryNo.stream()
                    .map(ServiceFunddetail::getTaxedamount)
                    .filter(java.util.Objects::nonNull)
                    .map(BigDecimal::valueOf)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            //求出之前的总的个税
            totalScore = taxationByBeneFiciaryNo.stream().mapToDouble(ServiceFunddetail::getTaxamount).sum();
            totalScore = taxationByBeneFiciaryNo.stream()
                    .map(ServiceFunddetail::getTaxamount)
                    .filter(java.util.Objects::nonNull)
                    .map(BigDecimal::valueOf)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        //计算出总的税后薪酬(包含本次)
        sum = sum + Double.valueOf(taxMoneyVo.getMoney());
        sum = sum.add(new BigDecimal(taxMoneyVo.getMoney()));
        //税前总金额
        String taxationBefore = TaxtUtils.getTaxationBefore(new BigDecimal(sum));
        String taxationBefore = TaxtUtils.getTaxationBefore(sum);
        //总个税
        BigDecimal tax = new BigDecimal(taxationBefore).subtract(new BigDecimal(sum));
        BigDecimal tax = new BigDecimal(taxationBefore).subtract(sum);
        //本次的税前金额
        String nowTaxMoney = TaxtUtils.getTaxationBefore(new BigDecimal(taxMoneyVo.getMoney()));
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceReimbursementServiceImpl.java
@@ -17,6 +17,8 @@
import com.ruoyi.common.utils.HttpClientKit;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.DtoConversionUtils;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.project.common.FundNotifyMessageBuilder;
import com.ruoyi.project.common.IdGeneratorUtils;
import com.ruoyi.project.domain.*;
import com.ruoyi.project.domain.dto.ServiceReimbursementDto;
@@ -27,6 +29,7 @@
import com.ruoyi.project.service.*;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.mapper.SysUserRoleMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysPostService;
import com.ruoyi.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
@@ -116,6 +119,35 @@
    @Value("${spring.profiles.active}")
    private String active;
    @Autowired
    private ISysConfigService sysConfigService;
    /**
     * 审批单是否上传医院协友(致远)系统:0=不上传(测试环境),1=上传(正式环境)。
     *
     * <p>2026-10-09 新增:原来用 {@code spring.profiles.active == druid} 来判断是否推送,
     * 导致测试环境与正式环境无法通过参数区分。改为系统参数控制,环境各自配置。
     */
    private static final String CFG_UPLOAD_OA = "sys.approval.upload.oa";
    /** 参数缺省值:上传(保持正式环境原有行为) */
    private static final boolean DEFAULT_UPLOAD_OA = true;
    /** 审批单是否需要上传医院协友(致远)系统 */
    private boolean isUploadOaEnabled() {
        String value = null;
        try {
            value = sysConfigService.selectConfigByKey(CFG_UPLOAD_OA);
        } catch (Exception e) {
            log.warn("读取参数 {} 失败,按默认值 {} 处理", CFG_UPLOAD_OA, DEFAULT_UPLOAD_OA, e);
        }
        if (value == null || value.trim().isEmpty()) {
            return DEFAULT_UPLOAD_OA;
        }
        String v = value.trim();
        return "1".equals(v) || "true".equalsIgnoreCase(v) || "y".equalsIgnoreCase(v) || "on".equalsIgnoreCase(v);
    }
    /**
@@ -259,7 +291,7 @@
            log.info("上传OA文件方法的入参:{}", serviceReimbursementdetailShareds.get(i));
            try {
                //如果是本地环境,就不调这个了
                if (!active.equals("druid")) uploadOAFileAndUpdateDb(serviceReimbursementdetailShareds.get(i));
                if (isUploadOaEnabled()) uploadOAFileAndUpdateDb(serviceReimbursementdetailShareds.get(i));
            } catch (Exception e) {
                log.error("Exception中reimShare的入参id为:{}", serviceReimbursementShared.getId());
                if (serviceReimbursementShared.getId() != null) {
@@ -333,6 +365,7 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> checkFund(LoginUser loginUser, CheckFundVO checkFundVO) {
        Map<String, String> allMap = new HashMap<>();
@@ -397,8 +430,9 @@
                    if (newSwitch == 1) {
                        aBoolean1 = sendData(serviceReimbursement);
                        if (!aBoolean1) {
                            allMap.put("500", "发送失败");
                            return allMap;
                            // 推送医院协友系统失败:整个审批不生效(事务回滚),允许下次重新审批
                            log.error("差旅审批单上传医院协友系统失败,审批不生效,id={},请重新审批", serviceReimbursement.getId());
                            throw new ServiceException("医院协友系统接收失败,本次审批未生效,请稍后重新审批");
                        }
                    }
                    //保存审批流程表
@@ -443,22 +477,25 @@
                    }
                }
                SysUser sysUser = sysUserService.selectUserByUserName(serviceReimbursement.getUserno());
                ArrayList<ConcurrentHashMap<String, Object>> contentList = new ArrayList<>();
                ConcurrentHashMap map = new ConcurrentHashMap();
                map.put("审批人:", loginUser.getUser().getUserName());
                map.put("审批时间:", new Date());
                map.put("意见:", "不通过");
                if (StringUtils.isNotBlank(checkFundVO.getFlowcontent()))
                    map.put("意见:", checkFundVO.getFlowcontent());
                contentList.add(map);
                //审批未通过:单据信息 + 出差信息 + 审批人/审批时间/意见(意见 = "审批未通过。" + 驳回意见)
                List<ConcurrentHashMap<String, Object>> contentList = FundNotifyMessageBuilder.newRows();
                FundNotifyMessageBuilder.addBillInfo(contentList, "0", serviceReimbursement.getUsername(),
                        serviceReimbursement.getAmountrequested(), null,
                        FundNotifyMessageBuilder.billNo(serviceReimbursement.getBh(),
                                serviceReimbursement.getId()));
                FundNotifyMessageBuilder.addTravelInfo(contentList, serviceReimbursement.getTravelers(),
                        serviceReimbursement.getReason());
                FundNotifyMessageBuilder.addAuditResult(contentList, loginUser.getUser().getNickName(), new Date(),
                        FundNotifyMessageBuilder.opinion(FundNotifyMessageBuilder.CONCLUSION_REJECT,
                                checkFundVO.getFlowcontent()));
                try {
                    DingTalkReqVo dingTalkReqVo = new DingTalkReqVo();
                    dingTalkReqVo.setTitle("财务系统驳回信息");
                    dingTalkReqVo.setTitle(FundNotifyMessageBuilder.TITLE);
                    dingTalkReqVo.setNumber(sysUser.getPhonenumber());
                    dingTalkReqVo.setContents(contentList);
                    dingTalkService.sendNotification(dingTalkReqVo);
                }catch (Exception e){
                    log.error("差旅---财务系统驳回信息报错了", e);
                } catch (Exception e) {
                    log.error("差旅---审批未通过钉钉通知失败,id={}", serviceReimbursement.getId(), e);
                }
            }
@@ -515,8 +552,9 @@
                    log.info("差旅newSwitch返回的值为:{}", newSwitch);
                    aBoolean1 = sendData(serviceReimbursement);
                    if (!aBoolean1) {
                        allMap.put("500", "发送失败");
                        return allMap;
                        // 推送医院协友系统失败:整个审批不生效(事务回滚),允许下次重新审批
                        log.error("差旅审批单上传医院协友系统失败,审批不生效,id={},请重新审批", serviceReimbursement.getId());
                        throw new ServiceException("医院协友系统接收失败,本次审批未生效,请稍后重新审批");
                    }
                }
            }
@@ -585,7 +623,30 @@
        return "未找到审批记录";
    }
    /**
     * 审批单上传医院协友(致远)系统。
     *
     * <p>2026-10-09:外层包一层兜底,保证**任何异常都不会向外抛**(原来 strRes 为 null 时
     * 会在 parseObject 后取 code 触发 NPE,把整个审批请求打成 500)。
     * 返回 false 表示推送失败,调用方按"审批不生效、允许下次重新审批"处理。
     */
    public Boolean sendData(ServiceReimbursement serviceReimbursement) {
        if (!isUploadOaEnabled()) {
            // 上传开关关闭(如测试环境):跳过上传并视为成功,绝不能因此阻断审批
            log.info("审批单上传医院协友系统已关闭(sys.approval.upload.oa != 1),跳过上传并视为成功,id={}",
                    serviceReimbursement == null ? null : serviceReimbursement.getId());
            return true;
        }
        try {
            return doSendData(serviceReimbursement);
        } catch (Throwable t) {
            log.error("差旅审批单上传医院协友系统异常,审批不生效,id={}",
                    serviceReimbursement == null ? null : serviceReimbursement.getId(), t);
            return false;
        }
    }
    private Boolean doSendData(ServiceReimbursement serviceReimbursement) {
        // fund和funddetail的数据获取
        addSharedData(serviceReimbursement.getId());
        List<ServiceReimbursementShared> serviceReimbursementSharedList = sharedService.getRemShareInfoByRemId(serviceReimbursement.getId());
@@ -631,7 +692,7 @@
        data.put("summaryId", serviceReimbursement1.getSummaryId() == null ? "" : serviceReimbursement1.getSummaryId());
        map.put("data", data);
        String strRes = null;
        if (!active.equals("druid")) {
        if (isUploadOaEnabled()) {
            //如果是本地环境,就不调这个了
            String token = getToken();
            JSONObject json1 = JSONObject.parseObject(token);
@@ -662,9 +723,28 @@
            }
        }
        log.error("-----Reimbursement-------------strRes的值为:{}", strRes);
        JSONObject jsonObject = JSONObject.parseObject(strRes);
        if (strRes == null || strRes.trim().isEmpty()) {
            // 医院协友系统无返回(超时/异常/未配置):推送失败,由调用方回滚整个审批
            log.error("差旅审批单上传医院协友系统失败:接口无返回内容,id={}", serviceReimbursement.getId());
            return false;
        }
        JSONObject jsonObject = null;
        try {
            jsonObject = JSONObject.parseObject(strRes);
        } catch (Exception e) {
            log.error("差旅审批单上传医院协友系统失败:返回内容无法解析,id={},strRes={}",
                    serviceReimbursement.getId(), strRes, e);
            return false;
        }
        if (jsonObject == null) {
            log.error("差旅审批单上传医院协友系统失败:返回内容为空,id={},strRes={}",
                    serviceReimbursement.getId(), strRes);
            return false;
        }
        String code = jsonObject.getString("code");
        if (!code.equals("0")) {
        if (code == null || !"0".equals(code)) {
            log.error("差旅审批单上传医院协友系统失败:code={},id={},strRes={}",
                    code, serviceReimbursement.getId(), strRes);
            return false;
        }
@@ -876,7 +956,7 @@
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("userName", "opo");
        map.put("password", "4126407a-9821-4874-be41-6568abd6dbe5");
        map.put("loginName", "OPO系统");
        map.put("loginName", "OPO系统审批");
        JSONObject jsonObj = new JSONObject(map);
ruoyi-project/src/main/java/com/ruoyi/project/service/impl/ServiceRelativesconfirmationServiceImpl.java
@@ -26,6 +26,7 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.ruoyi.common.utils.StringUtils;
@@ -221,6 +222,23 @@
            relativeConfirmationVO.setReporterno(user.getUserName());
        }
        List<RelativeConfirmationVO> relativeConfirmationVOS = serviceRelativesconfirmationMapper.selectVOList(relativeConfirmationVO);
        // 一次性取出各捐献基础对应的死亡时间,避免在循环里按 InfoID 逐条查见证表(N+1)
        List<Long> infoIds = new ArrayList<>();
        for (RelativeConfirmationVO item : relativeConfirmationVOS) {
            if (item.getInfoid() != null) {
                infoIds.add(item.getInfoid());
            }
        }
        Map<Long, Date> deathTimeMap = new HashMap<>();
        if (!infoIds.isEmpty()) {
            for (ServiceDonationwitness witness : serviceDonationwitnessMapper.selectDeathTimeByInfoIds(infoIds)) {
                if (witness.getInfoid() != null && !deathTimeMap.containsKey(witness.getInfoid())) {
                    deathTimeMap.put(witness.getInfoid(), witness.getDeathtime());
                }
            }
        }
        for (RelativeConfirmationVO relativeConfirmationVO1 : relativeConfirmationVOS) {
            //先给个年龄默认值(因为age是数字类型,不能放横杠,所以就所横扛放到单位上)
            relativeConfirmationVO1.setAgeunit("-");
@@ -228,22 +246,17 @@
            relativeConfirmationVO1.setAge2(null);
            //获取死亡时间,如果获取见证里有死亡时间,则进行计算他的年龄
            ServiceDonationwitness sdw = new ServiceDonationwitness();
            sdw.setInfoid(relativeConfirmationVO1.getInfoid());
            List<ServiceDonationwitness> serviceDonationwitnessList = serviceDonationwitnessMapper.selectServiceDonationwitnessList(sdw);
            if (!CollectionUtils.isEmpty(serviceDonationwitnessList) && relativeConfirmationVO1.getBirthday() != null) {
                Date deathtime = serviceDonationwitnessList.get(0).getDeathtime();
                if (ObjectUtils.isNotEmpty(deathtime)) {
                    Map<String, String> map = CalculateDateUtils.calculateAge(relativeConfirmationVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                    if (ObjectUtils.isEmpty(map)) {
                        log.error("RelativeConfirmationVO出生日期:{},RelativeConfirmationVO死亡日期:{}", relativeConfirmationVO1.getBirthday(), deathtime);
                        continue;
                    }
                    relativeConfirmationVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                    relativeConfirmationVO1.setAgeunit(map.get("ageUnit"));
                    relativeConfirmationVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                    relativeConfirmationVO1.setAgeunit2(map.get("ageUnit2"));
            Date deathtime = deathTimeMap.get(relativeConfirmationVO1.getInfoid());
            if (ObjectUtils.isNotEmpty(deathtime) && relativeConfirmationVO1.getBirthday() != null) {
                Map<String, String> map = CalculateDateUtils.calculateAge(relativeConfirmationVO1.getBirthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), deathtime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                if (ObjectUtils.isEmpty(map)) {
                    log.error("RelativeConfirmationVO出生日期:{},RelativeConfirmationVO死亡日期:{}", relativeConfirmationVO1.getBirthday(), deathtime);
                    continue;
                }
                relativeConfirmationVO1.setAge(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age")) ? Long.valueOf(map.get("age")) : null);
                relativeConfirmationVO1.setAgeunit(map.get("ageUnit"));
                relativeConfirmationVO1.setAge2(org.apache.commons.lang3.ObjectUtils.isNotEmpty(map.get("age2")) ? Long.valueOf(map.get("age2")) : null);
                relativeConfirmationVO1.setAgeunit2(map.get("ageUnit2"));
            }
        }
        return relativeConfirmationVOS;
ruoyi-project/src/main/resources/mapper/project/ServiceDonationwitnessMapper.xml
@@ -111,6 +111,23 @@
        </where>
    </select>
    <!--
      批量查询「捐献基础ID -> 死亡时间」,供列表页计算年龄使用。
      代替原先在记录循环中按 InfoID 逐条查询的 N+1 写法(首页有 N 条记录就发 N 次 SQL)。
      只返回 InfoID 与 DeathTime 两列;Infoid 与 DeathTime 均为空的行无意义,直接过滤,
      同时避免与 selectServiceDonationwitnessList 返回的行数不一致。
    -->
    <select id="selectDeathTimeByInfoIds" resultMap="ServiceDonationwitnessResult">
        select InfoID, DeathTime from service_donationwitness
        where InfoID is not null and DeathTime is not null
        <if test="infoIds != null and infoIds.size() > 0">
            and InfoID in
            <foreach collection="infoIds" item="infoId" open="(" separator="," close=")">
                #{infoId}
            </foreach>
        </if>
    </select>
    <select id="selectVOList" resultType="com.ruoyi.project.domain.vo.DonationWitnessVO">
        SELECT
        `service_donatebaseinfo`.`ID` AS `ID`,
ruoyi-project/src/main/resources/mapper/project/ServiceExternalpersonMapper.xml
@@ -43,7 +43,7 @@
            service_externalperson.del_flag = 0
            <if test="userno != null  and userno != ''">and UserNo = #{userno}</if>
            <if test="username != null  and username != ''">and UserName like concat('%', #{username}, '%')</if>
            <if test="usertype != null  and usertype != ''">and UserType = #{usertype}</if>
            <if test="usertype != null  and usertype != ''">and FIND_IN_SET(#{usertype}, UserType)</if>
            <if test="sex != null  and sex != ''">and Sex = #{sex}</if>
            <if test="telephone != null  and telephone != ''">and Telephone = #{telephone}</if>
            <if test="address != null  and address != ''">and Address = #{address}</if>
@@ -59,7 +59,7 @@
    <select id="getAllpeople" resultType="com.ruoyi.project.domain.ServiceExternalperson">
        <include refid="selectServiceExternalpersonVo"/>
        where service_externalperson.del_flag = 0 and UserType = #{usertype}
        where service_externalperson.del_flag = 0 and FIND_IN_SET(#{usertype}, UserType)
    </select>
    <select id="getInfoByUserNo" resultType="com.ruoyi.project.domain.ServiceExternalperson">
        <include refid="selectServiceExternalpersonVo"/>
ruoyi-project/src/main/resources/mapper/project/ServiceFunddetailMapper.xml
@@ -20,6 +20,7 @@
        <result property="unitname" column="UnitName"/>
        <result property="unitno" column="UnitNo"/>
        <result property="unituserno" column="UnitUserNo"/>
        <result property="isunit" column="IsUnit"/>
        <result property="title" column="Title"/>
        <result property="idcardtype" column="IDCardType"/>
        <result property="idcardno" column="IDCardNo"/>
@@ -83,6 +84,7 @@
               UnitName,
               UnitNo,
               UnitUserNo,
               IsUnit,
               Title,
               OrderNo,
               ItemCode,
@@ -206,6 +208,7 @@
        a.UnitName,
        a.UnitNo,
        a.UnitUserNo,
        a.IsUnit,
        a.Title,
        a.OrderNo,
        a.ItemCode,
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -2,6 +2,7 @@
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.bean.DtoConversionUtils;
import com.ruoyi.project.common.FundNotifyMessageBuilder;
import com.ruoyi.project.domain.*;
import com.ruoyi.project.domain.vo.DingTalkReqVo;
import com.ruoyi.project.service.*;
@@ -563,6 +564,14 @@
    }
    /**
     * 财务系统驳回信息钉钉通知(定时任务用)。
     *
     * @deprecated 2026-10-08 核实:本方法**已无调用方**(原调用点在本类中已注释),
     * 且 {@code sys_job} 中没有任何任务指向它,属于死代码。
     * 与"钉钉失败补偿/重试"无关,请勿据此认为存在补偿机制。保留仅为避免误删历史逻辑。
     */
    @Deprecated
    public void sendMeg(String[] split, String phone, String applyType, Integer fundType, Long fundId) throws ApiException {
        String content = null;
        String time = null;
@@ -607,7 +616,7 @@
        map.put("意见:" , content);
        contentList.add(map);
        DingTalkReqVo dingTalkReqVo = new DingTalkReqVo();
        dingTalkReqVo.setTitle("财务系统驳回信息");
        dingTalkReqVo.setTitle(FundNotifyMessageBuilder.TITLE);
        dingTalkReqVo.setNumber(phone);
        dingTalkReqVo.setContents(contentList);
        log.info("钉钉发送消息:{}" , dingTalkReqVo);