Browse Source

资金预警需求:
1.动账提醒、日常报销、日常报销夜间、大额交易预警调度任务提交
2.日常报销、大额交易预警报表代码提交

lisheng 3 weeks ago
parent
commit
4f276facb1
22 changed files with 2242 additions and 0 deletions
  1. 75 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/CommonUtils.java
  2. 45 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/DateUtils.java
  3. 219 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/FeishuSendMessageUtils.java
  4. 135 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/constant/BeiBeTransDetailConstant.java
  5. 47 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/constant/BillConstant.java
  6. 47 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/constant/LargeTransConfigConstant.java
  7. 39 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/constant/MsgWarnConfigConstant.java
  8. 116 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/constant/MsgWarnTemplateConstant.java
  9. 62 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/entity/MsgWarnMessageInfo.java
  10. 34 0
      main/java/kd/cosmic/jkjt/tmc/bei/common/enums/MsgWarnTypeEnum.java
  11. 49 0
      main/java/kd/cosmic/jkjt/tmc/bei/formplugin/detail/MsgWarnConfigEdit.java
  12. 63 0
      main/java/kd/cosmic/jkjt/tmc/bei/formplugin/detail/TransDetailMsgWarnList.java
  13. 126 0
      main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/DailyReimQueryListDataPlugin.java
  14. 111 0
      main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/LargeTransQueryListDataPlugin.java
  15. 15 0
      main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/TransDetailMsgListDataPlugin.java
  16. 29 0
      main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/TransDetailMsgWarnReportPlugin.java
  17. 141 0
      main/java/kd/cosmic/jkjt/tmc/bei/task/AccChangeMsgWarnTask.java
  18. 173 0
      main/java/kd/cosmic/jkjt/tmc/bei/task/DailyReimMsgWarnTask.java
  19. 151 0
      main/java/kd/cosmic/jkjt/tmc/bei/task/DailyReimNightMsgWarnTask.java
  20. 238 0
      main/java/kd/cosmic/jkjt/tmc/bei/task/LargeTransMsgWarnTask.java
  21. 156 0
      main/java/kd/cosmic/jkjt/tmc/bei/task/OtherAccountMsgWarnTask.java
  22. 171 0
      main/java/kd/cosmic/jkjt/tmc/bei/task/PersonalFinanceMsgWarnTask.java

+ 75 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/CommonUtils.java

@@ -0,0 +1,75 @@
+package kd.cosmic.jkjt.tmc.bei.common;
+
+import kd.bos.algo.DataSet;
+import kd.bos.algo.Row;
+import kd.bos.entity.report.FilterInfo;
+import kd.bos.entity.report.FilterItemInfo;
+import kd.bos.entity.report.parser.QFilterParserFactory;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QFilter;
+import kd.bos.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public class CommonUtils {
+
+    private static final Log logger = LogFactory.getLog(CommonUtils.class);
+
+    /**
+     * 将金额(BigDecimal)转换为“万元”单位,保留1位小数
+     *
+     * @param amount 金额(单位:元)
+     * @return 转换后的万元字符串,如 "0.2万元"
+     */
+    public static String convertToTenThousandYuan(BigDecimal amount) {
+        if (amount == null) {
+            return "0万元";
+        }
+
+        // 转换为万元:除以 10000
+        BigDecimal tenThousand = amount.divide(new BigDecimal("10000"), 1, RoundingMode.HALF_UP);
+        return tenThousand.stripTrailingZeros().toPlainString() + "万元";
+    }
+
+    public static Set getFieldValue(DataSet ds, String fieldName){
+        Iterator<Row> it = ds.copy().iterator();
+        Set<Object> fieldValueSet = new HashSet<>();
+        while (it.hasNext()){
+            fieldValueSet.add(it.next().get(fieldName));
+        }
+        return fieldValueSet;
+    }
+
+    public static DataSet joinDataSetList(List<DataSet> dataSetList){
+        DataSet dataSet = null;
+        for(DataSet dt : dataSetList){
+            if(dataSet == null){
+                dataSet = dt;
+            }else{
+                dataSet = dt.union(dataSet);
+            }
+        }
+        return dataSet;
+    }
+
+    public static QFilter getQFilterFormInfo(FilterInfo filterInfo,String filterName,String qFilterName){
+        if(filterInfo != null){
+            FilterItemInfo filterItem = filterInfo.getFilterItem(filterName);
+            if(filterItem != null){
+                QFilter filter = QFilterParserFactory.createQFilterProcessor(filterItem.getCompareType())
+                        .buildQFilterByFilterItem(filterItem);
+                if(StringUtils.isNotEmpty(qFilterName)){
+                    filter.__setProperty(qFilterName);
+                    return filter;
+                }
+            }
+        }
+        return null;
+    }
+}

+ 45 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/DateUtils.java

