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

Merge remote-tracking branch 'origin/master'

lxy_01 преди 6 дни
родител
ревизия
b5bc1f9f5b
променени са 35 файла, в които са добавени 654 реда и са изтрити 167 реда
  1. 0 101
      fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/controller/WxCorpInfoController.java
  2. 58 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/aop/NewLimitRequestAspect.java
  3. 12 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/config/NewLimitRequest.java
  4. 6 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/DingDingController.java
  5. 11 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/LeaveSheetController.java
  6. 4 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ProjectController.java
  7. 9 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java
  8. 7 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/TaskGroupController.java
  9. 61 4
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserCorpwxTimeController.java
  10. 2 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/CompanyDingdingService.java
  11. 10 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/DingDingService.java
  12. 2 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ProjectService.java
  13. 3 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/TaskGroupService.java
  14. 3 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/CompanyDingdingServiceImpl.java
  15. 110 12
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/DingDingServiceImpl.java
  16. 3 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/FeishuInfoServiceImpl.java
  17. 39 7
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java
  18. 29 2
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/TaskGroupServiceImpl.java
  19. 1 1
      fhKeeper/formulahousekeeper/timesheet-workshop/config/index.js
  20. 141 3
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/demo_index.html
  21. 27 3
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.css
  22. 1 1
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.js
  23. 42 0
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.json
  24. BIN
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.ttf
  25. BIN
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff
  26. BIN
      fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff2
  27. 4 4
      fhKeeper/formulahousekeeper/timesheet/src/components/taskComponent.vue
  28. 2 2
      fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue
  29. 1 1
      fhKeeper/formulahousekeeper/timesheet/src/views/project/gantt.vue
  30. 32 3
      fhKeeper/formulahousekeeper/timesheet/src/views/project/projectInside.vue
  31. 1 0
      fhKeeper/formulahousekeeper/timesheet/src/views/task/list.vue
  32. 0 18
      fhKeeper/formulahousekeeper/timesheet/src/views/team/index.vue
  33. 30 0
      fhKeeper/formulahousekeeper/timesheet/src/views/workReport/daily.vue
  34. 1 1
      fhKeeper/formulahousekeeper/timesheet_h5/src/views/expense/index.vue
  35. 2 1
      fhKeeper/formulahousekeeper/timesheet_mld/src/components/taskComponent.vue

+ 0 - 101
fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/controller/WxCorpInfoController.java

@@ -1,101 +0,0 @@
-package com.management.platform.controller;
-
-
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.management.platform.entity.User;
-import com.management.platform.entity.WxCorpInfo;
-import com.management.platform.mapper.UserMapper;
-import com.management.platform.service.WxCorpInfoService;
-import com.management.platform.task.TimingTask;
-import com.management.platform.util.HttpRespMsg;
-import com.management.platform.util.ListUtil;
-import org.springframework.http.HttpRequest;
-import org.springframework.util.StringUtils;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletRequest;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * <p>
- *  前端控制器
- * </p>
- *
- * @author Seyason
- * @since 2021-07-14
- */
-@RestController
-@RequestMapping("/wx-corp-info")
-public class WxCorpInfoController {
-
-    @Resource
-    WxCorpInfoService wxCorpInfoService;
-    @Resource
-    UserMapper userMapper;
-    @Resource
-    private TimingTask timingTask;
-
-    @RequestMapping("/testDownload")
-    public HttpRespMsg testDownload() {
-        HttpRespMsg msg = new HttpRespMsg();
-        msg.data = wxCorpInfoService.testDownloadFile();
-        return msg;
-    }
-
-    @RequestMapping("/saveContactSecret")
-    public HttpRespMsg saveContactSecret(WxCorpInfo info) {
-        HttpRespMsg msg = new HttpRespMsg();
-        msg.data = wxCorpInfoService.updateById(info);
-        return msg;
-    }
-
-    @RequestMapping("/get")
-    public HttpRespMsg get(Integer companyId) {
-        HttpRespMsg msg = new HttpRespMsg();
-        msg.data = wxCorpInfoService.getOne(new QueryWrapper<WxCorpInfo>().eq("company_id", companyId));
-        return msg;
-    }
-
-    @RequestMapping("/testSendTemplateMsg")
-    public HttpRespMsg testSendTemplateMsg(String userId) {
-        int companyId=7;
-        WxCorpInfo corpInfo = wxCorpInfoService.getOne(new QueryWrapper<WxCorpInfo>().eq("company_id", companyId));
-        String corpwxuserIds = "woy9TkCAAAPD6149u46N_Yi5ARSA4VFw";
-        System.out.println("发送给:"+corpwxuserIds);
-        //推送到企业微信
-//        String corpUid = user.getCorpwxUserid();
-        JSONObject json=new JSONObject();
-        JSONArray dataJson=new JSONArray();
-        JSONObject item=new JSONObject();
-        item.put("key","审核人");
-        item.put("value","$userName=woy9TkCAAAPD6149u46N_Yi5ARSA4VFw$");
-        dataJson.add(item);
-        json.put("template_id","tty9TkCAAANpvEtLrkPUGeOEd1-U7W2w");
-        JSONObject item2=new JSONObject();
-        item2.put("key","日期");
-        item2.put("value","2021-07-14");
-        dataJson.add(item2);
-        json.put("url","https://open.weixin.qq.com/connect/oauth2/authorize?appid=ww4e237fd6abb635af&redirect_uri=http://worktime.ttkuaiban.com/api/corpWXAuth&response_type=code&scope=snsapi_base&state=0#wechat_redirect");
-        json.put("content_item",dataJson);
-        wxCorpInfoService.sendWXCorpTemplateMsg(corpInfo,corpwxuserIds,json);
-        return new HttpRespMsg();
-    }
-
-    @RequestMapping("/wxLeaveTest")
-    public void wxLeaveTest() throws Exception {
-        timingTask.synWxLeave();
-    }
-
-    @RequestMapping("/batchTransferLicense")
-    public HttpRespMsg batchTransferLicense(HttpServletRequest request,String handoverId,String takeoverId) throws Exception {
-        return wxCorpInfoService.batchTransferLicense(request,handoverId,takeoverId);
-    }
-}
-

+ 58 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/aop/NewLimitRequestAspect.java

