Quellcode durchsuchen

Merge remote-tracking branch 'origin/master'

徐滕 vor 2 Wochen
Ursprung
Commit
e4e55bb8c7
39 geänderte Dateien mit 1013 neuen und 18 gelöschten Zeilen
  1. 5 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/flowable/factory/AssessApiFallbackFactory.java
  2. 5 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/flowable/feign/IAssessApi.java
  3. 5 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/DictApiFallbackFactory.java
  4. 5 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/UserApiFallbackFactory.java
  5. 8 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IDictApi.java
  6. 10 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IUserApi.java
  7. 108 0
      jeeplus-auth/src/main/java/com/jeeplus/auth/controller/LoginController.java
  8. 6 0
      jeeplus-auth/src/main/java/com/jeeplus/auth/model/LoginForm.java
  9. 2 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceMapper.xml
  10. 29 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceService.java
  11. 1 1
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/FinanceInvoiceDTO.java
  12. 134 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/utils/DingTalkUtil.java
  13. 0 1
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/program/configuration/projectList/controller/ProjectListController.java
  14. 3 1
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/program/configuration/projectList/mapper/xml/ProgramArchiveMapper.xml
  15. 5 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/program/configuration/projectList/service/dto/ProgramArchiveDto.java
  16. 38 3
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/controller/WorkContractController.java
  17. 38 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/domain/WorkContractCancel.java
  18. 49 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/domain/WorkContractInfo.java
  19. 12 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/mapper/WorkContractCancelMapper.java
  20. 22 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/mapper/WorkContractInfoMapper.java
  21. 135 4
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/mapper/xml/WorkContractInfoMapper.xml
  22. 94 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/service/WorkContractService.java
  23. 25 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/service/dto/WorkContractInfoDto.java
  24. 29 4
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/controller/CwFinanceInvoiceController.java
  25. 3 1
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceMapper.xml
  26. 27 2
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceService.java
  27. 10 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceDictDTO.java
  28. 2 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceInvoiceDTO.java
  29. 139 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/util/DingTalkUtil.java
  30. 6 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/DictApiImpl.java
  31. 5 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/UserApiImpl.java
  32. 4 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/DictValueMapper.java
  33. 3 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/UserMapper.java
  34. 11 1
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/DictValueMapper.xml
  35. 6 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/UserMapper.xml
  36. 7 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/DictTypeService.java
  37. 5 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/DictValueService.java
  38. 4 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/UserService.java
  39. 13 0
      jeeplus-modules/jeeplus-xxl-job-executor-sample/src/main/java/com/xxl/job/executor/service/jobhandler/SampleXxlJob.java

+ 5 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/flowable/factory/AssessApiFallbackFactory.java

@@ -100,6 +100,11 @@ public class AssessApiFallbackFactory implements FallbackFactory<IAssessApi> {
             public void redInvoiceRetryScheduledTask() {
 
             }
+
+            @Override
+            public void getThreeMonthsUnarchivedContracts() {
+
+            }
         };
     }
 }

+ 5 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/flowable/feign/IAssessApi.java

@@ -114,4 +114,9 @@ public interface IAssessApi {
     @GetMapping(value = "/finance/invoice/redInvoiceRetryScheduledTask")
     void redInvoiceRetryScheduledTask();
 
+    /**
+     * 评估合同超过3个月未归档自动提醒
+     */
+    @GetMapping(value = "/workContract/workContractInfo/getThreeMonthsUnarchivedContracts")
+    void getThreeMonthsUnarchivedContracts();
 }

+ 5 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/DictApiFallbackFactory.java

@@ -49,6 +49,11 @@ public class DictApiFallbackFactory implements FallbackFactory <IDictApi> {
             @Override
             public void updateDictValue(String label, String value) {
             }
+
+            @Override
+            public String getRealTimeDictValue(String label) {
+                return null;
+            }
         };
     }
 }

+ 5 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/UserApiFallbackFactory.java

@@ -115,6 +115,11 @@ public class UserApiFallbackFactory implements FallbackFactory <IUserApi> {
             }
 
             @Override
+            public UserDTO getByDdId(String ddId) {
+                return null;
+            }
+
+            @Override
             public String getOpenIdsByIds(String userIds) {
                 return null;
             }

+ 8 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IDictApi.java

@@ -66,4 +66,12 @@ public interface IDictApi {
      */
     @GetMapping(value = "/feign/sys/dict/updateDictValue")
     void updateDictValue(@RequestParam(value = "label") String label, @RequestParam(value = "value") String value);
+
+    /**
+     *  实时查询value值
+     * @param label
+     * @return
+     */
+    @GetMapping(value = "/feign/sys/dict/getRealTimeDictValue")
+    String getRealTimeDictValue(@RequestParam(value = "label") String label);
 }

+ 10 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IUserApi.java

@@ -173,6 +173,16 @@ public interface IUserApi {
     @GetMapping(value = BASE_URL + "/getByOpenId")
     UserDTO getByOpenId(@RequestParam("openId") String openId);
 
+
+    /**
+     * 根据钉钉id获取用户
+     *
+     * @param ddId
+     * @return
+     */
+    @GetMapping(value = BASE_URL + "/getByddId")
+    UserDTO getByDdId(@RequestParam("ddId") String ddId);
+
     /**
      * 根据id获取用户
      *

+ 108 - 0
jeeplus-auth/src/main/java/com/jeeplus/auth/controller/LoginController.java

@@ -233,6 +233,114 @@ public class LoginController {
 
 
     /**
+     * 钉钉免登录接口
+     *
+     * @param loginForm
+     * @return
+     */
+    @PostMapping("/sys/ddLogin")
+    @ApiLog(value = "钉钉免登录接口", type = LogTypeEnum.LOGIN)
+    @ApiOperation("钉钉免登录接口")
+    public ResponseEntity ddLogin(@RequestBody LoginForm loginForm) {
+        ResponseUtil responseUtil = new ResponseUtil ( );
+        UserDTO user = SpringUtil.getBean(IUserApi.class).getByDdId(loginForm.getDdId());
+        String loginUserName = user.getLoginName ();
+        String username = user.getLoginName ( );
+        String password = user.getPassword ( );
+        String code = loginForm.getCode ( );
+        Object redisValue = RedisUtils.getInstance().get(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName);
+        Integer redisLoginNumber = null;
+
+        if (redisValue != null) {
+            redisLoginNumber = Integer.valueOf(redisValue.toString());
+        } else {
+            // Redis 里没有这个键,给一个默认值,比如 0
+            redisLoginNumber = 0;
+        }
+        if(null == redisLoginNumber){
+            redisLoginNumber = 0;
+        }else{
+            redisLoginNumber ++ ;
+        }
+        RedisUtils.getInstance().set(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , redisLoginNumber);
+        //给登录次数记录设置6小时的过期时间
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay();
+        long secondsUntilMidnight = Duration.between(now, midnight).getSeconds();
+        RedisUtils.getInstance().expire(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , secondsUntilMidnight);
+
+        //字典中限制显示次数
+        Integer loginNumber = 5;
+        if (redisLoginNumber >= 10){
+            return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_MAX_COUNT );
+        }
+        if(redisLoginNumber > loginNumber){
+            if(StringUtils.isNotBlank(code)){
+                if ( !code.equals ( RedisUtils.getInstance ( ).get ( CacheNames.SYS_CACHE_CODE, loginForm.getUuid ( ) ) ) ) {
+                    return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_ERROR_ERROR_VALIDATE_CODE );
+                }
+            }else{
+                return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_CODE );
+            }
+        }
+        AuthenticationManager authenticationManager = SpringUtil.getBean ( AuthenticationManager.class );
+        SecurityUtils.login ( username, "Xg@sys9hB2!xWm", authenticationManager ); //登录操作
+
+        String domain = RequestUtils.getHeader ( "domain" );
+        if (domain.contains("ydddl")){
+
+        } else {
+            /**
+             * 单一登录判断
+             */
+            if ( !userApi.isEnableLogin ( tenantApi.getCurrentTenantId ( ), username ) ) {
+                throw new DisabledException ( ErrorConstants.LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE );
+            }
+        }
+
+        //登录成功,生成token
+        UserDTO userDTO = userApi.getByLoginName ( username, tenantApi.getCurrentTenantId ( ) );
+
+
+        if("樊莉".equals(userDTO.getName())){
+
+            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("黄玮", "10002");
+            if(!onLineUserList.isEmpty()){
+                throw new DisabledException ( "当前黄玮已登录系统," + ErrorConstants.LOGIN_ERROR );
+            }
+        }else if("黄玮".equals(userDTO.getName())){
+            List<UserDTO> onLineUserList = SpringUtil.getBean(IUserApi.class).getOnLineUserList("樊莉", "10002");
+            if(!onLineUserList.isEmpty()){
+                throw new DisabledException ( "当前樊莉已登录系统," + ErrorConstants.LOGIN_ERROR );
+            }
+        }
+
+
+        String token = TokenProvider.createAccessToken ( username );
+        responseUtil.add ( TokenProvider.TOKEN, token );
+        //更新登录信息
+        updateUserLoginInfo ( responseUtil, userDTO, token );
+
+        // 微信公众号第一次登录,将openId存入 user表中
+        if (StringUtils.isBlank(userDTO.getOpenId())) {
+            if (StringUtils.isNotBlank(loginForm.getOpenId())) {
+                userDTO.setOpenId(loginForm.getOpenId());
+                SpringUtil.getBean(IUserApi.class).saveOrUpdate(userDTO);
+            }
+        }
+
+        //删除redis中登录次数的信息
+        RedisUtils.getInstance ().delete ( CacheNames.USER_CACHE_LOGIN_CODE + loginUserName );
+
+        userDTO.setToken(token);
+        if(!"123456".equals(password) && !"Xg@sys9hB2!xWm".equals(password)){
+            userApi.updateUserUpPassword(userDTO);
+        }
+
+        return responseUtil.ok ( );
+    }
+
+    /**
      * cas登录
      * vue 传递ticket参数验证,并返回token
      */