@@ -0,0 +1,45 @@
+package kd.cosmic.jkjt.tmc.bei.common;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.Date;
+
+public class DateUtils {
+    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    private static final SimpleDateFormat DATE_FORMAT_DAY = new SimpleDateFormat("yyyy-MM-dd");
+
+    /**
+     * 获取 n 天前/后的指定小时的时间点
+     * @param days 偏移天数,如 -1 表示昨天,+1 表示明天
+     * @param hourOfDay 小时(24小时制),如 7 表示 7:00
+     * @return Date 对象
+     */
+    public static Date getDateByDayAndHour(int days, int hourOfDay) {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime targetTime = now
+                .plusDays(days)
+                .with(LocalTime.of(hourOfDay, 0, 0));
+
+        return Date.from(targetTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    /**
+     * 获取 n 分钟前的时间点
+     * @param minutes 分钟数
+     * @return Date 对象
+     */
+    public static Date getMinutesBefore(int minutes) {
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime minutesBefore = now.minusMinutes(minutes);
+        return Date.from(minutesBefore.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
+    public static String formatDate(Date date) {
+        return DATE_FORMAT.format(date);
+    }
+    public static String formatDateDay(Date date) {
+        return DATE_FORMAT_DAY.format(date);
+    }
+}

+ 219 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/FeishuSendMessageUtils.java

@@ -0,0 +1,219 @@
+package kd.cosmic.jkjt.tmc.bei.common;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.kingdee.bos.util.backport.Collections;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.utils.StringUtils;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.message.api.MessageChannels;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.servicehelper.workflow.MessageCenterServiceHelper;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.cosmic.jkjt.msg.feishu.FeishuUtil;
+import kd.cosmic.jkjt.msg.feishu.UUIDUtil;
+import kd.cosmic.jkjt.tmc.bei.common.entity.MsgWarnMessageInfo;
+
+import java.rmi.ConnectException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class FeishuSendMessageUtils {
+
+    private static final Log logger = LogFactory.getLog(FeishuSendMessageUtils.class);
+    /**
+     * 飞书发送消息集成地址
+     */
+    private static final String SENDMESSAGE_URL = getFeishuParams("SENDMESSAGE_URL");
+    /**
+     * 获取飞书用户id集成地址
+     */
+    private static final String GETUSERID_URL = getFeishuParams("GETUSERID_URL");
+    /**
+     * 应用id
+     * 注:飞书测试与正式环境的集成地址一致,通过应用id区分环境,所以飞书参数务必维护正确应用ID
+     */
+    private static final String APP_ID = getFeishuParams("APP_ID");
+    /**
+     * 应用密钥
+     */
+    private static final String APP_SERCRET = getFeishuParams("APP_SERCRET");
+
+
+    /**
+     * 发送飞书消息
+     * @param messageInfo
+     */
+    public static void sendFeishuMessage(MsgWarnMessageInfo messageInfo) {
+        //tenant_access_token 的最大有效期是 2 小时
+        String token = getToken();
+        sendFeishuMessage(messageInfo,token);
+    }
+
+    /**
+     * 获取飞书token
+     * @return
+     */
+    public static String getToken() {
+        String appId = getFeishuParams("APP_ID");
+        String appSercret = getFeishuParams("APP_SERCRET");
+        return FeishuUtil.getToken(appId, appSercret);
+    }
+
+    /**
+     * 发送飞书消息
+     * @param messageInfo
+     * @param token
+     */
+    public static void sendFeishuMessage(MsgWarnMessageInfo messageInfo , String token) {
+        boolean isSuccess = true;
+        StringBuffer desciption = new StringBuffer();
+        MessageInfo message = messageInfo.getMessageInfo();
+        // 修改消息类型,只发送云之家消息
+        message.setType(MessageInfo.TYPE_WARNING);
+        message.setNotifyType(MessageChannels.MC.getNumber());
+        // 发送消息通知消息中心
+        Map centerMsgResult = MessageCenterServiceHelper.batchSendMessages(Collections.singletonList(message));
+        if(centerMsgResult.containsKey("success") && centerMsgResult.containsKey("description")
+                && centerMsgResult.get("success") instanceof Boolean && !(Boolean) centerMsgResult.get("success")){
+            isSuccess = false;
+            desciption.append("消息中心发送失败:" + centerMsgResult.get("description")).append("\r\n");
+        }else{
+            desciption.append("消息中心发送成功").append("\r\n");;
+        }
+        List<Long> userIds = message.getUserIds();
+        DynamicObjectCollection userCol = QueryServiceHelper.query("bos_user", "phone,name", new QFilter("id", QCP.in, userIds).toArray());
+
+        String receiverNames = userCol.stream().map(r -> r.getString("name")).collect(Collectors.joining(";"));
+
+        List<String> userPhoneList = userCol.stream().map(r -> r.getString("phone")).collect(Collectors.toList());
+        for(String phone : userPhoneList){
+            String userId = getUserId(phone, token);
+            if(StringUtils.isNotEmpty(userId)){
+                JSONObject feishuRes = sendInteractiveMessage(messageInfo.getContent(), userId, token);
+
+                if(!"0".equals(feishuRes.getString("code"))){
+                    isSuccess = false;
+                    desciption.append("飞书消息发送失败:" + feishuRes.getString("msg"));
+                }else{
+                    desciption.append("飞书消息发送成功");
+                }
+            }
+        }
+
+        saveFeishuWarnMessageLog(isSuccess,messageInfo.getType(),desciption.toString(), messageInfo.getCompanyname(),
+                messageInfo.getUniqueKey(), receiverNames, message.getContent());
+    }
+
+    /**
+     * 发送飞书卡片消息
+     * @param msgContent 消息内容
+     * @param userid 接收用户id
+     * @param token
+     * @return
+     */
+    public static JSONObject sendInteractiveMessage(String msgContent, String userid, String token){
+        JSONObject requestObj = new JSONObject();
+        JSONObject responseObj = new JSONObject();
+        String uuid = "KD-" + UUIDUtil.generateUuid();
+        requestObj.put("receive_id", userid);
+        requestObj.put("msg_type","interactive");
+        requestObj.put("uuid", uuid);
+        requestObj.put("content", msgContent);
+        String requestStr = JSON.toJSONString(requestObj);
+        try {
+            logger.info("飞书卡片消息集成请求{}" , requestStr);
+            String feishuResponse = FeishuUtil.doPostByHttpClient(SENDMESSAGE_URL, requestStr,true,token, 3);
+            logger.info("飞书卡片消息集成响应{}" , feishuResponse);
+            return JSONObject.parseObject(feishuResponse);
+        } catch (Exception e) {
+            responseObj.put("code",-1);
+            responseObj.put("msg",String.format("飞书卡片消息集成异常【%s】",e.getMessage()));
+            logger.error("飞书卡片消息集成异常",e);
+        }
+        return responseObj;
+    }
+
+    public static String getUserId(String cell,String token) {
+        String user_id = "";
+        JSONObject m = new JSONObject();
+        JSONArray mobiles = new JSONArray();
+        mobiles.add(cell);
+        m.put("mobiles", mobiles);
+        try {
+            logger.info("获取用户json: " + m.toString());
+            String response = FeishuUtil.doPostByHttpClient(GETUSERID_URL, m.toJSONString(),true,token, 3);
+            JSONObject userRes = JSONObject.parseObject(response);
+            user_id = userRes.getJSONObject("data").getJSONArray("user_list").getJSONObject(0).getString("user_id");
+            logger.info("获取用户结束json: " + userRes.toString());
+        } catch (ConnectException e) {
+            // TODO Auto-generated catch block
+            logger.info(e.getMessage());
+            e.printStackTrace();
+        }
+        return user_id;
+    }
+
+    /**
+     * 获取飞书参数
+     * @param key
+     * @return
+     */
+    public static String getFeishuParams(String key){
+        DynamicObject paramInfo = QueryServiceHelper.queryOne("nckd_message_feishu",
+                "nckd_params",
+                new QFilter[]{new QFilter("number", QCP.equals, key)});
+        if(paramInfo != null){
+            return paramInfo.getString("nckd_params");
+        }
+        return "";
+    }
+
+    /**
+     * 删除已经发送的飞书消息
+     * @param uniqueKeyList
+     */
+    public static void removeSendedFeishuMessage(List<String> uniqueKeyList) {
+        List<String> sendUniqueKeyList = QueryServiceHelper.query("nckd_msgwarnlog",
+                        "nckd_uniquekey",
+                        new QFilter[]{new QFilter("nckd_uniquekey", QCP.equals, uniqueKeyList)})
+                .stream()
+                .map(r -> r.getString("nckd_uniquekey"))
+                .collect(Collectors.toList());
+        uniqueKeyList.removeAll(sendUniqueKeyList);
+    }
+
+    /**
+     * 保存飞书消息发送日志
+     * @param isSuccess 是否成功
+     * @param type 通知场景
+     * @param nckd_description 描述
+     * @param companyname 公司名称
+     * @param uniqueKey 唯一值,每种场景的唯一值规则不一样
+     * @param receiver 消息接收人
+     * @param content 消息内容,会做截取,无需记录详细完整的日志
+     */
+    public static void saveFeishuWarnMessageLog(boolean isSuccess,String type, String nckd_description,String companyname,
+                                                String uniqueKey, String receiver, String content) {
+        DynamicObject msgWarnLog = BusinessDataServiceHelper.newDynamicObject("nckd_msgwarnlog");
+        msgWarnLog.set("nckd_type", type);
+        msgWarnLog.set("nckd_companyname", companyname);
+        msgWarnLog.set("nckd_unique_key", uniqueKey);
+        msgWarnLog.set("nckd_receiver", receiver);
+        msgWarnLog.set("nckd_content", StringUtils.substring(content, 0,200));
+        msgWarnLog.set("nckd_sendtime", new Date());
+        msgWarnLog.set("nckd_success", isSuccess?"1":"0");
+        msgWarnLog.set("nckd_description", StringUtils.substring(nckd_description, 0,200));
+
+        SaveServiceHelper.save(new DynamicObject[]{msgWarnLog});
+    }
+}

+ 135 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/constant/BeiBeTransDetailConstant.java

@@ -0,0 +1,135 @@
+package kd.cosmic.jkjt.tmc.bei.common.constant;
+
+/**
+ * 交易明细常量接口
+ */
+public interface BeiBeTransDetailConstant extends BillConstant {
+
+    // 实体 ID
+    String ENTITYID = "bei_betransdetail_imp";
+
+    /**
+     * 交易明细编号
+     */
+    String KEY_BILLNO = "billno";
+
+    /**
+     * 交易日期
+     */
+    String KEY_BIZDATE = "bizdate";
+
+    /**
+     * 交易时间
+     */
+    String KEY_BIZTIME = "biztime";
+
+    /**
+     * 币别
+     */
+    String KEY_CURRENCY = "currency";
+
+    /**
+     * 金额
+     */
+    String KEY_AMOUNT = "amount";
+
+    /**
+     * 金额折成本币
+     */
+    String KEY_LOCAMT = "locamt";
+
+    /**
+     * 汇率
+     */
+    String KEY_EXCHANGERATE = "exchangerate";
+
+    /**
+     * 资金组织
+     */
+    String KEY_COMPANY = "company";
+
+    /**
+     * 摘要
+     */
+    String KEY_DESCRIPTION = "description";
+
+    /**
+     * 银行账号
+     */
+    String KEY_ACCOUNTBANK = "accountbank";
+
+    /**
+     * 开户银行
+     */
+    String KEY_BANK = "bank";
+
+    /**
+     * 业务参考号
+     */
+    String KEY_BIZREFNO = "bizrefno";
+
+    /**
+     * 付款金额
+     */
+    String KEY_DEBITAMOUNT = "debitamount";
+
+    /**
+     * 收款金额
+     */
+    String KEY_CREDITAMOUNT = "creditamount";
+
+    /**
+     * 对方户名
+     */
+    String KEY_OPPUNIT = "oppunit";
+
+    /**
+     * 对方账号
+     */
+    String KEY_OPPBANKNUMBER = "oppbanknumber";
+
+    /**
+     * 对方开户行
+     */
+    String KEY_OPPBANK = "oppbank";
+
+    /**
+     * 是否跟电子回单匹配
+     */
+    String KEY_ISMATCHERECEIPT = "ismatchereceipt";
+
+    /**
+     * 是否导入
+     */
+    String KEY_ISDATAIMPORT = "isdataimport";
+
+    /**
+     * 明细流水号
+     */
+    String KEY_DETAILID = "detailid";
+
+    /**
+     * 是否确认无回单
+     */
+    String KEY_ISNORECEIPT = "isnoreceipt";
+
+    /**
+     * 电子回单关联标记
+     */
+    String KEY_RECEIPTNO = "receiptno";
+
+    /**
+     * 数据来源
+     */
+    String KEY_DATASOURCE = "datasource";
+
+    /**
+     * 业务类型
+     */
+    String KEY_BITYPE = "bitype";
+
+    /**
+     * 是否退票
+     */
+    String KEY_ISREFUND = "isrefund";
+}

+ 47 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/constant/BillConstant.java

@@ -0,0 +1,47 @@
+package kd.cosmic.jkjt.tmc.bei.common.constant;
+
+public interface BillConstant {
+
+    /**
+     * 编码
+     */
+    String ID = "id";
+    /**
+     * 编码
+     */
+    String KEY_NUMBER = "number";
+
+    /**
+     * 编码
+     */
+    String KEY_BILLNO = "billno";
+
+    /**
+     * 名称
+     */
+    String KEY_NAME = "name";
+    /**
+     * 状态
+     */
+    String KEY_STATUS = "status";
+    /**
+     * 是否可用
+     */
+    String KEY_ENABLE = "enable";
+
+    /**
+     * 单据状态
+     */
+    String KEY_BILLSTATUS = "billstatus";
+
+    String KEY_MODIFIER = "modifier"; // 修改人
+    String KEY_CREATETIME = "createtime"; // 创建时间
+    String KEY_MODIFYTIME = "modifytime"; // 修改时间
+
+    String KEY_CREATOR = "creator"; // 创建人
+
+    String KEY_MASTERID = "masterid"; // 主数据内部码
+
+    String KEY_FBASEDATAID = "fbasedataid";
+
+}

+ 47 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/constant/LargeTransConfigConstant.java

@@ -0,0 +1,47 @@
+package kd.cosmic.jkjt.tmc.bei.common.constant;
+
+/**
+ * 大额配置常量接口
+ */
+public interface LargeTransConfigConstant extends BillConstant {
+
+    // 实体 ID
+    String ENTITYID = "nckd_largetransconfig";
+
+    /**
+     * 基本信息(fs_baseinfo)
+     */
+    String KEY_NCKD_COMPANY = "nckd_company"; // 组织
+
+    /**
+     * 预警规则(nckd_ruleruleentry)
+     */
+    interface RULEENTRY {
+        String ENTITYID = "nckd_ruleentry";
+
+        /**
+         * 最小金额
+         */
+        String KEY_NCKD_AMOUNT_LOWER = "nckd_amount_lower";
+
+        /**
+         * 最大金额
+         */
+        String KEY_NCKD_AMOUNT_HIGH = "nckd_amount_high";
+
+        /**
+         * 消息接收人
+         */
+        String KEY_NCKD_RECEIVER = "nckd_receiver";
+
+        /**
+         * 规则描述
+         */
+        String KEY_NCKD_DESCRIPTION = "nckd_description";
+
+        /**
+         * 规则名称
+         */
+        String KEY_NCKD_RULENAME = "nckd_rulename";
+    }
+}

+ 39 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/constant/MsgWarnConfigConstant.java

@@ -0,0 +1,39 @@
+package kd.cosmic.jkjt.tmc.bei.common.constant;
+
+/**
+ * 消息预警配置常量接口
+ */
+public interface MsgWarnConfigConstant extends BillConstant {
+
+    String ENTITYID = "nckd_msgwarnconfig";
+
+    /**
+     * 通知场景
+     */
+    String KEY_NCKD_TYPE = "nckd_type";
+
+    /**
+     * 组织
+     */
+    String KEY_NCKD_COMPANY = "nckd_company";
+
+    /**
+     * 财务负责人
+     */
+    String KEY_NCKD_FINANCE_DIRECTOR = "nckd_finance_director";
+
+    /**
+     * 财务分管领导
+     */
+    String KEY_NCKD_FINANCE_LEADER = "nckd_finance_leader";
+
+    /**
+     * 单位负责人
+     */
+    String KEY_NCKD_UNIT_DIRECTOR = "nckd_unit_director";
+
+    /**
+     * 上级单位监管人员
+     */
+    String KEY_NCKD_UNIT_LEADER = "nckd_unit_leader";
+}

+ 116 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/constant/MsgWarnTemplateConstant.java

@@ -0,0 +1,116 @@
+package kd.cosmic.jkjt.tmc.bei.common.constant;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+public class MsgWarnTemplateConstant {
+
+
+    /** 飞书消息模板 */
+    public static final String TEMPLATESTR = "{\"schema\":\"2.0\",\"config\":{\"update_multi\":true,\"style\":{\"text_size\":{\"normal_v2\":{\"default\":\"normal\",\"pc\":\"normal\",\"mobile\":\"heading\"}}}},\"body\":{\"direction\":\"vertical\",\"padding\":\"12px 12px 12px 12px\",\"elements\":[{\"tag\":\"div\",\"text\":{\"tag\":\"plain_text\",\"content\":\"付款信息\",\"text_size\":\"normal_v2\",\"text_align\":\"left\",\"text_color\":\"default\"},\"margin\":\"0px 0px 0px 0px\"}]},\"header\":{\"title\":{\"tag\":\"plain_text\",\"content\":\"\"},\"subtitle\":{\"tag\":\"plain_text\",\"content\":\"消息发送\"},\"template\":\"blue\",\"padding\":\"12px 12px 12px 12px\"}}";
+
+    // 收款通知模板
+
+
+
+    /** 大额转账消息格式化
+     * @param payerAccount   付方账户
+     * @param payerAccountId 付方账号
+     * @param payerBank      付方开户行
+     * @param amount         支出金额
+     * @param receiver       收方
+     * @param description    摘要
+     * @param time           时间
+     * @return               格式化后的字符串
+     */
+    public static String formatLargeTransMessage(String payerAccount, String payerAccountId, String payerBank, String amount,
+            String receiver, String description, String time) {
+        String MSGTEMPLATE_LARGETRANS = new StringBuffer()
+                .append("收款方:").append("%s").append("\r\n")
+                .append("支出金额:").append("%s").append("\r\n")
+                .append("付款方账户:").append("%s").append("\r\n")
+                .append("付款方账号:").append("%s").append("\r\n")
+                .append("付款方开户行:").append("%s").append("\r\n")
+                .append("摘要:").append("%s").append("\r\n")
+                .append("时间:").append("%s").append("\r\n")
+                .toString();
+        return String.format(MSGTEMPLATE_LARGETRANS,receiver,amount,payerAccount, payerAccountId, payerBank, description, time);
+    }
+
+    public static String formatDailyMessage(String payerAccount, String payerAccountId, String payerBank,
+                                                          String receiver, String paymentCount, String totalAmount) {
+        String MSGTEMPLATE_PAYMENT_NOTIFICATION = new StringBuffer()
+                .append("收款方:").append("%s").append("\r\n")
+                .append("合计金额:").append("%s").append("\r\n")
+                .append("付款方账户:").append("%s").append("\r\n")
+                .append("付款方账号:").append("%s").append("\r\n")
+                .append("付款方开户行:").append("%s").append("\r\n")
+                .append("付款笔数:").append("%s").append("\r\n")
+                .toString();
+
+        return String.format(MSGTEMPLATE_PAYMENT_NOTIFICATION,
+                receiver,
+                totalAmount,
+                payerAccount,
+                payerAccountId,
+                payerBank,
+                paymentCount);
+    }
+
+    /** 付款通知消息格式化
+     * @param amount         支出金额
+     * @param payerAccount   付方账户
+     * @param payerAccountId 付方账号
+     * @param payerBank      付方开户行
+     * @param receiver       收方
+     * @param description    摘要
+     * @param time           时间
+     * @return               格式化后的字符串
+     */
+    public static String formatAccChangeDebitMessage(String amount, String payerAccount, String payerAccountId, String payerBank,
+                                              String receiver, String description, String time) {
+        String MSGTEMPLATE_ACCCHANGE_DEBIT = new StringBuffer()
+                .append("收款方:").append("%s").append("\r\n")
+                .append("转出金额:").append("%s").append("\r\n")
+                .append("付款方账户:").append("%s").append("\r\n")
+                .append("付款方账号:").append("%s").append("\r\n")
+                .append("付款方开户行:").append("%s").append("\r\n")
+                .append("摘要:").append("%s").append("\r\n")
+                .append("时间:").append("%s").append("\r\n")
+                .toString();
+        return String.format(MSGTEMPLATE_ACCCHANGE_DEBIT, receiver,amount, payerAccount, payerAccountId, payerBank, description, time);
+    }
+
+
+
+    /** 收款通知消息格式化
+     * @param amount         到账金额
+     * @param payerAccount   付方账户
+     * @param payerAccountId 付方账号
+     * @param payerBank      付方开户行
+     * @param receiver       收方
+     * @param description    摘要
+     * @param time           到账时间
+     * @return               格式化后的字符串
+     */
+    public static String formatAccChangeCreditMessage(String amount, String payerAccount, String payerAccountId, String payerBank,
+                                              String receiver, String description, String time) {
+        String MSGTEMPLATE_ACCCHANGE_CREDIT = new StringBuffer()
+                .append("收款方:").append("%s").append("\r\n")
+                .append("到账金额:").append("%s").append("\r\n")
+                .append("付款方账户:").append("%s").append("\r\n")
+                .append("付款方账号:").append("%s").append("\r\n")
+                .append("付款方开户行:").append("%s").append("\r\n")
+                .append("摘要:").append("%s").append("\r\n")
+                .append("到账时间:").append("%s").append("\r\n")
+                .toString();
+        return String.format(MSGTEMPLATE_ACCCHANGE_CREDIT, receiver,amount, payerAccount, payerAccountId, payerBank, description, time);
+    }
+
+    public static String getFeishuTemplate(String title , String content) {
+        JSONObject templateObj = JSON.parseObject(TEMPLATESTR);
+        templateObj.getJSONObject("header").getJSONObject("subtitle").put("content", title);
+        templateObj.getJSONObject("body").getJSONArray("elements").getJSONObject(0).getJSONObject("text").put("content", content);
+        return templateObj.toJSONString();
+    }
+}

+ 62 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/entity/MsgWarnMessageInfo.java

@@ -0,0 +1,62 @@
+package kd.cosmic.jkjt.tmc.bei.common.entity;
+
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+
+import java.io.Serializable;
+
+public class MsgWarnMessageInfo implements Serializable {
+    private String type;
+    private String companyname;
+    private String uniqueKey;
+
+    private String content;
+    private MessageInfo messageInfo;
+
+    public MsgWarnMessageInfo(String type, String companyname, String uniqueKey, String content, MessageInfo messageInfo) {
+        this.type = type;
+        this.companyname = companyname;
+        this.uniqueKey = uniqueKey;
+        this.content = content;
+        this.messageInfo = messageInfo;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getCompanyname() {
+        return companyname;
+    }
+
+    public void setCompanyname(String companyname) {
+        this.companyname = companyname;
+    }
+
+    public String getUniqueKey() {
+        return uniqueKey;
+    }
+
+    public void setUniqueKey(String uniqueKey) {
+        this.uniqueKey = uniqueKey;
+    }
+
+    public MessageInfo getMessageInfo() {
+        return messageInfo;
+    }
+
+    public void setMessageInfo(MessageInfo messageInfo) {
+        this.messageInfo = messageInfo;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+}

+ 34 - 0
main/java/kd/cosmic/jkjt/tmc/bei/common/enums/MsgWarnTypeEnum.java

@@ -0,0 +1,34 @@
+package kd.cosmic.jkjt.tmc.bei.common.enums;
+
+public enum MsgWarnTypeEnum {
+
+    ACCCHANGE("accchange", "动账提醒"),
+    LARGETRANS("largetrans", "账户大额交易预警"),
+    DAILYREIM("dailyreim", "日常报销账户月度提醒"),
+    DAILYREIMNIGHT("dailyreimnight", "日常报销账户夜间提醒"),
+    PERSONALFINANCE("personalfinance", "涉及个人金融的业务专用账户"),
+    OTHERACCOUNT("otheraccount", "其他账户"),
+    NONDIRECTCON("nondirectcon", "非直连付款");
+    private String value;
+    private String name ;
+
+    private MsgWarnTypeEnum(String value, String name) {
+        this.value = value;
+        this.name = name;
+    }
+    public String getValue() {
+        return value;
+    }
+    public String getName() {
+        return name;
+    }
+    public static MsgWarnTypeEnum getByValue(String value) {
+        for (MsgWarnTypeEnum item : MsgWarnTypeEnum.values()) {
+            if (item.getValue().equals(value)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+}

+ 49 - 0
main/java/kd/cosmic/jkjt/tmc/bei/formplugin/detail/MsgWarnConfigEdit.java

@@ -0,0 +1,49 @@
+package kd.cosmic.jkjt.tmc.bei.formplugin.detail;
+
+import kd.bos.entity.datamodel.events.PropertyChangedArgs;
+import kd.bos.form.plugin.AbstractFormPlugin;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.util.EventObject;
+
+public class MsgWarnConfigEdit extends AbstractFormPlugin implements MsgWarnConfigConstant {
+
+
+    @Override
+    public void afterBindData(EventObject e) {
+        super.afterBindData(e);
+        setReceiversible();
+    }
+
+    @Override
+    public void propertyChanged(PropertyChangedArgs e) {
+        super.propertyChanged(e);
+        String name = e.getProperty().getName();
+        if(KEY_NCKD_TYPE.equals(name)){
+            setReceiversible();
+        }
+    }
+
+    /**
+     * 设置接收人可见性
+     */
+    protected void setReceiversible() {
+        String type = (String)getModel().getValue(KEY_NCKD_TYPE);
+        // 显示财务负责人、财务分管领导,隐藏单位负责人、上级单位监管人员
+        if(MsgWarnTypeEnum.DAILYREIM.getValue().equals(type) || MsgWarnTypeEnum.DAILYREIMNIGHT.getValue().equals(type)
+            || MsgWarnTypeEnum.PERSONALFINANCE.getValue().equals(type)){
+            getView().setVisible(Boolean.TRUE, KEY_NCKD_FINANCE_DIRECTOR,KEY_NCKD_FINANCE_LEADER);
+            getView().setVisible(Boolean.FALSE, KEY_NCKD_UNIT_DIRECTOR,KEY_NCKD_UNIT_LEADER);
+        }
+        // 隐藏单位负责人
+        if(MsgWarnTypeEnum.NONDIRECTCON.getValue().equals(type)){
+            getView().setVisible(Boolean.TRUE, KEY_NCKD_FINANCE_DIRECTOR,KEY_NCKD_FINANCE_LEADER,KEY_NCKD_UNIT_LEADER);
+            getView().setVisible(Boolean.FALSE, KEY_NCKD_UNIT_DIRECTOR);
+        }
+        // 全部显示
+        if(MsgWarnTypeEnum.OTHERACCOUNT.getValue().equals(type)){
+            getView().setVisible(Boolean.TRUE, KEY_NCKD_FINANCE_DIRECTOR,KEY_NCKD_FINANCE_LEADER,KEY_NCKD_UNIT_DIRECTOR,KEY_NCKD_UNIT_LEADER);
+        }
+    }
+}

+ 63 - 0
main/java/kd/cosmic/jkjt/tmc/bei/formplugin/detail/TransDetailMsgWarnList.java

@@ -0,0 +1,63 @@
+package kd.cosmic.jkjt.tmc.bei.formplugin.detail;
+
+import com.alibaba.fastjson.JSON;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.dataentity.entity.LocaleString;
+import kd.bos.filter.CommonFilterColumn;
+import kd.bos.filter.FilterColumn;
+import kd.bos.form.events.FilterContainerInitArgs;
+import kd.bos.form.events.FilterContainerSearchClickArgs;
+import kd.bos.form.events.SetFilterEvent;
+import kd.bos.form.field.ComboItem;
+import kd.bos.list.IListView;
+import kd.bos.list.plugin.AbstractListPlugin;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.util.StringUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.LargeTransConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class TransDetailMsgWarnList extends AbstractListPlugin {
+    private static final String DEFUAL_ORG_CACHE_KEY = "defual_org";
+    private static final String KEY_FILTER_MSGTYPE = "msgtype";
+
+    @Override
+    public void filterContainerInit(FilterContainerInitArgs args) {
+        super.filterContainerInit(args);
+        String billFormId = ((IListView) this.getView()).getBillFormId();
+        List<FilterColumn> commonFilterColumns = args.getCommonFilterColumns();
+        CommonFilterColumn companyFilter = (CommonFilterColumn)commonFilterColumns.stream()
+                .filter(filterColumn -> filterColumn.getFieldName().equals("company.name")).findFirst().get();
+        // 动账提醒
+        if( "nckd_rpt_accchange".equals(billFormId) && companyFilter != null){
+            List<ComboItem> comboItems = companyFilter.getComboItems();
+            // 获取动账提醒组织
+            DynamicObjectCollection configCol = QueryServiceHelper.query("nckd_accchangerange", "id,nckd_company.id", new QFilter[]{
+                    new QFilter(LargeTransConfigConstant.KEY_STATUS, QFilter.equals, "C"),
+                    new QFilter(LargeTransConfigConstant.KEY_ENABLE, QFilter.equals, "1")
+            });
+            List<Long> orgList = configCol.stream().map(r -> r.getLong("nckd_company.id")).collect(Collectors.toList());
+            comboItems.removeIf(comboItem -> !orgList.contains(comboItem.getValue()));
+        }
+    }
+
+    @Override
+    public void setFilter(SetFilterEvent e) {
+        super.setFilter(e);
+        String billFormId = ((IListView) this.getView()).getBillFormId();
+        // 其他账户
+        if("nckd_rpt_other".equals(billFormId)){
+
+            return ;
+        }
+        // 非直连支付
+        if( "nckd_rpt_accchange".equals(billFormId)){
+
+        }
+    }
+}

+ 126 - 0
main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/DailyReimQueryListDataPlugin.java

@@ -0,0 +1,126 @@
+package kd.cosmic.jkjt.tmc.bei.report.plugin;
+
+import kd.bos.algo.Algo;
+import kd.bos.algo.DataSet;
+import kd.bos.algo.DataType;
+import kd.bos.algo.RowMeta;
+import kd.bos.entity.report.AbstractReportListDataPlugin;
+import kd.bos.entity.report.FilterInfo;
+import kd.bos.entity.report.FilterItemInfo;
+import kd.bos.entity.report.ReportQueryParam;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.cosmic.jkjt.tmc.bei.common.CommonUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+public class DailyReimQueryListDataPlugin extends AbstractReportListDataPlugin {
+
+    @Override
+    public DataSet query(ReportQueryParam reportQueryParam, Object o) throws Throwable {
+        FilterInfo filterInfo = reportQueryParam.getFilter();
+        List<QFilter> filterList = new ArrayList<>();
+        QFilter companyFilter = CommonUtils.getQFilterFormInfo(filterInfo, "nckd_filter_company", "company");
+        QFilter bizdateFilter = CommonUtils.getQFilterFormInfo(filterInfo, "nckd_filter_bizdate", "biztime");
+        filterList.add(companyFilter);
+        filterList.add(bizdateFilter);
+        filterList.add(QFilter.isNotNull("oppunit"));
+        filterList.add(new QFilter("debitamount",QCP.large_than, BigDecimal.ZERO));
+        filterList.add(new QFilter("accountbank.isdefaultpay",QCP.equals, true));
+
+        FilterItemInfo typeFilter = filterInfo.getFilterItem("nckd_filter_type");
+        String type = (String)Optional.ofNullable(typeFilter).map(FilterItemInfo::getValue).orElse("1");
+        QFilter bizTimeFilter = null;
+        // 日常报销
+        if("1".equals( type)){
+            bizTimeFilter = new QFilter("Hour(biztime)", QCP.large_equals,9).and(new QFilter("Hour(biztime)", QCP.less_than,21));
+            filterList.add(bizTimeFilter);
+            DataSet sumAmountData = querySumAmountDataSet(filterList);
+            DataSet sumTimesData = querySumTimesDataSet(filterList);
+            return sumAmountData.union(sumTimesData);
+        }else{
+            bizTimeFilter = new QFilter("Hour(biztime)", QCP.large_equals,21).and(new QFilter("Hour(biztime)", QCP.less_equals,24))
+                    .or(new QFilter("Hour(biztime)", QCP.less_than,9));
+            filterList.add(bizTimeFilter);
+            String selector = "id,company.id,oppunit,accountbank.id,debitamount,biztime";
+
+            DataSet dataSet = QueryServiceHelper.queryDataSet(this.getClass().getName(), BeiBeTransDetailConstant.ENTITYID,
+                    selector, filterList.toArray(new QFilter[0]), "");
+
+            // 通过公司、我方账号,对方账号分组,求和
+            DataSet amountDataSet = dataSet
+                    .groupBy(new String[]{"company.id", "accountbank.id", "oppunit"}).sum("debitamount").count("totaltimes").finish()
+                    .select("company.id,oppunit,accountbank.id,debitamount totalamount,totaltimes");
+            Set companyIdSet = CommonUtils.getFieldValue(amountDataSet, "company.id");
+            DataSet companyDataSet = QueryServiceHelper
+                    .queryDataSet(this.getClass().getName(), "bos_org", "id,name", new QFilter("id", QCP.in, companyIdSet).toArray(), "");
+            // 查询银行账户
+            Set accountBankIdSet = CommonUtils.getFieldValue(amountDataSet, "accountbank.id");
+            DataSet accountBankDataSet = QueryServiceHelper
+                    .queryDataSet(this.getClass().getName(), "bd_accountbanks", "id,bankaccountnumber,bank.name", new QFilter("id", QCP.in, accountBankIdSet).toArray(), "");
+            return amountDataSet
+                    .leftJoin(companyDataSet).on("company.id", "id")
+                    .select( new String[] {"company.id nckd_company", "oppunit nckd_oppunit", "accountbank.id accountbankid","totaltimes nckd_count","totalamount nckd_amount"},  new String[] {"name nckd_payaccount"}).finish()
+                    .leftJoin(accountBankDataSet).on("accountbankid", "id")
+                    .select(new String[] {"nckd_company", "nckd_oppunit", "nckd_payaccount","nckd_count","nckd_amount","'night' nckd_source"}, new String[]{"bankaccountnumber nckd_payaccbanknumber", "bank.name nckd_payaccbank"})
+                    .finish();
+        }
+    }
+
+    protected DataSet querySumAmountDataSet(List<QFilter> filterList){
+        String selector = "id,company.id,oppunit,accountbank.id,debitamount,biztime";
+
+        DataSet dataSet = QueryServiceHelper.queryDataSet(this.getClass().getName(), BeiBeTransDetailConstant.ENTITYID,
+                selector, filterList.toArray(new QFilter[0]), "");
+
+        // 通过公司、我方账号,对方账号分组,求和
+        DataSet amountDataSet = dataSet
+                .groupBy(new String[]{"company.id", "accountbank.id", "oppunit"}).sum("debitamount").count("totaltimes").finish()
+                .select("company.id,oppunit,accountbank.id,debitamount totalamount,totaltimes").filter("totalamount > 1000000");
+        Set companyIdSet = CommonUtils.getFieldValue(amountDataSet, "company.id");
+        DataSet companyDataSet = QueryServiceHelper
+                .queryDataSet(this.getClass().getName(), "bos_org", "id,name", new QFilter("id", QCP.in, companyIdSet).toArray(), "");
+        // 查询银行账户
+        Set accountBankIdSet = CommonUtils.getFieldValue(amountDataSet, "accountbank.id");
+        DataSet accountBankDataSet = QueryServiceHelper
+                .queryDataSet(this.getClass().getName(), "bd_accountbanks", "id,bankaccountnumber,bank.name", new QFilter("id", QCP.in, accountBankIdSet).toArray(), "");
+        return amountDataSet
+                .leftJoin(companyDataSet).on("company.id", "id")
+                .select( new String[] {"company.id nckd_company", "oppunit nckd_oppunit", "accountbank.id accountbankid","totaltimes nckd_count","totalamount nckd_amount"},  new String[] {"name nckd_payaccount"}).finish()
+                .leftJoin(accountBankDataSet).on("accountbankid", "id")
+                .select(new String[] {"nckd_company", "nckd_oppunit", "nckd_payaccount","nckd_count","nckd_amount","'amount' nckd_source"}, new String[]{"bankaccountnumber nckd_payaccbanknumber", "bank.name nckd_payaccbank"})
+                .finish();
+
+    }
+
+    protected DataSet querySumTimesDataSet(List<QFilter> filterList){
+        String selector = "id,company.id,oppunit,accountbank.id,debitamount,biztime";
+
+        DataSet dataSet = QueryServiceHelper.queryDataSet(this.getClass().getName(), BeiBeTransDetailConstant.ENTITYID,
+                selector, filterList.toArray(new QFilter[0]), "");
+
+        // 通过公司、我方账号,对方账号分组,求和
+        DataSet amountDataSet = dataSet
+                .groupBy(new String[]{"company.id", "accountbank.id", "oppunit"}).sum("debitamount").count("totaltimes").finish()
+                .select("company.id,oppunit,accountbank.id,debitamount totalamount,totaltimes").filter("totaltimes > 20");
+        Set companyIdSet = CommonUtils.getFieldValue(amountDataSet, "company.id");
+        DataSet companyDataSet = QueryServiceHelper
+                .queryDataSet(this.getClass().getName(), "bos_org", "id,name", new QFilter("id", QCP.in, companyIdSet).toArray(), "");
+        // 查询银行账户
+        Set accountBankIdSet = CommonUtils.getFieldValue(amountDataSet, "accountbank.id");
+        DataSet accountBankDataSet = QueryServiceHelper
+                .queryDataSet(this.getClass().getName(), "bd_accountbanks", "id,bankaccountnumber,bank.name", new QFilter("id", QCP.in, accountBankIdSet).toArray(), "");
+        return amountDataSet
+                .leftJoin(companyDataSet).on("company.id", "id")
+                .select( new String[] {"company.id nckd_company", "oppunit nckd_oppunit", "accountbank.id accountbankid","totaltimes nckd_count","totalamount nckd_amount"},  new String[] {"name nckd_payaccount"}).finish()
+                .leftJoin(accountBankDataSet).on("accountbankid", "id")
+                .select(new String[] {"nckd_company", "nckd_oppunit", "nckd_payaccount","nckd_count","nckd_amount","'times' nckd_source"}, new String[]{"bankaccountnumber nckd_payaccbanknumber", "bank.name nckd_payaccbank"})
+                .finish();
+    }
+}

+ 111 - 0
main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/LargeTransQueryListDataPlugin.java

@@ -0,0 +1,111 @@
+package kd.cosmic.jkjt.tmc.bei.report.plugin;
+
+import kd.bos.algo.DataSet;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.entity.report.AbstractReportListDataPlugin;
+import kd.bos.entity.report.FilterInfo;
+import kd.bos.entity.report.FilterItemInfo;
+import kd.bos.entity.report.ReportQueryParam;
+import kd.bos.entity.report.parser.QFilterParserFactory;
+import kd.bos.logging.BizLog;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.cosmic.jkjt.tmc.bei.common.CommonUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.LargeTransConfigConstant;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class LargeTransQueryListDataPlugin extends AbstractReportListDataPlugin {
+    @Override
+    public DataSet query(ReportQueryParam reportQueryParam, Object o) throws Throwable {
+        List<DataSet> dataSetList = new ArrayList<>();
+        FilterInfo filterInfo = reportQueryParam.getFilter();
+        // 获取公司的过滤条件
+        FilterItemInfo companyFilter = filterInfo.getFilterItem("nckd_filter_company");
+        Set<Long> largeTransConfig = getLargeTransConfig(companyFilter);
+        largeTransConfig.stream().forEach(companyId -> dataSetList.add(getCompanyTransDataSet(companyId)));
+        return CommonUtils.joinDataSetList(dataSetList);
+    }
+
+    protected DataSet getCompanyTransDataSet(Long largeTransIdConfig) {
+        List<DataSet> dataSetList = new ArrayList<>();
+        DynamicObject configInfo = BusinessDataServiceHelper.loadSingle(largeTransIdConfig, LargeTransConfigConstant.ENTITYID);
+        DynamicObject company = configInfo.getDynamicObject(LargeTransConfigConstant.KEY_NCKD_COMPANY);
+        // 增加互斥锁,防止多个任务同时执行
+        DynamicObjectCollection ruleEntryCol = configInfo.getDynamicObjectCollection(LargeTransConfigConstant.RULEENTRY.ENTITYID);
+        ruleEntryCol.stream().forEach(ruleEntry -> dataSetList.add(getCompanyRuleEntryDataSet(company, ruleEntry)));
+        return CommonUtils.joinDataSetList(dataSetList);
+    }
+
+    /**
+     * 根据规则匹配发送消息
+     * @param company
+     * @param ruleEntry
+     */
+    protected DataSet getCompanyRuleEntryDataSet(DynamicObject company , DynamicObject ruleEntry){
+        // 最小金额
+        BigDecimal lowerAmount = ruleEntry.getBigDecimal(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_AMOUNT_LOWER);
+        // 最大金额
+        BigDecimal highAmount = ruleEntry.getBigDecimal(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_AMOUNT_HIGH);
+        if(!ruleEntry.getBoolean("nckd_enable")){
+            BizLog.log("规则没有启用s,不查询数据。");
+            return null;
+        }
+        String ruleName = ruleEntry.getString(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_RULENAME);
+        DynamicObjectCollection receiverCol = ruleEntry.getDynamicObjectCollection(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_RECEIVER);
+        if(receiverCol == null || receiverCol.isEmpty()){
+            BizLog.log("消息接收人为空,不查询数据。");
+            return null;
+        }
+        // 查询公司下,时间范围内符合规则条件的数据
+        return getTransDetailDataSet(ruleName, company, lowerAmount, highAmount);
+    }
+
+    protected DataSet getTransDetailDataSet(String ruleName, DynamicObject company , BigDecimal lowerAmount , BigDecimal highAmount){
+
+        FilterInfo filterInfo = getQueryParam().getFilter();
+        // 获取交易日期过滤条件
+        FilterItemInfo biztimeFilter = filterInfo.getFilterItem("nckd_filter_bizdate");
+        QFilter bizTimeQFilter = QFilterParserFactory.createQFilterProcessor(biztimeFilter.getCompareType())
+                .buildQFilterByFilterItem(biztimeFilter);
+        bizTimeQFilter.__setProperty("biztime");
+
+        List<QFilter> filterList = new ArrayList<>();
+        filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_COMPANY, QFilter.equals, company.getPkValue()));
+        filterList.add(bizTimeQFilter);
+        if(lowerAmount != null && lowerAmount.compareTo(BigDecimal.ZERO) > 0){
+            filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_DEBITAMOUNT, QFilter.large_equals, lowerAmount));
+        }
+        if (highAmount != null && highAmount.compareTo(BigDecimal.ZERO) > 0){
+            filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_DEBITAMOUNT, QFilter.less_than, highAmount));
+        }
+        String selector = String.format("'%s' nckd_company , '%s' nckd_rulename , %s nckd_oppunit , %s nckd_amount , '%s' nckd_payaccount , %s nckd_payaccbanknumber , %s nckd_payaccbank , %s nckd_description , %s nckd_biztime",
+                company.getPkValue(), ruleName, "oppunit", "debitamount", company.getString("name"), "accountbank.bankaccountnumber", "bank.name", "description", "biztime");
+        return QueryServiceHelper.queryDataSet(this.getClass().getName() ,BeiBeTransDetailConstant.ENTITYID, selector, filterList.toArray(new QFilter[0]),"bizdate desc");
+    }
+
+    protected Set<Long> getLargeTransConfig(FilterItemInfo companyFilter) {
+
+        List<QFilter> filterList = new ArrayList<>();
+        filterList.add(new QFilter(LargeTransConfigConstant.KEY_STATUS, QFilter.equals, "C"));
+        filterList.add(new QFilter(LargeTransConfigConstant.KEY_ENABLE, QFilter.equals, "1"));
+        if(companyFilter != null){
+            QFilter companyQFilter = QFilterParserFactory.createQFilterProcessor(companyFilter.getCompareType())
+                    .buildQFilterByFilterItem(companyFilter);
+            filterList.add(companyQFilter);
+        }
+        DynamicObjectCollection configCol = QueryServiceHelper.query(LargeTransConfigConstant.ENTITYID, "id,nckd_company.id", filterList.toArray(new QFilter[0]));
+        Map<Long, Long> companyIdMap = configCol.stream()
+                .collect(Collectors.toMap(r -> r.getLong("nckd_company.id"), r -> r.getLong("id"), (a, b) -> a));
+        return companyIdMap.values().stream().collect(Collectors.toSet());
+    }
+
+}

+ 15 - 0
main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/TransDetailMsgListDataPlugin.java

@@ -0,0 +1,15 @@
+package kd.cosmic.jkjt.tmc.bei.report.plugin;
+
+import kd.bos.algo.DataSet;
+import kd.bos.entity.report.AbstractReportListDataPlugin;
+import kd.bos.entity.report.ReportQueryParam;
+
+import java.util.Map;
+
+public class TransDetailMsgListDataPlugin extends AbstractReportListDataPlugin {
+    @Override
+    public DataSet query(ReportQueryParam reportQueryParam, Object o) throws Throwable {
+        Map<String, Object> customParam = reportQueryParam.getCustomParam();
+        return null;
+    }
+}

+ 29 - 0
main/java/kd/cosmic/jkjt/tmc/bei/report/plugin/TransDetailMsgWarnReportPlugin.java

@@ -0,0 +1,29 @@
+package kd.cosmic.jkjt.tmc.bei.report.plugin;
+
+import kd.bos.entity.report.FilterInfo;
+import kd.bos.entity.report.ReportQueryParam;
+import kd.bos.form.control.events.FilterContainerInitEvent;
+import kd.bos.report.events.CreateFilterInfoEvent;
+import kd.bos.report.plugin.AbstractReportFormPlugin;
+
+public class TransDetailMsgWarnReportPlugin extends AbstractReportFormPlugin {
+    @Override
+    protected void filterContainerInit(FilterContainerInitEvent contInitEvent, ReportQueryParam queryParam) {
+        super.filterContainerInit(contInitEvent, queryParam);
+
+    }
+
+    @Override
+    public void beforeQuery(ReportQueryParam queryParam) {
+        super.beforeQuery(queryParam);
+        FilterInfo filter = queryParam.getFilter();
+        filter.getFilterItems().removeIf(filterItemInfo -> filterItemInfo.getPropName().equals("nckd_warntype"));
+    }
+
+    @Override
+    public void beforeCreateFilterInfo(CreateFilterInfoEvent event) {
+        super.beforeCreateFilterInfo(event);
+
+    }
+
+}

+ 141 - 0
main/java/kd/cosmic/jkjt/tmc/bei/task/AccChangeMsgWarnTask.java

@@ -0,0 +1,141 @@
+package kd.cosmic.jkjt.tmc.bei.task;
+
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+import kd.bos.logging.BizLog;
+import kd.bos.orm.ORM;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.schedule.api.StopTask;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.util.StringUtils;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.DateUtils;
+import kd.cosmic.jkjt.tmc.bei.common.FeishuSendMessageUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.LargeTransConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnTemplateConstant;
+import kd.cosmic.jkjt.tmc.bei.common.entity.MsgWarnMessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 动账提醒任务
+ */
+public class AccChangeMsgWarnTask extends AbstractTask implements StopTask {
+
+    // 预警执行时间间隔,默认查询1个小时内的数据
+    private int changeMinute = 1*60 ;
+    private static final MsgWarnTypeEnum WARTYPE = MsgWarnTypeEnum.ACCCHANGE;
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        Set<Long> largeTransIdConfig = getAccChangeRange();
+        if(map.containsKey("minute") && StringUtils.isNotEmpty((String) map.get("minute"))){
+            changeMinute = Integer.parseInt((String) map.get("minute"));
+        }
+
+        largeTransIdConfig.forEach(rangeId -> accChangeSendMessage(rangeId));
+    }
+
+    private void accChangeSendMessage(Long rangeId) {
+        DynamicObject accChangeRangeInfo = BusinessDataServiceHelper.loadSingle(rangeId, "nckd_accchangerange");
+        DynamicObject company = accChangeRangeInfo.getDynamicObject("nckd_company");
+        DynamicObjectCollection receiverCol = accChangeRangeInfo.getDynamicObjectCollection("nckd_receiver");
+        if(receiverCol == null || receiverCol.isEmpty()){
+            BizLog.log("消息接收人为空,不进行消息发送。");
+            return;
+        }
+        List<Long> receiverIdList = receiverCol.stream().map(receiver -> (Long) receiver.getDynamicObject("fbasedataid").getPkValue()).collect(Collectors.toList());
+        DynamicObjectCollection transDetailData = getTransDetailData(company);
+        if(transDetailData == null || transDetailData.isEmpty()){
+            BizLog.log("没有符合条件的数据,不进行消息发送。");
+            return;
+        }
+        BizLog.log("transDetailData size " + transDetailData.size());
+        for(DynamicObject transDetail : transDetailData){
+            String uniqueKey = String.join("-", MsgWarnTypeEnum.ACCCHANGE.getValue(),transDetail.getString("billno"));
+            if(ORM.create().exists("nckd_msgwarnlog",new QFilter("nckd_unique_key", QCP.equals,uniqueKey).toArray())){
+                // 消息如果已发送,就不在执行
+                continue;
+            }
+            String messageContent = getMcCenterMessage(transDetail);
+
+            MessageInfo messageInfo = new MessageInfo();
+            messageInfo.setTitle(String.join("-",MsgWarnTypeEnum.ACCCHANGE.getName()));
+            messageInfo.setUserIds(receiverIdList);
+            messageInfo.setContent(messageContent);
+            messageInfo.setTag(WARTYPE.getName());
+            messageInfo.setSenderId(RequestContext.get().getCurrUserId());
+            messageInfo.setEntityNumber(BeiBeTransDetailConstant.ENTITYID);
+            messageInfo.setBizDataId(transDetail.getLong("id"));
+
+            String feishuMessage = MsgWarnTemplateConstant.getFeishuTemplate(WARTYPE.getName(),messageContent);
+            MsgWarnMessageInfo warnMessageInfo = new MsgWarnMessageInfo(WARTYPE.getValue(),
+                    company.getString("name"),
+                    uniqueKey,
+                    feishuMessage,
+                    messageInfo);
+            FeishuSendMessageUtils.sendFeishuMessage(warnMessageInfo);
+        }
+
+    }
+
+
+    protected String getMcCenterMessage(DynamicObject transDetail) {
+        BigDecimal debitAmount = transDetail.getBigDecimal(BeiBeTransDetailConstant.KEY_DEBITAMOUNT);
+        BigDecimal creditAmount = transDetail.getBigDecimal(BeiBeTransDetailConstant.KEY_CREDITAMOUNT);
+
+        // 付款
+        if(debitAmount != null && debitAmount.compareTo(BigDecimal.ZERO) > 0){
+            return MsgWarnTemplateConstant.formatAccChangeDebitMessage(
+                    debitAmount.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString(),
+                    transDetail.getString("company.name"),
+                    transDetail.getString("accountbank.bankaccountnumber"),
+                    transDetail.getString("bank.name"),
+                    transDetail.getString(BeiBeTransDetailConstant.KEY_OPPUNIT),
+                    transDetail.getString(BeiBeTransDetailConstant.KEY_DESCRIPTION),
+                    DateUtils.formatDate(transDetail.getDate(BeiBeTransDetailConstant.KEY_BIZTIME)));
+
+        }else{
+            return MsgWarnTemplateConstant.formatAccChangeCreditMessage(
+                    creditAmount.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString(),
+                    transDetail.getString("company.name"),
+                    transDetail.getString("oppbanknumber"),
+                    transDetail.getString("oppbank"),
+                    transDetail.getString("accountbank.bankaccountnumber"),
+                    transDetail.getString(BeiBeTransDetailConstant.KEY_DESCRIPTION),
+                    DateUtils.formatDate(transDetail.getDate(BeiBeTransDetailConstant.KEY_BIZTIME)));
+        }
+    }
+
+    protected DynamicObjectCollection getTransDetailData(DynamicObject company) {
+        List<QFilter> filterList = new ArrayList<>();
+        filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_COMPANY, QFilter.equals, company.getPkValue()));
+        Date beginDate = DateUtils.getMinutesBefore(changeMinute);
+        filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_BIZTIME, QFilter.large_equals, beginDate));
+        // 从银企同步的流水
+        filterList.add(new QFilter("accountbank.issetbankinterface", QFilter.equals, true));
+
+        return QueryServiceHelper.query(this.getClass().getName() ,BeiBeTransDetailConstant.ENTITYID,
+                "id,billno,company.name,accountbank.name,accountbank.bankaccountnumber,bank.name,debitamount,creditamount,oppunit,description,bizdate,biztime",
+                filterList.toArray(new QFilter[0]),"bizdate desc");
+    }
+        //TODO
+    protected Set<Long> getAccChangeRange() {
+        DynamicObjectCollection configCol = QueryServiceHelper.query("nckd_accchangerange", "id,nckd_company.id", new QFilter[]{
+                new QFilter(LargeTransConfigConstant.KEY_STATUS, QFilter.equals, "C"),
+                new QFilter(LargeTransConfigConstant.KEY_ENABLE, QFilter.equals, "1")
+        });
+        Map<Long, Long> companyIdMap = configCol.stream()
+                .collect(Collectors.toMap(r -> r.getLong("nckd_company.id"), r -> r.getLong("id"), (a, b) -> a));
+        return companyIdMap.values().stream().collect(Collectors.toSet());
+    }
+}