@@ -0,0 +1,58 @@
+package com.management.platform.aop;
+
+import com.management.platform.config.NewLimitRequest;
+import com.management.platform.util.HttpRespMsg;
+import com.management.platform.util.MessageUtils;
+import net.jodah.expiringmap.ExpirationPolicy;
+import net.jodah.expiringmap.ExpiringMap;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+@Aspect
+@Component
+public class NewLimitRequestAspect {
+    private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> book = new ConcurrentHashMap<>();
+    // 定义切点
+    // 让所有有@NewLimitRequest注解的方法都执行切面方法
+    @Pointcut("@annotation(newLimitRequest)")
+    public void excudeService(NewLimitRequest newLimitRequest) {
+    }
+
+    @Around("excudeService(newLimitRequest)")
+    public Object doAround(ProceedingJoinPoint pjp, NewLimitRequest newLimitRequest) throws Throwable {
+        HttpRespMsg httpRespMsg=new HttpRespMsg();
+        // 获得request对象
+        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
+        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
+        HttpServletRequest request = sra.getRequest();
+
+        // 获取Map对象, 如果没有则返回默认值
+        // 第一个参数是key, 第二个参数是默认值
+        ExpiringMap<String, Integer> map = book.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build());
+        Integer uCount = map.getOrDefault(request.getRemoteAddr(), 0);
+        if (uCount >= newLimitRequest.count()) { // 超过次数,不执行目标方法
+            //这里的返回对象类型根据controller方法的返回方式一致
+            httpRespMsg.setError(MessageUtils.message("request.countLimit"));
+            return httpRespMsg;
+        } else if (uCount == 0){ // 第一次请求时,设置开始有效时间
+            map.put(request.getRemoteAddr(), uCount + 1, ExpirationPolicy.CREATED, newLimitRequest.time(), TimeUnit.MILLISECONDS);
+        } else { // 未超过次数, 记录数据加一
+            map.put(request.getRemoteAddr(), uCount + 1);
+        }
+        book.put(request.getRequestURI(), map);
+
+        // result的值就是被拦截方法的返回值
+        return pjp.proceed();
+    }
+    
+}

+ 12 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/config/NewLimitRequest.java

@@ -0,0 +1,12 @@
+package com.management.platform.config;
+
+import java.lang.annotation.*;
+
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NewLimitRequest {
+    //毫秒,分钟,小时 之间的转换用算数
+    long time() default 60000; // 限制时间 单位:毫秒
+    int count() default Integer.MAX_VALUE; // 允许请求的次数
+}

+ 6 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/DingDingController.java

@@ -351,4 +351,10 @@ public class DingDingController {
         return dingDingService.initSuperManager(corpid, name);
     }
 
+    @RequestMapping("/getAttendanceScheduleResult")
+    public HttpRespMsg getAttendanceScheduleResult(Integer companyId, String userId, String date) {
+        User user = userMapper.selectById(userId);
+        return dingDingService.getAttendanceScheduleResult(companyId, user.getDingdingUserid(), date, date);
+    }
+
 }

+ 11 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/LeaveSheetController.java

@@ -81,6 +81,17 @@ public class LeaveSheetController {
 
     }
 
