Просмотр исходного кода

开票审批钉钉通知功能

huangguoce 2 недель назад
Родитель
Сommit
63ba3250b5
25 измененных файлов с 569 добавлено и 9 удалено
  1. 5 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/DictApiFallbackFactory.java
  2. 5 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/UserApiFallbackFactory.java
  3. 8 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IDictApi.java
  4. 10 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IUserApi.java
  5. 108 0
      jeeplus-auth/src/main/java/com/jeeplus/auth/controller/LoginController.java
  6. 6 0
      jeeplus-auth/src/main/java/com/jeeplus/auth/model/LoginForm.java
  7. 2 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/mapper/xml/FinanceInvoiceMapper.xml
  8. 29 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/FinanceInvoiceService.java
  9. 1 1
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/service/dto/FinanceInvoiceDTO.java
  10. 134 0
      jeeplus-modules/jeeplus-assess/src/main/java/com/jeeplus/assess/invoice/utils/DingTalkUtil.java
  11. 29 4
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/controller/CwFinanceInvoiceController.java
  12. 3 1
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/mapper/xml/CwFinanceInvoiceMapper.xml
  13. 27 2
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/CwFinanceInvoiceService.java
  14. 10 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceDictDTO.java
  15. 2 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/service/dto/CwFinanceInvoiceDTO.java
  16. 139 0
      jeeplus-modules/jeeplus-finance/src/main/java/com/jeeplus/finance/invoice/util/DingTalkUtil.java
  17. 6 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/DictApiImpl.java
  18. 5 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/UserApiImpl.java
  19. 4 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/DictValueMapper.java
  20. 3 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/UserMapper.java
  21. 11 1
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/DictValueMapper.xml
  22. 6 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/UserMapper.xml
  23. 7 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/DictTypeService.java
  24. 5 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/DictValueService.java
  25. 4 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/UserService.java

+ 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("接口异常,请重试!");
+        }
+    }
+}

+ 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();
     }