+ 173 - 0
main/java/kd/cosmic/jkjt/tmc/bei/task/DailyReimMsgWarnTask.java

@@ -0,0 +1,173 @@
+package kd.cosmic.jkjt.tmc.bei.task;
+
+import kd.bos.algo.DataSet;
+import kd.bos.algo.Row;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.db.DB;
+import kd.bos.db.DBRoute;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.MainEntityType;
+import kd.bos.exception.KDException;
+import kd.bos.logging.BizLog;
+import kd.bos.orm.ORM;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.schedule.api.StopTask;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.CommonUtils;
+import kd.cosmic.jkjt.tmc.bei.common.DateUtils;
+import kd.cosmic.jkjt.tmc.bei.common.FeishuSendMessageUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnTemplateConstant;
+import kd.cosmic.jkjt.tmc.bei.common.entity.MsgWarnMessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 日常报销账户月度提醒
+ */
+public class DailyReimMsgWarnTask extends AbstractTask implements StopTask {
+    private MsgWarnTypeEnum WARTYPE = MsgWarnTypeEnum.DAILYREIM;
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        // 查询生效的账户大额交易通知配置
+        Set<Long> largeTransIdConfig = getMsgWarnConfig();
+        largeTransIdConfig.forEach(configId -> transConfigSendMessage(configId));
+    }
+
+    protected Set<Long> getMsgWarnConfig() {
+        DynamicObjectCollection configCol = QueryServiceHelper.query(MsgWarnConfigConstant.ENTITYID, "id,nckd_company.id", new QFilter[]{
+                new QFilter(MsgWarnConfigConstant.KEY_NCKD_TYPE, QFilter.equals, WARTYPE.getValue()),
+                new QFilter(MsgWarnConfigConstant.KEY_STATUS, QFilter.equals, "C"),
+                new QFilter(MsgWarnConfigConstant.KEY_ENABLE, QFilter.equals, "1")
+        });
+        Map<Long, Long> companyIdMap = configCol.stream()
+                .collect(Collectors.toMap(r -> r.getLong("nckd_company.id"), r -> r.getLong("id"), (a, b) -> a));
+        return companyIdMap.values().stream().collect(Collectors.toSet());
+    }
+
+    protected void transConfigSendMessage(Long configId) {
+        DynamicObject msgWarnConfig = BusinessDataServiceHelper.loadSingle(configId, MsgWarnConfigConstant.ENTITYID);
+        DynamicObject company = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_COMPANY);
+        DynamicObject financeDirectors = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_DIRECTOR);
+        DynamicObject financeLeader = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_LEADER);
+
+        // 没有设置接收人时跳过
+        if(financeDirectors == null || financeLeader == null){
+            BizLog.log("消息接收人为空,不进行消息发送。");
+            return;
+        }
+        List<Long> receiverIdList = new ArrayList<>();
+        receiverIdList.add(financeDirectors.getLong("id"));
+        receiverIdList.add(financeLeader.getLong("id"));
+
+        //每月首个工作日上午9点发送上月预警信息
+        LocalDate today = LocalDate.now();
+        // 上个月第一天
+        LocalDate firstDayOfLastMonth = today.minusMonths(13).withDayOfMonth(1);
+        // 本月第一天
+        LocalDate firstDayOfThisMonth = today.withDayOfMonth(1);
+        Date beginDate = Date.from(firstDayOfLastMonth.atStartOfDay().atZone(java.time.ZoneId.systemDefault()).toInstant());
+        Date endDate = Date.from(firstDayOfThisMonth.atStartOfDay().atZone(java.time.ZoneId.systemDefault()).toInstant());
+        // 获取公司下,发生笔数超过20笔的账户
+        // 最大交易次数默认20
+        int maxTimes = 20;
+        BigDecimal maxAmount = new BigDecimal("100000000");
+        DynamicObjectCollection transDetailData = getTransDetailData(company, beginDate, endDate,maxAmount,maxTimes);
+        if(transDetailData == null || transDetailData.isEmpty()){
+            BizLog.log("没有符合条件的数据,不进行消息发送。");
+            return;
+        }
+        for(DynamicObject transDetail : transDetailData){
+            String uniqueKey = String.join("-", WARTYPE.getValue(),transDetail.getString("foppunit"));
+            if(ORM.create().exists("nckd_msgwarnlog",new QFilter("nckd_unique_key", QCP.equals,uniqueKey).toArray())){
+                // 消息如果已发送,就不在执行
+                continue;
+            }
+            String messageContent = getMcCenterMessage(company.getString("name"),transDetail);
+
+            MessageInfo messageInfo = new MessageInfo();
+            messageInfo.setTitle(String.join("-",WARTYPE.getName()));
+            messageInfo.setUserIds(receiverIdList);
+            messageInfo.setContent(messageContent);
+            messageInfo.setTag(WARTYPE.getName());
+            messageInfo.setSenderId(RequestContext.get().getCurrUserId());
+            messageInfo.setEntityNumber("nckd_msgwarnconfig");
+            messageInfo.setBizDataId(configId);
+
+            String feishuMessage = MsgWarnTemplateConstant.getFeishuTemplate(WARTYPE.getName(),messageContent);
+            MsgWarnMessageInfo warnMessageInfo = new MsgWarnMessageInfo(WARTYPE.getValue(),
+                    company.getString("name"),
+                    uniqueKey,
+                    feishuMessage,
+                    messageInfo);
+            FeishuSendMessageUtils.sendFeishuMessage(warnMessageInfo);
+        }
+
+    }
+
+    protected String getMcCenterMessage(String payerAccount , DynamicObject transDetail) {
+        Long accountBankId = transDetail.getLong("faccountbankid");
+        DynamicObject accountBank = QueryServiceHelper.queryOne("bd_accountbanks", "bankaccountnumber,bank.name",
+                new QFilter("id", QCP.equals,accountBankId).toArray());
+        return MsgWarnTemplateConstant.formatDailyMessage(
+                payerAccount,
+                accountBank.getString("bankaccountnumber"),
+                accountBank.getString("bank.name"),
+                transDetail.getString("foppunit"),
+                transDetail.getString("totaltimes"),
+                transDetail.getBigDecimal("totalamount").setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+    }
+
+    protected DynamicObjectCollection getTransDetailData(DynamicObject company , Date beginDate , Date endDate,BigDecimal maxAmount,int maxTimes){
+        DataSet transDetailDataSet = getTransDetailDataSet(company, beginDate, endDate,maxAmount,maxTimes);
+        // 看报表与消息的兼容程度,再看怎么处理,暂时转成动态对象集合
+        return ORM.create().toPlainDynamicObjectCollection(transDetailDataSet.copy());
+    }
+
+    protected DataSet getTransDetailDataSet(DynamicObject company , Date beginDate , Date endDate,BigDecimal maxAmount,int maxTimes){
+        MainEntityType dt = EntityMetadataCache.getDataEntityType(BeiBeTransDetailConstant.ENTITYID);
+        Long defualtAccountBankId = getDefualtAccountBankId(company);
+
+        String dbRouteKey = dt.getDBRouteKey();
+        String sumTimesSql = "SELECT faccountbankid, foppunit, COUNT(1) totaltimes, SUM(fdebitAmount) totalamount , 'times' source FROM t_bei_transdetail "
+                + "WHERE fcompanyid = ? and faccountbankid = ? AND fbiztime > ? AND fbiztime < ? AND (HOUR(fbiztime) >= 9 AND HOUR(fbiztime) < 21) "
+                + "AND TRIM(foppunit) <> '' AND fdebitAmount != 0 "
+                + "GROUP BY faccountbankid, foppunit HAVING COUNT(1) > ?";
+
+        DataSet sumTimesDataSet = DB.queryDataSet(this.getClass().getName(), DBRoute.of(dbRouteKey), sumTimesSql,
+                new Object[]{company.getPkValue(),defualtAccountBankId, beginDate, endDate, maxTimes});
+        Set oppUnitSet = CommonUtils.getFieldValue(sumTimesDataSet, "foppunit");
+
+        String sumAmountSql = "SELECT faccountbankid, foppunit, COUNT(1) totaltimes, SUM(fdebitAmount) totalamount , 'amount' source FROM t_bei_transdetail "
+                + "WHERE fcompanyid = ? and faccountbankid = ? AND fbiztime > ? AND fbiztime < ? AND (HOUR(fbiztime) >= 9 AND HOUR(fbiztime) < 21) "
+                + "AND TRIM(foppunit) <> '' AND fdebitAmount != 0 "
+                + "GROUP BY faccountbankid, foppunit HAVING SUM(fdebitAmount) > ?";
+        DataSet sumAmountDataSet = DB.queryDataSet(this.getClass().getName(), DBRoute.of(dbRouteKey), sumAmountSql,
+                new Object[]{company.getPkValue(), defualtAccountBankId,beginDate, endDate, maxAmount});
+
+        HashMap<String, Object> params = new HashMap<>();
+        params.put("var", oppUnitSet);
+        DataSet filterSumAmountDataSet = sumAmountDataSet.filter("foppunit not in var", params);
+        return sumTimesDataSet.union(filterSumAmountDataSet);
+    }
+
+    protected Long getDefualtAccountBankId(DynamicObject company){
+        DynamicObject accountBankBill = QueryServiceHelper.queryOne("bd_accountbanks", "id", new QFilter[]{
+                new QFilter("company", QCP.equals, company.getPkValue()),
+                new QFilter("isdefaultpay", QCP.equals, true)
+        });
+        return accountBankBill == null ? null : accountBankBill.getLong("id");
+    }
+}