+    @RequestMapping("/addLeaveSheet")
+    public HttpRespMsg addLeaveSheet(LeaveSheet sheet) {
+        if (sheet.getOwnerId() == null) {
+
+        }
+        leaveSheetService.save(sheet);
+        HttpRespMsg httpRespMsg = new HttpRespMsg();
+        httpRespMsg.setData(sheet);
+        return httpRespMsg;
+    }
+
     @RequestMapping("/cancel")
     public HttpRespMsg cancel(Integer id) {
         String userId = request.getHeader("Token");

+ 4 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ProjectController.java

@@ -138,6 +138,10 @@ public class ProjectController {
         return projectMainService.getAllMainProject(request);
     }
 
+    @RequestMapping("/getRemainingTime")
+    public HttpRespMsg getRemainingTime(Integer projectId) {
+        return projectService.getRemainingTime(projectId, request);
+    }
 
     /**
      * 预估工时表

+ 9 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.github.pagehelper.util.StringUtil;
 import com.management.platform.config.LimitRequest;
+import com.management.platform.config.NewLimitRequest;
 import com.management.platform.constant.Constant;
 import com.management.platform.entity.*;
 import com.management.platform.entity.vo.MonthWorkingTimeVO;
@@ -18,6 +19,7 @@ import com.management.platform.service.*;
 import com.management.platform.service.impl.WxCorpInfoServiceImpl;
 import com.management.platform.util.*;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.client.RestTemplate;
@@ -2874,6 +2876,13 @@ public class ReportController {
     @LimitRequest(count = 10)
     @PostMapping("/getReportListByToken")
     public HttpRespMsg getReportListByToken(@RequestBody String json){
+        HttpRespMsg httpRespMsg = reportService.getReportListByToken(json);
+        return httpRespMsg;
+    }
+
+    @NewLimitRequest(count = 10)
+    @PostMapping("/getReportListByTokenNew")
+    public HttpRespMsg getReportListByTokenNew(@RequestBody String json){
         return reportService.getReportListByToken(json);
     }
 

+ 7 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/TaskGroupController.java

@@ -353,7 +353,7 @@ public class TaskGroupController {
             QueryWrapper<User> eq = new QueryWrapper<User>().eq("id", request.getHeader("token"));
             User user = userMapper.selectOne(eq);
             //初始化任务分组
-            taskGroupService.initGroup(companyId, item.getProjectId(), user);
+            taskGroupService.initGroup(companyId, item.getProjectId(), user, null);
         }
         List<TaskGroup> list = taskGroupService.list(queryWrapper);
         list.forEach(l->{
@@ -560,5 +560,11 @@ public class TaskGroupController {
         User user = userMapper.selectById(request.getHeader("token"));
         return taskGroupService.setTemplate(user,setTemplate);
     }
+
+    @RequestMapping("/syncStagesToProjects")
+    public HttpRespMsg syncStagesToProjects(Integer templateId){
+        User user = userMapper.selectById(request.getHeader("token"));
+        return taskGroupService.syncStagesToProjects(user,templateId);
+    }
 }
 

+ 61 - 4
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserCorpwxTimeController.java

@@ -1786,9 +1786,66 @@ public class UserCorpwxTimeController {
             Sheet staffSheet = workbook.getSheetAt(1);
             System.out.println("正在处理第二个Sheet: " + staffSheet.getSheetName() + " (人员分配)");
             List<Map<String, Object>> staffAllocationResult = processStaffAllocationSheet(staffSheet, companyId, allUserList);
-            
+            //检查系统中是否存在表格中员工没有参与的项目
+            List<Project> projectList = projectMapper.selectList(new QueryWrapper<Project>().eq("company_id", companyId));
+            List<Participation> participationList = participationMapper.selectList(new QueryWrapper<Participation>().in("project_id", projectList.stream().map(Project::getId).collect(Collectors.toList())));
+
+            List<HashMap> shouldDeleteParticipation = new ArrayList<>();
+            for (Participation participation : participationList) {
+                Project p = projectList.stream().filter(ppo->ppo.getId().equals(participation.getProjectId())).findFirst().get();
+                //如果是项目经理,跳过检查
+                if (p.getInchargerId() != null && p.getInchargerId().equals(participation.getUserId())) {
+                    continue;
+                }
+                //检查是否存在
+                boolean find = false;
+                boolean findProjectInTable = false;
+                //检查项目是否在当前的表格中
+                for (Map<String, Object> map : staffAllocationResult) {
+                    Integer projectId = (Integer)map.get("projectId");
+                    if (projectId.equals(participation.getProjectId())) {
+                        findProjectInTable = true;
+                        break;
+                    }
+                }
+                //参与的项目不在当前表格中,跳过
+                if (!findProjectInTable) {
+                    continue;
+                }
+                for (Map<String, Object> map : staffAllocationResult) {
+                    Integer projectId = (Integer)map.get("projectId");
+                    String employeeId = (String)map.get("employeeId");
+                    if (projectId.equals(participation.getProjectId()) && employeeId.equals(participation.getUserId())) {
+                        find = true;
+                        break;
+                    }
+                }
+                if (!find) {
+                    HashMap map = new HashMap();
+                    map.put("id", participation.getId());
+                    map.put("projectId", participation.getProjectId());
+
+                    map.put("projectCode", p.getProjectCode());
+                    map.put("projectName", p.getProjectName());
+                    map.put("userId", participation.getUserId());
+                    User findUser = allUserList.stream().filter(u->u.getId().equals(participation.getUserId())).findFirst().get();
+                    map.put("userName", findUser.getName());
+                    map.put("jobNumber", findUser.getJobNumber());
+                    shouldDeleteParticipation.add(map);
+                }
+            }
+//                    msg.setError("员工 " + participation.getUserId() + " 没有参与项目 code =" + participation.getProjectId() + projectList.stream().filter(p->p.getId().equals(participation.getProjectId())).findFirst().get().getProjectCode() + ", name=" +
+//                            projectList.stream().filter(p->p.getId().equals(participation.getProjectId())).findFirst().get().getProjectName());
+//                    return msg;
             // 返回员工-项目-可填月份的数据集合
+//            HashMap retMap = new HashMap();
+//            retMap.put("data", staffAllocationResult);
+//            retMap.put("shouldDeleteParticipation", shouldDeleteParticipation);
             msg.data = staffAllocationResult;
+//            if (shouldDeleteParticipation.size() > 0) {
+//                //删除数据
+//                participationMapper.deleteBatchIds(shouldDeleteParticipation.stream().map(map->(Integer)map.get("id")).collect(Collectors.toList()));
+//            }
             System.out.println("项目和人员分配数据导入完成,共处理 " + staffAllocationResult.size() + " 条员工分配记录");
             IMPORTED_SHEET_ONE_DATA = staffAllocationResult;
         } catch (IOException e) {
@@ -2123,7 +2180,7 @@ public class UserCorpwxTimeController {
         LocalDate startMonth = now.withDayOfMonth(1).withMonth(1);
 
         if (headerRow != null) {
-            for (int colIndex = 11; colIndex < 11 + 12; colIndex++) { // 第12列开始(索引11)
+            for (int colIndex = 11; colIndex < 11 + 24; colIndex++) { // 第12列开始(索引11)
                 Cell cell = headerRow.getCell(colIndex);
                 if (cell != null) {
                     String monthStr = startMonth.plusMonths(colIndex - 11).format(DateTimeFormatter.ofPattern("yyyy-MM"));
@@ -2202,10 +2259,10 @@ public class UserCorpwxTimeController {
                     currentEmployee[0] = allUserList.stream().filter(u -> u.getJobNumber() != null && u.getJobNumber().equals(finalJobNumber)).findFirst().orElse(null);
                     if (currentEmployee[0] == null) {
                         // 查找员工 - 使用企业微信搜索结果进行匹配
-                        System.out.println("未找到员工姓名为 " + currentEmployeeName[0] + " 的员工,使用企业微信搜索");
+                        System.out.println("未找到员工工号为 " + finalJobNumber + " 的员工,使用企业微信搜索");
                         currentEmployee[0] = findUserByNameWithWxSearch(allUserList, currentEmployeeName[0], targetUserList, wxCorpInfo, null);
                         if (currentEmployee[0] == null) {
-                            System.out.println("未找到员工工号为 " + currentJobNumber[0] + " 的员工");
+                            System.out.println("未找到员工姓名为 " + currentEmployeeName[0] + " 的员工");
                             User missing = new User();
                             missing.setName(currentEmployeeName[0]);
                             missing.setJobNumber(currentJobNumber[0]);

+ 2 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/CompanyDingdingService.java

@@ -40,4 +40,6 @@ public interface CompanyDingdingService extends IService<CompanyDingding> {
     public void sendInnerLinkMsg(CompanyDingding dingding, String useridList, String title, String alertMsg);
 
     HttpRespMsg getSearchUserInfo(CompanyDingding dingding, String name, Integer searchType) throws Exception;
+
+
 }

+ 10 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/DingDingService.java

@@ -65,4 +65,14 @@ public interface DingDingService {
     HttpRespMsg refreshUserCardTime(Integer companyId, String userId, String date);
 
     UserDingdingTime listUserScheduleByDay(Integer companyId, String userId, String date);
+
+    /**
+     * 获取员工的考勤打卡记录
+     * @param companyId 公司ID
+     * @param userIds 员工ID列表,多个用逗号分隔
+     * @param workDateFrom 开始日期 格式:yyyy-MM-dd
+     * @param workDateTo 结束日期 格式:yyyy-MM-dd
+     * @return 考勤打卡记录
+     */
+    HttpRespMsg getAttendanceScheduleResult(Integer companyId, String userIds, String workDateFrom, String workDateTo);
 }

+ 2 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ProjectService.java

@@ -344,4 +344,6 @@ public interface ProjectService extends IService<Project> {
     void syncHongHuData(int honghuCompId);
 
     HttpRespMsg fixQingJianData();
+
+    HttpRespMsg getRemainingTime(Integer projectId, HttpServletRequest request);
 }

+ 3 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/TaskGroupService.java

@@ -23,5 +23,7 @@ public interface TaskGroupService extends IService<TaskGroup> {
 
     HttpRespMsg createFromTemplate(String templateJson, Integer projectId,String projectIds);
 
-    void initGroup(Integer companyId,Integer projectId, User user);
+    void initGroup(Integer companyId,Integer projectId, User user, Integer templateId);
+
+    HttpRespMsg syncStagesToProjects(User user, Integer templateId);
 }

+ 3 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/CompanyDingdingServiceImpl.java

@@ -36,6 +36,7 @@ import javax.annotation.Resource;
 import java.net.URLEncoder;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
@@ -635,4 +636,6 @@ public class CompanyDingdingServiceImpl extends ServiceImpl<CompanyDingdingMappe
         return msg;
     }
 
+
+
 }

+ 110 - 12
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/DingDingServiceImpl.java