+ 6 - 0
jeeplus-auth/src/main/java/com/jeeplus/auth/model/LoginForm.java

@@ -37,4 +37,10 @@ public class LoginForm {
      */
     @ApiModelProperty("用户关注公众号的唯一id")
     private String openId;
+
+    /**
+     * ddId
+     */
+    @ApiModelProperty("用户的钉钉Id")
+    private String ddId;
 }

+ 2 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceMapper.xml

@@ -398,11 +398,13 @@
     <select id="queryById" resultMap="BaseResultMap">
         select
         <include refid="Base_Column_List"></include>,
+        d.ID_ AS invoiceTaskId,
         su.name as reconciliationPeopleName,
         su1.name as billingPeopleRealName
         from finance_invoice fi
         left join sys_user su on fi.reconciliation_people = su.id
         left join sys_user su1 on fi.billing_people_real = su1.id
+        LEFT JOIN act_ru_task d ON fi.proc_ins_id = d.PROC_INST_ID_
         where fi.del_flag = '0' and fi.id = ${id}
     </select>
 

+ 29 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceService.java

@@ -14,12 +14,14 @@ import com.google.common.collect.Lists;
 import com.jeeplus.assess.invoice.domain.OMS.InvoiceDown.OMSInvoiceDetailInfo;
 import com.jeeplus.assess.invoice.thread.ApprovalThread;
 import com.jeeplus.assess.invoice.thread.RedApprovalThread;
+import com.jeeplus.assess.invoice.utils.DingTalkUtil;
 import com.jeeplus.assess.invoice.utils.OMS.FileHandlingUtil;
 import com.jeeplus.common.TokenProvider;
 import com.jeeplus.core.query.QueryWrapperGenerator;
 import com.jeeplus.flowable.feign.IFlowableApi;
 import com.jeeplus.sys.domain.User;
 import com.jeeplus.sys.domain.WorkAttachmentInfo;
+import com.jeeplus.sys.feign.IDictApi;
 import com.jeeplus.sys.feign.IRoleApi;
 import com.jeeplus.sys.feign.IUserApi;
 import com.jeeplus.sys.feign.IWorkAttachmentApi;
@@ -79,6 +81,9 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
     @Value("${oms.omsEnabled}")
     private Boolean omsEnabled;
 
+    @Resource
+    private DingTalkUtil dingTalkUtil;
+
     private static final String unitName = "兴光评估";
 
     Pattern mobilePattern = Pattern.compile("^1[3-9]\\d{9}$");
@@ -451,6 +456,15 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
     }
 
     public String saveForm(FinanceInvoiceDTO financeInvoiceDTO) throws Exception{
+
+
+
+        //添加钉钉通知处理
+        boolean isDDNotice = false;
+        if(financeInvoiceDTO.getStatus().equals("2")){
+            isDDNotice = true;
+        }
+
         String isOms = "0";
         if("5".equals(financeInvoiceDTO.getStatus())){
             //IsOmsBilling为0时,需要走系统开票
@@ -650,9 +664,24 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
             FinanceInvoiceDTO dto = queryById(financeInvoice.getId());
             new RedApprovalThread(dto,financeInvoiceDTO.getInvoiceNumberStr(), "21").start();
         }
+
+        //钉钉通知
+        if(isDDNotice){
+            //根据字典判断是否钉钉通知用户
+            String realTimeDictValue = SpringUtil.getBean(IDictApi.class).getRealTimeDictValue("是否接收钉钉通知");
+            if(realTimeDictValue.equals("1")){
+                FinanceInvoiceDTO dto = queryById(financeInvoice.getId());
+                addDingtalkNotice(dto);
+            }
+        }
         return financeInvoice.getId();
     }
 
+    public String addDingtalkNotice(FinanceInvoiceDTO dto){
+        dingTalkUtil.addDingtalkNotice(dto);
+        return "ok";
+    }
+
     /**
      * 判断是否开票逻辑
      */

+ 1 - 1
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/FinanceInvoiceDTO.java

@@ -357,6 +357,6 @@ public class FinanceInvoiceDTO extends BaseDTO {
 
     private String isSmsNotice; //是否短信通知
     private String isOmsBilling; //是否手动开票
-
+    private String invoiceTaskId;//开票任务id
     private static final long serialVersionUID = 1L;
 }

+ 134 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/utils/DingTalkUtil.java