+ 151 - 0
main/java/kd/cosmic/jkjt/tmc/bei/task/DailyReimNightMsgWarnTask.java

@@ -0,0 +1,151 @@
+package kd.cosmic.jkjt.tmc.bei.task;
+
+import kd.bos.algo.DataSet;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.db.DB;
+import kd.bos.db.DBRoute;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.MainEntityType;
+import kd.bos.exception.KDException;
+import kd.bos.logging.BizLog;
+import kd.bos.orm.ORM;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.schedule.api.StopTask;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.CommonUtils;
+import kd.cosmic.jkjt.tmc.bei.common.DateUtils;
+import kd.cosmic.jkjt.tmc.bei.common.FeishuSendMessageUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnTemplateConstant;
+import kd.cosmic.jkjt.tmc.bei.common.entity.MsgWarnMessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 日常报销账户月度提醒
+ */
+public class DailyReimNightMsgWarnTask extends AbstractTask implements StopTask {
+    private MsgWarnTypeEnum WARTYPE = MsgWarnTypeEnum.DAILYREIMNIGHT;
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        // 查询生效的账户大额交易通知配置
+        Set<Long> largeTransIdConfig = getMsgWarnConfig();
+        largeTransIdConfig.forEach(configId -> transConfigSendMessage(configId));
+    }
+
+    protected Set<Long> getMsgWarnConfig() {
+        DynamicObjectCollection configCol = QueryServiceHelper.query(MsgWarnConfigConstant.ENTITYID, "id,nckd_company.id", new QFilter[]{
+                new QFilter(MsgWarnConfigConstant.KEY_NCKD_TYPE, QFilter.equals, WARTYPE.getValue()),
+                new QFilter(MsgWarnConfigConstant.KEY_STATUS, QFilter.equals, "C"),
+                new QFilter(MsgWarnConfigConstant.KEY_ENABLE, QFilter.equals, "1")
+        });
+        Map<Long, Long> companyIdMap = configCol.stream()
+                .collect(Collectors.toMap(r -> r.getLong("nckd_company.id"), r -> r.getLong("id"), (a, b) -> a));
+        return companyIdMap.values().stream().collect(Collectors.toSet());
+    }
+
+    protected void transConfigSendMessage(Long configId) {
+        DynamicObject msgWarnConfig = BusinessDataServiceHelper.loadSingle(configId, MsgWarnConfigConstant.ENTITYID);
+        DynamicObject company = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_COMPANY);
+        DynamicObject financeDirectors = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_DIRECTOR);
+        DynamicObject financeLeader = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_LEADER);
+
+        // 没有设置接收人时跳过
+        if(financeDirectors == null || financeLeader == null){
+            BizLog.log("消息接收人为空,不进行消息发送。");
+            return;
+        }
+        List<Long> receiverIdList = new ArrayList<>();
+        receiverIdList.add(financeDirectors.getLong("id"));
+        receiverIdList.add(financeLeader.getLong("id"));
+        // 获取昨天晚上九点到第二天早上九点的数据
+        Date beginDate = DateUtils.getDateByDayAndHour(-1, 21);
+        Date endDate = DateUtils.getDateByDayAndHour(0, 9);
+
+        DynamicObjectCollection transDetailData = getTransDetailData(company, beginDate, endDate,null,0);
+        if(transDetailData == null || transDetailData.isEmpty()){
+            BizLog.log("没有符合条件的数据,不进行消息发送。");
+            return;
+        }
+        for(DynamicObject transDetail : transDetailData){
+            String uniqueKey = String.join("-", WARTYPE.getValue(),transDetail.getString("foppunit"));
+            if(ORM.create().exists("nckd_msgwarnlog",new QFilter("nckd_unique_key", QCP.equals,uniqueKey).toArray())){
+                // 消息如果已发送,就不在执行
+                continue;
+            }
+            String messageContent = getMcCenterMessage(company.getString("name"),transDetail);
+
+            MessageInfo messageInfo = new MessageInfo();
+            messageInfo.setTitle(String.join("-",WARTYPE.getName()));
+            messageInfo.setUserIds(receiverIdList);
+            messageInfo.setContent(messageContent);
+            messageInfo.setTag(WARTYPE.getName());
+            messageInfo.setSenderId(RequestContext.get().getCurrUserId());
+            messageInfo.setEntityNumber("nckd_msgwarnconfig");
+            messageInfo.setBizDataId(configId);
+
+            String feishuMessage = MsgWarnTemplateConstant.getFeishuTemplate(WARTYPE.getName(),messageContent);
+            MsgWarnMessageInfo warnMessageInfo = new MsgWarnMessageInfo(WARTYPE.getValue(),
+                    company.getString("name"),
+                    uniqueKey,
+                    feishuMessage,
+                    messageInfo);
+            FeishuSendMessageUtils.sendFeishuMessage(warnMessageInfo);
+        }
+
+    }
+
+    protected String getMcCenterMessage(String payerAccount , DynamicObject transDetail) {
+        Long accountBankId = transDetail.getLong("faccountbankid");
+        DynamicObject accountBank = QueryServiceHelper.queryOne("bd_accountbanks", "bankaccountnumber,bank.name",
+                new QFilter("id", QCP.equals,accountBankId).toArray());
+        return MsgWarnTemplateConstant.formatDailyMessage(
+                payerAccount,
+                accountBank.getString("bankaccountnumber"),
+                accountBank.getString("bank.name"),
+                transDetail.getString("foppunit"),
+                transDetail.getString("totaltimes"),
+                transDetail.getBigDecimal("totalamount").setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+    }
+
+    protected DynamicObjectCollection getTransDetailData(DynamicObject company , Date beginDate , Date endDate,BigDecimal maxAmount,int maxTimes){
+        DataSet transDetailDataSet = getTransDetailDataSet(company, beginDate, endDate,maxAmount,maxTimes);
+        // 看报表与消息的兼容程度,再看怎么处理,暂时转成动态对象集合
+        return ORM.create().toPlainDynamicObjectCollection(transDetailDataSet.copy());
+    }
+
+    protected DataSet getTransDetailDataSet(DynamicObject company , Date beginDate , Date endDate,BigDecimal maxAmount,int maxTimes){
+        MainEntityType dt = EntityMetadataCache.getDataEntityType(BeiBeTransDetailConstant.ENTITYID);
+        Long defualtAccountBankId = getDefualtAccountBankId(company);
+
+        
+        String dbRouteKey = dt.getDBRouteKey();
+        String nightSql = "SELECT faccountbankid, foppunit, COUNT(1) totaltimes, SUM(fdebitAmount) totalamount , 'times' source FROM t_bei_transdetail "
+                + "WHERE fcompanyid = ? and faccountbankid = ? AND fbiztime >= ? AND fbiztime < ? "
+                + "AND TRIM(foppunit) <> '' AND fdebitAmount != 0 "
+                + "GROUP BY faccountbankid, foppunit";
+
+        return DB.queryDataSet(this.getClass().getName(), DBRoute.of(dbRouteKey), nightSql,
+                new Object[]{company.getPkValue(),defualtAccountBankId, beginDate, endDate});
+    }
+
+    protected Long getDefualtAccountBankId(DynamicObject company){
+        DynamicObject accountBankBill = QueryServiceHelper.queryOne("bd_accountbanks", "id", new QFilter[]{
+                new QFilter("company", QCP.equals, company.getPkValue()),
+                new QFilter("isdefaultpay", QCP.equals, true)
+        });
+        return accountBankBill == null ? null : accountBankBill.getLong("id");
+    }
+}