@@ -2247,15 +2247,6 @@ public class DingDingServiceImpl implements DingDingService {
             userList.add(userMapper.selectById(targetUserId));
         } else {
             userList = userMapper.selectList(new QueryWrapper<User>().eq("company_id", dingding.getCompanyId()).eq("is_active", 1));
-//            for (User user : userList) {
-//                if (user.getName().equals("杨云鑫")) {
-//                    System.out.println("杨云鑫。。。。" + user.getDingdingUserid());
-//                    List<User> nbewList = new ArrayList<>();
-//                    nbewList.add(user);
-//                    userList = nbewList;
-//                    break;
-//                }
-//            }
         }
         String accessToken = getCorpAccessToken(dingding);
         if (userList.size() > 50) {
@@ -2949,13 +2940,11 @@ public class DingDingServiceImpl implements DingDingService {
                         //第一天
                         long seconds = end.toEpochSecond(ZoneOffset.ofHours(8)) - start.toEpochSecond(ZoneOffset.ofHours(8));
                         double workHours = DateTimeUtil.getHoursFromDouble(DateTimeUtil.getHoursFromSeconds((int) seconds));
-                        System.out.println("出差时长=="+workHours);
                         userDingdingTime.setWorkHours((float)workHours);
                     } else if (endDateStr.equals(date)) {
                         //最后一天
                         long seconds = end.toEpochSecond(ZoneOffset.ofHours(8)) - start.toEpochSecond(ZoneOffset.ofHours(8));
                         double workHours = DateTimeUtil.getHoursFromDouble(DateTimeUtil.getHoursFromSeconds((int) seconds));
-                        System.out.println("出差时长=="+workHours);
                         userDingdingTime.setWorkHours((float)workHours);
                     } else {
                         //中间天,就是全天
@@ -2972,7 +2961,6 @@ public class DingDingServiceImpl implements DingDingService {
                     //就是当天,计算时长
                     long seconds = end.toEpochSecond(ZoneOffset.ofHours(8)) - start.toEpochSecond(ZoneOffset.ofHours(8));
                     double workHours = DateTimeUtil.getHoursFromDouble(DateTimeUtil.getHoursFromSeconds((int) seconds));
-                    System.out.println("出差时长=="+workHours);
                     userDingdingTime.setWorkHours((float)workHours);
                 }
                 if (userDingdingTime.getWorkHours() > 8.0) {
@@ -3009,4 +2997,114 @@ public class DingDingServiceImpl implements DingDingService {
         }
         return deptId;
     }
+
+    private List<Long> getSchedulePlanIds(String accessToken, String workDate, String userId, long offset) {
+        List<Long> planIds = new ArrayList<>();
+        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/attendance/listschedule");
+        OapiAttendanceListscheduleRequest req = new OapiAttendanceListscheduleRequest();
+        req.setWorkDate(StringUtils.parseDateTime(workDate + " 11:11:11"));
+        req.setOffset(offset);
+        req.setSize(200L);
+        OapiAttendanceListscheduleResponse rsp = null;
+        try {
+            rsp = client.execute(req, accessToken);
+            System.out.println(rsp.getBody());
+            JSONObject jsonObject = JSONObject.parseObject(rsp.getBody());
+            if (jsonObject.getInteger("errcode") == 0) {
+                JSONObject resultObj = jsonObject.getJSONObject("result");
+                Boolean hasMore = resultObj.getBoolean("has_more");
+                JSONArray jsonArray = resultObj.getJSONArray("schedules");
+                /**
+                 *数据格式为 {
+                 *                     "check_type": "OnDuty",
+                 *                     "class_id": 677995086,
+                 *                     "class_setting_id": 599315627,
+                 *                     "group_id": 685935028,
+                 *                     "plan_check_time": "2020-11-11 09:30:00",
+                 *                     "plan_id": 157062792171,
+                 *                     "userid": "user01"
+                 *                }
+                 */
+
+                for (int i=0;i<jsonArray.size(); i++) {
+                    JSONObject scheduleObj = jsonArray.getJSONObject(i);
+                    String planId = scheduleObj.getString("plan_id");
+                    String userIdStr = scheduleObj.getString("userid");
+                    if (userIdStr.equals(userId)) {
+                        planIds.add(Long.parseLong(planId));
+                    }
+                }
+                if (hasMore) {
+                    List<Long> list = getSchedulePlanIds(accessToken, workDate, userId, offset+200);
+                    if (list.size() > 0) {
+                        planIds.addAll(list);
+                    }
+                }
+            }
+        } catch (ApiException e) {
+            throw new RuntimeException(e);
+        }
+        return planIds;
+    }
+
+    /**
+     * 获取员工的考勤打卡记录
+     * 调用钉钉接口:https://oapi.dingtalk.com/topapi/attendance/schedule/result/listbyids
+     * 参考文档:https://open.dingtalk.com/document/development/query-the-results-of-a-batch-of-tasks
+     * @param companyId 公司ID
+     * @param userId 员工钉钉ID列表,多个用逗号分隔
+     * @param workDateFrom 开始日期 格式:yyyy-MM-dd
+     * @param workDateTo 结束日期 格式:yyyy-MM-dd
+     * @return 考勤打卡记录
+     */
+    @Override
+    public HttpRespMsg getAttendanceScheduleResult(Integer companyId, String userId, String workDateFrom, String workDateTo) {
+        HttpRespMsg msg = new HttpRespMsg();
+        try {
+            // 获取公司钉钉配置信息
+            CompanyDingding dingding = companyDingdingMapper.selectOne(new QueryWrapper<CompanyDingding>().eq("company_id", companyId));
+            if (dingding == null) {
+                msg.setError("未找到公司钉钉配置信息");
+                return msg;
+            }
+
+            // 获取accessToken
+            String accessToken = getCorpAccessToken(dingding);
+            if (accessToken == null) {
+                msg.setError("获取accessToken失败");
+                return msg;
+            }
+
+            List<Long> planIds = getSchedulePlanIds(accessToken, workDateFrom, userId, 0);
+            System.out.println("planIds===="+planIds);
+            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/attendance/schedule/result/listbyids");
+            OapiAttendanceScheduleResultListbyidsRequest req = new OapiAttendanceScheduleResultListbyidsRequest();
+            String oaManagerDid = dingding.getOaManagerDingid();
+            req.setOpUserId(oaManagerDid);
+            String planIdsString = planIds.stream().map(Object::toString).collect(Collectors.joining(","));
+            req.setScheduleIds(planIdsString);
+            OapiAttendanceScheduleResultListbyidsResponse rsp = client.execute(req, accessToken);
+            System.out.println(rsp.getBody());
+            // 解析返回结果
+            JSONObject json = JSONObject.parseObject(rsp.getBody());
+            if (json.getInteger("errcode") == 0) {
+                // 成功获取数据
+                msg.data = json.getJSONObject("result");
+                System.out.println("成功获取考勤打卡记录,员工ID:" + userId + ",日期范围:" + workDateFrom + " 至 " + workDateTo);
+            } else {
+                // 接口返回错误
+                msg.setError("钉钉接口返回错误:" + json.getString("errmsg"));
+                System.err.println("获取考勤打卡记录失败:" + json.getString("errmsg"));
+            }
+
+        } catch (ApiException e) {
+            e.printStackTrace();
+            msg.setError("调用钉钉接口异常:" + e.getMessage());
+        } catch (Exception e) {
+            e.printStackTrace();
+            msg.setError("获取考勤打卡记录异常:" + e.getMessage());
+        }
+
+        return msg;
+    }
 }

+ 3 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/FeishuInfoServiceImpl.java

@@ -145,7 +145,9 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
                 RestTemplate restTemplate = new RestTemplate();
                 MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
                 headers.setContentType(type);
-                headers.add("Authorization","Bearer "+getTenantAccessToken(feishuInfo.getAppId()));
+                String token = getTenantAccessToken(feishuInfo.getAppId());
+                System.out.println("飞书token======"+ token);
+                headers.add("Authorization","Bearer "+token);
                 HttpEntity<JSONObject> httpEntity = new HttpEntity<>(null, headers);
                 ResponseEntity<String> ResponseEntity = restTemplate.exchange(url,HttpMethod.GET,httpEntity,String.class);
                 if (ResponseEntity.getStatusCode() == HttpStatus.OK) {

+ 39 - 7
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java

@@ -2064,7 +2064,7 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
                     id = project.getId();
                     //项目管理专业版要自动生成任务分组
                     if (company.getPackageProject() == 1) {
-                        taskGroupService.initGroup(companyId, id, user);
+                        taskGroupService.initGroup(companyId, id, user, null);
                     }
                     OperationRecord operationRecord=new OperationRecord();
                     operationRecord.setProjectName(project.getProjectName());
@@ -2558,7 +2558,10 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
                 projectMapper.cleanPublicProjectData(id);
             }
         }
