Преглед на файлове

谷元, 丰凯利, 航食调整

pruple_boy преди 1 година
родител
ревизия
9e2b98e7b6
променени са 24 файла, в които са добавени 759 реда и са изтрити 175 реда
  1. 11 1
      mjava-cloudpure/src/main/java/com.malk.cloudpure/controller/XBBController.java
  2. 129 79
      mjava-fengkaili/src/main/java/com/malk/fengkaili/service/impl/FKLImplService.java
  3. BIN
      mjava-fengkaili/src/main/resources/templates/Template_days.xlsx
  4. BIN
      mjava-fengkaili/src/main/resources/templates/Template_month.xlsx
  5. 1 2
      mjava-guyuan/src/main/java/com/malk/guyuan/controller/IVController.java
  6. 37 3
      mjava-guyuan/src/main/resources/static/mjs/mjs.js
  7. 1 1
      mjava-guyuan/src/main/resources/static/mjs/mjs.min.js
  8. 37 3
      mjava-guyuan/target/classes/static/mjs/mjs.js
  9. 1 1
      mjava-guyuan/target/classes/static/mjs/mjs.min.js
  10. 48 2
      mjava-hangshi/src/main/java/com/malk/hangshi/controller/HSController.java
  11. 1 1
      mjava/src/main/java/com/malk/config/WebConfiguration.java
  12. 78 0
      mjava/src/main/java/com/malk/server/dingtalk/DDConfigSign.java
  13. 6 1
      mjava/src/main/java/com/malk/server/dingtalk/DDR.java
  14. 6 8
      mjava/src/main/java/com/malk/service/dingtalk/DDClient.java
  15. 1 1
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Contacts.java
  16. 16 0
      mjava/src/main/java/com/malk/service/dingtalk/DDService.java
  17. 20 41
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient.java
  18. 54 4
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplService.java
  19. 56 11
      mjava/src/main/java/com/malk/utils/UtilNumber.java
  20. 7 7
      mjava/src/main/resources/static/mjs/mjs.js
  21. 1 1
      mjava/src/main/resources/static/mjs/mjs.min.js
  22. 240 0
      mjava/target/classes/META-INF/spring-configuration-metadata.json
  23. 7 7
      mjava/target/classes/static/mjs/mjs.js
  24. 1 1
      mjava/target/classes/static/mjs/mjs.min.js

+ 11 - 1
mjava-cloudpure/src/main/java/com.malk.cloudpure/controller/XBBController.java

@@ -1,9 +1,12 @@
 package com.malk.cloudpure.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
 import com.malk.server.common.McException;
 import com.malk.server.common.McR;
 import com.malk.server.xbongbong.XBBConf;
+import com.malk.service.aliwork.YDClient;
 import com.malk.service.xbongbong.XBBClient;
 import com.malk.utils.UtilMap;
 import lombok.extern.slf4j.Slf4j;
@@ -115,15 +118,22 @@ public class XBBController {
     }
 
 
+    @Autowired
+    private YDClient ydClient;
+
     @PostMapping("test")
     McR test() {
 
-        return McR.success(xbbClient.getFormDefine(864843, 0)); // 钉钉 864836 tb 1003094
+//        return McR.success(xbbClient.getFormDefine(864843, 0)); // 钉钉 864836 tb 1003094
 //        return McR.success(xbbClient.getFormList("", 1, 201));
 //        return McR.success(likeCusterList("钉钉客户", "计算机", "16808499470903077"));
 
 //        return McR.success(xbbClient.getFormList("", 1, 601));
 //        return McR.success(xbbClient.getFormDefine(864840, 0));
+
+        return McR.success(ydClient.queryData(YDParam.builder()
+                .formUuid("FORM-MP966K81ET7DZ7ZFFY4Z770D3U9F2TV2HD3LL9").build(), YDConf.FORM_QUERY.retrieve_search_form));
+
     }
 }
 

+ 129 - 79
mjava-fengkaili/src/main/java/com/malk/fengkaili/service/impl/FKLImplService.java

@@ -164,7 +164,6 @@ public class FKLImplService implements FKLService {
         return value;
     }
 
-
     /// 缓存考勤自定义列
     private List<Map> columns;
 