+ 238 - 0
main/java/kd/cosmic/jkjt/tmc/bei/task/LargeTransMsgWarnTask.java

@@ -0,0 +1,238 @@
+package kd.cosmic.jkjt.tmc.bei.task;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.algo.DataSet;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+import kd.bos.logging.BizLog;
+import kd.bos.orm.ORM;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.schedule.api.StopTask;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.CommonUtils;
+import kd.cosmic.jkjt.tmc.bei.common.DateUtils;
+import kd.cosmic.jkjt.tmc.bei.common.FeishuSendMessageUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.LargeTransConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnTemplateConstant;
+import kd.cosmic.jkjt.tmc.bei.common.entity.MsgWarnMessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ * 大额交易预警任务
+ */
+public class LargeTransMsgWarnTask extends AbstractTask implements StopTask {
+
+    private String taskType ;
+
+    private static final MsgWarnTypeEnum WARTYPE = MsgWarnTypeEnum.LARGETRANS;
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        // 查询生效的账户大额交易通知配置
+        Set<Long> largeTransIdConfig = getLargeTransConfig();
+
+        this.taskType = (String) map.get("taskType");
+
+        largeTransIdConfig.forEach(configId -> transConfigSendMessage(configId));
+    }
+
+    /**
+     * 根据账户大额交易通知配置,发送消息
+     * @param largeTransIdConfig
+     */
+    protected void transConfigSendMessage(Long largeTransIdConfig) {
+
+        DynamicObject configInfo = BusinessDataServiceHelper.loadSingle(largeTransIdConfig, LargeTransConfigConstant.ENTITYID);
+        DynamicObject company = configInfo.getDynamicObject(LargeTransConfigConstant.KEY_NCKD_COMPANY);
+        // 增加互斥锁,防止多个任务同时执行
+        DynamicObjectCollection ruleEntryCol = configInfo.getDynamicObjectCollection(LargeTransConfigConstant.RULEENTRY.ENTITYID);
+        ruleEntryCol.stream().forEach(ruleEntry -> sendMessageMatchRule(company, ruleEntry));
+    }
+
+    /**
+     * 根据规则匹配发送消息
+     * @param company
+     * @param ruleEntry
+     */
+    protected void sendMessageMatchRule(DynamicObject company , DynamicObject ruleEntry){
+        // 最小金额
+        BigDecimal lowerAmount = ruleEntry.getBigDecimal(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_AMOUNT_LOWER);
+        // 最大金额
+        BigDecimal highAmount = ruleEntry.getBigDecimal(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_AMOUNT_HIGH);
+        if(!ruleEntry.getBoolean("nckd_enable")){
+            return;
+        }
+        String ruleName = ruleEntry.getString(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_RULENAME);
+        DynamicObjectCollection receiverCol = ruleEntry.getDynamicObjectCollection(LargeTransConfigConstant.RULEENTRY.KEY_NCKD_RECEIVER);
+        if(receiverCol == null || receiverCol.isEmpty()){
+            BizLog.log("消息接收人为空,不进行消息发送。");
+            return;
+        }
+        List<Long> receiverIdList = receiverCol.stream().map(receiver -> (Long) receiver.getDynamicObject(LargeTransConfigConstant.KEY_FBASEDATAID).getPkValue()).collect(Collectors.toList());
+        // 查询公司下,时间范围内符合规则条件的数据
+        DynamicObjectCollection transDetailData = getTransDetailData(company, lowerAmount, highAmount);
+
+        if(transDetailData == null || transDetailData.isEmpty()){
+            BizLog.log("没有符合条件的数据,不进行消息发送。");
+            return;
+        }
+        for(DynamicObject transDetail : transDetailData){
+            String uniqueKey = String.join("-",WARTYPE.getValue(),transDetail.getString("billno"));
+//            if(ORM.create().exists("nckd_msgwarnlog",new QFilter("nckd_unique_key", QCP.equals,uniqueKey).toArray())){
+//                // 消息如果已发送,就不在执行
+//                continue;
+//            }
+            String messageContent = getMcCenterMessage(transDetail);
+
+            MessageInfo messageInfo = new MessageInfo();
+            messageInfo.setTitle(String.join("-",WARTYPE.getName(),ruleName));
+            messageInfo.setUserIds(receiverIdList);
+            messageInfo.setContent(messageContent);
+            messageInfo.setTag(WARTYPE.getName());
+            messageInfo.setSenderId(RequestContext.get().getCurrUserId());
+            messageInfo.setEntityNumber(BeiBeTransDetailConstant.ENTITYID);
+            messageInfo.setBizDataId(transDetail.getLong("id"));
+
+            String feishuMessage = MsgWarnTemplateConstant.getFeishuTemplate(WARTYPE.getName() + "-" + ruleName,messageContent);
+            MsgWarnMessageInfo warnMessageInfo = new MsgWarnMessageInfo(WARTYPE.getValue(),
+                    company.getString("name"),
+                    uniqueKey,
+                    feishuMessage,
+                    messageInfo);
+            FeishuSendMessageUtils.sendFeishuMessage(warnMessageInfo);
+        }
+    }
+
+    /**
+     * 获取飞书卡片消息
+     * @param transDetail
+     * @return
+     */
+    protected String getFeishuCardMessage(DynamicObject transDetail,String title){
+        BigDecimal debitAmount = transDetail.getBigDecimal(BeiBeTransDetailConstant.KEY_DEBITAMOUNT);
+        // 收款:我方账户是收款账户,对方账户是付款账户,付款则反之
+        if(debitAmount != null && debitAmount.compareTo(BigDecimal.ZERO) > 0){
+
+            return MsgWarnTemplateConstant.getFeishuTemplate("大额交易预警-" + title,getMcCenterMessage(transDetail));
+//            JSONObject dataObj = new JSONObject();
+//             dataObj.put("template_id",FeishuSendMessageUtils.getFeishuParams("LARGETRANS_MSGTEMPLATE_ID"));
+//            JSONObject variableObj = new JSONObject();
+//            // 为 variableObj 赋值
+//            variableObj.put("payerAccount", transDetail.getString("company.name")); // 付方账户
+//            variableObj.put("payerAccountNumber", transDetail.getString("accountbank.bankaccountnumber")); // 付方账号
+//            variableObj.put("payerBank", transDetail.getString("bank.name")); // 付方开户行
+//            variableObj.put("amount", CommonUtils.convertToTenThousandYuan(debitAmount)); // 支出金额
+//            variableObj.put("receiver", transDetail.getString(BeiBeTransDetailConstant.KEY_OPPUNIT)); // 收方
+//            variableObj.put("description", transDetail.getString(BeiBeTransDetailConstant.KEY_DESCRIPTION)); // 摘要
+//            variableObj.put("time", DateUtils.formatDate(transDetail.getDate(BeiBeTransDetailConstant.KEY_BIZTIME))); // 时间,格式化为字符串
+//
+//            dataObj.put("template_variable", variableObj);
+//
+//            JSONObject contentObj = new JSONObject();
+//            contentObj.put("type","template");
+//            contentObj.put("data",dataObj);
+//            return JSON.toJSONString(templateObj);
+        }
+        return "";
+    }
+
+
+    /**
+     * 获取mc中心发送的消息信息
+     * @param transDetail
+     * @return
+     */
+    protected String getMcCenterMessage(DynamicObject transDetail){
+        BigDecimal debitAmount = transDetail.getBigDecimal(BeiBeTransDetailConstant.KEY_DEBITAMOUNT);
+        // 收款:我方账户是收款账户,对方账户是付款账户,付款则反之
+        if(debitAmount != null && debitAmount.compareTo(BigDecimal.ZERO) > 0){
+            return MsgWarnTemplateConstant.formatLargeTransMessage(
+                    transDetail.getString("company.name"),
+                    transDetail.getString("accountbank.bankaccountnumber"),
+                    transDetail.getString("bank.name"),
+                    CommonUtils.convertToTenThousandYuan(debitAmount),
+                    transDetail.getString(BeiBeTransDetailConstant.KEY_OPPUNIT),
+                    transDetail.getString(BeiBeTransDetailConstant.KEY_DESCRIPTION),
+                    DateUtils.formatDate(transDetail.getDate(BeiBeTransDetailConstant.KEY_BIZTIME)));
+
+        }
+        return "";
+    }
+    /**
+     * 获取公司下,时间范围内符合规则条件数据
+     * @param company
+     * @param lowerAmount
+     * @param highAmount
+     * @return
+     */
+    protected DynamicObjectCollection getTransDetailData(DynamicObject company , BigDecimal lowerAmount , BigDecimal highAmount){
+        if("daytime".equals(taskType)){
+            // 查询当前7:00-17:00的数据
+            Date beginDate = DateUtils.getDateByDayAndHour(0, 7);
+            Date endDate = DateUtils.getDateByDayAndHour(0, 17);
+            return getTransDetailData(company, beginDate, endDate, lowerAmount, highAmount);
+        }else{
+            // 查询昨天17:00至次日7:00
+            Date beginDate = DateUtils.getDateByDayAndHour(-1, 17);
+            Date endDate = DateUtils.getDateByDayAndHour(0, 7);
+            return getTransDetailData(company, beginDate, endDate, lowerAmount, highAmount);
+        }
+    }
+
+
+    protected DynamicObjectCollection getTransDetailData(DynamicObject company , Date beginDate , Date endDate,
+                                                         BigDecimal lowerAmount , BigDecimal highAmount){
+        DataSet transDetailDataSet = getTransDetailDataSet(company, beginDate, endDate, lowerAmount, highAmount);
+        // 看报表与消息的兼容程度,再看怎么处理,暂时转成动态对象集合
+        return ORM.create().toPlainDynamicObjectCollection(transDetailDataSet.copy());
+    }
+
+    protected DataSet getTransDetailDataSet(DynamicObject company , Date beginDate , Date endDate ,
+                                         BigDecimal lowerAmount , BigDecimal highAmount){
+        List<QFilter> filterList = new ArrayList<>();
+        filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_COMPANY, QFilter.equals, company.getPkValue()));
+        if(beginDate != null){
+            filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_BIZTIME, QFilter.large_equals, beginDate));
+        }
+        if(endDate != null){
+            filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_BIZTIME, QFilter.less_than, endDate));
+        }
+        if(lowerAmount != null && lowerAmount.compareTo(BigDecimal.ZERO) > 0){
+            filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_DEBITAMOUNT, QFilter.large_equals, lowerAmount));
+        }
+        if (highAmount != null && highAmount.compareTo(BigDecimal.ZERO) > 0){
+            filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_DEBITAMOUNT, QFilter.less_than, highAmount));
+        }
+
+        return QueryServiceHelper.queryDataSet(this.getClass().getName() ,BeiBeTransDetailConstant.ENTITYID,
+                "id,billno,company.name,accountbank.name,accountbank.bankaccountnumber,bank.name,debitamount,oppunit,description,bizdate,biztime",
+                filterList.toArray(new QFilter[0]),"bizdate desc");
+    }
+
+
+    /**
+     * 获取大额交易配置,通过配置来执行调度
+     * @return
+     */
+    protected Set<Long> getLargeTransConfig() {
+        DynamicObjectCollection configCol = QueryServiceHelper.query(LargeTransConfigConstant.ENTITYID, "id,nckd_company.id", new QFilter[]{
+                new QFilter(LargeTransConfigConstant.KEY_STATUS, QFilter.equals, "C"),
+                new QFilter(LargeTransConfigConstant.KEY_ENABLE, QFilter.equals, "1")
+        });
+        Map<Long, Long> companyIdMap = configCol.stream()
+                .collect(Collectors.toMap(r -> r.getLong("nckd_company.id"), r -> r.getLong("id"), (a, b) -> a));
+        return companyIdMap.values().stream().collect(Collectors.toSet());
+    }
+}