@@ -0,0 +1,134 @@
+package com.jeeplus.assess.invoice.utils;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiGettokenRequest;
+import com.dingtalk.api.request.OapiMessageCorpconversationAsyncsendV2Request;
+import com.dingtalk.api.response.OapiGettokenResponse;
+import com.dingtalk.api.response.OapiMessageCorpconversationAsyncsendV2Response;
+import com.jeeplus.assess.invoice.service.dto.FinanceInvoiceDTO;
+import com.jeeplus.sys.feign.IRoleApi;
+import com.jeeplus.sys.feign.IUserApi;
+import com.jeeplus.sys.service.dto.RoleDTO;
+import com.jeeplus.sys.service.dto.UserDTO;
+import com.taobao.api.ApiException;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @Author hgc
+ * @Date 2026/3/12/9:44
+ * @ClassName DingTalkUtil
+ * @Description
+ */
+@Component
+public class DingTalkUtil {
+    private static String appKey;
+    private static String appSecret;
+    private static String agentId;
+    private static String accessTokenUrl;
+    private static String templateUrl;
+
+
+    @Value("${dingTalk.appKey:}")
+    public void setAppKey(String appKey) {
+        DingTalkUtil.appKey = appKey;
+    }
+
+    @Value("${dingTalk.appSecret:}")
+    public void setAppSecret(String appSecret) {
+        DingTalkUtil.appSecret = appSecret;
+    }
+
+    @Value("${dingTalk.agentId:}")
+    public void setAgentId(String agentId) {
+        DingTalkUtil.agentId = agentId;
+    }
+
+    @Value("${dingTalk.accessTokenUrl:}")
+    public void setAccessTokenUrl(String accessTokenUrl) {
+        DingTalkUtil.accessTokenUrl = accessTokenUrl;
+    }
+
+    @Value("${dingTalk.templateUrl:}")
+    public void setTemplateUrl(String templateUrl) {
+        DingTalkUtil.templateUrl = templateUrl;
+    }
+
+
+    private static String templateText =
+            "### 开票审批提醒\n\n" +
+                    "**实际开票单位:** %s\n\n" +
+                    "**发票金额(元):** %s\n\n" +
+                    "**申请人:** %s\n\n" +
+                    "---\n\n" +
+                    "[点击进入审批](%s)";
+
+    public static String getToken(){
+        // 增加空值校验,避免NPE
+        if (appKey == null || appSecret == null || accessTokenUrl == null) {
+            System.err.println("钉钉配置未正确注入:appKey=" + appKey + ", appSecret=" + appSecret + ", accessTokenUrl=" + accessTokenUrl);
+            return null;
+        }
+
+        try {
+            DingTalkClient client = new DefaultDingTalkClient(accessTokenUrl);
+            OapiGettokenRequest req = new OapiGettokenRequest();
+            req.setAppkey(appKey);
+            req.setAppsecret(appSecret);
+            req.setHttpMethod("GET");
+            OapiGettokenResponse rsp = client.execute(req);
+            System.out.println("获取到钉钉AccessToken:" + rsp.getAccessToken());
+            return rsp.getAccessToken();
+        } catch (ApiException e) {  // 捕获具体异常,而非泛化Exception
+            System.err.println("获取钉钉AccessToken失败:" + e.getMessage());
+            e.printStackTrace();
+        } catch (Exception e) {
+            System.err.println("获取钉钉AccessToken出现未知异常:" + e.getMessage());
+            e.printStackTrace();
+        }
+        return null;
+    }
+    public static ResponseEntity addDingtalkNotice(FinanceInvoiceDTO cwFinanceInvoiceDTO) {
+        try {
+            RoleDTO roleDTO = SpringUtil.getBean(IRoleApi.class).getRoleDTOByName2("发票管理员");
+            List<UserDTO> notifiedPartyUsers =SpringUtil.getBean(IUserApi.class).findListByRoleId(roleDTO.getId());
+            UserDTO assigneeDTO = notifiedPartyUsers.get(0);
+
+            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
+            OapiMessageCorpconversationAsyncsendV2Request req = new OapiMessageCorpconversationAsyncsendV2Request();
+
+            String token = getToken();
+            req.setAgentId(Long.parseLong(agentId));
+            //接受审批通知的钉钉id
+            req.setUseridList(assigneeDTO.getDdId());
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setMsgtype("markdown");
+            OapiMessageCorpconversationAsyncsendV2Request.Markdown markdown = new OapiMessageCorpconversationAsyncsendV2Request.Markdown();
+            markdown.setTitle("开票审批提醒");
+            String finalUrl = String.format(templateUrl, assigneeDTO.getDdId(),cwFinanceInvoiceDTO.getId());
+            String markdownText = String.format(
+                    templateText,
+                    cwFinanceInvoiceDTO.getBillingWorkplaceReal(),
+                    cwFinanceInvoiceDTO.getAccount(),
+                    cwFinanceInvoiceDTO.getBillingPeopleRealName(),
+                    finalUrl
+            );
+            markdown.setText(markdownText);
+            msg.setMarkdown(markdown);
+            req.setMsg(msg);
+            OapiMessageCorpconversationAsyncsendV2Response rsp = client.execute(req, token);
+            return ResponseEntity.ok(rsp.getBody());
+        }  catch (ApiException e) {
+            e.printStackTrace(); // 打印错误日志
+            return ResponseEntity.badRequest().body("接口异常,请重试!");
+        } catch (Exception e) {
+            e.printStackTrace(); // 打印错误日志
+            return ResponseEntity.badRequest().body("接口异常,请重试!");
+        }
+    }
+}

+ 0 - 1
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/program/configuration/projectList/controller/ProjectListController.java

@@ -862,5 +862,4 @@ public class ProjectListController {
     public void insertMembers(String projectId,String membersId,String userId) throws Exception {
         projectListService.addMembers(projectId,membersId,userId);
     }
-
 }

+ 3 - 1
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/program/configuration/projectList/mapper/xml/ProgramArchiveMapper.xml

@@ -166,10 +166,12 @@
         ppli.project_mould as projectMould,
         su1.name as currentDisposePersonName,
         su3.name as signatureEvaluatorSecondName,
-        su2.name as signatureEvaluatorFirstName
+        su2.name as signatureEvaluatorFirstName,
+        wci.filed_type as contractStatus
         from program_archive pa
         left join program_report_no prn on prn.program_id = pa.program_id and prn.del_flag = '0'
         left join program_project_list_info ppli on ppli.id = pa.program_id and ppli.del_flag = '0'
+        left join work_contract_info wci on ppli.contract_id = wci.id and wci.del_flag = '0'
         left join sys_user su1 on pa.current_dispose_person= su1.id
         left join sys_user su2 on pa.signature_evaluator_first= su2.id
         left join sys_user su3 on pa.signature_evaluator_second= su3.id

+ 5 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/program/configuration/projectList/service/dto/ProgramArchiveDto.java

@@ -256,5 +256,10 @@ public class ProgramArchiveDto extends BaseDTO {
      */
     private String projectMould;
 
+    /**
+     * 合同归档状态
+     */
+    private String contractStatus;
+
     private static final long serialVersionUID = 1L;
 }

+ 38 - 3
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/controller/WorkContractController.java

@@ -5,16 +5,16 @@ import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.jeeplus.assess.program.configuration.projectList.service.dto.ProgramArchiveDto;
+import com.jeeplus.assess.workContract.domain.WorkContractCancel;
 import com.jeeplus.common.utils.ResponseUtil;
 import com.jeeplus.core.query.QueryWrapperGenerator;
 //import com.jeeplus.sys.utils.TicketQueryUtils;
 import com.jeeplus.assess.program.configuration.projectList.domain.ProgramProjectListInfo;
-import com.jeeplus.assess.workClientInfo.domain.WorkClientInfo;
-import com.jeeplus.assess.workClientInfo.domain.dto.WorkClientInfosDto;
-import com.jeeplus.assess.workClientInfo.service.WorkClientService;
 import com.jeeplus.assess.workContract.domain.WorkContractInfo;
 import com.jeeplus.assess.workContract.service.WorkContractService;
 import com.jeeplus.assess.workContract.service.dto.WorkContractInfoDto;