-        //250214新需求:添加项目部门关联关系
+        //250214新需求:添加项目部门关联关系,先清空之前的表
+        projectDeptRelateMapper.delete(new LambdaQueryWrapper<ProjectDeptRelate>()
+                .eq(ProjectDeptRelate::getProjectId,id)
+        );
         if(org.apache.commons.lang3.StringUtils.isNotBlank(deptIds)){
             List<String> deptList = null;
             try {
@@ -2567,13 +2570,9 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
                 httpRespMsg.setError("部门解析失败");
                 return httpRespMsg;
             }
-            projectDeptRelateMapper.delete(new LambdaQueryWrapper<ProjectDeptRelate>()
-                    .eq(ProjectDeptRelate::getProjectId,id)
-            );
             if(CollectionUtils.isNotEmpty(deptList)&&id!=null){
                 projectDeptRelateMapper.insertBatch(id,deptList);
             }
-
         }
         return httpRespMsg;
     }
@@ -13913,7 +13912,7 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
         User user = userMapper.selectById(request.getHeader("TOKEN"));
         List<Project> projectList = projectMapper.selectList(new QueryWrapper<Project>().eq("company_id", user.getCompanyId()));
         projectList.forEach(p->{
-            taskGroupService.initGroup(companyId, p.getId(), user);
+            taskGroupService.initGroup(companyId, p.getId(), user, null);
         });
 
         return httpRespMsg;
@@ -14312,6 +14311,39 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
         return msg;
     }
 