+ 156 - 0
main/java/kd/cosmic/jkjt/tmc/bei/task/OtherAccountMsgWarnTask.java

@@ -0,0 +1,156 @@
+package kd.cosmic.jkjt.tmc.bei.task;
+
+import kd.bos.algo.DataSet;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.db.DB;
+import kd.bos.db.DBRoute;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.MainEntityType;
+import kd.bos.exception.KDException;
+import kd.bos.logging.BizLog;
+import kd.bos.orm.ORM;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.schedule.api.StopTask;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.CommonUtils;
+import kd.cosmic.jkjt.tmc.bei.common.DateUtils;
+import kd.cosmic.jkjt.tmc.bei.common.FeishuSendMessageUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnTemplateConstant;
+import kd.cosmic.jkjt.tmc.bei.common.entity.MsgWarnMessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 其他账户每日提醒提醒
+ */
+public class OtherAccountMsgWarnTask extends AbstractTask implements StopTask {
+    private MsgWarnTypeEnum WARTYPE = MsgWarnTypeEnum.OTHERACCOUNT;
+    private int changeMinute = 2*60 ;
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        // 查询生效的账户大额交易通知配置
+        Set<Long> largeTransIdConfig = getMsgWarnConfig();
+        largeTransIdConfig.forEach(configId -> transConfigSendMessage(configId));
+    }
+
+    protected Set<Long> getMsgWarnConfig() {
+        DynamicObjectCollection configCol = QueryServiceHelper.query(MsgWarnConfigConstant.ENTITYID, "id,nckd_company.id", new QFilter[]{
+                new QFilter(MsgWarnConfigConstant.KEY_NCKD_TYPE, QFilter.equals, WARTYPE.getValue()),
+                new QFilter(MsgWarnConfigConstant.KEY_STATUS, QFilter.equals, "C"),
+                new QFilter(MsgWarnConfigConstant.KEY_ENABLE, QFilter.equals, "1")
+        });
+        Map<Long, Long> companyIdMap = configCol.stream()
+                .collect(Collectors.toMap(r -> r.getLong("nckd_company.id"), r -> r.getLong("id"), (a, b) -> a));
+        return companyIdMap.values().stream().collect(Collectors.toSet());
+    }
+
+    protected void transConfigSendMessage(Long configId) {
+        DynamicObject msgWarnConfig = BusinessDataServiceHelper.loadSingle(configId, MsgWarnConfigConstant.ENTITYID);
+        DynamicObject company = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_COMPANY);
+        DynamicObject financeDirectors = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_DIRECTOR);
+        DynamicObject financeLeader = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_LEADER);
+
+        // 没有设置接收人时跳过
+        if(financeDirectors == null || financeLeader == null){
+            BizLog.log("消息接收人为空,不进行消息发送。");
+            return;
+        }
+        List<Long> receiverIdList = new ArrayList<>();
+        receiverIdList.add(financeDirectors.getLong("id"));
+        receiverIdList.add(financeLeader.getLong("id"));
+        // 获取昨天晚上九点到第二天早上九点的数据
+        Date beginDate = DateUtils.getMinutesBefore(changeMinute);
+        // 获取公司下,发生笔数超过20笔的账户
+        // 最大交易次数默认20
+        int maxTimes = 20;
+        BigDecimal maxAmount = new BigDecimal("100000000");
+        DynamicObjectCollection transDetailData = getTransDetailData(company, beginDate);
+        if(transDetailData == null || transDetailData.isEmpty()){
+            BizLog.log("没有符合条件的数据,不进行消息发送。");
+            return;
+        }
+        for(DynamicObject transDetail : transDetailData){
+            String uniqueKey = String.join("-", WARTYPE.getValue(),transDetail.getString("foppunit"));
+            if(ORM.create().exists("nckd_msgwarnlog",new QFilter("nckd_unique_key", QCP.equals,uniqueKey).toArray())){
+                // 消息如果已发送,就不在执行
+                continue;
+            }
+            String messageContent = getMcCenterMessage(company.getString("name"),transDetail);
+
+            MessageInfo messageInfo = new MessageInfo();
+            messageInfo.setTitle(String.join("-",WARTYPE.getName()));
+            messageInfo.setUserIds(receiverIdList);
+            messageInfo.setContent(messageContent);
+            messageInfo.setTag(WARTYPE.getName());
+            messageInfo.setSenderId(RequestContext.get().getCurrUserId());
+            messageInfo.setEntityNumber("nckd_msgwarnconfig");
+            messageInfo.setBizDataId(configId);
+
+            String feishuMessage = MsgWarnTemplateConstant.getFeishuTemplate(WARTYPE.getName(),messageContent);
+            MsgWarnMessageInfo warnMessageInfo = new MsgWarnMessageInfo(WARTYPE.getValue(),
+                    company.getString("name"),
+                    uniqueKey,
+                    feishuMessage,
+                    messageInfo);
+            FeishuSendMessageUtils.sendFeishuMessage(warnMessageInfo);
+        }
+
+    }
+
+    protected String getMcCenterMessage(String payerAccount , DynamicObject transDetail) {
+        Long accountBankId = transDetail.getLong("faccountbankid");
+        DynamicObject accountBank = QueryServiceHelper.queryOne("bd_accountbanks", "bankaccountnumber,bank.name",
+                new QFilter("id", QCP.equals,accountBankId).toArray());
+        return MsgWarnTemplateConstant.formatDailyMessage(
+                payerAccount,
+                accountBank.getString("bankaccountnumber"),
+                accountBank.getString("bank.name"),
+                transDetail.getString("foppunit"),
+                transDetail.getString("totaltimes"),
+                transDetail.getBigDecimal("totalamount").setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+    }
+
+    protected DynamicObjectCollection getTransDetailData(DynamicObject company , Date beginDate){
+        DataSet transDetailDataSet = getTransDetailDataSet(company, beginDate);
+        // 看报表与消息的兼容程度,再看怎么处理,暂时转成动态对象集合
+        return ORM.create().toPlainDynamicObjectCollection(transDetailDataSet.copy());
+    }
+
+    /**
+     * 获取账户交易明细
+     * 1.非日常报销账户
+     * 2.
+     * @param company
+     * @param beginDate
+     * @return
+     */
+    protected DataSet getTransDetailDataSet(DynamicObject company , Date beginDate){
+        List<QFilter> filterList = new ArrayList<>();
+        filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_COMPANY, QFilter.equals, company.getPkValue()));
+        if(beginDate != null){
+            filterList.add(new QFilter(BeiBeTransDetailConstant.KEY_BIZTIME, QFilter.large_equals, beginDate));
+        }
+        return QueryServiceHelper.queryDataSet(this.getClass().getName() ,BeiBeTransDetailConstant.ENTITYID,
+                "id,billno,company.name,accountbank.name,accountbank.bankaccountnumber,bank.name,debitamount,oppunit,description,bizdate,biztime",
+                filterList.toArray(new QFilter[0]),"bizdate desc");
+    }
+
+    protected Long getDefualtAccountBankId(DynamicObject company){
+        DynamicObject accountBankBill = QueryServiceHelper.queryOne("bd_accountbanks", "id", new QFilter[]{
+                new QFilter("company", QCP.equals, company.getPkValue()),
+                new QFilter("isdefaultpay", QCP.equals, true)
+        });
+        return accountBankBill == null ? null : accountBankBill.getLong("id");
+    }
+}