+import com.jeeplus.logging.annotation.ApiLog;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -50,6 +50,9 @@ public class WorkContractController {
         if (ObjectUtil.isNotEmpty(workClientInfo) && StringUtils.isNotBlank(workClientInfo.getCreateById())) {
             workContractInfoQueryWrapper.eq("a.create_by_id", workClientInfo.getCreateById()).or().like("c.name", workClientInfo.getCreateById());
         }
+        if(StringUtils.isNotBlank(workClientInfo.getCancelStatus())){
+            workContractInfoQueryWrapper.eq("wcc.status",workClientInfo.getCancelStatus());
+        }
         IPage<WorkContractInfo> list = workContractService.list(page, workContractInfoQueryWrapper, clientName, contractDates, contractAmounts, statusList, status,contractType);
         return ResponseEntity.ok (list);
     }
@@ -107,4 +110,36 @@ public class WorkContractController {
         boolean data = workContractService.checkName(name);
         return data;
     }
+
+    /**
+     * 根据id修改状态cancelStatus
+     */
+    @ApiOperation(value = "根据id修改状态cancelStatus")
+    @PostMapping(value = "updatecancelStatusById")
+    public ResponseEntity updatecancelStatusById(@RequestBody WorkContractCancel workContractCancel) {
+        String id = workContractService.updatecancelStatusById(workContractCancel);
+        return ResponseUtil.newInstance().add("businessTable", "work_contract_cancel").add("businessId", id).ok ("操作成功");
+    }
+
+    /**
+     * 根据项目id查询关联的合同
+     * @return
+     */
+    @ApiLog("根据项目id查询关联的合同")
+    @GetMapping("/getContractByProgramId")
+    public ResponseEntity<WorkContractInfoDto> getContractByProgramId(@RequestParam("programId") String programId) throws Exception {
+        WorkContractInfoDto workContractInfo = workContractService.getContractByProgramId(programId);
+        return ResponseEntity.ok(workContractInfo);
+    }
+
+
+    /**
+     * 查询超过三个月未归档的合同
+     */
+    @ApiLog("查询超过三个月未归档的合同")
+    @GetMapping("/getThreeMonthsUnarchivedContracts")
+    public void getThreeMonthsUnarchivedContracts() throws Exception {
+        workContractService.getThreeMonthsUnarchivedContracts();
+    }
+
 }

+ 38 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/domain/WorkContractCancel.java

@@ -0,0 +1,38 @@
+package com.jeeplus.assess.workContract.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.jeeplus.core.domain.BaseEntity;
+import lombok.Data;
+
+@Data
+@TableName("work_contract_cancel")
+public class WorkContractCancel extends BaseEntity {
+
+    /**
+     * 合同id
+     */
+    private String contractInfoId;
+    /**
+     * 流程id
+     */
+    private String procInsId;
+    private String processDefinitionId;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 原因
+     */
+    private String reason;
+
+
+    /**
+     * 当前类型
+     */
+    private String type;
+
+
+}

+ 49 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/domain/WorkContractInfo.java