@@ -183,15 +182,24 @@ public class FKLImplService implements FKLService {
 
         // 考勤列, 假期信息定义
         List<String> columnNames = Arrays.asList("旷工天数", "出勤天数", "工作时长", "考勤结果", "出差时长", "迟到次数", "早退次数", "下班缺卡次数", "上班缺卡次数", "外出时长", "休息日加班", "工作日加班", "节假日加班", "严重迟到次数", "应出勤天数");
+        AtomicReference<String> fileId_attendance_days = new AtomicReference<>(""); // 出勤天数字段id
+        AtomicReference<String> fileId_attendance_result = new AtomicReference<>(""); // 考勤结果字段id
         List<Map> columns = getColumns();
         Map columnIds = new HashMap();
         // 假期单独返回, 钉钉产品规则
         List<String> leaveNames = columns.stream().filter(column -> {
+                    if ("出勤天数".equals(column.get("name"))) {
+                        fileId_attendance_days.set(String.valueOf(column.get("id")));
+                    }
+                    if ("考勤结果".equals(column.get("name"))) {
+                        fileId_attendance_result.set(String.valueOf(column.get("id")));
+                    }
                     // 列类型储存id映射名称为map, 考勤数据返回仅保留列id
                     if (columnNames.contains(column.get("name"))) {
                         columnIds.put(column.get("id").toString(), column.get("name"));
                         return false;
                     }
+
                     return column.get("alias").equals("leave_");
                 }
         ).map(column -> String.valueOf(column.get("name"))).collect(Collectors.toList());
@@ -202,7 +210,8 @@ public class FKLImplService implements FKLService {
         userInfos.forEach(po -> {
             Map attendanceInfo = UtilMap.map("员工ID, 员工姓名, 员工工号, 所属部门, 考勤状态", po.getUserId(), po.getName(), po.getJobNumber(), po.getDeptName(), po.getRemark());
             // 累计月度汇总
-            ddClient_attendance.getAttColumnVal(ddClient.getAccessToken(), po.getUserId(), queryIds, start, end).forEach(column -> {
+            List<Map> attendanceList = ddClient_attendance.getAttColumnVal(ddClient.getAccessToken(), po.getUserId(), queryIds, start, end);
+            attendanceList.forEach(column -> {
                 String id = ((Map) column.get("column_vo")).get("id").toString();
                 String name = String.valueOf(columnIds.get(id)); // 接口仅返回列id, 通过map映射
                 attendanceInfo.put(name, _reduceAttendance(column, name, "column_vals"));
@@ -320,39 +329,36 @@ public class FKLImplService implements FKLService {
                                         if (day_1.equals("缺卡") && result.contains("09:00")) {
                                             day_1 = "";
                                         }
-                                        // prd 请假3小时以内标记为zc, 按照小时请假 [调休, 哺乳假, 事假]
+                                        // prd 请假3小时以内标记为zc, 按照小时请假 [调休, 哺乳假, 事假];
                                         String[] arr = status.split(" ");
                                         float hour = Float.valueOf((arr[arr.length - 1].replace("小时", "")));
-                                        if (hour < 3.0f) { // <3 同时9点申请标识zc, 避免不能统计外出情况
+                                        if (hour < 3.0f && !tmp.equals("外出")) { // <3 同时9点申请标识zc, 避免不能统计外出情况
                                             continue;
                                         } else {
                                             // prd 请假3小时以内标记为zc, 区分上午与下午, 午休从12-13分割
                                             String sStart = status.split(" ")[1].split("到")[0].replace(":", "");
                                             type = type.length() > 0 && !tmp.equals(type) ? type + " " + tmp : tmp; // 兼容一天提交两次外出情况
-                                            if (Integer.valueOf(sStart) >= 1200) {
+                                            // 兼容跨天请假场景
+                                            boolean sDate = date.equals(status.split(" ")[0].replace(tmp, ""));
+                                            boolean eDate = date.equals(status.split(" ")[1].split("到")[1]);
+                                            if (Integer.valueOf(sStart) >= 1200 && sDate) {
                                                 day_2 = tmp;
                                             } else {
                                                 String sEnd = status.split(" ")[2].replace(":", "");
-                                                if (Integer.valueOf(sStart) < 800) {
+                                                if (Integer.valueOf(sStart) < 800 || !sDate) {
                                                     sStart = "0800";
                                                 }
                                                 float hourZao = Duration.between(UtilDateTime.parseLocal(sStart, "HHmm"), UtilDateTime.parseLocal("1200", "HHmm")).toMinutes() / 60f;
-                                                if (hourZao >= 3.0f) {
+                                                if (hourZao >= 3.0f || (hourZao > 0f && tmp.equals("外出"))) {
                                                     day_1 += day_1.length() > 0 ? " " + tmp : tmp;
                                                 }
-                                                if (Integer.valueOf(sEnd) > 1700) {
+                                                if (Integer.valueOf(sEnd) > 1700 || !eDate) {
                                                     sEnd = "1700";
                                                 }
                                                 float hourWan = Duration.between(UtilDateTime.parseLocal("1300", "HHmm"), UtilDateTime.parseLocal(sEnd, "HHmm")).toMinutes() / 60f;
-                                                if (hourWan > 3.0f) {
+                                                if (hourWan > 3.0f || (hourWan > 0f && tmp.equals("外出"))) {
                                                     day_2 += day_2.length() > 0 ? " " + tmp : tmp;
                                                 }
-                                                // 外出兼容9点申请, 排班是8点情况
-                                                if (result.contains("外出") && Integer.valueOf(sStart) <= 900 && Integer.valueOf(sEnd) >= 1700) {
-                                                    type = "外出";
-                                                    day_1 = type;
-                                                    day_2 = type;
-                                                }
                                             }
                                         }
                                     } else if (status.contains("出差")) {
@@ -406,8 +412,8 @@ public class FKLImplService implements FKLService {
                 }
             });
             // 累计假期数据
-            float leave_duration = 0f;
-            float leave_all = 0f;
+            float leave_duration = 0f;  // 法定假期调整时长: 分钟
+            float leave_all = 0f;   // 请假总时长: 天 [屏蔽3小时以内] - 新, 仅事假扣除出勤, 通过字段配置解决
             for (Map column : ddClient_attendance.getLeaveTimeByNames(ddClient.getAccessToken(), po.getUserId(), leaveNames, start, end)) {
                 String name = ((Map) column.get("columnvo")).get("name").toString(); // 接口返回列名称
                 float value = (Float) _reduceAttendance(column, name, "columnvals");
@@ -419,11 +425,27 @@ public class FKLImplService implements FKLService {
                         leave_duration += value * 60f * 8f;
                     }
                 }
-                if (Arrays.asList("调休", "事假", "哺乳假").contains(name)) {
-                    leave_all += value / 8f;
-                } else {
-                    leave_all += value;
+                // prd 病假,产假,事假扣除出勤天数. 因事假按照小时请假, 3小时内记录为出勤, 3-4小时为半天, 4小时以上记录为一天, 因此钉钉后台未设置自动扣减
+                if (Arrays.asList("病假", "产假", "事假").contains(name)) {
+                    if (name.equals("事假")) {
+                        // 系统已自动过滤, 午休时间 [跨天场景]
+                        if (value > 8f) {
+                            leave_all += Math.floor(value / 8f);
+                        }
+                        float hours = value % 8;
+                        if (hours > 0f) {
+                            // prd 1. 3小时以下不扣除;  2. 大于等于3,小于6为半天;  3. 大于等于6为1天
+                            if (hours >= 6.0f) {
+                                leave_all += 1.0f;
+                            } else if (hours >= 3f) {
+                                leave_all += 0.5f;
+                            }
+                        }
+                    } else {
+                        leave_all += value;
+                    }
                 }
+
                 attendanceInfo.put(name, value);
             }
             // 数据处理, 请假折算天
@@ -443,7 +465,24 @@ public class FKLImplService implements FKLService {
                 attendanceInfo.put("考勤结果", "入职日期" + UtilDateTime.formatDate(po.getHiredDate()) + "; " + attendanceInfo.get("考勤结果"));
             }
             attendanceInfo.put("缺卡次数", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "上班缺卡次数") + UtilMap.getFloat(attendanceInfo, "下班缺卡次数")));
+            attendanceInfo.put("出勤天数_sys", attendanceInfo.get("出勤天数")); // prd 离职1号计算, 请假扣除, 部分员工旷工算出勤, 扣除休息打卡出勤
             if (ObjectUtils.isNotEmpty(po.getLeaveDate()) && UtilDateTime.parseDateTime(start).before(po.getLeaveDate()) && UtilDateTime.parseDateTime(end).after(po.getLeaveDate())) {
+                // prd 离职员工出勤天数是否可以只记录员工离职当月1号
+                Optional option = attendanceList.stream().filter(item -> {
+                    /// 线程安全, 对象获取值
+                    String id = (((Map) item.get("column_vo"))).get("id").toString();
+                    return fileId_attendance_days.get().equals(id);
+                }).findAny();
+                if (option.isPresent()) {
+                    List<Map> dataList = (List<Map>) ((Map) option.get()).get("column_vals");
+                    for (Map data : dataList) {
+                        if (UtilDateTime.parseDate(data.get("date").toString()).getMonth() != UtilDateTime.parseDate(end).getMonth()) {
+                            log.info("离职从1号计算出勤, {}, {}, {}, {}", po.getName(), data.get("date"), UtilMap.getFloat(attendanceInfo, "出勤天数_sys"), UtilMap.getFloat(data, "value"));
+                            attendanceInfo.put("出勤天数_sys", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "出勤天数_sys") - UtilMap.getFloat(data, "value")));
+                        }
+                    }
+                }
+                // 缺卡补录
                 Optional optional = Arrays.stream(attendanceInfo.get("考勤结果").toString().split("; ")).filter(item -> item.equals("下班缺卡" + UtilDateTime.formatDate(po.getLeaveDate()))).findAny();
                 if (optional.isPresent()) {
                     exception_duration = 480f;
@@ -457,15 +496,27 @@ public class FKLImplService implements FKLService {
             float tiaoxiu_duration = UtilMap.getFloat(attendanceInfo, "调休") * 60f;
             attendanceInfo.put("调休时长", UtilNumber.formatPrecisionValue(tiaoxiu_duration));
             attendanceInfo.put("法定假调整时长", UtilNumber.formatPrecisionValue(leave_duration));
-            attendanceInfo.put("总时长", UtilNumber.formatPrecisionValue(system_duration + leave_duration + tiaoxiu_duration + exception_duration - overTime));
-            // ppExt 钉钉接口休息如出差半天系统也返回出勤天数1, 存在异常; 休息日加班也会记录为出勤, 考勤字段调整无效
-            attendanceInfo.put("出勤天数_prd", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "出勤天数") - leave_all));
+            // prd [新] 汇总表: 不取系统调休。总时长计算取 0,返回列表也为 0
+            attendanceInfo.put("总时长", UtilNumber.formatPrecisionValue(system_duration + leave_duration + exception_duration - overTime));
+            // prd 请假扣除出勤天数  ppExt 钉钉接口休息如出差半天系统也返回出勤天数1, 存在异常; 休息日加班也会记录为出勤, 考勤字段调整无效
+            attendanceInfo.put("出勤天数_sys", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "出勤天数_sys") - leave_all));
+            // prd 公假打卡的天数无需记录到出勤天数中, 包含出差部分
+            Optional optional = attendanceList.stream().filter(item -> {
+                /// 线程安全, 对象获取值
+                String id = (((Map) item.get("column_vo"))).get("id").toString();
+                return fileId_attendance_result.get().equals(id);
+            }).findAny();
+            if (optional.isPresent()) {
+                List<Map> dataList = (List<Map>) ((Map) optional.get()).get("column_vals");
+                int days_overTime = dataList.stream().filter(item -> String.valueOf(item.get("value")).contains("休息并打卡") || String.valueOf(item.get("value")).contains("休息,出差")).collect(Collectors.toList()).size();
+                attendanceInfo.put("出勤天数_sys", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendanceInfo, "出勤天数_sys") - days_overTime));
+            }
             attendanceInfos.add(attendanceInfo);
         });
         // prd 26-25周期非自然月逻辑 [获取出现最多次作为法定应出勤天数] 考勤应出勤天数和班组 + 人员挂钩, ppExt 排班天数钉钉查询没有接口
         float workMin = (Float) UtilList.maxFrequencyObject(attendanceInfos.stream().map(item -> UtilMap.getFloat(item, "出勤天数")).collect(Collectors.toList())) * 60 * 8;
 
-        // prd 数据处理
+        // prd 数据处理 [ppExt 月度汇总统计真实数据, 月度明细按照zc规则统计]
         int order = 0;
         for (Map attendance : attendanceInfos) {
             if (attendance.containsKey("总时长") && workMin > 0) {
@@ -473,19 +524,22 @@ public class FKLImplService implements FKLService {
             }
             order++;
             attendance.put("序号", String.valueOf(order));
+            // 调休按照半天\一天进行取整, 补充尾差
+            attendance.put("出勤天数_sys", UtilNumber.roundHalf(UtilMap.getFloat(attendance, "出勤天数_sys")));
             // prd 月度汇总表和月度明细表是否可实现部分无需打卡的员工
             if ("无需打卡".equals(attendance.get("考勤状态"))) {
-                attendance.put("旷工天数", 0);
-                attendance.put("缺卡次数", 0);
-                attendance.put("上班缺卡次数", 0);
-                attendance.put("下班缺卡次数", 0);
-                attendance.put("迟到次数", 0);
-                attendance.put("早退次数", 0);
                 if (!Objects.isNull(days)) {
+                    attendance.put("旷工天数", 0);
+                    attendance.put("缺卡次数", 0);
+                    attendance.put("上班缺卡次数", 0);
+                    attendance.put("上班缺卡次数", 0);
+                    attendance.put("下班缺卡次数", 0);
+                    attendance.put("迟到次数", 0);
+                    attendance.put("早退次数", 0);
                     for (Object key : attendance.keySet()) {
                         String prop = String.valueOf(key);
                         if (prop.contains("_") || prop.contains("-")) {
-                            String val = String.valueOf(attendance.get(prop)).replace("旷工", "").replace("缺卡", "").replace("迟到", "").replace("早退", "");
+                            String val = String.valueOf(attendance.get(prop)).replace("旷工", "").replace("缺卡", "").replace("迟到", "").replace("早退", "").trim();
                             // 忽略考勤异常 | 考勤静默用户
                             if (StringUtils.isBlank(val) || val.equals("/")) {
                                 attendance.put(prop, "zc");
@@ -497,61 +551,57 @@ public class FKLImplService implements FKLService {
                 } else {
                     List<String> vals = new ArrayList<>();
                     for (String cont : String.valueOf(attendance.get("考勤结果")).split("; ")) {
-                        if (!cont.contains("旷工") && !cont.contains("缺卡") && !cont.contains("迟到") && !cont.contains("早退")) {
+                        // 缺卡情况下, 存在请假, 需要保留
+                        if (cont.contains("缺卡,") || (!cont.contains("旷工") && !cont.contains("缺卡") && !cont.contains("迟到") && !cont.contains("早退"))) {
                             vals.add(cont);
                         }
                     }
                     attendance.put("考勤结果", String.join("; ", vals));
+                    // prd 部分无需打卡的员工旷工、缺卡、迟到、早退的天数需要记录到出勤天数中
+                    attendance.put("出勤天数_sys", UtilNumber.formatPrecisionValue(UtilMap.getFloat(attendance, "出勤天数_sys") + UtilMap.getFloat(attendance, "旷工天数")));
                 }
             }
-            // prd 异常与假期统计对应状态数据, 出勤天数就是实际到公司工作的天数[zc状态], 兼容3小时需求逻辑
-            AtomicReference<Float> days_rest = new AtomicReference<>(0f);
-            AtomicReference<Float> days_tiaoxiu = new AtomicReference<>(0f);
-            AtomicReference<Float> days_shijia = new AtomicReference<>(0f);
-            AtomicReference<Float> days_burujia = new AtomicReference<>(0f);
-            AtomicReference<Float> days_waichu = new AtomicReference<>(0f);
-            AtomicReference<Float> days_chidao = new AtomicReference<>(0f);
-            AtomicReference<Float> days_zaotui = new AtomicReference<>(0f);
-            AtomicReference<Float> days_kuangong = new AtomicReference<>(0f);
-            attendance.put("出勤天数_prd", attendance.keySet().stream().reduce(0f, (acc, cur) -> {
-                if (cur.toString().contains("_")) {
-                    // 外出标注为zc?
-                    if (attendance.get(cur).equals("外出")) {
-//                        attendance.put(cur, "zc");
-                        days_waichu.updateAndGet(v -> new Float((float) (v + 0.5)));
-                    }
-                    // 累计汇总天数
-                    if (attendance.get(cur).equals("zc")) {
-                        return Float.valueOf(String.valueOf(acc)) + 0.5;
-                    } else if (attendance.get(cur).equals("公假")) {
-                        days_rest.updateAndGet(v -> new Float((float) (v + 0.5)));
-                    } else if (attendance.get(cur).equals("调休")) {
-                        days_tiaoxiu.updateAndGet(v -> new Float((float) (v + 0.5)));
-                    } else if (attendance.get(cur).equals("事假")) {
-                        days_shijia.updateAndGet(v -> new Float((float) (v + 0.5)));
-                    } else if (attendance.get(cur).equals("哺乳假")) {
-                        days_burujia.updateAndGet(v -> new Float((float) (v + 0.5)));
-                    } else if (attendance.get(cur).equals("迟到")) {
-                        days_chidao.updateAndGet(v -> new Float((float) (v + 0.5)));
-                    } else if (attendance.get(cur).equals("早退")) {
-                        days_zaotui.updateAndGet(v -> new Float((float) (v + 0.5)));
-                    } else if (attendance.get(cur).equals("旷工")) {
-                        days_kuangong.updateAndGet(v -> new Float((float) (v + 0.5)));
+            if (!Objects.isNull(days)) {
+                // prd 异常与假期统计对应状态数据, 出勤天数就是实际到公司工作的天数[zc状态], 兼容3小时需求逻辑
+                AtomicReference<Float> days_rest = new AtomicReference<>(0f);
+                AtomicReference<Float> days_tiaoxiu = new AtomicReference<>(0f);
+                AtomicReference<Float> days_shijia = new AtomicReference<>(0f);
+                AtomicReference<Float> days_burujia = new AtomicReference<>(0f);
+                AtomicReference<Float> days_chidao = new AtomicReference<>(0f);
+                AtomicReference<Float> days_zaotui = new AtomicReference<>(0f);
+                AtomicReference<Float> days_kuangong = new AtomicReference<>(0f);
+                attendance.put("出勤天数_prd", attendance.keySet().stream().reduce(0f, (acc, cur) -> {
+                    if (cur.toString().contains("_")) {
+                        // 累计汇总天数 [出勤天数包含 外出, 缺卡, zc]; ppExt 未加入考勤组人员, 会全月统计为zc, 钉钉返回为空, 没有判断条件. 因此需要手动调整 [极端情况]
+                        if (Arrays.asList("zc", "缺卡", "外出").contains(attendance.get(cur))) {
+                            return Float.valueOf(String.valueOf(acc)) + 0.5;
+                        } else if (attendance.get(cur).equals("公假")) {
+                            days_rest.updateAndGet(v -> new Float((float) (v + 0.5)));
+                        } else if (attendance.get(cur).equals("调休")) {
+                            days_tiaoxiu.updateAndGet(v -> new Float((float) (v + 0.5)));
+                        } else if (attendance.get(cur).equals("事假")) {
+                            days_shijia.updateAndGet(v -> new Float((float) (v + 0.5)));
+                        } else if (attendance.get(cur).equals("哺乳假")) {
+                            days_burujia.updateAndGet(v -> new Float((float) (v + 0.5)));
+                        } else if (attendance.get(cur).equals("迟到")) {
+                            days_chidao.updateAndGet(v -> new Float((float) (v + 0.5)));
+                        } else if (attendance.get(cur).equals("早退")) {
+                            days_zaotui.updateAndGet(v -> new Float((float) (v + 0.5)));
+                        } else if (attendance.get(cur).equals("旷工")) {
+                            days_kuangong.updateAndGet(v -> new Float((float) (v + 0.5)));
+                        }
                     }
-                }
-                return acc;
-            }));
-            attendance.put("公假天数_prd", days_rest.get());
-            attendance.put("调休天数_prd", days_tiaoxiu.get());
-            attendance.put("事假天数_prd", days_shijia.get());
-            attendance.put("哺乳假天数_prd", days_burujia.get());
-            attendance.put("外出天数_prd", days_waichu.get());
-            attendance.put("迟到次数_prd", days_chidao.get());
-            attendance.put("早退次数_prd", days_zaotui.get());
-            attendance.put("旷工天数_prd", days_kuangong.get());
+                    return acc;
+                }));
+                attendance.put("公假天数_prd", days_rest.get());
+                attendance.put("调休天数_prd", days_tiaoxiu.get());
+                attendance.put("事假天数_prd", days_shijia.get());
+                attendance.put("哺乳假天数_prd", days_burujia.get());
+                attendance.put("迟到次数_prd", days_chidao.get());
+                attendance.put("早退次数_prd", days_zaotui.get());
+                attendance.put("旷工天数_prd", days_kuangong.get());
+            }
         }
-
-
         // 记录月度明细日期, 进行排序 [接口返回已排序]
 //        if (UtilList.isNotEmpty(days)) {
 //            Collections.sort(days, Comparator.comparingLong(o -> Long.valueOf(o.replace("-", ""))));

BIN
mjava-fengkaili/src/main/resources/templates/Template_days.xlsx


BIN
mjava-fengkaili/src/main/resources/templates/Template_month.xlsx


+ 1 - 2
mjava-guyuan/src/main/java/com/malk/guyuan/controller/IVController.java

@@ -45,8 +45,7 @@ import java.util.stream.Collectors;
 @RestController
 @RequestMapping
 public class IVController {
-
-
+    
     @Autowired
     private TXYInvoice txyInvoice;
 

+ 37 - 3
mjava-guyuan/src/main/resources/static/mjs/mjs.js

@@ -3918,11 +3918,17 @@ var upperLimit = 30000;
 /** 授权企业ID */
 var corpId$1 = "";
 
-/** 授权登录加密字符 */
+/** jsApi授权登录加密字符 */
 var nonceStr$1 = "";
 
+/** jsApi授权微应用id */
+var agentId = "";
+
+/** jsApi授权微应用页面 */
+var ticketUrl = "";
+
 /** 导出配置信息 */
-var config = { api: api, timeout: timeout, token: token, pageSize: pageSize, detailCount: detailCount, upperLimit: upperLimit, corpId: corpId$1, nonceStr: nonceStr$1 };
+var config = { api: api, timeout: timeout, token: token, pageSize: pageSize, detailCount: detailCount, upperLimit: upperLimit, corpId: corpId$1, nonceStr: nonceStr$1, agentId: agentId, ticketUrl: ticketUrl };
 
 var browser = {};
 
@@ -9252,6 +9258,17 @@ var guyuan = {
     if (idx >= 0) {
       return mjs.com.toastError("\u7B2C\u3010" + (idx + 1) + "\u3011\u8BB0\u5F55, \u62A5\u9500\u91D1\u989D\u5DF2\u5927\u4E8E\u53D1\u7968\u91D1\u989D\uFF01");
     }
+    // prd 分类求和, 费用明细中的预付款金额, 不能大于预算金额
+    idx = -1;
+    mjs.$this.$('tableField_krmybpq6').getValue().forEach(function (item, index) {
+      if ((item.numberField_kxr3f3zy || item.numberField_kwd4ep08) > (item.numberField_kxr3f3zx || item.numberField_kwd4ep06)) {
+        idx = index;
+        return;
+      }
+    });
+    if (idx >= 0) {
+      return mjs.com.toastError("\u7B2C\u3010" + (idx + 1) + "\u3011\u8BB0\u5F55, \u652F\u4ED8\u91D1\u989D\u5DF2\u5927\u4E8E\u62A5\u9500\u91D1\u989D\uFF01");
+    }
     // 兼容: 退回为监听宜搭dom事件, 先执行接口调用, 才会校验宜搭必填, 过滤无效调用 || 先匹配校验是否可调用
     var bx = (mjs.$this.$("numberField_krn54uoe") || mjs.$this.$("numberField_kroa4wk1")).getValue();
     var fk = (mjs.$this.$("numberField_krn7ufyt") || mjs.$this.$("numberField_krf2spcw")).getValue();
@@ -9262,6 +9279,23 @@ var guyuan = {
   }
 };
 
+/*** mjs 之 航食 ***/
+
+var hangshi = {
+
+  // 修改公共配置
+  init: function init() {
+    {
+      mjs.conf.api = "https://mc.cloudpure.cn/api/hangshi/";
+    }
+    mjs.conf.corpId = "ding6bd8f2716554297135c2f4657eb6378f";
+    mjs.conf.nonceStr = "ABCD-HANGSHI";
+    mjs.conf.agentId = "2554541552";
+    mjs.conf.ticketUrl = "https://www.aliwork.com/o/hangshi";
+    return this; // this 指向当前项目本身
+  }
+};
+
 /*** mc 系列之 mjs
  * 对接宜搭公共JavaScript库
  * 公共库地址:https://mc.cloudpure.cn/mjs/mjs.min.js
@@ -9291,7 +9325,7 @@ var init = function () {
             this.request = { dp: dp, xhr: request, net: { crossDomainByScript: crossDomainByScript } }; // 请求
             this.ding = ding;
             this.corp = {
-              cp: cp, guyuan: guyuan
+              cp: cp, guyuan: guyuan, hangshi: hangshi
               // 输出日志;
             };msg = "mjs load success. \u2668 \u8BBF\u95EE\u5E94\u7528: " + pageConfig.appType + " " + pageConfig.appName + " \xA9\uFE0F \u7248\u6743\u8BF7\u8BF7\u8054\u7CFB: https://www.aliwork.com/o/mc";
 

Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
mjava-guyuan/src/main/resources/static/mjs/mjs.min.js


+ 37 - 3
mjava-guyuan/target/classes/static/mjs/mjs.js

@@ -3918,11 +3918,17 @@ var upperLimit = 30000;
 /** 授权企业ID */
 var corpId$1 = "";
 
-/** 授权登录加密字符 */
+/** jsApi授权登录加密字符 */
 var nonceStr$1 = "";
 
+/** jsApi授权微应用id */
+var agentId = "";
+
+/** jsApi授权微应用页面 */
+var ticketUrl = "";
+
 /** 导出配置信息 */
-var config = { api: api, timeout: timeout, token: token, pageSize: pageSize, detailCount: detailCount, upperLimit: upperLimit, corpId: corpId$1, nonceStr: nonceStr$1 };
+var config = { api: api, timeout: timeout, token: token, pageSize: pageSize, detailCount: detailCount, upperLimit: upperLimit, corpId: corpId$1, nonceStr: nonceStr$1, agentId: agentId, ticketUrl: ticketUrl };
 
 var browser = {};
 
@@ -9252,6 +9258,17 @@ var guyuan = {
     if (idx >= 0) {
       return mjs.com.toastError("\u7B2C\u3010" + (idx + 1) + "\u3011\u8BB0\u5F55, \u62A5\u9500\u91D1\u989D\u5DF2\u5927\u4E8E\u53D1\u7968\u91D1\u989D\uFF01");
     }
+    // prd 分类求和, 费用明细中的预付款金额, 不能大于预算金额
+    idx = -1;
+    mjs.$this.$('tableField_krmybpq6').getValue().forEach(function (item, index) {
+      if ((item.numberField_kxr3f3zy || item.numberField_kwd4ep08) > (item.numberField_kxr3f3zx || item.numberField_kwd4ep06)) {
+        idx = index;
+        return;
+      }
+    });
+    if (idx >= 0) {
+      return mjs.com.toastError("\u7B2C\u3010" + (idx + 1) + "\u3011\u8BB0\u5F55, \u652F\u4ED8\u91D1\u989D\u5DF2\u5927\u4E8E\u62A5\u9500\u91D1\u989D\uFF01");
+    }
     // 兼容: 退回为监听宜搭dom事件, 先执行接口调用, 才会校验宜搭必填, 过滤无效调用 || 先匹配校验是否可调用
     var bx = (mjs.$this.$("numberField_krn54uoe") || mjs.$this.$("numberField_kroa4wk1")).getValue();
     var fk = (mjs.$this.$("numberField_krn7ufyt") || mjs.$this.$("numberField_krf2spcw")).getValue();
@@ -9262,6 +9279,23 @@ var guyuan = {
   }
 };
 
+/*** mjs 之 航食 ***/
+
+var hangshi = {
+
+  // 修改公共配置
+  init: function init() {
+    {
+      mjs.conf.api = "https://mc.cloudpure.cn/api/hangshi/";
+    }
+    mjs.conf.corpId = "ding6bd8f2716554297135c2f4657eb6378f";
+    mjs.conf.nonceStr = "ABCD-HANGSHI";
+    mjs.conf.agentId = "2554541552";
+    mjs.conf.ticketUrl = "https://www.aliwork.com/o/hangshi";
+    return this; // this 指向当前项目本身
+  }
+};
+
 /*** mc 系列之 mjs
  * 对接宜搭公共JavaScript库
  * 公共库地址:https://mc.cloudpure.cn/mjs/mjs.min.js
@@ -9291,7 +9325,7 @@ var init = function () {
             this.request = { dp: dp, xhr: request, net: { crossDomainByScript: crossDomainByScript } }; // 请求
             this.ding = ding;
             this.corp = {
-              cp: cp, guyuan: guyuan
+              cp: cp, guyuan: guyuan, hangshi: hangshi
               // 输出日志;
             };msg = "mjs load success. \u2668 \u8BBF\u95EE\u5E94\u7528: " + pageConfig.appType + " " + pageConfig.appName + " \xA9\uFE0F \u7248\u6743\u8BF7\u8BF7\u8054\u7CFB: https://www.aliwork.com/o/mc";
 

Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
mjava-guyuan/target/classes/static/mjs/mjs.min.js


+ 48 - 2
mjava-hangshi/src/main/java/com/malk/hangshi/controller/HSController.java

@@ -3,7 +3,10 @@ package com.malk.hangshi.controller;
 import com.alibaba.fastjson.JSON;
 import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
 import com.malk.server.common.McR;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.DDConfigSign;
 import com.malk.service.aliwork.YDClient;
 import com.malk.service.dingtalk.DDClient;
 import com.malk.service.dingtalk.DDClient_Contacts;
@@ -12,10 +15,13 @@ import com.malk.utils.UtilServlet;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -50,10 +56,10 @@ public class HSController {
      * 自愿上报动态审批
      */
     @PostMapping("user/approve")
-    List<String> approveFKGL(HttpServletRequest request) {
+    List<String> approveAQGL(HttpServletRequest request) {
 
         Map param = UtilServlet.getParamMap(request);
-        log.info("自愿上报动态审批, {}", param);
+        log.info("自愿上报安全管理, {}", param);
 
         List<Map> list = (List<Map>) ydClient.queryData(YDParam.builder()
                 .formUuid("FORM-3C866TC1RA29KJXD7XF4N4ZXW9932Z69W6CFLR")
@@ -62,5 +68,45 @@ public class HSController {
         List<String> userIds = list.stream().map(item -> String.valueOf(((Map) item.get("formData")).get("textField_lfw8yizu"))).collect(Collectors.toList());
         return userIds;
     }
+
+    /**
+     * 自愿上报动态审批
+     */
+    @PostMapping("user/compId")
+    List<String> approveZRBM(HttpServletRequest request) {
+
+        Map param = UtilServlet.getParamMap(request);
+        log.info("自愿上责任部门, {}", param);
+        return Arrays.asList(String.valueOf(param.get("userId")).split(","));
+    }
+
+    @Autowired
+    private DDConf ddConf;
+
+    /**
+     * jsApi 注册
+     */
+    @PostMapping("register")
+    McR register(@RequestBody Map<String, String> data) {
+
+        McException.assertParamException_Null(data, "url", "nonceStr");
+        String jsTicket = ddClient.getJsApiTicket(ddClient.getAccessToken());
+        String nonceStr = data.get("nonceStr");
+        long timeStamp = new Date().getTime();
+        String url = data.get("url");
+        String signature = DDConfigSign.sign(jsTicket, nonceStr, timeStamp, url);
+        return McR.success(UtilMap.map("nonceStr, agentId, timeStamp, corpId, signature", nonceStr, ddConf.getAgentId(), timeStamp, ddConf.getCorpId(), signature));
+    }
+
+    /**
+     * jsApi 免登
+     */
+    @PostMapping("user/code")
+    McR userCodeAuth(@RequestBody Map<String, String> data) {
+        McException.assertParamException_Null(data, "code");
+        Map rsp = ddClient.getUserInfoByCode(ddClient.getAccessToken(), data.get("code"));
+        return McR.success(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), rsp.get("userid").toString()));
+    }
+
 }
 

+ 1 - 1
mjava/src/main/java/com/malk/config/WebConfiguration.java

@@ -46,7 +46,7 @@ public class WebConfiguration implements WebMvcConfigurer {
      */
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry) {
-        registry.addResourceHandler("/mjs/**").addResourceLocations("classpath:/static/mjs/");
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/static/");
         registry.addResourceHandler("/web/**").addResourceLocations("classpath:/static/web/");
         registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
         registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");

+ 78 - 0
mjava/src/main/java/com/malk/server/dingtalk/DDConfigSign.java

@@ -0,0 +1,78 @@
+package com.malk.server.dingtalk;
+
+import lombok.SneakyThrows;
+
+import java.net.URL;
+import java.net.URLDecoder;
+import java.security.MessageDigest;
+import java.util.Formatter;
+import java.util.Random;
+
+/**
+ * 计算dd.config的签名参数
+ **/
+public class DDConfigSign {
+
+    /**
+     * 计算dd.config的签名参数
+     *
+     * @param jsticket  通过微应用appKey获取的jsticket
+     * @param nonceStr  自定义固定字符串
+     * @param timeStamp 当前时间戳
+     * @param url       调用dd.config的当前页面URL
+     */
+    @SneakyThrows
+    public static String sign(String jsticket, String nonceStr, long timeStamp, String url) {
+        String plain = "jsapi_ticket=" + jsticket + "&noncestr=" + nonceStr + "&timestamp=" + timeStamp + "&url=" + decodeUrl(url);
+        MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
+        sha1.reset();
+        sha1.update(plain.getBytes("UTF-8"));
+        return byteToHex(sha1.digest());
+    }
+
+    // 字节数组转化成十六进制字符串
+    private static String byteToHex(final byte[] hash) {
+        Formatter formatter = new Formatter();
+        for (byte b : hash) {
+            formatter.format("%02x", b);
+        }
+        String result = formatter.toString();
+        formatter.close();
+        return result;
+    }
+
+    /**
+     * 因为ios端上传递的url是encode过的,android是原始的url。开发者使用的也是原始url,
+     * 所以需要把参数进行一般urlDecode
+     */
+    private static String decodeUrl(String url) throws Exception {
+        URL urler = new URL(url);
+        StringBuilder urlBuffer = new StringBuilder();
+        urlBuffer.append(urler.getProtocol());
+        urlBuffer.append(":");
+        if (urler.getAuthority() != null && urler.getAuthority().length() > 0) {
+            urlBuffer.append("//");
+            urlBuffer.append(urler.getAuthority());
+        }
+        if (urler.getPath() != null) {
+            urlBuffer.append(urler.getPath());
+        }
+        if (urler.getQuery() != null) {
+            urlBuffer.append('?');
+            urlBuffer.append(URLDecoder.decode(urler.getQuery(), "utf-8"));
+        }
+        return urlBuffer.toString();
+    }
+
+    /// test
+    public static String getRandomStr(int count) {
+        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        Random random = new Random();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < count; i++) {
+            int number = random.nextInt(base.length());
+            sb.append(base.charAt(number));
+        }
+        return sb.toString();
+    }
+}

+ 6 - 1
mjava/src/main/java/com/malk/server/dingtalk/DDR.java

@@ -25,11 +25,16 @@ public class DDR<T> extends VenR {
     private T result;
 
     /**
-     * token 接口
+     * token
      */
     private String accessToken;
     private int expiresIn;
 
+    /**
+     * ticket
+     */
+    private String ticket;
+
     /**
      * 审批实例ID
      */

+ 6 - 8
mjava/src/main/java/com/malk/service/dingtalk/DDClient.java

@@ -22,15 +22,13 @@ public interface DDClient {
     Map initTokenHeader();
 
     /**
-     * 上传审批附件
-     *
-     * @param urlFile      远程文件地址
-     * @param fileNamePure 文件名称[后缀自动通过地址获取]
+     * 获取js_ticket
      */
-    Map uploadFileFormUrl(String accessToken, String userId, String urlFile, String fileNamePure);
+    String getJsApiTicket(String accessToken);
 
-    Map uploadFileFormUrl(String accessToken, String userId, String filePath);
-
-  
+    /**
+     * 通过免登码获取用户信息
+     */
+    Map getUserInfoByCode(String accessToken, String code);
 }
 

+ 1 - 1
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Contacts.java

@@ -37,7 +37,7 @@ public interface DDClient_Contacts {
     List<String> listDepartmentUserId(String access_token, long dept_id);
 
     /**
-     * 查询用户详情 [如入职时间, 需要再=在花名册添加员工可见, 接口才会返回]
+     * 查询用户详情 [如入职时间 手机号等, 需要再=在花名册添加员工可见, 接口才会返回]
      */
     Map getUserInfoById(String access_token, String userId);
 

+ 16 - 0
mjava/src/main/java/com/malk/service/dingtalk/DDService.java

@@ -15,6 +15,22 @@ public interface DDService {
      */
     Map queryVacationQuota_balance(String access_token, String op_userid, String leave_code, String userids, int offset, int size);
 
+    /**
+     * 上传审批附件
+     *
+     * @param urlFile      远程文件地址
+     * @param fileNamePure 文件名称[后缀自动通过地址获取]
+     */
+    Map uploadFileFormUrl(String accessToken, String userId, String urlFile, String fileNamePure);
+
+    /**
+     * 上传审批附件
+     *
+     * @param filePath 已存在文件, 本地file绝对路径
+     */
+    Map uploadFileFormUrl(String accessToken, String userId, String filePath);
+
+    
     // todo 通讯录部门结构返回; 通讯录全量数据同步
 
     /**

+ 20 - 41
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient.java

@@ -1,23 +1,18 @@
 package com.malk.service.dingtalk.impl;
 
-import com.malk.server.common.FilePath;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDR;
-import com.malk.server.dingtalk.DDR_New;
 import com.malk.service.dingtalk.DDClient;
-import com.malk.service.dingtalk.DDClient_Contacts;
-import com.malk.service.dingtalk.DDClient_Storage;
-import com.malk.utils.*;
+import com.malk.utils.UtilHttp;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilToken;
 import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.io.File;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 @Slf4j
@@ -72,45 +67,29 @@ public class DDImplClient implements DDClient {
         return DDConf.initTokenHeader(getAccessToken());
     }
 
-    @Autowired
-    private DDClient_Storage ddClient_storage;
-
-    @Autowired
-    private DDClient_Contacts ddClient_contacts;
-
-    @Autowired
-    private FilePath filePath;
-
     /**
-     * 上传审批附件
+     * 获取jsapi_ticket
      *
-     * @param urlFile      远程文件地址
-     * @param fileNamePure 文件名称[后缀自动通过地址获取]
+     * @apiNote https://open.dingtalk.com/document/orgapp/obtain-jsapi_ticket
      */
+    @Synchronized
     @Override
-    public Map uploadFileFormUrl(String accessToken, String userId, String urlFile, String fileNamePure) {
-        // 下载到本地
-        String fileName = fileNamePure + urlFile.substring(urlFile.lastIndexOf(".")).toLowerCase();
-        File file = UtilFile.mkdirIfNot(fileName, filePath.getPath().getFile());
-        UtilHttp.doDownload(urlFile, file);
-        return uploadFileFormUrl(accessToken, userId, file.getAbsolutePath());
+    public String getJsApiTicket(String accessToken) {
+        String ticket = UtilToken.get("invalid-ticket-dingtalk");
+        if (StringUtils.isNotBlank(ticket)) return ticket;
+        DDR r = DDR.doGet("https://oapi.dingtalk.com/get_jsapi_ticket", null, UtilMap.map("access_token", accessToken));
+        log.info("响应ticket, {}", r.getAccessToken());
+        ticket = r.getTicket();
+        // token失效自动重置: DD重新调用会重置过期时间
+        UtilToken.put("invalid-ticket-dingtalk", ticket, r.getExpiresIn() * 1000L);
+        return ticket;
     }
 
+    /**
+     * 通过免登码获取用户信息
+     */
     @Override
-    public Map uploadFileFormUrl(String accessToken, String userId, String filePath) {
-        // 获取用户unionId
-        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(accessToken, userId).get("unionid"));
-        // 获取储存空间
-        String spaceId = ddClient_storage.getSpacesInfos(accessToken, userId, null);
-        // 添加空间用户权限
-        List<Map> members = Arrays.asList(UtilMap.map("type, id, corpId", "USER", unionId, ddConf.getCorpId()));
-        ddClient_storage.addSpacePermissions(accessToken, spaceId, "0", unionId, "EDITOR", members, null);
-        // 获取空间上传信息
-        DDR_New ddr_new = ddClient_storage.getUploadInfos(accessToken, spaceId, unionId, null);
-        // 执行文件上传
-        String resourceUrl = ((List<String>) ddr_new.getHeaderSignatureInfo().get("resourceUrls")).get(0);
-        ddClient_storage.uploadFiles(resourceUrl, (Map<String, String>) ddr_new.getHeaderSignatureInfo().get("headers"), filePath);
-        // 提交文件
-        return ddClient_storage.commitFiles(accessToken, spaceId, unionId, ddr_new.getUploadKey(), String.valueOf(UtilList.getLast(filePath.split("/"))), "0", null);
+    public Map getUserInfoByCode(String accessToken, String code) {
+        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo", null, DDConf.initTokenParams(accessToken), UtilMap.map("code", code)).getResult();
     }
 }

+ 54 - 4
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplService.java

@@ -1,11 +1,13 @@
 package com.malk.service.dingtalk.impl;
 
 import cn.hutool.core.util.ObjectUtil;
+import com.malk.server.common.FilePath;
 import com.malk.server.dingtalk.DDConf;
-import com.malk.service.dingtalk.DDClient_Attendance;
-import com.malk.service.dingtalk.DDClient_Contacts;
-import com.malk.service.dingtalk.DDClient_Workflow;
-import com.malk.service.dingtalk.DDService;
+import com.malk.server.dingtalk.DDR_New;
+import com.malk.service.dingtalk.*;
+import com.malk.utils.UtilFile;
+import com.malk.utils.UtilHttp;
+import com.malk.utils.UtilList;
 import com.malk.utils.UtilMap;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
@@ -13,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.io.File;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -31,6 +34,15 @@ public class DDImplService implements DDService {
     @Autowired
     private DDClient_Contacts ddClient_contacts;
 
+    @Autowired
+    private DDClient_Storage ddClient_storage;
+
+    @Autowired
+    private FilePath filePath;
+
+    @Autowired
+    private DDConf ddConf;
+
     /**
      * 新发起审批15s内不允许撤销, 异步执行 [审批同意/拒绝只能通过节点操作, 系统无法直接介入]   -- 异步需要中转一层进行触发, client为原子接口
      */
@@ -69,6 +81,44 @@ public class DDImplService implements DDService {
         return result;
     }
 
+    /**
+     * 上传审批附件
+     *
+     * @param urlFile      远程文件地址
+     * @param fileNamePure 文件名称[后缀自动通过地址获取]
+     */
+    @Override
+    public Map uploadFileFormUrl(String accessToken, String userId, String urlFile, String fileNamePure) {
+        // 下载到本地
+        String fileName = fileNamePure + urlFile.substring(urlFile.lastIndexOf(".")).toLowerCase();
+        File file = UtilFile.mkdirIfNot(fileName, filePath.getPath().getFile());
+        UtilHttp.doDownload(urlFile, file);
+        return uploadFileFormUrl(accessToken, userId, file.getAbsolutePath());
+    }
+
+    /**
+     * 上传审批附件
+     *
+     * @param filePath 已存在文件, 本地file绝对路径
+     */
+    @Override
+    public Map uploadFileFormUrl(String accessToken, String userId, String filePath) {
+        // 获取用户unionId
+        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(accessToken, userId).get("unionid"));
+        // 获取储存空间
+        String spaceId = ddClient_storage.getSpacesInfos(accessToken, userId, null);
+        // 添加空间用户权限
+        List<Map> members = Arrays.asList(UtilMap.map("type, id, corpId", "USER", unionId, ddConf.getCorpId()));
+        ddClient_storage.addSpacePermissions(accessToken, spaceId, "0", unionId, "EDITOR", members, null);
+        // 获取空间上传信息
+        DDR_New ddr_new = ddClient_storage.getUploadInfos(accessToken, spaceId, unionId, null);
+        // 执行文件上传
+        String resourceUrl = ((List<String>) ddr_new.getHeaderSignatureInfo().get("resourceUrls")).get(0);
+        ddClient_storage.uploadFiles(resourceUrl, (Map<String, String>) ddr_new.getHeaderSignatureInfo().get("headers"), filePath);
+        // 提交文件
+        return ddClient_storage.commitFiles(accessToken, spaceId, unionId, ddr_new.getUploadKey(), String.valueOf(UtilList.getLast(filePath.split("/"))), "0", null);
+    }
+
     /**
      * 判断员工是否在指定部门
      */

+ 56 - 11
mjava/src/main/java/com/malk/utils/UtilNumber.java

@@ -15,13 +15,17 @@ import java.util.regex.Pattern;
  */
 public class UtilNumber {
 
-    // 货币格式化
+    /**
+     * 货币格式化
+     */
     public static final String formatCurrency(Number number) {
         DecimalFormat curFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(Locale.CHINA);
         return curFormat.format(number);
     }
 
-    // 去除CHY货币标识
+    /**
+     * 去除CHY货币标识
+     */
     public static final String replaceCurrencyCHY(String cur) {
         return cur.replace("¥", "").replace("¥", "");
     }
@@ -34,7 +38,9 @@ public class UtilNumber {
         return pattern.matcher(num).matches();
     }
 
-    // 去除CHY货币标识
+    /**
+     * 去除CHY货币标识
+     */
     public static final BigDecimal replaceCurrencyCHYToDecimal(String cur) {
         String num = replaceCurrencyCHY(cur);
         if (StringUtils.isBlank(num) || !matchNumber(num)) {
@@ -43,35 +49,54 @@ public class UtilNumber {
         return new BigDecimal(num);
     }
 
-    // 百分比格式化
+    /**
+     * 百分比格式化
+     */
     public static final String formatPercent(Number number) {
         DecimalFormat percentFormat = (DecimalFormat) NumberFormat.getPercentInstance(Locale.CHINA);
         percentFormat.setMinimumFractionDigits(2);
         return percentFormat.format(number);
     }
 
-    // 小数位精度格式
+    /**
+     * 小数位精度格式
+     */
     public static final String formatPrecisionString(double number) {
-        DecimalFormat df = new DecimalFormat("#.00");
+        return formatPrecisionString(number, "#.00");
+    }
+
+    /**
+     * 小数位精度格式
+     */
+    public static final String formatPrecisionString(double number, String pattern) {
+        DecimalFormat df = new DecimalFormat(pattern);
         return df.format(new BigDecimal(number));
     }
 
-    // 小数位精度格式
+    /**
+     * 小数位精度格式
+     */
     public static final double formatPrecisionValue(double number) {
         return Double.valueOf(formatPrecisionString(number));
     }
 
-    // 小数位精度格式
+    /**
+     * 小数位精度格式
+     */
     public static final double formatPrecisionValue(float number) {
         return Double.valueOf(formatPrecisionString(number));
     }
 
-    // 小数位精度格式
+    /**
+     * 小数位精度格式
+     */
     public static final String formatPrecisionString(float number) {
         return formatPrecisionString(Double.valueOf(String.valueOf(number)));
     }
 
-    // 非数值型字符串, 空字符串兼容
+    /**
+     * 非数值型字符串, 空字符串兼容
+     */
     public static final BigDecimal setBigDecimal(String strNum) {
         BigDecimal decimal = null;
         if (StringUtils.isNotBlank(strNum)) {
@@ -84,12 +109,32 @@ public class UtilNumber {
         return decimal;
     }
 
-    // 判断两个字符串, 转数值后是否相等
+    /**
+     * 判断两个字符串, 转数值后是否相等
+     */
     public static final int compareBigDecimal(String n1, String n2) {
         return setBigDecimal(n1).compareTo(setBigDecimal(n2));
     }
 
+    /**
+     * 判断两个字符串, 转数值后是否相等
+     */
     public static final boolean equalBigDecimal(String n1, String n2) {
         return compareBigDecimal(n1, n2) == 0;
     }
+
+    /**
+     * 尾数: 0.5取整逻辑, 小于0.5为0.5, 大于0.5为1
+     */
+    public static double roundHalf(double num) {
+        double half = num % 1;
+        if (half != 0 && half != 0.5) {
+            if (half > 0.5f) {
+                num = Math.floor(num) + 1.0f;
+            } else {
+                num = Math.floor(num) + 0.5f;
+            }
+        }
+        return num;
+    }
 }

+ 7 - 7
mjava/src/main/resources/static/mjs/mjs.js

@@ -1,5 +1,5 @@
 /*!
- * mjs.js v0.2.0
+ * static.js v0.2.0
  * (c) 2018-2022 mcli
  */
 (function (global, factory) {
@@ -2145,7 +2145,7 @@
                 };
 
                 resolve(resp);
-                // const msg = `mjs load failure. ♨ 访问应用: ${resp.data.appType} ${resp.message} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
+                // const msg = `static load failure. ♨ 访问应用: ${resp.data.appType} ${resp.message} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
                 // reject(msg);
             }, 750);
         });
@@ -2353,7 +2353,7 @@
 // // 提交页面: button事件的3种绑定方式以及触发逻辑:https://www.cnblogs.com/ooo0/p/7742214.html
 // dom.closeCurrentTabForSubmit = function (that) {
 //   // 退回再提交走审批事件, 目前退回为加签逻辑 ==> 退回页面环境为编辑态值2
-//   if (mjs.env) return;
+//   if (static.env) return;
 //   function closeTab () {
 //     // 执行顺序: onclick > addEventListener > beforeSubmit
 //     // 特别说明:在提交情况下,页面存在beforeSubmit,相关的逻辑在beforeSubmit下要手动调用,因为这里是直接触发若是有判断情况下决定权交给beforeSubmit处理
@@ -5841,7 +5841,7 @@
         }
     };
 
-    /*** mjs 之 福氏钉钉SAP接口对接
+    /*** static 之 福氏钉钉SAP接口对接
      * 对接宜搭公共JavaScript库
      * 版权请联系:https://www.aliwork.com/o/mjs
      * 项目库地址: https://ding.practek.cn:38080/api/mjs/mjs.min.js
@@ -5972,7 +5972,7 @@
         }
     };
 
-    /*** mc 系列之 mjs
+    /*** mc 系列之 static
      * 对接宜搭公共JavaScript库
      * 版权请联系:https://www.aliwork.com/o/mjs
      * 公共库地址:https://aliwork.zitoo.com.cn/mc/js/bom/mjs.min.js
@@ -6001,7 +6001,7 @@
 
                                 // 输出日志;
                             };
-                            msg = "mjs load success. \u2668 \u8BBF\u95EE\u5E94\u7528: " + pageConfig.appType + " " + pageConfig.appName + " \xA9\uFE0F \u7248\u6743\u8BF7\u8BF7\u8054\u7CFB: https://www.aliwork.com/o/mc";
+                            msg = "static load success. \u2668 \u8BBF\u95EE\u5E94\u7528: " + pageConfig.appType + " " + pageConfig.appName + " \xA9\uFE0F \u7248\u6743\u8BF7\u8BF7\u8054\u7CFB: https://www.aliwork.com/o/mc";
 
                             console.log(msg, mjs, config$$1);
 
@@ -6023,4 +6023,4 @@
     Object.defineProperty(exports, '__esModule', {value: true});
 
 })));
-//# sourceMappingURL=mjs.js.map
+//# sourceMappingURL=static.js.map

Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
mjava/src/main/resources/static/mjs/mjs.min.js


+ 240 - 0
mjava/target/classes/META-INF/spring-configuration-metadata.json

@@ -35,6 +35,46 @@
       "type": "com.malk.server.common.FilePath$Path",
       "sourceType": "com.malk.server.common.FilePath$Path"
     },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path",
+      "type": "com.malk.server.common.FilePath$Path",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
     {
       "name": "file.source",
       "type": "com.malk.server.common.FilePath$Source",
@@ -45,6 +85,46 @@
       "type": "com.malk.server.common.FilePath$Source",
       "sourceType": "com.malk.server.common.FilePath$Source"
     },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source",
+      "type": "com.malk.server.common.FilePath$Source",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
     {
       "name": "fxiaoke",
       "type": "com.malk.server.fxiaoke.FXKConf",
@@ -169,16 +249,176 @@
       "type": "java.lang.String",
       "sourceType": "com.malk.server.common.FilePath$Path"
     },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.file",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
     {
       "name": "file.path.image",
       "type": "java.lang.String",
       "sourceType": "com.malk.server.common.FilePath$Path"
     },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.image",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
     {
       "name": "file.path.tmp",
       "type": "java.lang.String",
       "sourceType": "com.malk.server.common.FilePath$Path"
     },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.path.tmp",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Path"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
+    {
+      "name": "file.source.fonts",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.common.FilePath$Source"
+    },
     {
       "name": "file.source.fonts",
       "type": "java.lang.String",

+ 7 - 7
mjava/target/classes/static/mjs/mjs.js

@@ -1,5 +1,5 @@
 /*!
- * mjs.js v0.2.0
+ * static.js v0.2.0
  * (c) 2018-2022 mcli
  */
 (function (global, factory) {
@@ -2145,7 +2145,7 @@
                 };
 
                 resolve(resp);
-                // const msg = `mjs load failure. ♨ 访问应用: ${resp.data.appType} ${resp.message} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
+                // const msg = `static load failure. ♨ 访问应用: ${resp.data.appType} ${resp.message} ©️ 版权请请联系: https://www.aliwork.com/o/mc`;
                 // reject(msg);
             }, 750);
         });
@@ -2353,7 +2353,7 @@
 // // 提交页面: button事件的3种绑定方式以及触发逻辑:https://www.cnblogs.com/ooo0/p/7742214.html
 // dom.closeCurrentTabForSubmit = function (that) {
 //   // 退回再提交走审批事件, 目前退回为加签逻辑 ==> 退回页面环境为编辑态值2
-//   if (mjs.env) return;
+//   if (static.env) return;
 //   function closeTab () {
 //     // 执行顺序: onclick > addEventListener > beforeSubmit
 //     // 特别说明:在提交情况下,页面存在beforeSubmit,相关的逻辑在beforeSubmit下要手动调用,因为这里是直接触发若是有判断情况下决定权交给beforeSubmit处理
@@ -5841,7 +5841,7 @@
         }
     };
 
-    /*** mjs 之 福氏钉钉SAP接口对接
+    /*** static 之 福氏钉钉SAP接口对接
      * 对接宜搭公共JavaScript库
      * 版权请联系:https://www.aliwork.com/o/mjs
      * 项目库地址: https://ding.practek.cn:38080/api/mjs/mjs.min.js
@@ -5972,7 +5972,7 @@
         }
     };
 
-    /*** mc 系列之 mjs
+    /*** mc 系列之 static
      * 对接宜搭公共JavaScript库
      * 版权请联系:https://www.aliwork.com/o/mjs
      * 公共库地址:https://aliwork.zitoo.com.cn/mc/js/bom/mjs.min.js
@@ -6001,7 +6001,7 @@
 
                                 // 输出日志;
                             };
-                            msg = "mjs load success. \u2668 \u8BBF\u95EE\u5E94\u7528: " + pageConfig.appType + " " + pageConfig.appName + " \xA9\uFE0F \u7248\u6743\u8BF7\u8BF7\u8054\u7CFB: https://www.aliwork.com/o/mc";
+                            msg = "static load success. \u2668 \u8BBF\u95EE\u5E94\u7528: " + pageConfig.appType + " " + pageConfig.appName + " \xA9\uFE0F \u7248\u6743\u8BF7\u8BF7\u8054\u7CFB: https://www.aliwork.com/o/mc";
 
                             console.log(msg, mjs, config$$1);
 
@@ -6023,4 +6023,4 @@
     Object.defineProperty(exports, '__esModule', {value: true});
 
 })));
-//# sourceMappingURL=mjs.js.map
+//# sourceMappingURL=static.js.map

Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
mjava/target/classes/static/mjs/mjs.min.js