+ 171 - 0
main/java/kd/cosmic/jkjt/tmc/bei/task/PersonalFinanceMsgWarnTask.java

@@ -0,0 +1,171 @@
+package kd.cosmic.jkjt.tmc.bei.task;
+
+import kd.bos.algo.DataSet;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.db.DB;
+import kd.bos.db.DBRoute;
+import kd.bos.entity.EntityMetadataCache;
+import kd.bos.entity.MainEntityType;
+import kd.bos.exception.KDException;
+import kd.bos.logging.BizLog;
+import kd.bos.orm.ORM;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.schedule.api.StopTask;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.workflow.engine.msg.info.MessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.CommonUtils;
+import kd.cosmic.jkjt.tmc.bei.common.FeishuSendMessageUtils;
+import kd.cosmic.jkjt.tmc.bei.common.constant.BeiBeTransDetailConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnConfigConstant;
+import kd.cosmic.jkjt.tmc.bei.common.constant.MsgWarnTemplateConstant;
+import kd.cosmic.jkjt.tmc.bei.common.entity.MsgWarnMessageInfo;
+import kd.cosmic.jkjt.tmc.bei.common.enums.MsgWarnTypeEnum;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 涉及个人金融的业务专用账户提醒
+ */
+public class PersonalFinanceMsgWarnTask extends AbstractTask implements StopTask {
+    private MsgWarnTypeEnum WARTYPE = MsgWarnTypeEnum.DAILYREIM;
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        // 查询生效的账户大额交易通知配置
+        Set<Long> largeTransIdConfig = getMsgWarnConfig();
+        largeTransIdConfig.forEach(configId -> transConfigSendMessage(configId));
+    }
+
+    protected Set<Long> getMsgWarnConfig() {
+        DynamicObjectCollection configCol = QueryServiceHelper.query(MsgWarnConfigConstant.ENTITYID, "id,nckd_company.id", new QFilter[]{
+                new QFilter(MsgWarnConfigConstant.KEY_NCKD_TYPE, QFilter.equals, WARTYPE.getValue()),
+                new QFilter(MsgWarnConfigConstant.KEY_STATUS, QFilter.equals, "C"),
+                new QFilter(MsgWarnConfigConstant.KEY_ENABLE, QFilter.equals, "1")
+        });
+        Map<Long, Long> companyIdMap = configCol.stream()
+                .collect(Collectors.toMap(r -> r.getLong("nckd_company.id"), r -> r.getLong("id"), (a, b) -> a));
+        return companyIdMap.values().stream().collect(Collectors.toSet());
+    }
+
+    protected void transConfigSendMessage(Long configId) {
+        DynamicObject msgWarnConfig = BusinessDataServiceHelper.loadSingle(configId, MsgWarnConfigConstant.ENTITYID);
+        DynamicObject company = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_COMPANY);
+        DynamicObject financeDirectors = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_DIRECTOR);
+        DynamicObject financeLeader = msgWarnConfig.getDynamicObject(MsgWarnConfigConstant.KEY_NCKD_FINANCE_LEADER);
+
+        // 没有设置接收人时跳过
+        if(financeDirectors == null || financeLeader == null){
+            BizLog.log("消息接收人为空,不进行消息发送。");
+            return;
+        }
+        List<Long> receiverIdList = new ArrayList<>();
+        receiverIdList.add(financeDirectors.getLong("id"));
+        receiverIdList.add(financeLeader.getLong("id"));
+
+        //每月首个工作日上午9点发送上月预警信息
+        LocalDate today = LocalDate.now();
+        // 上个月第一天
+        LocalDate firstDayOfLastMonth = today.minusMonths(13).withDayOfMonth(1);
+        // 本月第一天
+        LocalDate firstDayOfThisMonth = today.withDayOfMonth(1);
+        Date beginDate = Date.from(firstDayOfLastMonth.atStartOfDay().atZone(java.time.ZoneId.systemDefault()).toInstant());
+        Date endDate = Date.from(firstDayOfThisMonth.atStartOfDay().atZone(java.time.ZoneId.systemDefault()).toInstant());
+        // 获取公司下,发生笔数超过20笔的账户
+        // 最大交易次数默认20
+        int maxTimes = 20;
+        BigDecimal maxAmount = new BigDecimal("100000000");
+        DynamicObjectCollection transDetailData = getTransDetailData(company, beginDate, endDate,maxAmount,maxTimes);
+        if(transDetailData == null || transDetailData.isEmpty()){
+            BizLog.log("没有符合条件的数据,不进行消息发送。");
+            return;
+        }
+        for(DynamicObject transDetail : transDetailData){
+            String uniqueKey = String.join("-", WARTYPE.getValue(),transDetail.getString("foppunit"));
+            if(ORM.create().exists("nckd_msgwarnlog",new QFilter("nckd_unique_key", QCP.equals,uniqueKey).toArray())){
+                // 消息如果已发送,就不在执行
+                continue;
+            }
+            String messageContent = getMcCenterMessage(company.getString("name"),transDetail);
+
+            MessageInfo messageInfo = new MessageInfo();
+            messageInfo.setTitle(String.join("-",WARTYPE.getName()));
+            messageInfo.setUserIds(receiverIdList);
+            messageInfo.setContent(messageContent);
+            messageInfo.setTag(WARTYPE.getName());
+            messageInfo.setSenderId(RequestContext.get().getCurrUserId());
+            messageInfo.setEntityNumber("nckd_msgwarnconfig");
+            messageInfo.setBizDataId(configId);
+
+            String feishuMessage = MsgWarnTemplateConstant.getFeishuTemplate(WARTYPE.getName(),messageContent);
+            MsgWarnMessageInfo warnMessageInfo = new MsgWarnMessageInfo(WARTYPE.getValue(),
+                    company.getString("name"),
+                    uniqueKey,
+                    feishuMessage,
+                    messageInfo);
+            FeishuSendMessageUtils.sendFeishuMessage(warnMessageInfo);
+        }
+
+    }
+
+    protected String getMcCenterMessage(String payerAccount , DynamicObject transDetail) {
+        Long accountBankId = transDetail.getLong("faccountbankid");
+        DynamicObject accountBank = QueryServiceHelper.queryOne("bd_accountbanks", "bankaccountnumber,bank.name",
+                new QFilter("id", QCP.equals,accountBankId).toArray());
+        return MsgWarnTemplateConstant.formatDailyMessage(
+                payerAccount,
+                accountBank.getString("bankaccountnumber"),
+                accountBank.getString("bank.name"),
+                transDetail.getString("foppunit"),
+                transDetail.getString("totaltimes"),
+                transDetail.getBigDecimal("totalamount").setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString());
+    }
+
+    protected DynamicObjectCollection getTransDetailData(DynamicObject company , Date beginDate , Date endDate,BigDecimal maxAmount,int maxTimes){
+        DataSet transDetailDataSet = getTransDetailDataSet(company, beginDate, endDate,maxAmount,maxTimes);
+        // 看报表与消息的兼容程度,再看怎么处理,暂时转成动态对象集合
+        return ORM.create().toPlainDynamicObjectCollection(transDetailDataSet.copy());
+    }
+
+    protected DataSet getTransDetailDataSet(DynamicObject company , Date beginDate , Date endDate,BigDecimal maxAmount,int maxTimes){
+        MainEntityType dt = EntityMetadataCache.getDataEntityType(BeiBeTransDetailConstant.ENTITYID);
+        Long defualtAccountBankId = getDefualtAccountBankId(company);
+
+        String dbRouteKey = dt.getDBRouteKey();
+        String sumTimesSql = "SELECT faccountbankid, foppunit, COUNT(1) totaltimes, SUM(fdebitAmount) totalamount , 'times' source FROM t_bei_transdetail "
+                + "WHERE fcompanyid = ? and faccountbankid = ? AND fbiztime > ? AND fbiztime < ? AND (HOUR(fbiztime) > 9 AND HOUR(fbiztime) < 21) "
+                + "AND TRIM(foppunit) <> '' AND fdebitAmount != 0 "
+                + "GROUP BY faccountbankid, foppunit HAVING COUNT(1) > ?";
+
+        DataSet sumTimesDataSet = DB.queryDataSet(this.getClass().getName(), DBRoute.of(dbRouteKey), sumTimesSql,
+                new Object[]{company.getPkValue(),defualtAccountBankId, beginDate, endDate, maxTimes});
+        Set oppUnitSet = CommonUtils.getFieldValue(sumTimesDataSet, "foppunit");
+
+        String sumAmountSql = "SELECT faccountbankid, foppunit, COUNT(1) totaltimes, SUM(fdebitAmount) totalamount , 'amount' source FROM t_bei_transdetail "
+                + "WHERE fcompanyid = ? and faccountbankid = ? AND fbiztime > ? AND fbiztime < ? AND (HOUR(fbiztime) > 9 AND HOUR(fbiztime) < 21) "
+                + "AND TRIM(foppunit) <> '' AND fdebitAmount != 0 "
+                + "GROUP BY faccountbankid, foppunit HAVING SUM(fdebitAmount) > ?";
+        DataSet sumAmountDataSet = DB.queryDataSet(this.getClass().getName(), DBRoute.of(dbRouteKey), sumAmountSql,
+                new Object[]{company.getPkValue(), defualtAccountBankId,beginDate, endDate, maxAmount});
+
+        HashMap<String, Object> params = new HashMap<>();
+        params.put("var", oppUnitSet);
+        DataSet filterSumAmountDataSet = sumAmountDataSet.filter("foppunit not in var", params);
+        return sumTimesDataSet.union(filterSumAmountDataSet);
+    }
+
+    protected Long getDefualtAccountBankId(DynamicObject company){
+        DynamicObject accountBankBill = QueryServiceHelper.queryOne("bd_accountbanks", "id", new QFilter[]{
+                new QFilter("company", QCP.equals, company.getPkValue()),
+                new QFilter("isdefaultpay", QCP.equals, true)
+        });
+        return accountBankBill == null ? null : accountBankBill.getLong("id");
+    }
+}