@@ -136,6 +136,48 @@ public class WorkContractInfo extends BaseEntity {
     private String status;
 
     /**
+     * 作废状态
+     */
+    @TableField(exist = false)
+    private String cancelStatus;
+    /**
+     * 类型(作废,恢复)
+     */
+    @TableField(exist = false)
+    private String cancelType;
+
+
+    /**
+     * 作废/恢复原因
+     */
+    @TableField(exist = false)
+    private String reason;
+
+    /**
+     * 作废流程id
+     */
+    @TableField(exist = false)
+    private String procInsIdCancel;
+
+    /**
+     * 作废数据id
+     */
+    @TableField(exist = false)
+    private String cancelId;
+
+    /**
+     * 作废流程任务id
+     */
+    @TableField(exist = false)
+    private String taskIdCancel;
+
+    /**
+     * 作废流程审核人
+     */
+    @TableField(exist = false)
+    private  List<String> auditUserIdsCancel;
+
+    /**
      * 归档状态
      */
     @Query(tableColumn = "d.filed_type")
@@ -181,4 +223,11 @@ public class WorkContractInfo extends BaseEntity {
     @TableField(exist = false)
     private List<String> auditUserIdsFiled;
 
+
+    /**
+     * 合同关联的报告号
+     */
+    @TableField(exist = false)
+    private List<String> reportNo;
+
 }

+ 12 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/mapper/WorkContractCancelMapper.java

@@ -0,0 +1,12 @@
+package com.jeeplus.assess.workContract.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.jeeplus.assess.workContract.domain.WorkContractCancel;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface WorkContractCancelMapper extends BaseMapper<WorkContractCancel> {
+
+}

+ 22 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/mapper/WorkContractInfoMapper.java

@@ -51,6 +51,11 @@ public interface WorkContractInfoMapper extends BaseMapper<WorkContractInfo> {
 
     void updateStatusById(@Param("id") String id, @Param("status")String status);
 
+
+    void updatecancelById(@Param("id") String id, @Param("cancelStatus")String cancelStatus, @Param("type")String type, @Param("reason")String reson);
+
+    void updatecancelStatusById(@Param("id") String id, @Param("cancelStatus")String cancelStatus, @Param("type")String type, @Param("reason")String reson);
+
     IPage<ProgramProjectListInfo> relationProjectList(@Param("id") String id, Page<ProgramProjectListInfo> page);
 
     /**
@@ -61,4 +66,21 @@ public interface WorkContractInfoMapper extends BaseMapper<WorkContractInfo> {
     List<WorkContractInfoDto> getInfoByClientId (@Param("clientId") String clientId);
     //查找合同中与项目编号年份一致的最后一条评估合同的编号
     String getNoByYear(@Param("projectYear")String projectYear, @Param("contractNum")String contractNum);
+
+    List<String> getReportNoByContractId (@Param("id") String id);
+
+    /**
+     * 根据项目id查询合同信息
+     * @param programId
+     * @return
+     */
+    WorkContractInfoDto getContractByProgramId (@Param("programId") String programId);
+
+    /**
+     * 查询超过3个月未归档的合同
+     * @return
+     */
+    List<WorkContractInfoDto> getThreeMonthsUnarchivedContracts();
+
+
 }

+ 135 - 4
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/mapper/xml/WorkContractInfoMapper.xml

@@ -34,6 +34,11 @@
 			a.proc_ins_id,
 			a.process_definition_id,
 			a.status,
+			wcc.status as cancelStatus,
+			wcc.id as cancelId,
+			wcc.type as cancelType,
+			artCan.ID_ as taskIdCancel,
+			wcc.proc_ins_id AS procInsIdCancel,
 			f.borrow_type,
 			b.ID_ as task_id,
 			a.create_by_id as create_id,
@@ -48,6 +53,10 @@
 			LEFT JOIN act_ru_task e ON d.proc_ins_id = e.PROC_INST_ID_
 			LEFT JOIN work_contract_borrow f ON a.id = f.contract_info_id
 			LEFT JOIN act_ru_task g ON f.proc_ins_id = g.PROC_INST_ID_
+			LEFT JOIN work_contract_cancel wcc ON a.id = wcc.contract_info_id and wcc.del_flag = 0
+			LEFT JOIN act_ru_task artCan ON wcc.proc_ins_id = artCan.PROC_INST_ID_
+
+
 		${ew.customSqlSegment}
 		ORDER BY a.update_time DESC
 	</select>
@@ -82,6 +91,11 @@
 			a.create_by_id,
 			a.update_time,
 			a.status,
+			wcc.status as cancelStatus,
+			wcc.proc_ins_id AS procInsIdCancel,
+			wcc.reason AS reason,
+			wcc.id as cancelId,
+			wcc.type as cancelType,
 			a.update_by_id,
 			a.del_flag,
 			d.id as contract_file_id,
@@ -89,8 +103,9 @@
 		FROM
 			work_contract_info a
 			LEFT JOIN work_contract_file d ON a.id = d.contract_info_id and d.del_flag = 0
+			LEFT JOIN work_contract_cancel wcc ON a.id = wcc.contract_info_id and wcc.del_flag = 0
 		WHERE
-			a.id = #{id}
+			(a.id = #{id} or wcc.id = #{id})
 			AND a.del_flag = 0
 	</select>
 
@@ -172,6 +187,14 @@
 		UPDATE work_contract_info SET `status` = #{status} WHERE del_flag = 0 AND id = #{id}
 	</update>
 
+	<update id="updatecancelStatusById">
+		UPDATE work_contract_cancel SET
+		                                `status` = #{cancelStatus} ,
+		                                `type` = #{type},
+										reason = #{reason}
+		                            WHERE del_flag = 0 AND id = #{id}
+	</update>
+
 	<select id="relationProjectList" resultType="com.jeeplus.assess.program.configuration.projectList.domain.ProgramProjectListInfo">
 		SELECT
            a.id,
@@ -230,11 +253,13 @@
            a.mode,
            a.status,
            a.proc_ins_id,
-           a.process_definition_id
+           a.process_definition_id,
+		   pc.status as cancellationStatus
         FROM
             program_project_list_info a
             LEFT JOIN sys_user b ON a.create_by_id = b.id
             LEFT JOIN sys_user c ON a.project_manager = c.id
+			left join program_cancellation pc on pc.project_id = a.id and pc.del_flag = '0'
 		WHERE
 			a.contract_id = #{id}
             ORDER BY a.update_time DESC
@@ -282,7 +307,7 @@
 			a.client_id = #{clientId}
 			AND a.del_flag = 0
 	</select>
-	
+
 	
 	<select id="getNoByYear" resultType="string">
 		SELECT `no`
@@ -293,5 +318,111 @@
 		ORDER BY `no` DESC
 		LIMIT 1;
 	</select>
-	
+
+
+	<select id="getReportNoByContractId" resultType="string">
+		SELECT prn.report_no FROM work_contract_info wci
+		    LEFT JOIN program_project_list_info ppli on ppli.contract_id = wci.id and ppli.del_flag=0
+			LEFT JOIN program_report_no prn on prn.program_id = ppli.id and prn.del_flag=0
+		Where wci.id=#{id}
+	</select>
+
+	<select id="getContractByProgramId" resultType="com.jeeplus.assess.workContract.service.dto.WorkContractInfoDto">
+		SELECT
+			a.id,
+			a.client_id,
+			a.client_name,
+			a.`no`,
+			a.`name`,
+			a.contract_date,
+			a.effective_date,
+			a.closing_date,
+			a.contract_type,
+			a.contract_amount_type,
+			a.contract_amount,
+			a.contract_opposite,
+			a.contract_fee,
+			a.fees,
+			a.describes,
+			a.contract_special,
+			a.remarks,
+			a.filed_by,
+			a.filed_date,
+			a.filed_no,
+			a.filed_remarks,
+			a.proc_ins_id,
+			a.borrow_type,
+			a.create_time,
+			a.create_by_id,
+			a.update_time,
+			a.status,
+			wcc.status as cancelStatus,
+			wcc.proc_ins_id AS procInsIdCancel,
+			wcc.reason AS reason,
+			wcc.id as cancelId,
+			wcc.type as cancelType,
+			a.update_by_id,
+			a.del_flag
+		FROM
+			work_contract_info a
+				LEFT JOIN program_project_list_info ppli ON a.id = ppli.contract_id and ppli.del_flag =0
+				LEFT JOIN work_contract_cancel wcc ON a.id = wcc.contract_info_id and wcc.del_flag = 0
+		WHERE
+			ppli.id = #{programId}
+	</select>
+
+	<select id="getThreeMonthsUnarchivedContracts" resultType="com.jeeplus.assess.workContract.service.dto.WorkContractInfoDto">
+		SELECT
+			a.id,
+			a.client_id,
+			a.client_name,
+			a.`no`,
+			a.`name`,
+			a.contract_date,
+			a.effective_date,
+			a.closing_date,
+			a.contract_type,
+			a.contract_amount_type,
+			a.contract_amount,
+			a.contract_opposite,
+			a.contract_fee,
+			a.fees,
+			a.describes,
+			a.contract_special,
+			a.remarks,
+			a.filed_by,
+			a.filed_date,
+			a.filed_no,
+			d.confirm_filed_no,
+			a.filed_remarks,
+			a.proc_ins_id,
+			a.borrow_type,
+			a.create_time,
+			a.create_by_id as `createBy.id`,
+			a.update_time,
+			a.status,
+			wcc.status as cancelStatus,
+			wcc.proc_ins_id AS procInsIdCancel,
+			wcc.reason AS reason,
+			wcc.id as cancelId,
+			wcc.type as cancelType,
+			a.update_by_id,
+			a.del_flag,
+			d.id as contract_file_id,
+			d.filed_type
+		FROM
+			work_contract_info a
+				LEFT JOIN work_contract_file d ON a.id = d.contract_info_id and d.del_flag = 0
+				LEFT JOIN work_contract_cancel wcc ON a.id = wcc.contract_info_id and wcc.del_flag = 0
+		WHERE
+		a.filed_type != '5'
+		AND a.del_flag='0'
+	    AND (
+        wcc.status != '5'
+        AND wcc.status != '6'
+        OR wcc.status IS NULL
+    	)
+    	AND a.create_time &lt; DATE_SUB(NOW(), INTERVAL 3 MONTH)
+		</select>
+
 </mapper>

+ 94 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/service/WorkContractService.java

@@ -8,7 +8,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.google.common.collect.Lists;
 import com.jeeplus.assess.program.configuration.projectList.domain.ProgramProjectListInfo;
+import com.jeeplus.assess.workContract.domain.WorkContractCancel;
 import com.jeeplus.assess.workContract.domain.WorkContractInfo;
+import com.jeeplus.assess.workContract.mapper.WorkContractCancelMapper;
 import com.jeeplus.assess.workContract.mapper.WorkContractInfoMapper;
 import com.jeeplus.assess.workContract.service.dto.WorkContractInfoDto;
 import com.jeeplus.common.TokenProvider;
@@ -30,6 +32,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
+import java.text.SimpleDateFormat;
 import java.util.*;
 
 @Service
@@ -47,6 +50,9 @@ public class WorkContractService {
     @Resource
     private IFlowableApi flowTaskService;
 
+    @Resource
+    private WorkContractCancelMapper workContractCancelMapper;
+
     public String saveInfo(WorkContractInfoDto workContractInfoDto) throws Exception {
         if (StringUtils.isNotEmpty(workContractInfoDto.getId())) {
             WorkContractInfo info = workContractInfoMapper.selectById(workContractInfoDto.getId());
@@ -105,6 +111,19 @@ public class WorkContractService {
                     i.setAuditUserIdsFiled(flowTaskService.getTaskAuditUsers(i.getTaskFiledId()));  // 获取数据审核人
                 }
             }
+            // 作废
+            if (StringUtils.isNotBlank(i.getTaskIdCancel()) && StringUtils.isNotBlank(i.getCancelStatus())) {
+                if ("2".equals(i.getCancelStatus())) { // “审核中”的数据要获取数据审核人
+                    i.setAuditUserIdsCancel(flowTaskService.getTaskAuditUsers(i.getTaskIdCancel()));  // 获取数据审核人
+                }
+            }
+
+            //查询合同关联报告号
+            List<String> reportNoList = workContractInfoMapper.getReportNoByContractId(i.getId());
+            if (reportNoList != null && !reportNoList.isEmpty()) {
+                i.setReportNo(reportNoList);
+            }
+
         });
         return pageList;
     }
@@ -199,6 +218,21 @@ public class WorkContractService {
     }
 
     /**
+     * 根据id修改状态cancelStatus
+     */
+    public String updatecancelStatusById(WorkContractCancel workContractCancel) {
+        WorkContractInfo info = workContractInfoMapper.findById(workContractCancel.getContractInfoId());
+        if(StringUtils.isNotBlank(info.getCancelId())){
+            workContractCancelMapper.updateById(workContractCancel);
+        }else{
+            workContractCancelMapper.insert(workContractCancel);
+            info.setCancelId(workContractCancel.getId());
+        }
+        return info.getCancelId();
+    }
+
+
+    /**
      * 保存附件信息
      * @param list 待保存的附件列表
      * @param userDTO 当前登录用户
@@ -512,4 +546,64 @@ public class WorkContractService {
         return false;
 
     }
+
+    /**
+     * 根据项目id查询关联的合同
+     * @return
+     */
+    public WorkContractInfoDto getContractByProgramId(String programId){
+        WorkContractInfoDto contract = workContractInfoMapper.getContractByProgramId(programId);
+        return contract;
+    }
+
+    /**
+     * 查询超过三个月未归档的合同
+     */
+    public void getThreeMonthsUnarchivedContracts(){
+        List<WorkContractInfoDto> threeMonthsUnarchivedContracts = workContractInfoMapper.getThreeMonthsUnarchivedContracts();
+        if (CollectionUtil.isEmpty(threeMonthsUnarchivedContracts)) {
+            return;
+        }
+        IUserApi userApi = SpringUtil.getBean(IUserApi.class);
+        for (WorkContractInfoDto contract : threeMonthsUnarchivedContracts) {
+            try {
+                if(!contract.getFiledType().equals("5") && ( contract.getCancelStatus() == null  || !contract.getCancelStatus().equals("5"))){
+                    // 通知内容
+                    String message = "合同:【" + contract.getName() + "】已超过3个月未归档,请及时归档";
+                    UserDTO user = userApi.getById(contract.getCreateBy().getId());
+                    if (user == null) {
+                        continue;
+                    }
+                    // 发送通知
+                    sendNotify(user, contract.getProcessDefinitionId(), contract.getProcInsId(), message);
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 查询超过三个月未归档的合同发送提醒
+     * @param userDto
+     * @param defId
+     * @param procInsId
+     * @param title
+     */
+    public void sendNotify(UserDTO userDto,String defId,String procInsId,String title ){
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String day = format.format(new Date());
+        Map<String ,String > map = new HashMap<>();
+        map.put("taskId",procInsId);
+        map.put("title",title);
+        map.put("defId",defId);
+        map.put("taskName","合同超期未归档提醒");
+        map.put("createUser","管理员");
+        map.put("createTime",day);
+        map.put("noticeName",userDto.getName());
+        map.put("noticeId",userDto.getId());
+        map.put("createById",userDto.getId());
+        SpringUtil.getBean ( IFlowableApi.class ).add(map);
+    }
+
 }

+ 25 - 0
jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/workContract/service/dto/WorkContractInfoDto.java

@@ -1,5 +1,6 @@
 package com.jeeplus.assess.workContract.service.dto;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.jeeplus.core.service.dto.BaseDTO;
 import com.jeeplus.sys.domain.WorkAttachmentInfo;
@@ -104,6 +105,30 @@ public class WorkContractInfoDto extends BaseDTO {
     private String status;
 
     /**
+     * 状态
+     */
+    private String cancelStatus;
+    /**
+     * 作废流程id
+     */
+    private String procInsIdCancel;
+
+    /**
+     * 作废原因
+     */
+    private String reason;
+
+    /**
+     * 作废数据id
+     */
+    private String cancelId;
+
+    /**
+     * 作废数据id
+     */
+    private String cancelType;
+
+    /**
      * 附件信息
      */
     private List<WorkAttachmentInfo> workAttachmentList;

+ 29 - 4
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/controller/CwFinanceInvoiceController.java

@@ -14,10 +14,7 @@ import com.jeeplus.common.utils.ResponseUtil;
 import com.jeeplus.finance.invoice.domain.CwFinanceInvoice;
 import com.jeeplus.finance.invoice.service.CwFinanceInvoiceService;
 import com.jeeplus.finance.invoice.service.OMS.*;
-import com.jeeplus.finance.invoice.service.dto.CwFinanceDTO;
-import com.jeeplus.finance.invoice.service.dto.CwFinanceImportDTO;
-import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDTO;
-import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDetailDTO;
+import com.jeeplus.finance.invoice.service.dto.*;
 import com.jeeplus.finance.invoice.util.EasyPoiUtil;
 import com.jeeplus.finance.utils.FileUtil;
 import com.jeeplus.flowable.feign.IFlowableApi;
@@ -991,4 +988,32 @@ public class CwFinanceInvoiceController {
         folder.delete(); // 删除空文件夹或者文件
     }
 
+
+
+    /**
+     * 修改字典,提供给发票管理员使用
+     */
+    @PostMapping(value = "/updateDictValue")
+    public ResponseEntity<String> updateDictValue(@RequestBody CwFinanceDictDTO dto) {
+       try {
+           SpringUtil.getBean ( IDictApi.class ).updateDictValue (dto.getLabel(), dto.getValue());
+           return ResponseEntity.ok("操作成功");
+       }catch (RuntimeException e){
+           return ResponseEntity.ok("操作失败,请稍后重试");
+       }
+    }
+
+    /**
+     * 获取钉钉通知字典值
+     */
+    @PostMapping(value = "/getDingNoticeDictValue")
+    public ResponseEntity<String> getDingNoticeDictValue(@RequestBody CwFinanceDictDTO dto) {
+        try {
+            String realTimeDictValue = SpringUtil.getBean(IDictApi.class).getRealTimeDictValue(dto.getLabel());
+            return ResponseEntity.ok(realTimeDictValue);
+        }catch (RuntimeException e){
+            return ResponseEntity.ok("操作失败,请稍后重试");
+        }
+    }
+
 }

+ 3 - 1
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceMapper.xml

@@ -420,13 +420,15 @@
         DISTINCT
         <include refid="Base_Column_List"></include>,
         su.name as reconciliationPeopleName,
-        su1.name as billingPeopleRealName
+        su1.name as billingPeopleRealName,
+        d.ID_ AS invoiceTaskId
         from cw_finance_invoice fi
         left join cw_finance_invoice_base fib on fib.invoice_id = fi.id
         left join cw_project_report_new_line rpl on fib.program_id=rpl.report_id
         left join sys_user su on fi.reconciliation_people = su.id
         left join sys_user su1 on fi.billing_people_real = su1.id
         left join cw_project_records pr on fib.program_id=pr.id
+        LEFT JOIN act_ru_task d ON fi.proc_ins_id = d.PROC_INST_ID_
         where fi.del_flag = '0' and fi.id = #{id}
     </select>
 

+ 27 - 2
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceService.java

@@ -22,6 +22,7 @@ import com.jeeplus.finance.invoice.service.dto.*;
 import com.jeeplus.finance.invoice.service.mapstruct.*;
 import com.jeeplus.finance.invoice.thread.ApprovalThread;
 import com.jeeplus.finance.invoice.thread.RedApprovalThread;
+import com.jeeplus.finance.invoice.util.DingTalkUtil;
 import com.jeeplus.finance.invoice.util.OMS.FileHandlingUtil;
 import com.jeeplus.finance.projectRecords.mapper.CwProjectRecordsMapper;
 import com.jeeplus.finance.projectRecords.service.dto.CwProjectRecordsDTO;
@@ -99,6 +100,9 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
     private CwWorkClientBillingService cwWorkClientBillingService;
     @Resource
     private CwWorkClientService cwWorkClientService;
+    @Resource
+    private DingTalkUtil dingTalkUtil;
+
 
     Pattern mobilePattern = Pattern.compile("^1[3-9]\\d{9}$");
 
@@ -709,7 +713,15 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
         return cwFinanceInvoiceDTO;
     }
 
+
     public CwFinanceInvoice saveForm(CwFinanceInvoiceDTO cwFinanceInvoiceDTO) throws Exception {
+
+        //添加钉钉通知处理
+        boolean isDDNotice = false;
+        if(cwFinanceInvoiceDTO.getStatus().equals("2")){
+            isDDNotice = true;
+        }
+
         String isOms = "0";
         if("5".equals(cwFinanceInvoiceDTO.getStatus())){
             //IsOmsBilling为0时,需要走系统开票
@@ -931,19 +943,32 @@ public class CwFinanceInvoiceService extends ServiceImpl<CwFinanceInvoiceMapper,
             }
         }
 
-        //蓝票
+        ////蓝票
         if("1".equals(isOms)){
             CwFinanceInvoiceDTO dto = queryById(cwFinanceInvoice.getId());
             new ApprovalThread(dto, "21").start();
         }
-        //红票
+        ////红票
         if("2".equals(isOms)){
             CwFinanceInvoiceDTO dto = queryById(cwFinanceInvoice.getId());
             new RedApprovalThread(dto,cwFinanceInvoiceDTO.getInvoiceNumberStr(), "21").start();
         }
+        //钉钉通知
+        if(isDDNotice){
+            //根据字典判断是否钉钉通知用户
+            String realTimeDictValue = SpringUtil.getBean(IDictApi.class).getRealTimeDictValue("是否接收钉钉通知");
+            if(realTimeDictValue.equals("1")){
+                CwFinanceInvoiceDTO dto = queryById(cwFinanceInvoice.getId());
+                addDingtalkNotice(dto);
+            }
+        }
         return cwFinanceInvoice;
     }
 
+    public String addDingtalkNotice(CwFinanceInvoiceDTO cwFinanceInvoiceDTO){
+        dingTalkUtil.addDingtalkNotice(cwFinanceInvoiceDTO);
+        return "ok";
+    }
     public String handleDoInvoice(CwFinanceInvoiceDTO financeInvoiceDTO){
         boolean OMS_ENABLED = omsEnabled;
         //保存数据结束,触发开票接口

+ 10 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceDictDTO.java

@@ -0,0 +1,10 @@
+package com.jeeplus.finance.invoice.service.dto;
+
+import lombok.Data;
+
+@Data
+public class CwFinanceDictDTO {
+    private String label;
+    private String value;
+    private String type;
+}

+ 2 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceInvoiceDTO.java

@@ -421,5 +421,7 @@ public class CwFinanceInvoiceDTO extends BaseDTO {
     private String isSmsNotice;//是否短信通知
     private String isOmsBilling; //是否手动开票
 
+    private String invoiceTaskId;//开票任务id
+
     private static final long serialVersionUID = 1L;
 }

+ 139 - 0
jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/util/DingTalkUtil.java

@@ -0,0 +1,139 @@
+package com.jeeplus.finance.invoice.util;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiGettokenRequest;
+import com.dingtalk.api.request.OapiMessageCorpconversationAsyncsendV2Request;
+import com.dingtalk.api.request.OapiV2UserCreateRequest;
+import com.dingtalk.api.response.OapiGettokenResponse;
+import com.dingtalk.api.response.OapiMessageCorpconversationAsyncsendV2Response;
+import com.dingtalk.api.response.OapiV2UserCreateResponse;
+import com.jeeplus.common.TokenProvider;
+import com.jeeplus.finance.invoice.service.dto.CwFinanceInvoiceDTO;
+import com.jeeplus.sys.feign.IRoleApi;
+import com.jeeplus.sys.feign.IUserApi;
+import com.jeeplus.sys.service.dto.RoleDTO;
+import com.jeeplus.sys.service.dto.UserDTO;
+import com.taobao.api.ApiException;
+import org.apache.poi.util.StringUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import java.net.URLEncoder;
+import java.util.List;
+
+/**
+ * @Author hgc
+ * @Date 2026/3/12/9:44
+ * @ClassName DingTalkUtil
+ * @Description
+ */
+@Component
+public class DingTalkUtil {
+    private static String appKey;
+    private static String appSecret;
+    private static String agentId;
+    private static String accessTokenUrl;
+    private static String templateUrl;
+
+
+    @Value("${dingTalk.appKey:}")
+    public void setAppKey(String appKey) {
+        DingTalkUtil.appKey = appKey;
+    }
+
+    @Value("${dingTalk.appSecret:}")
+    public void setAppSecret(String appSecret) {
+        DingTalkUtil.appSecret = appSecret;
+    }
+
+    @Value("${dingTalk.agentId:}")
+    public void setAgentId(String agentId) {
+        DingTalkUtil.agentId = agentId;
+    }
+
+    @Value("${dingTalk.accessTokenUrl:}")
+    public void setAccessTokenUrl(String accessTokenUrl) {
+        DingTalkUtil.accessTokenUrl = accessTokenUrl;
+    }
+
+    @Value("${dingTalk.templateUrl:}")
+    public void setTemplateUrl(String templateUrl) {
+        DingTalkUtil.templateUrl = templateUrl;
+    }
+
+    private static String templateText =
+            "### 开票审批提醒\n\n" +
+                    "**实际开票单位:** %s\n\n" +
+                    "**发票金额(元):** %s\n\n" +
+                    "**申请人:** %s\n\n" +
+                    "---\n\n" +
+                    "[点击进入审批](%s)";
+
+    public static String getToken(){
+        // 增加空值校验,避免NPE
+        if (appKey == null || appSecret == null || accessTokenUrl == null) {
+            System.err.println("钉钉配置未正确注入:appKey=" + appKey + ", appSecret=" + appSecret + ", accessTokenUrl=" + accessTokenUrl);
+            return null;
+        }
+
+        try {
+            DingTalkClient client = new DefaultDingTalkClient(accessTokenUrl);
+            OapiGettokenRequest req = new OapiGettokenRequest();
+            req.setAppkey(appKey);
+            req.setAppsecret(appSecret);
+            req.setHttpMethod("GET");
+            OapiGettokenResponse rsp = client.execute(req);
+            System.out.println("获取到钉钉AccessToken:" + rsp.getAccessToken());
+            return rsp.getAccessToken();
+        } catch (ApiException e) {  // 捕获具体异常,而非泛化Exception
+            System.err.println("获取钉钉AccessToken失败:" + e.getMessage());
+            e.printStackTrace();
+        } catch (Exception e) {
+            System.err.println("获取钉钉AccessToken出现未知异常:" + e.getMessage());
+            e.printStackTrace();
+        }
+        return null;
+    }
+    public static ResponseEntity addDingtalkNotice(CwFinanceInvoiceDTO cwFinanceInvoiceDTO) {
+        try {
+            RoleDTO roleDTO = SpringUtil.getBean(IRoleApi.class).getRoleDTOByName2("发票管理员");
+            List<UserDTO> notifiedPartyUsers =SpringUtil.getBean(IUserApi.class).findListByRoleId(roleDTO.getId());
+            UserDTO assigneeDTO = notifiedPartyUsers.get(0);
+
+            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
+            OapiMessageCorpconversationAsyncsendV2Request req = new OapiMessageCorpconversationAsyncsendV2Request();
+
+            String token = getToken();
+            req.setAgentId(Long.parseLong(agentId));
+            //接受审批通知的钉钉id
+            req.setUseridList(assigneeDTO.getDdId());
+            OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
+            msg.setMsgtype("markdown");
+            OapiMessageCorpconversationAsyncsendV2Request.Markdown markdown = new OapiMessageCorpconversationAsyncsendV2Request.Markdown();
+            markdown.setTitle("开票审批提醒");
+            String finalUrl = String.format(templateUrl, assigneeDTO.getDdId(),cwFinanceInvoiceDTO.getId());
+            String markdownText = String.format(
+                    templateText,
+                    cwFinanceInvoiceDTO.getBillingWorkplaceReal(),
+                    cwFinanceInvoiceDTO.getAccount(),
+                    cwFinanceInvoiceDTO.getBillingPeopleRealName(),
+                    finalUrl
+            );
+            markdown.setText(markdownText);
+            msg.setMarkdown(markdown);
+            req.setMsg(msg);
+            OapiMessageCorpconversationAsyncsendV2Response rsp = client.execute(req, token);
+            return ResponseEntity.ok(rsp.getBody());
+        }  catch (ApiException e) {
+            e.printStackTrace(); // 打印错误日志
+            return ResponseEntity.badRequest().body("接口异常,请重试!");
+        } catch (Exception e) {
+            e.printStackTrace(); // 打印错误日志
+            return ResponseEntity.badRequest().body("接口异常,请重试!");
+        }
+    }
+}

+ 6 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/DictApiImpl.java

@@ -62,4 +62,10 @@ public class DictApiImpl implements IDictApi {
     public void updateDictValue(String label, String value) {
         dictTypeService.updateDictValue(label, value);
     }
+
+    @Override
+    public String getRealTimeDictValue(String label) {
+        return dictTypeService.getRealTimeDictValue(label);
+    }
+
 }

+ 5 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/UserApiImpl.java

@@ -139,6 +139,11 @@ public class UserApiImpl implements IUserApi {
     }
 
     @Override
+    public UserDTO getByDdId(String ddId) {
+        return userService.getByDdId(ddId);
+    }
+
+    @Override
     public String getOpenIdsByIds(String userIds) {
         // 使用逗号分隔字符串并转换为数组
         String[] usersArray = userIds.split(",");

+ 4 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/DictValueMapper.java

@@ -21,4 +21,8 @@ public interface DictValueMapper extends BaseMapper <DictValue> {
     List<DictValue> getByName(@Param("dictName") String dictName);
 
     void updateDictValue(@Param("label")String label, @Param("value")String value);
+
+    String getRealTimeDictValue(@Param("label")String label);
+
+
 }

+ 3 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/UserMapper.java

@@ -41,6 +41,9 @@ public interface UserMapper extends BaseMapper <User> {
     @InterceptorIgnore(tenantLine = "true")
     UserDTO getByOpenId(@Param("openId") String openId);
 
+
+    @InterceptorIgnore(tenantLine = "true")
+    UserDTO getByDdId(@Param("ddId") String ddId);
     /**
      * 获取岗位列表
      *

+ 11 - 1
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/DictValueMapper.xml

@@ -19,6 +19,16 @@
           value = #{value},
           update_time = now()
           WHERE label = #{label} AND del_flag = 0
-
     </update>
+
+
+    <select id="getRealTimeDictValue" resultType="string">
+        select a.value
+        from sys_dict_value a
+        where a.label = #{label} and
+            a.del_flag = 0
+    </select>
+
+
+
 </mapper>

+ 6 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/UserMapper.xml

@@ -269,6 +269,7 @@
     <select id="findListByRole" resultMap="userResult">
         SELECT
         <include refid="userColumns"/>,
+        a.dd_id AS "ddId",
         r.id AS "roleDTO.id",
         r.name AS "roleDTO.name",
         r.en_name AS "roleDTO.enname"
@@ -733,6 +734,11 @@ select a.id, a.company_id as "companyDTO.id", a.office_id as "officeDTO.id", a.l
         login_name, password
         FROM sys_user a where open_id = #{openId}
     </select>
+    <select id="getByDdId" resultType="com.jeeplus.sys.service.dto.UserDTO">
+        SELECT
+            login_name, password
+        FROM sys_user a where dd_id = #{ddId}
+    </select>
     <select id="getAllOpenIds" resultType="java.lang.String">
         SELECT open_id FROM sys_user WHERE del_flag = 0 and open_id is not null
     </select>

+ 7 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/DictTypeService.java

@@ -84,4 +84,11 @@ public class DictTypeService extends ServiceImpl <DictTypeMapper, DictType> {
         dictValueService.updateDictValue ( label, value );
     }
 
+
+    public String  getRealTimeDictValue(String label) {
+      return  dictValueService.getRealTimeDictValue ( label );
+    }
+
+
+
 }

+ 5 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/DictValueService.java

@@ -32,5 +32,10 @@ public class DictValueService extends ServiceImpl <DictValueMapper, DictValue> {
         baseMapper.updateDictValue(label,value);
     }
 
+    public String getRealTimeDictValue(String label){
+        return baseMapper.getRealTimeDictValue(label);
+    }
+
+
 }
 

+ 4 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/UserService.java

@@ -86,6 +86,10 @@ public class UserService extends ServiceImpl<UserMapper, User> {
         return baseMapper.getByOpenId(openId);
     }
 
+    public UserDTO getByDdId(String ddId) {
+        return baseMapper.getByDdId(ddId);
+    }
+
     public List<String> getAllOpenIds() {
         return baseMapper.getAllOpenIds();
     }

+ 13 - 0
jeeplus-modules/jeeplus-xxl-job-executor-sample/src/main/java/com/xxl/job/executor/service/jobhandler/SampleXxlJob.java

@@ -1191,4 +1191,17 @@ public class SampleXxlJob {
         }
     }
 
+
+    /**
+     * 评估合同超过3个月未归档自动提醒
+     */
+    @XxlJob("getThreeMonthsUnarchivedContracts")
+    public void getThreeMonthsUnarchivedContracts() {
+        try {
+            SpringUtil.getBean(IAssessApi.class).getThreeMonthsUnarchivedContracts();
+        } catch (Exception e) {
+            logger.warn("调用失败,可能服务不存在", e);
+        }
+    }
+
 }