+    @Override
+    public HttpRespMsg getRemainingTime(Integer projectId, HttpServletRequest request) {
+        HttpRespMsg msg = new HttpRespMsg();
+        Project project = projectMapper.selectById(projectId);
+        if(project.getManDay()!=null){
+            TimeType timeType = timeTypeMapper.selectById(project.getCompanyId());
+            BigDecimal bigDecimal=new BigDecimal(project.getManDay());
+            bigDecimal=bigDecimal.multiply(new BigDecimal(timeType.getAllday()));
+            String format = String.format("%.1f", bigDecimal.doubleValue());
+            project.setEstimatedWorkTime(format);
+            //todo:计算剩余工时
+            QueryWrapper<Report> queryWrapper = new QueryWrapper<>();
+            queryWrapper.select("project_id, sum(working_time) as working_time").eq("project_id", projectId)
+                    .and(wrapper -> wrapper.eq("state", 0).or().eq("state", 1)
+                            ).groupBy("project_id");
+            if(project.getManDayStartDate()!=null){
+                //reports = reportList.stream().filter(rl -> rl.getProjectId().equals(project.getId())&&(rl.getCreateDate().isAfter(project.getManDayStartDate())||rl.getCreateDate().isEqual(project.getManDayStartDate()))).collect(Collectors.toList());
+                queryWrapper.ge("create_date",project.getManDayStartDate());
+            }
+            Report report = reportMapper.selectOne(queryWrapper);
+            if (report == null) {
+                msg.data = String.format("%.1fh", bigDecimal.doubleValue());
+            } else {
+                bigDecimal=bigDecimal.subtract(new BigDecimal(report.getWorkingTime()));
+                msg.data = String.format("%.1fh", bigDecimal.doubleValue());
+            }
+        } else {
+            msg.data = "无限制";
+        }
+
+        return msg;
+    }
+
     public void initGroup(Integer companyId, Integer projectId) {
         User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                 .eq(User::getRoleName, "超级管理员")

+ 29 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/TaskGroupServiceImpl.java

@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -65,6 +66,8 @@ public class TaskGroupServiceImpl extends ServiceImpl<TaskGroupMapper, TaskGroup
     private TaskFilesService taskFilesService;
     @Autowired
     private TaskFilesMapper taskFilesMapper;
+    @Autowired
+    private TaskGroupService taskGroupService;
 
     @Override
     public void saveGroupIncharger(TaskGroup taskGroup,User user) {
@@ -209,8 +212,14 @@ public class TaskGroupServiceImpl extends ServiceImpl<TaskGroupMapper, TaskGroup
     }
 
     @Override
-    public void initGroup(Integer companyId, Integer projectId, User user) {
-        List<GroupTemplate> groupTemplates = groupTemplateMapper.selectList(new QueryWrapper<GroupTemplate>().eq("company_id", user.getCompanyId()).eq("cre_with_pro", true));
+    public void initGroup(Integer companyId, Integer projectId, User user, Integer templateId) {
+        QueryWrapper<GroupTemplate> queryWrapper = new QueryWrapper<GroupTemplate>();
+        if (templateId != null) {
+            queryWrapper.eq("id", templateId);
+        } else {
+            queryWrapper.eq("company_id", user.getCompanyId()).eq("cre_with_pro", true);
+        }
+        List<GroupTemplate> groupTemplates = groupTemplateMapper.selectList(queryWrapper);
         if (groupTemplates.size()==0){
             //创建默认分组
             TaskGroup group = new TaskGroup();
@@ -275,6 +284,24 @@ public class TaskGroupServiceImpl extends ServiceImpl<TaskGroupMapper, TaskGroup
         }
     }
 
+    @Override
+    public HttpRespMsg syncStagesToProjects(User user, Integer templateId) {
+        //查找同名分组名称
+        GroupTemplate groupTemplate = groupTemplateMapper.selectById(templateId);
+        Integer companyId = groupTemplate.getCompanyId();
+        List<Project> projectList = projectMapper.selectList(new QueryWrapper<Project>().select("id").eq("company_id", companyId));
+        if (projectList.size() > 0) {
+            List<Integer> ids = projectList.stream().map(Project::getId).collect(Collectors.toList());
+            for (Integer id : ids) {
+                //先删除项目下的该分组的相关数据
+                taskGroupMapper.delete(new QueryWrapper<TaskGroup>().eq("project_id", id).eq("name", groupTemplate.getName()));
+                taskGroupService.initGroup(companyId, id, user, groupTemplate.getId());
+            }
+
+        }
+        return new HttpRespMsg();
+    }
+
     @Override
     @Transactional
     public HttpRespMsg setTemplate(User user,setTemplate setTemplate){

+ 1 - 1
fhKeeper/formulahousekeeper/timesheet-workshop/config/index.js

@@ -4,7 +4,7 @@ var path = require('path')
 // var ip = '47.101.180.183'
 // var ip = '47.100.37.243'
 // var ip = '192.168.10.2'
-//var ip = '192.168.2.12'
+// var ip = '43.137.14.81'
 
 var os = require('os'), ip = '', ifaces = os.networkInterfaces() // 获取本机ip
 for (var i in ifaces) {

+ 141 - 3
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/demo_index.html

@@ -54,6 +54,42 @@
       <div class="content unicode" style="display: block;">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+              <span class="icon iconfont">&#xe619;</span>
+                <div class="name">bug</div>
+                <div class="code-name">&amp;#xe619;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe76f;</span>
+                <div class="name">location</div>
+                <div class="code-name">&amp;#xe76f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe61f;</span>
+                <div class="name">立项审批</div>
+                <div class="code-name">&amp;#xe61f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe618;</span>
+                <div class="name">产品</div>
+                <div class="code-name">&amp;#xe618;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69a;</span>
+                <div class="name">智能优化</div>
+                <div class="code-name">&amp;#xe69a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe772;</span>
+                <div class="name">钉钉</div>
+                <div class="code-name">&amp;#xe772;</div>
+              </li>
+          
             <li class="dib">
               <span class="icon iconfont">&#xe625;</span>
                 <div class="name">推送</div>
@@ -492,9 +528,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1672729800404') format('woff2'),
-       url('iconfont.woff?t=1672729800404') format('woff'),
-       url('iconfont.ttf?t=1672729800404') format('truetype');
+  src: url('iconfont.woff2?t=1766996481311') format('woff2'),
+       url('iconfont.woff?t=1766996481311') format('woff'),
+       url('iconfont.ttf?t=1766996481311') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -520,6 +556,60 @@
       <div class="content font-class">
         <ul class="icon_lists dib-box">
           
+          <li class="dib">
+            <span class="icon iconfont firerock-iconbug"></span>
+            <div class="name">
+              bug
+            </div>
+            <div class="code-name">.firerock-iconbug
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont firerock-iconlocation"></span>
+            <div class="name">
+              location
+            </div>
+            <div class="code-name">.firerock-iconlocation
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont firerock-iconlixiangshenpi"></span>
+            <div class="name">
+              立项审批
+            </div>
+            <div class="code-name">.firerock-iconlixiangshenpi
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont firerock-iconchanpin"></span>
+            <div class="name">
+              产品
+            </div>
+            <div class="code-name">.firerock-iconchanpin
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont firerock-iconzhinengyouhua"></span>
+            <div class="name">
+              智能优化
+            </div>
+            <div class="code-name">.firerock-iconzhinengyouhua
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont firerock-icondingding"></span>
+            <div class="name">
+              钉钉
+            </div>
+            <div class="code-name">.firerock-icondingding
+            </div>
+          </li>
+          
           <li class="dib">
             <span class="icon iconfont firerock-icontuisong"></span>
             <div class="name">
@@ -1177,6 +1267,54 @@
       <div class="content symbol">
           <ul class="icon_lists dib-box">
           
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#firerock-iconbug"></use>
+                </svg>
+                <div class="name">bug</div>
+                <div class="code-name">#firerock-iconbug</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#firerock-iconlocation"></use>
+                </svg>
+                <div class="name">location</div>
+                <div class="code-name">#firerock-iconlocation</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#firerock-iconlixiangshenpi"></use>
+                </svg>
+                <div class="name">立项审批</div>
+                <div class="code-name">#firerock-iconlixiangshenpi</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#firerock-iconchanpin"></use>
+                </svg>
+                <div class="name">产品</div>
+                <div class="code-name">#firerock-iconchanpin</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#firerock-iconzhinengyouhua"></use>
+                </svg>
+                <div class="name">智能优化</div>
+                <div class="code-name">#firerock-iconzhinengyouhua</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#firerock-icondingding"></use>
+                </svg>
+                <div class="name">钉钉</div>
+                <div class="code-name">#firerock-icondingding</div>
+            </li>
+          
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#firerock-icontuisong"></use>

+ 27 - 3
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 2390497 */
-  src: url('iconfont.woff2?t=1672729800404') format('woff2'),
-       url('iconfont.woff?t=1672729800404') format('woff'),
-       url('iconfont.ttf?t=1672729800404') format('truetype');
+  src: url('iconfont.woff2?t=1766996481311') format('woff2'),
+       url('iconfont.woff?t=1766996481311') format('woff'),
+       url('iconfont.ttf?t=1766996481311') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,30 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.firerock-iconbug:before {
+  content: "\e619";
+}
+
+.firerock-iconlocation:before {
+  content: "\e76f";
+}
+
+.firerock-iconlixiangshenpi:before {
+  content: "\e61f";
+}
+
+.firerock-iconchanpin:before {
+  content: "\e618";
+}
+
+.firerock-iconzhinengyouhua:before {
+  content: "\e69a";
+}
+
+.firerock-icondingding:before {
+  content: "\e772";
+}
+
 .firerock-icontuisong:before {
   content: "\e625";
 }

Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.js


+ 42 - 0
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.json

@@ -5,6 +5,48 @@
   "css_prefix_text": "firerock-icon",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "5524676",
+      "name": "bug",
+      "font_class": "bug",
+      "unicode": "e619",
+      "unicode_decimal": 58905
+    },
+    {
+      "icon_id": "2510117",
+      "name": "location",
+      "font_class": "location",
+      "unicode": "e76f",
+      "unicode_decimal": 59247
+    },
+    {
+      "icon_id": "12762893",
+      "name": "立项审批",
+      "font_class": "lixiangshenpi",
+      "unicode": "e61f",
+      "unicode_decimal": 58911
+    },
+    {
+      "icon_id": "4464993",
+      "name": "产品",
+      "font_class": "chanpin",
+      "unicode": "e618",
+      "unicode_decimal": 58904
+    },
+    {
+      "icon_id": "666901",
+      "name": "智能优化",
+      "font_class": "zhinengyouhua",
+      "unicode": "e69a",
+      "unicode_decimal": 59034
+    },
+    {
+      "icon_id": "20375943",
+      "name": "钉钉",
+      "font_class": "dingding",
+      "unicode": "e772",
+      "unicode_decimal": 59250
+    },
     {
       "icon_id": "10936691",
       "name": "推送",

BIN
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.ttf


BIN
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff


BIN
fhKeeper/formulahousekeeper/timesheet/src/assets/iconfont/iconfont.woff2


+ 4 - 4
fhKeeper/formulahousekeeper/timesheet/src/components/taskComponent.vue

@@ -34,7 +34,7 @@
                         <el-option v-for="item in relationdata" :key="item.id" :label="item.name" :value="item.id"></el-option>
                     </el-select>
                 </el-form-item>
-                <el-form-item :label="$t('types')" v-if="!showMmeiLaiDe">
+                <el-form-item :label="$t('types')" >
                     <el-select v-model="addForm.taskType" style="width:100%;" :disabled="((this.addForm.id != null && user.id != this.addForm.createrId && currentProject.inchargerId != user.id) && !permissions.projectManagement && !permissions.editAnyTask) && !(groupResponsibleId == user.id)" @change="selchg()">
                         <el-option v-for="item in taskTypeList" :key="item.id" :label="item.name" :value="item.id">
                             <i :class="item.icon" ></i>
@@ -855,9 +855,9 @@ export default {
         viewList: [{id:1,name:this.$t('alltaskss')},{id:2,name:this.$t('taskinprogress')},{id:3,name:this.$t('missionscompleted')},{id:4,name:this.$t('taskscheduled')},
         {id:5,name:this.$t('createdthetask')},{id:6,name:this.$t('missionIwason')},{id:7,name:this.$t('todaytask')},{id:8,name:this.$t('taskthatisoverdue')}],
         importanceList:[{id:0,name:this.$t('yi-ban')},{id:1,name:this.$t('zhong-yao')},{id:2,name:this.$t('jin-ji')},],
-        taskTypeList:[{id:0,name:this.$t('other.task'), icon:"iconfont firerock-iconrenwu"},{id:1,name:this.$t('other.milestone'),icon:"iconfont firerock-iconicon-"},{id:2,name:this.$t('risk'),icon:"iconfont firerock-iconfengxian"}],
-        taskTypeColor:[getThemeColor(),'#8613ad','#bf0404'],
-        taskTypeIcon:['iconfont firerock-iconrenwu','iconfont firerock-iconicon-','iconfont firerock-iconfengxian'],
+        taskTypeList:[{id:0,name:this.$t('other.task'), icon:"iconfont firerock-iconrenwu"},{id:1,name:this.$t('other.milestone'),icon:"iconfont firerock-iconicon-"},{id:2,name:this.$t('risk'),icon:"iconfont firerock-iconfengxian"}, {id:3, name:'BUG', icon: "iconfont firerock-iconbug"}],
+        taskTypeColor:[getThemeColor(),'#8613ad','#bf0404','#ff0000'],
+        taskTypeIcon:['iconfont firerock-iconrenwu','iconfont firerock-iconicon-','iconfont firerock-iconfengxian','iconfont firerock-iconbug'],
         taskStatusList:[this.$t('ongoing'),this.$t('state.completed'),this.$t('state.undone')],
         //优先级
         taskLevelColor:['#262626','#E6A23C','#F56C6C'],

+ 2 - 2
fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue

@@ -2254,8 +2254,8 @@ export default {
       addFormVisible:false,
       title:'',
       childrenList:[],
-      taskTypeTxt:[this.$t('other.task'), this.$t('other.milestone'), this.$t('risk')],
-      taskTypeObj: [{id: '0',value: this.$t('other.task')},{id: '1',value: this.$t('other.milestone')},{id: '2',value: this.$t('risk')}],
+      taskTypeTxt:[this.$t('other.task'), this.$t('other.milestone'), this.$t('risk'), 'BUG'],
+      taskTypeObj: [{id: '0',value: this.$t('other.task')},{id: '1',value: this.$t('other.milestone')},{id: '2',value: this.$t('risk')},{id: '3',value: 'BUG'}],
       taskTypeId: null,
       taskStatusTxt:[this.$t('ongoing'),this.$t('state.completed'),this.$t('state.undone')],
       statusTxt:["-",this.$t('ongoing'),this.$t('state.completed'),this.$t('state.undone')],

+ 1 - 1
fhKeeper/formulahousekeeper/timesheet/src/views/project/gantt.vue

@@ -214,7 +214,7 @@ export default {
         type_task: this.$t('other.task'),
         type_project: this.$t('other.project'),
         type_milestone: this.$t('other.milestone'),
-
+        type_bug: 'BUG',
         minutes: this.$t('fenZhong'),
         hours: this.$t('time.hour'),
         days: this.$t('time.day'),

+ 32 - 3
fhKeeper/formulahousekeeper/timesheet/src/views/project/projectInside.vue

@@ -1085,6 +1085,9 @@
                                 <el-link :underline="false" type="primary" style="color:#aaa;margin-left:10px;"
                                  @click="deleteTemplate(t)"><i class="el-icon-delete" v-show="t.isSystem == 0" ></i>
                                 </el-link>
+                                <el-link :underline="false" type="primary" style="color:#aaa;margin-left:10px;"
+                                 @click="syncTemplate(t)"><i :class="isSyncingTemplate?'el-icon-loading':'el-icon-refresh'" v-show="t.isSystem == 0" ></i>
+                                </el-link>
                                 </div>
                                 <div style="margin-top:10px;">
                                     <span style="color:#8c8c8c;font-size:12px;" v-for="(s,index) in t.stagesList" :key="s.id">
@@ -1329,6 +1332,7 @@
         
         data() {
             return {
+                isSyncingTemplate: false,
                 modGroupManDayDialog: false,
                 isManageDept: false,
                 componentFlg: false,
@@ -1360,9 +1364,9 @@
                 viewList: [{id:1,name:this.$t('alltaskss')},{id:2,name:this.$t('taskinprogress')},{id:3,name:this.$t('missionscompleted')},{id:4,name:this.$t('taskscheduled')},
                 {id:5,name:this.$t('createdthetask')},{id:6,name:this.$t('missionIwason')},{id:7,name:this.$t('todaytask')},{id:8,name:this.$t('taskthatisoverdue')}],
                 importanceList:[{id:0,name:this.$t('yi-ban')},{id:1,name:this.$t('zhong-yao')},{id:2,name:this.$t('jin-ji')},],
-                taskTypeList:[{id:0,name:this.$t('other.task'), icon:"iconfont firerock-iconrenwu"},{id:1,name:this.$t('other.milestone'),icon:"iconfont firerock-iconicon-"},{id:2,name:this.$t('risk'),icon:"iconfont firerock-iconfengxian"}],
-                taskTypeColor:[getThemeColor(),'#8613ad','#bf0404'],
-                taskTypeIcon:['iconfont firerock-iconrenwu','iconfont firerock-iconicon-','iconfont firerock-iconfengxian'],
+                taskTypeList:[{id:0,name:this.$t('other.task'), icon:"iconfont firerock-iconrenwu"},{id:1,name:this.$t('other.milestone'),icon:"iconfont firerock-iconicon-"},{id:2,name:this.$t('risk'),icon:"iconfont firerock-iconfengxian"}, {id:3, name:'BUG', icon: "iconfont firerock-iconbug"}],
+                taskTypeColor:[getThemeColor(),'#8613ad','#bf0404','#ff0000'],
+        taskTypeIcon:['iconfont firerock-iconrenwu','iconfont firerock-iconicon-','iconfont firerock-iconfengxian','iconfont firerock-iconbug'],
                 taskStatusList:[this.$t('ongoing'),this.$t('state.completed'),this.$t('state.undone')],
                 //优先级
                 taskLevelColor:['#262626','#E6A23C','#F56C6C'],
@@ -2136,6 +2140,31 @@
                     templateId: this.setTemplateData.id
                 })
             },
+            //同步模板的阶段和任务
+            syncTemplate(t) {
+                var that = this;
+                this.$confirm('同步该分组模板内的任务列表以及所有任务至已创建的项目,已存在同名的任务分组将被覆盖,您确定吗?', this.$t('other.prompts'), {
+                    //type: 'warning'
+                }).then(() => {
+                    this.isSyncingTemplate = true;
+                    this.http.post('/task-group/syncStagesToProjects', {templateId: t.id},
+                        res => {
+                            this.isSyncingTemplate = false;
+                            if (res.code == "ok") {
+                                this.$message({
+                                    message: '同步成功',
+                                    type: "success"
+                                });
+                            } else {
+                                this.$message({
+                                    message: res.msg,
+                                    type: "error"
+                                });
+                            }
+                        }
+                    );
+                });
+            },
             //删除模板
             deleteTemplate(t) {
                 var that = this;

+ 1 - 0
fhKeeper/formulahousekeeper/timesheet/src/views/task/list.vue

@@ -41,6 +41,7 @@
                             <el-option :label="$t('other.task')" value="0"></el-option>
                             <el-option :label="$t('other.milestone')" value="1"></el-option>
                             <el-option :label="$t('risk')" value="2"></el-option>
+                            <el-option label="BUG" value="3"></el-option>
                         </el-select>
                     </div>
                 </el-form-item>

+ 0 - 18
fhKeeper/formulahousekeeper/timesheet/src/views/team/index.vue

@@ -133,27 +133,9 @@
                     <el-form-item style="float:right;" v-if="user.timeType.syncAd==1">
                         <el-link type="primary" :underline="false" @click="syncAd">{{ '同步AD域控通讯录'}}</el-link>
                     </el-form-item>
-
-                    <!-- <el-form-item style="float:right;" v-if="user.dingdingUserid == null && permissions.structureImport">
-                        <el-link type="primary" :underline="false" href="./upload/人员导入模板.xlsx" download="人员导入模板.xlsx">模板下载</el-link>
-                    </el-form-item> -->
-                    <!-- <el-form-item  v-if="depData != null && depData.id != -1 && depData.id != 0" style="float:right;border: 0.5px solid #20a0ff;height: 27px;margin-top: 6px;">
-                    </el-form-item> -->
-                    <!-- <el-form-item style="float:right;">
-                        <el-link type="danger" v-if="depData != null && depData.id != -1 && depData.id != 0" :underline="false" @click="deleteDep(null)">删除部门</el-link>
-                    </el-form-item>
-                    <el-form-item style="float:right;">
-                        <el-link type="primary" v-if="depData != null && depData.id != -1 && depData.id != 0" :underline="false" @click="createDepartment(-2)">新增子部门</el-link>
-                    </el-form-item> -->
                     <el-form-item style="float:right;">
                         <span style="color: #666666">{{ $t('jiao-se') }}</span>
                         <el-select v-model="roleId" :placeholder="$t('defaultText.pleaseChoose')" @change="page = 1;getUser()" style="width: 120px" clearable size="small">
-                            <!-- <el-option
-                            v-for="item in rolesa"
-                            :key="item.value"
-                            :label="item.label"
-                            :value="item.value">
-                            </el-option> -->
                             <el-option v-for="item in acquireRoleList" :key="item.id" :label="item.rolename" :value="item.id"></el-option>
                         </el-select>
                     </el-form-item>

+ 30 - 0
fhKeeper/formulahousekeeper/timesheet/src/views/workReport/daily.vue

@@ -575,6 +575,12 @@
                                     </el-option>
                                 </el-select>
                             </template>
+
+                            <template v-if="user.companyId == 10 || user.companyId == 862">
+                                <span style="margin-left:10px;">剩余工时:{{ workForm.domains[index].remainingTime }}</span>
+                                <el-button type="default" style="margin-left:5px;" size="small" icon="el-icon-refresh" v-if="workForm.domains[index].projectId"
+                                @click="getProjectRemainingTime(workForm.domains[index])"></el-button>
+                            </template>
                             
                             <el-link v-if="(index >= 1 || workForm.domains.length > 1)&&domain.canEdit" type="primary" :underline="false" @click="delDomain(index)" style="float:right;margin-right:15%;"
                                 :disabled="workForm.domains.length==0?true:(workForm.domains[index].state>=2?false:true)">
@@ -3981,6 +3987,26 @@
                     }
                 );
             },
+            //获取项目剩余工时
+            getProjectRemainingTime(domain) {
+                var projectId = domain.projectId;
+                this.http.post('/project/getRemainingTime',{projectId},
+                res => {
+                    if (res.code == "ok") {
+                        this.$set(domain, 'remainingTime', res.data);
+                    } else {
+                        this.$set(domain, 'remainingTime', res.msg);
+                    }
+                },
+                error => {
+                    this.checkinLoading = false;
+                    this.$message({
+                        message: error,
+                        type: "error"
+                    });
+                    }
+                );
+            },
            
             loadCheckInData() {
                 if (this.importWxParam.date == null) {
@@ -5280,6 +5306,10 @@
                 if((reportExtraField4Name || reportExtraField5Name) && domain.groupId) {
                     this.getInfoByProjectId(domain, index)
                 }
+                //获取剩余工时
+                if (this.user.companyId == 10 || this.user.companyId == 862) {
+                    this.getProjectRemainingTime(domain);
+                }
             },
             getInfoByProjectId(domain, index, flag = true) {
                 const { projectId } = domain

+ 1 - 1
fhKeeper/formulahousekeeper/timesheet_h5/src/views/expense/index.vue

@@ -345,7 +345,7 @@ export default {
             currentDate1: new Date(),
             currentDate2: new Date(),
             minDate: new Date(2020, 0, 1),
-            maxDate: new Date(2025, 11, 31),
+            maxDate: new Date(2035, 11, 31),
             confirmLoading: false,
             denyLoading: false,
 

+ 2 - 1
fhKeeper/formulahousekeeper/timesheet_mld/src/components/taskComponent.vue

@@ -387,7 +387,8 @@
     <div slot="footer" class="dialog-footer foooot">
         <!-- <el-button v-if="(user.id == showMmeiLaiDeData.checkFirstId && showMmeiLaiDeData.taskStatus == 3) || (user.id == showMmeiLaiDeData.checkSecondId && [4, '4', '1', 1].includes(showMmeiLaiDeData.taskStatus))" @click.native="deleteTask()" style="float:left;">{{ $t('btn.delete') }}</el-button>
         <el-button v-if="showMmeiLaiDeData.taskPlan && showMmeiLaiDeData.taskStatus == 2" @click.native="deleteTask()" style="float:left;">删除</el-button> -->
-        <el-button v-if="addForm.createrId == user.id && addForm.taskStatus == 2" @click.native="deleteTask()" style="float:left;">删除</el-button>
+        <!--在已撤回或者被驳回的情况下可以删除-->
+        <el-button v-if="addForm.createrId == user.id && (addForm.taskStatus == 2 || addForm.taskStatus == 5 || addForm.taskStatus == 6)" @click.native="deleteTask()" style="float:left;">删除</el-button>
         <el-button @click.native="closeBounceds()">{{ $t('btn.cancel') }}</el-button>
 
         <template v-if="addForm.taskStatus == 3 && addForm.createrId == user.id">