Explorar el Código

医智锐的费用报销

QuYueTing hace 1 mes
padre
commit
749dc364ea
Se han modificado 33 ficheros con 958 adiciones y 106 borrados
  1. 18 3
      fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  2. 19 0
      fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/util/WorkDayCalculateUtils.java
  3. 2 2
      fhKeeper/formulahousekeeper/management-platform-yzr/src/main/java/com/management/platform/service/impl/ExpenseSheetServiceImpl.java
  4. 52 38
      fhKeeper/formulahousekeeper/management-platform-yzr/src/main/java/com/management/platform/service/impl/FeishuInfoServiceImpl.java
  5. 9 2
      fhKeeper/formulahousekeeper/management-platform-yzr/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java
  6. 1 1
      fhKeeper/formulahousekeeper/management-platform-yzr/src/main/resources/application.yml
  7. 2 2
      fhKeeper/formulahousekeeper/management-platform-yzr/src/main/resources/mapper/ReportMapper.xml
  8. 7 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/DepartmentController.java
  9. 2 2
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ExpenseSheetController.java
  10. 48 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/FeishuInfoController.java
  11. 9 4
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/UserCorpwxTimeController.java
  12. 5 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExpenseItem.java
  13. 6 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExpenseSheet.java
  14. 12 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/LeaveSheet.java
  15. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ExpenseSheetService.java
  16. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/FeishuInfoService.java
  17. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/DepartmentServiceImpl.java
  18. 34 17
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExpenseSheetServiceImpl.java
  19. 535 2
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/FeishuInfoServiceImpl.java
  20. 29 3
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/LeaveSheetServiceImpl.java
  21. 9 2
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  22. 1 1
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/WxCorpInfoServiceImpl.java
  23. 55 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/util/WorkDayCalculateUtils.java
  24. 4 2
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ExpenseItemMapper.xml
  25. 2 1
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ExpenseSheetMapper.xml
  26. 3 1
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/LeaveSheetMapper.xml
  27. 2 2
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ReportMapper.xml
  28. 10 12
      fhKeeper/formulahousekeeper/timesheet-yzr/src/views/expense/expense.vue
  29. 54 5
      fhKeeper/formulahousekeeper/timesheet/src/views/expense/expense.vue
  30. 1 1
      fhKeeper/formulahousekeeper/timesheet/src/views/project/list.vue
  31. 1 0
      fhKeeper/formulahousekeeper/timesheet/src/views/project/projectInside.vue
  32. 23 0
      fhKeeper/formulahousekeeper/timesheet/src/views/team/index.vue
  33. BIN
      fhKeeper/formulahousekeeper/webttkuaiban/src/main/resources/static/download/timesheet.apk

+ 18 - 3
fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -3891,6 +3891,11 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 curUserLeaveList.forEach(leave->{
                     LocalDate startDate1 = leave.getStartDate();
                     LocalDate endDate1 = leave.getEndDate();
+                    //取当天的所有请假的合计请假时长
+                    double curLeaveTime = curUserLeaveList.stream().filter(leaveSheet -> leaveSheet.getStartDate().equals(startDate1) && leaveSheet.getEndDate().equals(endDate1))
+                            .mapToDouble(LeaveSheet::getTimeHours).sum();
+                    double curLeaveDays = curUserLeaveList.stream().filter(leaveSheet -> leaveSheet.getStartDate().equals(startDate1) && leaveSheet.getEndDate().equals(endDate1))
+                            .mapToDouble(LeaveSheet::getTimeDays).sum();
                     //该范围内的都算请假
                     int i=0;
                     while(true) {
@@ -3902,7 +3907,16 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                             Object str = find.get().get("workingTime");
                             if (str instanceof Double) {
                                 //String newStr = (double)find.get().get("workingTime")+"(请假)";
-                                String newStr = (double)find.get().get("workingTime")+"("+MessageUtils.message("leave.leave")+leave.getTimeHours()+"h)";
+                                double leaveHours = 0.0;
+                                if (leave.getStartDate().isEqual(leave.getEndDate())) {
+                                    leaveHours = curLeaveTime;
+                                } else {
+                                    //跨天请假
+                                    leaveHours = curLeaveTime/curLeaveDays;
+                                }
+                                //将leaveHours四舍五入到1位小数
+                                leaveHours = Math.round(leaveHours*10)/10.0;
+                                String newStr = (double)find.get().get("workingTime")+"("+MessageUtils.message("leave.leave")+leaveHours+"h)";
                                 find.get().put("workingTime", newStr);
                             }
                         } else {
@@ -3910,10 +3924,11 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                             if (WorkDayCalculateUtils.isWorkDay(workDate)) {
                                 Map<String, Object> leaveMap = new HashMap<>();
                                 leaveMap.put("createDate", leaveDateStr);
-                                if(leave.getTimeHours() >= timeType.getAllday()){
+                                if(curLeaveTime >= timeType.getAllday()){
                                     leaveMap.put("workingTime", MessageUtils.message("leave.leaveOfDay"));
                                 } else {
-                                    leaveMap.put("workingTime", MessageUtils.message("leave.leave")+leave.getTimeHours()+"h");
+                                    curLeaveTime = Math.round(curLeaveTime*10)/10.0;
+                                    leaveMap.put("workingTime", MessageUtils.message("leave.leave")+curLeaveTime+"h");
                                 }
                                 worktimeList.add(leaveMap);
                             }

+ 19 - 0
fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/util/WorkDayCalculateUtils.java

@@ -127,6 +127,25 @@ public class WorkDayCalculateUtils {
                 "2025-10-01","2025-10-02","2025-10-03","2025-10-06","2025-10-07","2025-10-08",//国庆节
         });
         YEAR_DEFINE.put("2025", map2025);
+        HashMap<String, String[]> map2026 = new HashMap<>();
+        //除了周末的特殊工作日
+        map2026.put(KEY_SPECIAL_WORK_DAYS, new String[]{"2026-01-04",//元旦
+                "2026-02-14","2026-02-28",//春节
+                "2026-05-09",//劳动节
+                "2026-09-20",//端午节
+                "2026-10-10",//国庆节
+        });
+        //除了周末的特殊休息日,例如国庆中秋春节
+        map2026.put(KEY_SPECIAL_REST_DAYS, new String[]{
+                "2026-01-01","2026-01-02",//元旦
+                "2026-02-16", "2026-02-17", "2026-02-18", "2026-02-19", "2026-02-20","2026-02-23",//春节
+                "2026-04-06",//清明节
+                "2026-05-01","2026-05-04","2026-05-05",//劳动节
+                "2026-06-19",//端午节
+                "2026-09-25",//中秋节
+                "2026-10-01","2026-10-02","2026-10-05","2026-10-06","2026-10-07",//国庆节
+        });
+        YEAR_DEFINE.put("2026", map2026);
     }
 
     /**

+ 2 - 2
fhKeeper/formulahousekeeper/management-platform-yzr/src/main/java/com/management/platform/service/impl/ExpenseSheetServiceImpl.java

@@ -502,8 +502,8 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
         ExpenseAuditSetting expenseAuditSetting = expenseAuditSettingMapper.selectById(sheet.getCompanyId());
         List<Project> projectList = projectMapper.selectList(new QueryWrapper<Project>().eq("company_id", user.getCompanyId()));
         List<EquipmentOwner> equipmentOwnerList = equipmentOwnerMapper.selectList(new QueryWrapper<EquipmentOwner>().eq("company_id", user.getCompanyId()));
-        if (!StringUtils.isEmpty(sheet.getCode())) {
-            queryWrapper.eq("code", sheet.getCode());
+        if (!StringUtils.isEmpty(sheet.getProjectCode())) {
+            queryWrapper.eq("project_code", sheet.getProjectCode());
         }
         //增加状态
         if (sheet.getStatus() != null) {

+ 52 - 38
fhKeeper/formulahousekeeper/management-platform-yzr/src/main/java/com/management/platform/service/impl/FeishuInfoServiceImpl.java

@@ -1001,6 +1001,7 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
             LocalDate end = LocalDate.parse(endDate, formatter);
             long daysBetween = ChronoUnit.DAYS.between(start, end);
             JSONArray instanceList = null;
+            System.out.println("daysBetween==="+daysBetween);
             if (daysBetween > 29) {
                 instanceList = new JSONArray();
                 for (LocalDate fromDate = start; !fromDate.isAfter(end); fromDate = fromDate.plusDays(30)) {
@@ -1047,8 +1048,8 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
                 JSONObject instance = instanceList.getJSONObject(i).getJSONObject("instance");
                 String instanceCode = instance.getString("code");
                 
-                System.out.println("========== 处理第 " + (i + 1) + " 个实例 ==========");
-                System.out.println("实例ID: " + instanceCode);
+//                System.out.println("========== 处理第 " + (i + 1) + " 个实例 ==========");
+//                System.out.println("实例ID: " + instanceCode);
                 
                 try {
                     // 检查是否已存在
@@ -1056,7 +1057,7 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
                     boolean needRefresh = needResyncProjectExpenseSheetList.stream().anyMatch(item -> item.getInstanceCode().equals(instanceCode));
                     System.out.println("模板编码:"+instanceCode+", 需要刷新=" + needRefresh);
                     if (oldItem != null && !needRefresh) {
-                        System.out.println("报销单已存在,且无需刷新项目,跳过处理。instance_code: " + instanceCode);
+//                        System.out.println("报销单已存在,且无需刷新项目,跳过处理。instance_code: " + instanceCode);
                         skipCount++;
                         continue;
                     }
@@ -1119,7 +1120,6 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
             String status = instanceDetail.getString("status");
             Long startTime = instanceDetail.getLong("start_time");
             String formStr = instanceDetail.getString("form");
-            
             // 根据user_id查询用户信息
             User user = userMapper.selectOne(
                 new QueryWrapper<User>()
@@ -1212,7 +1212,6 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
                             .toLocalDate();
                     expenseSheet.setCreateDate(createDate);
                 }
-//                HttpRespMsg msg = expenseSheetService.getNextCode(user.getId());
                 expenseSheet.setCode(serialNumber);
                 // 设置状态:根据飞书审批状态映射
                 // PENDING-待审核, APPROVED-审核通过, REJECTED-驳回, CANCELED-已撤回
@@ -1227,18 +1226,27 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
                     sheetStatus = 1; // 待审核
                 }
                 expenseSheet.setStatus(sheetStatus);
-
                 // 插入ExpenseSheet
                 int insertResult = expenseSheetMapper.insert(expenseSheet);
                 if (insertResult <= 0) {
                     System.out.println("插入ExpenseSheet失败");
                     return false;
                 }
+            } else {
+                // 更新ExpenseSheet
+                expenseSheet.setTotalAmount(totalAmount);
+                expenseSheet.setRemark(expenseReason);
+                expenseSheet.setProjectId(projectId);
+                expenseSheet.setProjectCode(projectCode);
+                int updateResult = expenseSheetMapper.updateById(expenseSheet);
+                if (updateResult <= 0) {
+                    System.out.println("更新ExpenseSheet失败");
+                    return false;
+                }
             }
             int sheetStatus = expenseSheet.getStatus();
             
             Integer expenseSheetId = expenseSheet.getId();
-            System.out.println("插入ExpenseSheet成功,ID: " + expenseSheetId);
 
             //先删除老的吧
             if (sheetExists) {
@@ -1338,11 +1346,11 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
         headers.add("Authorization", "Bearer " + getTenantAccessToken(appId));
         
         JSONObject requestBody = new JSONObject();
-        requestBody.put("page_size", 100);
+//        requestBody.put("page_size", 100);
         requestBody.put("approval_code",approvalCode);
         requestBody.put("instance_start_time_from", startTime);
         requestBody.put("instance_start_time_to", endTime);
-        requestBody.put("user_id_type", "user_id");
+//        requestBody.put("user_id_type", "user_id");
         if (!StringUtils.isEmpty(userId)) {
             requestBody.put("user_id", userId);
         }
@@ -1350,14 +1358,14 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
 
         HttpEntity<JSONObject> httpEntity = new HttpEntity<>(requestBody, headers);
         
-        System.out.println("请求审批实例列表URL: " + url);
+        System.out.println("请求审批实例列表URL: " + url+"?page_size=100&user_id_type=user_id");
         System.out.println("请求参数: " + requestBody.toJSONString());
         
         ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
         
         if (responseEntity.getStatusCode() == HttpStatus.OK) {
             String resp = responseEntity.getBody();
-            System.out.println("审批实例列表响应数据: " + resp);
+//            System.out.println("审批实例列表响应数据: " + resp);
             
             JSONObject respJson = JSONObject.parseObject(resp);
             if (respJson.getInteger("code") == 0) {
@@ -1371,16 +1379,32 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
                     // 处理分页
                     Boolean hasMore = data.getBoolean("has_more");
                     String pageToken = data.getString("page_token");
-                    
-                    while (hasMore != null && hasMore && pageToken != null) {
-                        System.out.println("存在更多数据,继续获取,page_token: " + pageToken);
-                        JSONArray nextPageData = getApprovalInstanceListWithPageToken(approvalCode, appId, startTime, endTime, pageToken, userId);
-                        if (nextPageData != null && nextPageData.size() > 0) {
-                            result.addAll(nextPageData);
+                    int i=0;
+                    while (hasMore != null && hasMore && !StringUtils.isEmpty(pageToken)) {
+                        i++;
+                        System.out.println("i==============="+i);
+                        if (i>100) {
+                            System.out.println("获取审批实例列表超过100次, 检测到异常,停止!!!");
+                            break;
+                        }
+//                        System.out.println("存在更多数据,继续获取,page_token: " + pageToken);
+                        JSONObject nextPageJson = getApprovalInstanceListWithPageToken(approvalCode, appId, startTime, endTime, pageToken, userId);
+//                        System.out.println("获取到分页数据:"+nextPageJson.toJSONString());
+                        if (nextPageJson != null && nextPageJson.getInteger("code") == 0) {
+                            JSONObject nextData = nextPageJson.getJSONObject("data");
+                            if (nextData != null) {
+                                JSONArray nextInstanceList = nextData.getJSONArray("instance_list");
+                                if (nextInstanceList != null && nextInstanceList.size() > 0) {
+                                    result.addAll(nextInstanceList);
+                                }
+                                hasMore = nextData.getBoolean("has_more");
+                                pageToken = nextData.getString("page_token");
+                            } else {
+                                hasMore = false;
+                            }
+                        } else {
+                            hasMore = false;
                         }
-                        
-                        // 更新分页信息(这里简化处理,实际需要递归或循环处理)
-                        break;
                     }
                 }
             } else {
@@ -1398,22 +1422,20 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
     /**
      * 带分页token获取审批实例列表
      */
-    private JSONArray getApprovalInstanceListWithPageToken(String approvalCode, String appId, long startTime, long endTime, String pageToken, String userId) throws Exception {
-        JSONArray result = new JSONArray();
-        
-        String url = GET_APPROVAL_INSTANCE_LIST;
+    private JSONObject getApprovalInstanceListWithPageToken(String approvalCode, String appId, long startTime, long endTime, String pageToken, String userId) throws Exception {
+        String url = GET_APPROVAL_INSTANCE_LIST + "?page_size=100&user_id_type=user_id&page_token=" + pageToken;
         HttpHeaders headers = new HttpHeaders();
         MediaType type = MediaType.parseMediaType("application/json; charset=utf-8");
         headers.setContentType(type);
         headers.add("Authorization", "Bearer " + getTenantAccessToken(appId));
         
         JSONObject requestBody = new JSONObject();
-        requestBody.put("page_size", 100);
-        requestBody.put("page_token", pageToken);
+//        requestBody.put("page_size", 100);
+//        requestBody.put("page_token", pageToken);
         requestBody.put("approval_code",approvalCode);
         requestBody.put("instance_start_time_from", startTime);
         requestBody.put("instance_start_time_to", endTime);
-        requestBody.put("user_id_type", "user_id");
+//        requestBody.put("user_id_type", "user_id");
         if (!StringUtils.isEmpty(userId)) {
             requestBody.put("user_id", userId);
         }
@@ -1425,18 +1447,10 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
         if (responseEntity.getStatusCode() == HttpStatus.OK) {
             String resp = responseEntity.getBody();
             JSONObject respJson = JSONObject.parseObject(resp);
-            if (respJson.getInteger("code") == 0) {
-                JSONObject data = respJson.getJSONObject("data");
-                if (data != null) {
-                    JSONArray instanceList = data.getJSONArray("instance_list");
-                    if (instanceList != null && instanceList.size() > 0) {
-                        result.addAll(instanceList);
-                    }
-                }
-            }
+            return respJson;
+        } else {
+            return null;
         }
-        
-        return result;
     }
     
     /**

+ 9 - 2
fhKeeper/formulahousekeeper/management-platform-yzr/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java

@@ -13649,8 +13649,15 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
     public HttpRespMsg timeCostAndExpenseByProject(HttpServletRequest request, String startDate, String endDate, Integer projectId) {
         HttpRespMsg httpRespMsg = new HttpRespMsg();
         Map<String, Object> map = reportMapper.selectCostTimeByProject(startDate, endDate, projectId);
-        Double expense = expenseItemMapper.selectExpenseByProject(startDate, endDate, projectId, null);
-        map.put("expense",expense);
+        QueryWrapper<ExpenseSheet> queryWrapper = new QueryWrapper<ExpenseSheet>().select("sum(total_amount) as total_amount").eq("status", 0).eq("project_id", projectId);
+        if (!StringUtils.isEmpty(startDate)) {
+            queryWrapper.ge("create_date", startDate);
+        }
+        if (!StringUtils.isEmpty(endDate)) {
+            queryWrapper.le("create_date", endDate);
+        }
+        ExpenseSheet expense = expenseSheetMapper.selectOne(queryWrapper);
+        map.put("expense",(expense == null || expense.getTotalAmount()==null)?0:expense.getTotalAmount());
         httpRespMsg.data = map;
         return httpRespMsg;
     }

+ 1 - 1
fhKeeper/formulahousekeeper/management-platform-yzr/src/main/resources/application.yml

@@ -15,7 +15,7 @@ spring:
       location: C:/upload/
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://localhost:3306/man_yzr_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&useSSL=false&allowPublicKeyRetrieval=true
+    url: jdbc:mysql://localhost:3306/man_yzr?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&useSSL=false&allowPublicKeyRetrieval=true
     username: root
     password: P011430@Huoshi*
     hikari:

+ 2 - 2
fhKeeper/formulahousekeeper/management-platform-yzr/src/main/resources/mapper/ReportMapper.xml

@@ -1021,7 +1021,7 @@
         AND not exists(
         select 1 from finance_exclude_project fep where fep.company_id = report.company_id and fep.use_ym = #{ym} and fep.project_id = report.project_id
         )
-        GROUP BY project_id, report.creator_id;
+        GROUP BY project_id, report.creator_id order by project_id;
     </select>
 
     <select id="getUploadThirdReportData" resultType="java.util.Map">
@@ -1036,7 +1036,7 @@
           AND r.state=1
           AND r.create_date &gt;= #{startDate}
           AND r.create_date &lt; #{endDate}
-        group by p.id,u.id,tg.id
+        group by p.id,u.id,tg.id order by p.id
     </select>
     <select id="getReportFillStatus" resultType="java.util.Map">
         SELECT DATE_FORMAT(r.create_date,'%Y-%m-%d') AS createDate, IF (MAX(r.state) = 1, MIN(r.state), MAX(r.state)) AS state,sum(r.working_time) as workingTime

+ 7 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/DepartmentController.java

@@ -40,6 +40,13 @@ public class DepartmentController {
         return departmentService.getDepartmentList(request);
     }
 
+    @RequestMapping("/get")
+    public HttpRespMsg get(HttpServletRequest request,Integer id) {
+        HttpRespMsg httpRespMsg = new HttpRespMsg();
+        httpRespMsg.data = departmentService.getById(id);
+        return httpRespMsg;
+    }
+
     /**
      * 对部门进行排序
      * @param request

+ 2 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ExpenseSheetController.java

@@ -84,7 +84,7 @@ public class ExpenseSheetController {
     }
 
     @RequestMapping("/list")
-    public HttpRespMsg list(ExpenseSheet sheet,Integer sendState,Integer projectId,String startDate, String endDate, @RequestParam Integer pageIndex, @RequestParam Integer pageSize) {
+    public HttpRespMsg list(ExpenseSheet sheet,Integer sendState,Integer projectId,String startDate, String endDate, @RequestParam(required = false, defaultValue = "false" ) Boolean noProjectFlag, @RequestParam Integer pageIndex, @RequestParam Integer pageSize) {
         String token = request.getHeader("TOKEN");
         User user = userMapper.selectById(token);
         List<SysRichFunction> functionList = sysFunctionMapper.getRoleFunctions(user.getRoleId(), "查看全部报销单");
@@ -95,7 +95,7 @@ public class ExpenseSheetController {
             }
         }
         sheet.setCompanyId(user.getCompanyId());
-        return expenseSheetService.queryList(sheet,projectId, startDate, endDate, pageIndex, pageSize);
+        return expenseSheetService.queryList(sheet,projectId, startDate, endDate, noProjectFlag, pageIndex, pageSize);
     }
 
     @RequestMapping("/getDetail")

+ 48 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/FeishuInfoController.java

@@ -27,8 +27,11 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -139,6 +142,44 @@ public class FeishuInfoController {
         return msg;
     }
 
+    @RequestMapping("/refreshExpenseSheet")
+    public HttpRespMsg refreshExpenseSheet(Integer companyId, String userId, String startDate, String endDate, String ymonth, HttpServletRequest request){
+        HttpRespMsg msg = new HttpRespMsg();
+        try {
+            if (ymonth != null) {
+                startDate = ymonth.replace("-", "") + "01";
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
+                //判断当前日期
+                LocalDate now = LocalDate.now();
+                DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
+                LocalDate formatDate = LocalDate.parse(startDate, dtf);
+                if (formatDate.isAfter(now)) {
+                    msg.setError("不可超过当前月");
+                } else if (now.getYear() == formatDate.getYear() && now.getMonth() == formatDate.getMonth()) {
+                    endDate = dtf.format(now);
+                } else {
+                    //计算当月的最后一天
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.set(Calendar.YEAR, Integer.parseInt(ymonth.substring(0, 4)));
+                    calendar.set(Calendar.MONTH, Integer.parseInt(ymonth.substring(5, 7)) - 1);
+                    calendar.set(Calendar.DATE, calendar.getActualMaximum(Calendar.DATE));
+                    endDate = simpleDateFormat.format(calendar.getTime());
+                }
+            }
+            FeishuInfo feishuInfo = feishuInfoService.getOne(new QueryWrapper<FeishuInfo>().eq("company_id",companyId));
+            User targetUser = null;
+            if (userId != null) {
+                targetUser = userMapper.selectById(userId);
+            }
+            msg = feishuInfoService.getExpenseFeeResult(feishuInfo, targetUser, startDate, endDate);
+        } catch (Exception e) {
+            e.printStackTrace();
+            msg.setError(e.getMessage());
+            return msg;
+        }
+        return msg;
+    }
+
     /**
      * 初始化内部应用的系统数据
      * @return
@@ -1061,6 +1102,13 @@ public class FeishuInfoController {
             }
         }
         if(newUserList.size()>0){
+            //对比active状态,如果我们系统里面是停用的,就不要启用了;
+            List<User> inActiveUserListInRange = userMapper.selectList(new QueryWrapper<User>().eq("is_active", 0).eq("company_id", feishuInfo.getCompanyId()).in("feishu_userid", newUserList.stream().map(User::getFeishuUserid).collect(Collectors.toList())));
+            for (User user : newUserList) {
+                if (inActiveUserListInRange.stream().anyMatch(inactive -> inactive.getFeishuUserid().equals(user.getFeishuUserid()))) {
+                    user.setIsActive(0);//保持当前数据库中的用户的停用状态
+                }
+            }
             userService.saveOrUpdateBatch(newUserList);
         }
         return msg;

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

@@ -1745,11 +1745,16 @@ public class UserCorpwxTimeController {
      * @return 处理结果,包含员工-项目-可填月份的数据集合
      */
     @RequestMapping("/importProjectAndStaffAllocation")
-    public HttpRespMsg importProjectAndStaffAllocation(MultipartFile multipartFile, HttpServletRequest request) {
+    public HttpRespMsg importProjectAndStaffAllocation(Integer year,MultipartFile multipartFile, HttpServletRequest request) {
         HttpRespMsg msg = new HttpRespMsg();
 //        String token = request.getHeader("TOKEN");
 //        User user = userMapper.selectById(token);
         Integer companyId = 7544;
+
+        if (year == null) {
+            msg.setError("year年份参数不能为空");
+            return msg;
+        }
         
         String fileName = multipartFile.getOriginalFilename();
         File file = new File(fileName == null ? "file" : fileName);
@@ -1785,7 +1790,7 @@ public class UserCorpwxTimeController {
             // 处理第二个sheet:人员分配
             Sheet staffSheet = workbook.getSheetAt(1);
             System.out.println("正在处理第二个Sheet: " + staffSheet.getSheetName() + " (人员分配)");
-            List<Map<String, Object>> staffAllocationResult = processStaffAllocationSheet(staffSheet, companyId, allUserList);
+            List<Map<String, Object>> staffAllocationResult = processStaffAllocationSheet(year, 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())));
@@ -2164,7 +2169,7 @@ public class UserCorpwxTimeController {
      * 前面7列是合并的多行数据,形成人员-项目的一对多关系,项目和月份又是一对多的关系
      * 返回员工-项目-可填月份的数据集合
      */
-    private List<Map<String, Object>> processStaffAllocationSheet(Sheet staffSheet, Integer companyId, List<User> allUserList) {
+    private List<Map<String, Object>> processStaffAllocationSheet(Integer startYear,Sheet staffSheet, Integer companyId, List<User> allUserList) {
         List<Map<String, Object>> result = new ArrayList<>();
         
         if (staffSheet.getLastRowNum() == 0) {
@@ -2177,7 +2182,7 @@ public class UserCorpwxTimeController {
         List<String> monthColumns = new ArrayList<>();
         LocalDate now = LocalDate.now();
         //设置日期为2025-01-01,以便计算月份
-        LocalDate startMonth = now.withDayOfMonth(1).withMonth(1);
+        LocalDate startMonth = now.withDayOfMonth(1).withMonth(1).withYear(startYear);
 
         if (headerRow != null) {
             for (int colIndex = 11; colIndex < 11 + 24; colIndex++) { // 第12列开始(索引11)

+ 5 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExpenseItem.java

@@ -105,6 +105,11 @@ public class ExpenseItem extends Model<ExpenseItem> {
     @TableField("auditor_id")
     private String auditorId;
 
+    /**
+     * 项目编号(外部系统同步过来)
+     */
+    @TableField("project_code")
+    private String projectCode;
 
     @TableField(exist = false)
     private String projectName;

+ 6 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/ExpenseSheet.java

@@ -157,6 +157,12 @@ public class ExpenseSheet extends Model<ExpenseSheet> {
     @TableField("equipment_owner_id")
     private Integer equipmentOwnerId;
 
+    /**
+     * 外部系统实例code
+     */
+    @TableField("instance_code")
+    private String instanceCode;
+
     @TableField(exist = false)
     private String equipmentOwnerName;
 

+ 12 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/entity/LeaveSheet.java

@@ -168,6 +168,18 @@ public class LeaveSheet extends Model<LeaveSheet> {
     @TableField("file_urls")
     private String fileUrls;
 
+    /**
+     * 请假开始时间(含日期时分)
+     */
+    @TableField("begin_time")
+    private String beginTime;
+
+    /**
+     * 请假结束时间(含日期时分)
+     */
+    @TableField("end_time")
+    private String endTime;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

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

@@ -22,7 +22,7 @@ public interface ExpenseSheetService extends IService<ExpenseSheet> {
 
     HttpRespMsg delete(Integer id);
 
-    HttpRespMsg queryList(ExpenseSheet sheet,Integer projectId, String startDate, String endDate, Integer pageIndex,  Integer pageSize);
+    HttpRespMsg queryList(ExpenseSheet sheet,Integer projectId, String startDate, String endDate, boolean noProjectFlag,Integer pageIndex,  Integer pageSize);
 
     HttpRespMsg getNextCode(String userId);
 

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

@@ -48,5 +48,5 @@ public interface FeishuInfoService extends IService<FeishuInfo> {
 
     HttpRespMsg getCardTimeResult(FeishuInfo feishuInfo, User targetUser, String startDate, String endDate);
 
-    HttpRespMsg getExpenseFeeResult(FeishuInfo feishuInfo, User targetUser, String startDate, String endDate);
+    HttpRespMsg getExpenseFeeResult(FeishuInfo feishuInfo, User targetUser, String startDate, String endDate) throws Exception;
 }

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

@@ -231,7 +231,7 @@ public class DepartmentServiceImpl extends ServiceImpl<DepartmentMapper, Departm
                         //取消了部门管理员
                         departmentMapper.updateNullManager(departmentId);
 
-                        User manageDeptOldUser= userMapper.selectOne(new QueryWrapper<User>().eq("manage_dept_id", departmentId));
+                        User manageDeptOldUser= userMapper.selectOne(new QueryWrapper<User>().eq("company_id", companyId).eq("manage_dept_id", departmentId).last("limit 1").eq("manage_dept_id", departmentId));
                         if (manageDeptOldUser != null) {
                             //检查这个人是否有其他负责的部门
                             Department otherDeptOne = departmentMapper.selectOne(new QueryWrapper<Department>().eq("manager_id", oldManagerId).last("limit 1"));

+ 34 - 17
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ExpenseSheetServiceImpl.java

@@ -536,7 +536,7 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
     }
 
     @Override
-    public HttpRespMsg queryList(ExpenseSheet sheet,Integer projectId, String startDate, String endDate, Integer pageIndex, Integer pageSize) {
+    public HttpRespMsg queryList(ExpenseSheet sheet,Integer projectId, String startDate, String endDate, boolean noProjectFlag, Integer pageIndex, Integer pageSize) {
         QueryWrapper<ExpenseSheet> queryWrapper = new QueryWrapper<ExpenseSheet>();
         //当前用户
         String token = request.getHeader("TOKEN");
@@ -667,6 +667,15 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
             if (user.getCompanyId()==Constant.ZHE_ZHONG_COMPANY_ID&& org.apache.commons.lang3.StringUtils.isNotEmpty(user.getRoleName())&&user.getRoleName().equals("费用管理员")){
                 queryWrapper.eq("operator_id",user.getId());
             }
+            if (noProjectFlag){
+                List<ExpenseItem> noProjectItems = expenseItemMapper.selectList(new QueryWrapper<ExpenseItem>().select("distinct expense_id").isNull("project_id"));
+                if (!noProjectItems.isEmpty()){
+                    List<Integer> expenseIds = noProjectItems.stream().map(ExpenseItem::getExpenseId).collect(Collectors.toList());
+                    queryWrapper.in("id",expenseIds);
+                } else {
+                    queryWrapper.eq("id",-1);//构造伪查询条件
+                }
+            }
             IPage<ExpenseSheet> listIPager = expenseSheetMapper.selectPage(new Page<>(pageIndex, pageSize),
                     queryWrapper);
             List<ExpenseSheet> records = listIPager.getRecords();
@@ -744,24 +753,32 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
         List<Project> Project = projectMapper.selectList(new QueryWrapper<Project>().eq("company_id", expenseSheet.getCompanyId()));
         ExpenseAuditSetting expenseAuditSetting = expenseAuditSettingMapper.selectById(expenseSheet.getCompanyId());
         for (ExpenseItem expenseItem : list) {
-            for (Project project : Project) {
-                if ((project.getId().equals(expenseItem.getProjectId()))){
-                    if (token.equals(expenseItem.getAuditorId())) {
-                        expenseItem.setIsIncharger(1);
-                    } else {
-                        expenseItem.setIsIncharger(0);
-                    }
-                    expenseItem.setProjectName(project.getProjectName());
-                    //获取审核人姓名
-                    if (!(expenseAuditSetting == null || expenseAuditSetting.getAuditType() == 0)) {
-                        if (expenseItem.getAuditorId() != null) {
-                            User user = userMapper.selectById(expenseItem.getAuditorId());
-                            if (user != null) {
-                                expenseItem.setAuditorName(user.getName());
+            if (expenseItem.getProjectId() == null) {
+                if (expenseItem.getProjectCode() != null) {
+                    expenseItem.setProjectName(expenseItem.getProjectCode()+"(未关联成功)");
+                } else {
+                    expenseItem.setProjectName("未关联成功");
+                }
+            } else {
+                for (Project project : Project) {
+                    if ((project.getId().equals(expenseItem.getProjectId()))){
+                        if (token.equals(expenseItem.getAuditorId())) {
+                            expenseItem.setIsIncharger(1);
+                        } else {
+                            expenseItem.setIsIncharger(0);
+                        }
+                        expenseItem.setProjectName(project.getProjectName());
+                        //获取审核人姓名
+                        if (!(expenseAuditSetting == null || expenseAuditSetting.getAuditType() == 0)) {
+                            if (expenseItem.getAuditorId() != null) {
+                                User user = userMapper.selectById(expenseItem.getAuditorId());
+                                if (user != null) {
+                                    expenseItem.setAuditorName(user.getName());
+                                }
                             }
                         }
+                        break;
                     }
-                    break;
                 }
             }
         }
@@ -1446,7 +1463,7 @@ public class ExpenseSheetServiceImpl extends ServiceImpl<ExpenseSheetMapper, Exp
                 if(optional.isPresent()){
                     item.add(optional.get().getProjectName());
                 }else {
-                    item.add("");
+                    item.add(expenseItem.getProjectCode());
                 }
                 item.add(expenseItem.getHappenDate());
                 item.add(expenseItem.getInvoiceType()!=null&&expenseItem.getInvoiceType()==0?"增值税专用发票":"增值税普通发票");

+ 535 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/FeishuInfoServiceImpl.java

@@ -9,6 +9,7 @@ import com.management.platform.constant.Constant;
 import com.management.platform.entity.*;
 import com.management.platform.entity.bo.*;
 import com.management.platform.mapper.*;
+import com.management.platform.service.ExpenseSheetService;
 import com.management.platform.service.FeishuInfoService;
 import com.management.platform.service.UserFvTimeService;
 import com.management.platform.util.HttpRespMsg;
@@ -28,6 +29,7 @@ import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -74,6 +76,13 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
 
     public static final String GET_APPROVAL_INSTANCE_LIST = "https://open.feishu.cn/open-apis/approval/v4/instances/query";
 
+    public static final String GET_APPROVAL_INSTANCE_INFO = "https://open.feishu.cn/open-apis/approval/v4/instances/:instance_id";
+
+    public static final Map<Integer, String> EXPENSE_APPROVAL_CODE_MAP = new HashMap<>();
+    static {
+        //医智锐科技
+        EXPENSE_APPROVAL_CODE_MAP.put(8484, "E17B8057-1847-4BA2-8DDF-BBD28C57B909");
+    }
 
     @Resource
     FeishuInfoMapper feishuInfoMapper;
@@ -88,12 +97,24 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
     UserFvTimeMapper userFvTimeMapper;
     @Resource
     LeaveSheetMapper leaveSheetMapper;
+    @Resource
+    ExpenseSheetMapper expenseSheetMapper;
+    @Resource
+    ExpenseItemMapper expenseItemMapper;
+    @Resource
+    ProjectMapper projectMapper;
     @Autowired
     private RestTemplate restTemplate;
     @Autowired
     private TimeTypeMapper timeTypeMapper;
     @Autowired
     private UserFvTimeService userFvTimeService;
+    @Autowired
+    private ExpenseSheetService expenseSheetService;
+    @Autowired
+    private ExpenseTypeMapper expenseTypeMapper;
+    @Autowired
+    private ExpenseMainTypeMapper expenseMainTypeMapper;
 
     @Override
     public String getAppAccessToken(FeishuInfo feishuInfo) {
@@ -123,6 +144,12 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
         String result = "";
         FeishuInfo feishuInfo = getOne(new QueryWrapper<FeishuInfo>().eq("app_id", appId));
         if (feishuInfo != null) {
+            //先取数据库里面的,如果过期时间小于当前时间,重新获取
+            LocalDateTime expireTime = feishuInfo.getExpireTime();
+            if (expireTime.isAfter(LocalDateTime.now())) {
+                System.out.println("读取到缓存的token======" + feishuInfo.getAccessToken());
+                return feishuInfo.getAccessToken();
+            }
             String url = GET_TENANT_ACCESS_TOKEN;
             HttpHeaders headers = new HttpHeaders();
             RestTemplate restTemplate = new RestTemplate();
@@ -954,9 +981,515 @@ public class FeishuInfoServiceImpl extends ServiceImpl<FeishuInfoMapper, FeishuI
     }
 
     @Override
-    public HttpRespMsg getExpenseFeeResult(FeishuInfo feishuInfo, User targetUser, String startDate, String endDate) {
+    public HttpRespMsg getExpenseFeeResult(FeishuInfo feishuInfo, User targetUser, String startDate, String endDate) throws Exception {
+        HttpRespMsg msg = new HttpRespMsg();
+        
+        if (feishuInfo == null) {
+            msg.setError("飞书信息不能为空");
+            return msg;
+        }
+        
+        try {
+            // 第一步:调用GET_APPROVAL_INSTANCE_LIST获取审批实例列表
+            System.out.println("========== 开始获取费用报销审批实例列表 ==========");
+            System.out.println("开始时间: " + startDate + ", 结束时间: " + endDate);
+            String userId = targetUser == null?null:targetUser.getJobNumber();
+
+            //日期间隔如果超过30天,需要分批查询
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
+            LocalDate start = LocalDate.parse(startDate, formatter);
+            LocalDate end = LocalDate.parse(endDate, formatter);
+            long daysBetween = ChronoUnit.DAYS.between(start, end);
+            JSONArray instanceList = null;
+            if (daysBetween > 29) {
+                instanceList = new JSONArray();
+                for (LocalDate fromDate = start; !fromDate.isAfter(end); fromDate = fromDate.plusDays(30)) {
+                    LocalDate toDate = fromDate.plusDays(29);
+                    if (toDate.isAfter(end)) {
+                        toDate = end;
+                    }
+                    System.out.println("分批查询fromDate=" + fromDate + ", toDate=" + toDate);
+                    JSONArray tmpList = getApprovalInstanceList(feishuInfo.getCompanyId(), feishuInfo.getAppId(), formatter.format(fromDate), formatter.format(toDate), userId);
+                    if (tmpList != null && tmpList.size() > 0) {
+                        instanceList.addAll(tmpList);
+                    }
+                }
+
+            } else{
+                instanceList = getApprovalInstanceList(feishuInfo.getCompanyId(), feishuInfo.getAppId(), startDate, endDate, userId);
+            }
+
+
+            
+            if (instanceList == null || instanceList.size() == 0) {
+                System.out.println("未查询到费用报销审批实例");
+                msg.setData(new JSONArray());
+                return msg;
+            }
+            
+            System.out.println("查询到审批实例数量: " + instanceList.size());
+            
+            // 第二步:遍历实例列表,调用GET_APPROVAL_INSTANCE_INFO获取每个实例的详情并解析存储
+            int successCount = 0;
+            int skipCount = 0;
+            int errorCount = 0;
+
+            List<String> instanceCodeList = new ArrayList<>();
+            for (int i = 0; i < instanceList.size(); i++) {
+                JSONObject instance = instanceList.getJSONObject(i).getJSONObject("instance");
+                String instanceCode = instance.getString("code");
+                instanceCodeList.add(instanceCode);
+            }
+            List<ExpenseSheet> oldExpenseSheetList = expenseSheetMapper.selectList(
+                    new QueryWrapper<ExpenseSheet>().eq("company_id", feishuInfo.getCompanyId()).in("instance_code", instanceCodeList));
+            List<ExpenseSheet> needResyncProjectExpenseSheetList = new ArrayList<>();
+            //查询项目没有匹配上的数据
+            if (oldExpenseSheetList.size() > 0) {
+                List<Integer> sheetIds = oldExpenseSheetList.stream().map(ExpenseSheet::getId).collect(Collectors.toList());
+                List<ExpenseItem> noProjectItemList = expenseItemMapper.selectList(new QueryWrapper<ExpenseItem>().select("expense_id").in("expense_id", sheetIds).isNull("project_id"));
+                //取到没有项目的报销单
+                final List<Integer> expenseIds = noProjectItemList.stream().map(ExpenseItem::getExpenseId).distinct().collect(Collectors.toList());
+                needResyncProjectExpenseSheetList = oldExpenseSheetList.stream().filter(es -> expenseIds.contains(es.getId())).collect(Collectors.toList());
+            }
+            for (int i = 0; i < instanceList.size(); i++) {
+                JSONObject instance = instanceList.getJSONObject(i).getJSONObject("instance");
+                String instanceCode = instance.getString("code");
+                
+                System.out.println("========== 处理第 " + (i + 1) + " 个实例 ==========");
+                System.out.println("实例ID: " + instanceCode);
+                
+                try {
+                    // 检查是否已存在
+                    ExpenseSheet oldItem  = oldExpenseSheetList.stream().filter(item -> item.getInstanceCode().equals(instanceCode)).findFirst().orElse(null);
+                    boolean needRefresh = needResyncProjectExpenseSheetList.stream().anyMatch(item -> item.getInstanceCode().equals(instanceCode));
+                    System.out.println("模板编码:"+instanceCode+", 需要刷新=" + needRefresh);
+                    if (oldItem != null && !needRefresh) {
+                        System.out.println("报销单已存在,且无需刷新项目,跳过处理。instance_code: " + instanceCode);
+                        skipCount++;
+                        continue;
+                    }
+                    
+                    // 调用接口获取实例详情
+                    JSONObject instanceDetail = getApprovalInstanceInfo(feishuInfo.getAppId(), instanceCode);
+                    
+                    if (instanceDetail != null) {
+                        // 解析并存储数据
+                        boolean parseResult = parseAndSaveExpenseData(instanceDetail, feishuInfo.getCompanyId(), oldItem);
+                        if (parseResult) {
+                            successCount++;
+                            System.out.println("报销单处理成功。instance_code: " + instanceCode);
+                        } else {
+                            errorCount++;
+                            System.out.println("报销单处理失败。instance_code: " + instanceCode);
+                        }
+                    } else {
+                        errorCount++;
+                        System.out.println("获取实例详情失败,实例ID: " + instanceCode);
+                    }
+                } catch (Exception e) {
+                    errorCount++;
+                    System.out.println("处理实例异常,实例ID: " + instanceCode + ", 异常信息: " + e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+            
+            System.out.println("========== 费用报销数据同步完成 ==========");
+            System.out.println("总数: " + instanceList.size() + ", 成功: " + successCount + ", 跳过: " + skipCount + ", 失败: " + errorCount);
+            
+            JSONObject resultData = new JSONObject();
+            resultData.put("total", instanceList.size());
+            resultData.put("success", successCount);
+            resultData.put("skip", skipCount);
+            resultData.put("error", errorCount);
+            msg.setData(resultData);
+            
+        } catch (Exception e) {
+            System.out.println("获取费用报销数据异常: " + e.getMessage());
+            e.printStackTrace();
+            throw e;
+        }
+        
+        return msg;
+    }
+    
+    /**
+     * 解析并保存报销单数据
+     * @param instanceDetail 实例详情
+     * @param companyId 公司ID
+     * @return 是否成功
+     */
+    private boolean parseAndSaveExpenseData(JSONObject instanceDetail, Integer companyId, ExpenseSheet expenseSheet) {
+        try {
+            // 解析基本信息
+            String instanceCode = instanceDetail.getString("instance_code");
+            String userId = instanceDetail.getString("user_id");
+            String serialNumber = instanceDetail.getString("serial_number");
+            String status = instanceDetail.getString("status");
+            Long startTime = instanceDetail.getLong("start_time");
+            String formStr = instanceDetail.getString("form");
+            
+            // 根据user_id查询用户信息
+            User user = userMapper.selectOne(
+                new QueryWrapper<User>()
+                    .eq("job_number", userId)
+                    .eq("company_id", companyId)
+            );
+            
+            if (user == null) {
+                System.out.println("未找到用户,job_number: " + userId);
+                return false;
+            }
+            
+            // 解析form字段
+            JSONArray formArray = JSONArray.parseArray(formStr);
+            
+            // 提取关键字段
+            String expenseType = null; // 报销类型
+            String expenseReason = null; // 报销事由
+            String projectCode = null; // 项目编号
+            Double totalAmount = null; // 费用汇总
+            JSONArray expenseDetails = null; // 费用明细
+            
+            for (int i = 0; i < formArray.size(); i++) {
+                JSONObject formItem = formArray.getJSONObject(i);
+                String name = formItem.getString("name");
+                
+                if ("报销类型".equals(name)) {
+                    expenseType = formItem.getString("value");
+                } else if ("报销事由".equals(name)) {
+                    expenseReason = formItem.getString("value");
+                } else if ("项目编号".equals(name)) {
+                    projectCode = formItem.getString("value");
+                } else if ("费用汇总".equals(name)) {
+                    Object valueObj = formItem.get("value");
+                    if (valueObj instanceof Number) {
+                        totalAmount = ((Number) valueObj).doubleValue();
+                    } else if (valueObj instanceof String) {
+                        totalAmount = Double.parseDouble((String) valueObj);
+                    }
+                } else if ("费用明细".equals(name)) {
+                    expenseDetails = formItem.getJSONArray("value");
+                }
+            }
+            // 根据项目编号查询项目ID
+            Integer projectId = null;
+            if (!StringUtils.isEmpty(projectCode)) {
+                Project project = projectMapper.selectOne(
+                    new QueryWrapper<Project>()
+                        .eq("project_code", projectCode)
+                        .eq("company_id", companyId)
+                );
+                if (project != null) {
+                    projectId = project.getId();
+                } else {
+                    System.out.println("未找到项目,project_code: " + projectCode);
+                }
+            }
+            
+            // 创建ExpenseSheet对象
+            boolean sheetExists = expenseSheet != null;
+            if (expenseSheet == null) {
+                expenseSheet = new ExpenseSheet();
+                expenseSheet.setCompanyId(companyId);
+                expenseSheet.setOwnerId(user.getId());
+                expenseSheet.setOwnerName(user.getName());
+                expenseSheet.setInstanceCode(instanceCode);
+                expenseSheet.setTotalAmount(totalAmount);
+                expenseSheet.setRemark(expenseReason);
+                expenseSheet.setSendState(1);//已发放
+                //默认为差旅费用
+                ExpenseMainType expenseMainTypeObj = expenseMainTypeMapper.selectOne(new QueryWrapper<ExpenseMainType>().eq("name", expenseType).eq("company_id", companyId));
+                if (expenseMainTypeObj == null) {
+                    String defaultExpenseType = "差旅费用";
+                    expenseMainTypeObj = expenseMainTypeMapper.selectOne(new QueryWrapper<ExpenseMainType>().eq("name", defaultExpenseType).eq("company_id", companyId));
+                    if (expenseMainTypeObj == null) {
+                        //默认取第一个类型
+                        expenseMainTypeObj = expenseMainTypeMapper.selectOne(new QueryWrapper<ExpenseMainType>().eq("company_id", companyId).last("limit 1"));
+                    }
+                }
+                expenseSheet.setType(expenseMainTypeObj.getId());
+
+                int ticketNum = expenseDetails == null? 0: expenseDetails.size();
+                expenseSheet.setTicketNum(ticketNum);
+                // 设置创建日期(从startTime转换)
+                if (startTime != null) {
+                    LocalDate createDate = Instant.ofEpochMilli(startTime)
+                            .atZone(ZoneId.of("Asia/Shanghai"))
+                            .toLocalDate();
+                    expenseSheet.setCreateDate(createDate);
+                }
+                HttpRespMsg msg = expenseSheetService.getNextCode(user.getId());
+                expenseSheet.setCode((String)msg.getData());
+
+
+                // 设置状态:根据飞书审批状态映射
+                // PENDING-待审核, APPROVED-审核通过, REJECTED-驳回, CANCELED-已撤回
+                Integer sheetStatus = 1; // 默认待审核
+                if ("APPROVED".equals(status)) {
+                    sheetStatus = 0; // 审核通过
+                } else if ("REJECTED".equals(status)) {
+                    sheetStatus = 2; // 驳回
+                } else if ("CANCELED".equals(status)) {
+                    sheetStatus = 3; // 已撤回
+                } else if ("PENDING".equals(status)) {
+                    sheetStatus = 1; // 待审核
+                }
+                expenseSheet.setStatus(sheetStatus);
+
+                // 插入ExpenseSheet
+                int insertResult = expenseSheetMapper.insert(expenseSheet);
+                if (insertResult <= 0) {
+                    System.out.println("插入ExpenseSheet失败");
+                    return false;
+                }
+            }
+            int sheetStatus = expenseSheet.getStatus();
+            
+            Integer expenseSheetId = expenseSheet.getId();
+            System.out.println("插入ExpenseSheet成功,ID: " + expenseSheetId);
+
+            //先删除老的吧
+            if (sheetExists) {
+                expenseItemMapper.delete(new QueryWrapper<ExpenseItem>().eq("expense_id", expenseSheetId));
+            }
+            // 解析并插入费用明细
+            if (expenseDetails != null && expenseDetails.size() > 0) {
+                DateTimeFormatter dateFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+                
+                for (int i = 0; i < expenseDetails.size(); i++) {
+                    JSONArray detailArray = expenseDetails.getJSONArray(i);
+                    
+                    String content = null; // 内容
+                    String happenDate = null; // 日期
+                    Double amount = null; // 金额
+                    
+                    for (int j = 0; j < detailArray.size(); j++) {
+                        JSONObject detailItem = detailArray.getJSONObject(j);
+                        String itemName = detailItem.getString("name");
+                        
+                        if ("内容".equals(itemName)) {
+                            content = detailItem.getString("value");
+                        } else if ("日期(年-月-日)".equals(itemName)) {
+                            String dateValue = detailItem.getString("value");
+                            if (!StringUtils.isEmpty(dateValue)) {
+                                try {
+                                    // 解析ISO 8601格式的日期时间
+                                    LocalDateTime dateTime = LocalDateTime.parse(dateValue, dateFormatter);
+                                    happenDate = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+                                } catch (Exception e) {
+                                    System.out.println("日期解析失败: " + dateValue);
+                                }
+                            }
+                        } else if ("金额".equals(itemName)) {
+                            Object amountObj = detailItem.get("value");
+                            if (amountObj instanceof Number) {
+                                amount = ((Number) amountObj).doubleValue();
+                            } else if (amountObj instanceof String) {
+                                amount = Double.parseDouble((String) amountObj);
+                            }
+                        }
+                    }
+                    
+                    // 创建ExpenseItem对象
+                    ExpenseItem expenseItem = new ExpenseItem();
+                    expenseItem.setExpenseId(expenseSheetId);
+                    expenseItem.setProjectId(projectId); // 每个明细都设置项目ID
+                    expenseItem.setProjectCode(projectCode);
+                    expenseItem.setHappenDate(happenDate);
+                    expenseItem.setAmount(amount);
+//                    expenseItem.setRemark(content);
+                    expenseItem.setInvoiceType(1);//默认增值税普通发票
+                    expenseItem.setExpenseType(expenseType);
+                    expenseItem.setStatus(sheetStatus); // 与主表状态保持一致
+                    
+                    // 插入ExpenseItem
+                    int itemInsertResult = expenseItemMapper.insert(expenseItem);
+                    if (itemInsertResult <= 0) {
+                        System.out.println("插入ExpenseItem失败,明细索引: " + i);
+                    } else {
+                        System.out.println("插入ExpenseItem成功,ID: " + expenseItem.getId());
+                    }
+                }
+            }
+            
+            return true;
+            
+        } catch (Exception e) {
+            System.out.println("解析并保存报销单数据异常: " + e.getMessage());
+            e.printStackTrace();
+            return false;
+        }
+    }
+    
+    /**
+     * 获取审批实例列表
+     * @param appId 应用ID
+     * @param startDate 开始时间 格式:yyyyMMdd
+     * @param endDate 结束时间 格式:yyyyMMdd
+     * @return 审批实例列表
+     */
+    private JSONArray getApprovalInstanceList(Integer companyId, String appId, String startDate, String endDate, String userId) throws Exception {
+        JSONArray result = new JSONArray();
+        
+        // 将yyyyMMdd格式转换为时间戳(秒)
+        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
+        LocalDate startLocalDate = LocalDate.parse(startDate, dtf);
+        LocalDate endLocalDate = LocalDate.parse(endDate, dtf);
+        
+        long startTime = startLocalDate.atStartOfDay(ZoneId.of("Asia/Shanghai")).toEpochSecond()*1000;
+        long endTime = endLocalDate.atTime(23, 59, 59).atZone(ZoneId.of("Asia/Shanghai")).toEpochSecond()*1000;
+        
+        String url = GET_APPROVAL_INSTANCE_LIST;
+        //审批表单的code
+        String approvalCode = EXPENSE_APPROVAL_CODE_MAP.get(companyId);
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=utf-8");
+        headers.setContentType(type);
+        headers.add("Authorization", "Bearer " + getTenantAccessToken(appId));
+        
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("page_size", 100);
+        requestBody.put("approval_code",approvalCode);
+        requestBody.put("instance_start_time_from", startTime);
+        requestBody.put("instance_start_time_to", endTime);
+        requestBody.put("user_id_type", "user_id");
+        if (!StringUtils.isEmpty(userId)) {
+            requestBody.put("user_id", userId);
+        }
+        requestBody.put("instance_status", "APPROVED");
+
+        HttpEntity<JSONObject> httpEntity = new HttpEntity<>(requestBody, headers);
+        
+        System.out.println("请求审批实例列表URL: " + url);
+        System.out.println("请求参数: " + requestBody.toJSONString());
+        
+        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
+        
+        if (responseEntity.getStatusCode() == HttpStatus.OK) {
+            String resp = responseEntity.getBody();
+            System.out.println("审批实例列表响应数据: " + resp);
+            
+            JSONObject respJson = JSONObject.parseObject(resp);
+            if (respJson.getInteger("code") == 0) {
+                JSONObject data = respJson.getJSONObject("data");
+                if (data != null) {
+                    JSONArray instanceList = data.getJSONArray("instance_list");
+                    if (instanceList != null && instanceList.size() > 0) {
+                        result.addAll(instanceList);
+                    }
+                    
+                    // 处理分页
+                    Boolean hasMore = data.getBoolean("has_more");
+                    String pageToken = data.getString("page_token");
+                    
+                    while (hasMore != null && hasMore && pageToken != null) {
+                        System.out.println("存在更多数据,继续获取,page_token: " + pageToken);
+                        JSONArray nextPageData = getApprovalInstanceListWithPageToken(approvalCode, appId, startTime, endTime, pageToken, userId);
+                        if (nextPageData != null && nextPageData.size() > 0) {
+                            result.addAll(nextPageData);
+                        }
+                        
+                        // 更新分页信息(这里简化处理,实际需要递归或循环处理)
+                        break;
+                    }
+                }
+            } else {
+                System.out.println("获取审批实例列表失败: " + respJson.getInteger("code") + ", " + respJson.getString("msg"));
+                throw new Exception("获取审批实例列表失败: " + respJson.getInteger("code") + ", " + respJson.getString("msg"));
+            }
+        } else {
+            System.out.println("获取审批实例列表HTTP请求失败: " + responseEntity.getStatusCode());
+            throw new Exception("获取审批实例列表HTTP请求失败: " + responseEntity.getStatusCode());
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 带分页token获取审批实例列表
+     */
+    private JSONArray getApprovalInstanceListWithPageToken(String approvalCode, String appId, long startTime, long endTime, String pageToken, String userId) throws Exception {
+        JSONArray result = new JSONArray();
         
-        return null;
+        String url = GET_APPROVAL_INSTANCE_LIST;
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=utf-8");
+        headers.setContentType(type);
+        headers.add("Authorization", "Bearer " + getTenantAccessToken(appId));
+        
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("page_size", 100);
+        requestBody.put("page_token", pageToken);
+        requestBody.put("approval_code",approvalCode);
+        requestBody.put("instance_start_time_from", startTime);
+        requestBody.put("instance_start_time_to", endTime);
+        requestBody.put("user_id_type", "user_id");
+        if (!StringUtils.isEmpty(userId)) {
+            requestBody.put("user_id", userId);
+        }
+        requestBody.put("instance_status", "APPROVED");
+        HttpEntity<JSONObject> httpEntity = new HttpEntity<>(requestBody, headers);
+        
+        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
+        
+        if (responseEntity.getStatusCode() == HttpStatus.OK) {
+            String resp = responseEntity.getBody();
+            JSONObject respJson = JSONObject.parseObject(resp);
+            if (respJson.getInteger("code") == 0) {
+                JSONObject data = respJson.getJSONObject("data");
+                if (data != null) {
+                    JSONArray instanceList = data.getJSONArray("instance_list");
+                    if (instanceList != null && instanceList.size() > 0) {
+                        result.addAll(instanceList);
+                    }
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 获取审批实例详情
+     * @param appId 应用ID
+     * @param instanceId 审批实例ID
+     * @return 审批实例详情
+     */
+    private JSONObject getApprovalInstanceInfo(String appId, String instanceId) throws Exception {
+        JSONObject result = null;
+        
+        String url = GET_APPROVAL_INSTANCE_INFO.replace(":instance_id", instanceId);
+        HttpHeaders headers = new HttpHeaders();
+        MediaType type = MediaType.parseMediaType("application/json; charset=utf-8");
+        headers.setContentType(type);
+        headers.add("Authorization", "Bearer " + getTenantAccessToken(appId));
+        
+        HttpEntity<JSONObject> httpEntity = new HttpEntity<>(null, headers);
+        
+        System.out.println("请求审批实例详情URL: " + url);
+        
+        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
+        
+        if (responseEntity.getStatusCode() == HttpStatus.OK) {
+            String resp = responseEntity.getBody();
+            System.out.println("审批实例详情响应数据: " + resp);
+            
+            JSONObject respJson = JSONObject.parseObject(resp);
+            if (respJson.getInteger("code") == 0) {
+                JSONObject data = respJson.getJSONObject("data");
+                if (data != null) {
+                    result = data;
+                }
+            } else {
+                System.out.println("获取审批实例详情失败: " + respJson.getInteger("code") + ", " + respJson.getString("msg"));
+                throw new Exception("获取审批实例详情失败: " + respJson.getInteger("code") + ", " + respJson.getString("msg"));
+            }
+        } else {
+            System.out.println("获取审批实例详情HTTP请求失败: " + responseEntity.getStatusCode());
+            throw new Exception("获取审批实例详情HTTP请求失败: " + responseEntity.getStatusCode());
+        }
+        
+        return result;
     }
 
 

+ 29 - 3
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/LeaveSheetServiceImpl.java

@@ -973,7 +973,6 @@ public class LeaveSheetServiceImpl extends ServiceImpl<LeaveSheetMapper, LeaveSh
                                 List<String> collect = realDataList.stream().map(LeaveSheet::getProcinstId).distinct().collect(Collectors.toList());
                                 List<LeaveSheet> existLeaveSheetList = leaveSheetMapper.selectList(new LambdaQueryWrapper<LeaveSheet>().in(LeaveSheet::getProcinstId, collect).eq(LeaveSheet::getCompanyId, specialCompanyId));
                                 for (LeaveSheet tmp : realDataList) {
-                                    System.out.println("请假人:"+tmp.getOwnerId()+","+tmp.getOwnerName()+", "+tmp.getStartDate().toString());
                                     Optional<LeaveSheet> first = existLeaveSheetList.stream().filter(t -> t.getProcinstId().equals(tmp.getProcinstId())).findFirst();
                                     if (first.isPresent()) {
                                         tmp.setId(first.get().getId());
@@ -1010,9 +1009,36 @@ public class LeaveSheetServiceImpl extends ServiceImpl<LeaveSheetMapper, LeaveSh
                                                 .eq(LeaveSheet::getCompanyId, specialCompanyId)
                                                 .le(LeaveSheet::getStartDate, date).ge(LeaveSheet::getEndDate, date));
                                         if (!CollectionUtils.isEmpty(leaveSheetList)) {
-                                            double leaveHours = leaveSheetList.stream().mapToDouble(LeaveSheet::getTimeHours).sum();
+//                                            double leaveHours = leaveSheetList.stream().mapToDouble(LeaveSheet::getTimeHours).sum();
+//                                            System.out.println("date==" + date + "user==" +leaveSheetList.get(0).getOwnerName());
+                                            //计算当前请假总时长
+                                            double leaveHours = 0;
+                                            for (LeaveSheet oneSheet : leaveSheetList) {
+                                                if (oneSheet.getStartDate().equals(oneSheet.getEndDate())) {
+                                                    //单日请假
+                                                    leaveHours = oneSheet.getTimeHours();
+                                                } else {
+                                                    //跨天请假,判断是第几天
+                                                    String beginTime = oneSheet.getBeginTime() == null?"09:00":oneSheet.getBeginTime();
+                                                    String endTime = oneSheet.getEndTime() == null?"18:00":oneSheet.getEndTime();
+                                                    System.out.println("beginTime="+beginTime+",endTime="+endTime);
+                                                    if (oneSheet.getStartDate().isEqual(date)) {
+                                                        //第一天,到下班截止
+                                                        endTime = "18:00";
+                                                    } else if (oneSheet.getEndDate().isEqual(date)) {
+                                                        //最后一天,从上班开始
+                                                        beginTime = "09:00";
+                                                    } else {
+                                                        //中间天,全天
+                                                        beginTime = "09:00";
+                                                        endTime = "18:00";
+                                                    }
+                                                    //重新计算请假时长
+                                                    leaveHours += WorkDayCalculateUtils.calculateLeaveWorkHours(beginTime, endTime, "12:00", "13:00");
+                                                }
+                                            }
                                             if (leaveHours > 0) {
-                                                //跨天请假的
+                                                //跨天请假的,判断当前是属于
                                                 if (leaveHours > 8.0) {
                                                     leaveHours = 8.0;
                                                 }

+ 9 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -5392,8 +5392,15 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                     //取当天的所有请假的合计请假时长
                     double curLeaveTime = curUserLeaveList.stream().filter(leaveSheet -> leaveSheet.getStartDate().equals(startDate1) && leaveSheet.getEndDate().equals(endDate1))
                             .mapToDouble(LeaveSheet::getTimeHours).sum();
-                    double curLeaveDays = curUserLeaveList.stream().filter(leaveSheet -> leaveSheet.getStartDate().equals(startDate1) && leaveSheet.getEndDate().equals(endDate1))
-                            .mapToDouble(LeaveSheet::getTimeDays).sum();
+                    double curLeaveDays = 0;
+                    if (startDate1.equals(endDate1)) {
+                        //一天内的请假
+                        curLeaveDays = 1;
+                    } else {
+                        curLeaveDays = curUserLeaveList.stream().filter(leaveSheet -> leaveSheet.getStartDate().equals(startDate1) && leaveSheet.getEndDate().equals(endDate1) && leaveSheet.getTimeDays() != null)
+                                .mapToDouble(LeaveSheet::getTimeDays).sum();
+                    }
+
                     //该范围内的都算请假
                     int i=0;
                     while(true) {

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

@@ -407,7 +407,7 @@ public class WxCorpInfoServiceImpl extends ServiceImpl<WxCorpInfoMapper, WxCorpI
                 else if ("task".equals(pageRouter)) {
                     //费用报销
                     title = "工时管家:任务到期通知";
-                    if (msgType.equals(TEXT_CARD_MSG_TASK_PLAN_NEED_CHECK)) {
+                    if (msgType != null &&msgType.equals(TEXT_CARD_MSG_TASK_PLAN_NEED_CHECK)) {
                         title = "工作计划待审核";
                     }
                 }

+ 55 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/util/WorkDayCalculateUtils.java

@@ -1,7 +1,9 @@
 package com.management.platform.util;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.DateFormatUtils;
 
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.DayOfWeek;
@@ -358,6 +360,11 @@ public class WorkDayCalculateUtils {
         LocalDate date2 = LocalDate.of(2023, 11, 30);
 //        System.out.println(sameWeek(date1, date2));
 //        System.out.println(sameMonth(date1, date2));
+
+        double workHours = calculateLeaveWorkHours("09:00", "18:00", "12:00", "13:00");
+        System.out.println("leaveHours = " + workHours);
+        workHours = calculateLeaveWorkHours("14:00", "18:00", "12:00", "13:00");
+        System.out.println("leaveHours = " + workHours);
     }
 
     public static List<String> getAllHolidays(String firstDay, String endDate) {
@@ -376,4 +383,52 @@ public class WorkDayCalculateUtils {
         }
         return dateList;
     }
+
+    public static double calculateLeaveWorkHours(String startTime, String endTime, String restBeginTime, String restEndTime) {
+        //中间来的得从下午上班时间开始算
+        if (!StringUtils.isEmpty(restBeginTime) && !StringUtils.isEmpty(restEndTime)) {
+            if (startTime.compareTo(restBeginTime) > 0 && startTime.compareTo(restEndTime) < 0) {
+                startTime = restEndTime;
+            }
+            if (endTime.compareTo(restBeginTime) > 0 && endTime.compareTo(restEndTime) < 0) {
+                endTime = restBeginTime;
+            }
+        }
+
+        String[] startParts = startTime.split(":");
+        boolean isEndNextDay = endTime.startsWith("次日");
+        if (endTime.startsWith("次日")) {
+            endTime = endTime.substring(3);
+            System.out.println("去掉次日后="+endTime);
+        }
+        String[] endParts = endTime.split(":");
+
+        int startHour = Integer.parseInt(startParts[0]);
+        int startMinute = Integer.parseInt(startParts[1]);
+        int endHour = Integer.parseInt(endParts[0]) + (isEndNextDay ? 24 : 0);
+        int endMinute = Integer.parseInt(endParts[1]);
+
+        double hours = (endHour - startHour) + (endMinute - startMinute) / 60.0;
+
+        // 减去午休时间(假设12:00-13:00为午休)
+        int restBeginHour = Integer.parseInt(restBeginTime.split(":")[0]);
+        int restEndHour = Integer.parseInt(restEndTime.split(":")[0]);
+        if (startHour <= restBeginHour && endHour >= restEndHour) {
+            hours -= 1;
+        }
+
+        double minPart = hours - (int)hours;
+
+        //按0.5小时对齐
+        if (minPart > 0 && minPart < 0.5) {
+            minPart = 0;
+        } else if (minPart > 0.5) {
+            minPart = 0.5;
+        }
+        hours = (int)hours + minPart;
+        //四舍五入到小数点后一位
+        BigDecimal bigDecimal = new BigDecimal(hours);
+        bigDecimal = bigDecimal.setScale(1, BigDecimal.ROUND_HALF_UP);
+        return bigDecimal.doubleValue();
+    }
 }

+ 4 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ExpenseItemMapper.xml

@@ -18,6 +18,7 @@
         <result column="pic" property="pic" />
         <result column="status" property="status" />
         <result column="auditor_id" property="auditorId" />
+        <result column="project_code" property="projectCode" />
     </resultMap>
     <resultMap id="UserBaseResultMap" type="com.management.platform.entity.vo.ExpenseItemVO">
         <id column="id" property="id" />
@@ -38,14 +39,15 @@
         <result column="corpwxUserId" property="corpwxUserId" />
         <result column="corpwxDeptId" property="corpwxDeptId" />
         <result column="corpDdDeptId" property="corpDdDeptId" />
+        <result column="project_code" property="projectCode" />
     </resultMap>
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, expense_id, project_id, happen_date, invoice_type, invoice_no, tax_percent, tax_value, amount, remark, expense_type, pic, status, auditor_id
+        id, expense_id, project_id, happen_date, invoice_type, invoice_no, tax_percent, tax_value, amount, remark, expense_type, pic, status, auditor_id, project_code
     </sql>
     <select id="getUserExpenseDetail" resultMap="UserBaseResultMap">
         select a.id, a.expense_id, a.project_id, a.happen_date, a.invoice_type, a.tax_percent, a.tax_value, a.amount, a.remark, a.expense_type, a.pic,a.status,
-               user.name as username,user.corpwx_userid as corpwxUserId, department.department_name,department.corpwx_deptid as corpwxDeptId,department.dd_deptid as corpDdDeptId
+               user.name as username,user.corpwx_userid as corpwxUserId, department.department_name,department.corpwx_deptid as corpwxDeptId,department.dd_deptid as corpDdDeptId, a.project_code
         from expense_item a
                  left join expense_sheet b on a.expense_id = b.id
                  left join user on user.id = b.owner_id

+ 2 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ExpenseSheetMapper.xml

@@ -23,11 +23,12 @@
         <result column="review_process" property="reviewProcess" />
         <result column="pay_way_id" property="payWayId" />
         <result column="pay_way_name" property="payWayName" />
+        <result column="instance_code" property="instanceCode" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, code, company_id, owner_id, owner_name, create_date, ticket_num, type, status, remark, total_amount, operator_id, deny_reason, send_state, first_checker_id, second_checker_id, review_process, pay_way_id, pay_way_name
+        id, code, company_id, owner_id, owner_name, create_date, ticket_num, type, status, remark, total_amount, operator_id, deny_reason, send_state, first_checker_id, second_checker_id, review_process, pay_way_id, pay_way_name, instance_code
     </sql>
 
 </mapper>

+ 3 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/LeaveSheetMapper.xml

@@ -25,11 +25,13 @@
         <result column="procinst_id" property="procinstId" />
         <result column="gmt_finished" property="gmtFinished" />
         <result column="cur_audit_setting_id" property="curAuditSettingId" />
+        <result column="begin_time" property="beginTime" />
+        <result column="end_time" property="endTime" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, company_id, owner_id, owner_name, start_date, end_date, leave_type, status, remark, operator_id, time_hours, time_days, indate, time_type, tel, auditor_id, auditor_name, auditor_type, procinst_id, gmt_finished, cur_audit_setting_id
+        id, company_id, owner_id, owner_name, start_date, end_date, leave_type, status, remark, operator_id, time_hours, time_days, indate, time_type, tel, auditor_id, auditor_name, auditor_type, procinst_id, gmt_finished, cur_audit_setting_id, begin_time, end_time
     </sql>
     <insert id="batchInsert">
         insert into leave_sheet(company_id, owner_id, owner_name,start_date, end_date, leave_type, status, remark

+ 2 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ReportMapper.xml

@@ -1021,7 +1021,7 @@
         AND not exists(
         select 1 from finance_exclude_project fep where fep.company_id = report.company_id and fep.use_ym = #{ym} and fep.project_id = report.project_id
         )
-        GROUP BY project_id, report.creator_id;
+        GROUP BY project_id, report.creator_id order by project_id;
     </select>
 
     <select id="getUploadThirdReportData" resultType="java.util.Map">
@@ -1036,7 +1036,7 @@
           AND r.state=1
           AND r.create_date &gt;= #{startDate}
           AND r.create_date &lt; #{endDate}
-        group by p.id,u.id,tg.id
+        group by p.id,u.id,tg.id order by p.id
     </select>
     <select id="getReportFillStatus" resultType="java.util.Map">
         SELECT DATE_FORMAT(r.create_date,'%Y-%m-%d') AS createDate, IF (MAX(r.state) = 1, MIN(r.state), MAX(r.state)) AS state,sum(r.working_time) as workingTime

+ 10 - 12
fhKeeper/formulahousekeeper/timesheet-yzr/src/views/expense/expense.vue

@@ -95,18 +95,18 @@
                   ref="selectCat" @selectCal="selectCal" :filterable="true"></selectCat>
               </el-form-item>
               <!-- 单据编号 -->
-              <!-- <el-form-item :label="$t('receiptnumber')">
-                <el-input v-model="code" size="small" :placeholder="$t('receiptnumber')" clearable="true"
+              <el-form-item label="项目编号">
+                <el-input v-model="projectCode" size="small" placeholder="项目编号" clearable="true"
                   style="width: 120px"></el-input>
-              </el-form-item> -->
-              <el-form-item :label="$t('other.project')">
+              </el-form-item>
+              <!-- <el-form-item :label="$t('other.project')">
                 <el-select v-model="selectProject" size="small" style="width: 162px" clearable filterable>
                   <el-option v-for="item in projectList" :label="item.projectName" :value="item.id" :key="item.id">
                     <span style="float: left">{{ item.projectName }}</span>
                     <span style="float: right; color: #8492a6;">{{ item.projectCode }}</span>
                   </el-option>
                 </el-select>
-              </el-form-item>
+              </el-form-item> -->
               
               <!-- 费用主类型 -->
               <el-form-item :label="$t('feiYongZhuLeiXing')">
@@ -130,7 +130,7 @@
                     :end-placeholder="$t('time.endDate')" style="width: 280px">
                   </el-date-picker>
                 </el-form-item>
-                <el-form-item v-if="currentClick == '2-1' && user.companyId == 8484 && (user.roleName =='超级管理员' || user.roleName =='系统管理员')">
+                <el-form-item v-if="currentClick == '2-1' && user.companyId == 8484 && (user.roleName =='超级管理员' || user.roleName =='系统管理员'|| user.roleName =='财务管理员')">
                   <el-checkbox v-model="noProjectFlag" size="small" >无项目数据</el-checkbox>
                 </el-form-item>
                 <el-form-item>
@@ -143,7 +143,7 @@
                   <el-button :loading="exportingData"  @click="exportDocumentFile()" size="small">{{ $t('baoXiaoPingZhengDaoChu') }}</el-button>
                 </el-form-item>
                 <!-- <span>审核模式:{{ auditTypeItem.auditType }}</span> -->
-                 <el-form-item v-if="currentClick == '2-1' && (user.roleName =='超级管理员' || user.roleName =='系统管理员')">
+                 <el-form-item v-if="currentClick == '2-1' && (user.roleName =='超级管理员' || user.roleName =='系统管理员' || user.roleName =='财务管理员')">
                   <el-button @click="showSyncFeiShuDialog = true" size="small">同步飞书报销单</el-button>
                 </el-form-item>
                 
@@ -850,7 +850,7 @@ export default {
       ProjectList: [], // 项目列表
       companyId: [], // 人员的id
       dialogVisible: false,
-      code: null,
+      projectCode:null,
       addForm: { code: null, ownerId: null, createDate: null, ticketNum: 1, type: 0, remark: null, totalAmount: 0, },
       page: 1,
       size: 20,
@@ -1766,7 +1766,7 @@ export default {
       var param = {
         pageIndex: this.page,
         pageSize: this.size,
-        code: this.code,
+        projectCode: this.projectCode,
         // createDate: this.date,
         startDate: stat,
         endDate: end,
@@ -1780,9 +1780,7 @@ export default {
       if (this.permissions.costExpenseRelease) {
         param.sendState = this.sendState
       }
-      if (this.user.companyId == 8484) {
-        param.noProjectFlag = this.noProjectFlag;
-      }
+      param.noProjectFlag = this.noProjectFlag;
       this.list = [];
       // this.total = 0;
       this.http.post('/expense-sheet/list', param,

+ 54 - 5
fhKeeper/formulahousekeeper/timesheet/src/views/expense/expense.vue

@@ -350,6 +350,9 @@
                     :end-placeholder="$t('time.endDate')" style="width: 280px">
                   </el-date-picker>
                 </el-form-item>
+                <el-form-item v-if="currentClick == '2-1' && user.companyId == 8484 && (user.roleName =='超级管理员' || user.roleName =='系统管理员')">
+                  <el-checkbox v-model="noProjectFlag" size="small" >无项目数据</el-checkbox>
+                </el-form-item>
                 <el-form-item>
                   <el-button @click="getList" size="small">{{ $t('find') }}</el-button>
                 </el-form-item>
@@ -359,13 +362,17 @@
                 <el-form-item v-if="currentClick == '2-1'">
                   <el-button :loading="exportingData" @click="exportDocumentFile()" size="small">{{ $t('baoXiaoPingZhengDaoChu') }}</el-button>
                 </el-form-item>
-                <el-form-item v-if="currentClick == '2-1' && permissions.costExpenseRelease">
+                <el-form-item v-if="currentClick == '2-1' && permissions.costExpenseRelease&& user.companyId != 8484">
                   <el-button @click="documentIssuance(1)" size="small">{{ $t('faFang') }}</el-button>
                 </el-form-item>
-                <el-form-item v-if="currentClick == '2-1' && permissions.costExpenseRelease">
+                <el-form-item v-if="currentClick == '2-1' && permissions.costExpenseRelease&& user.companyId != 8484">
                   <el-button @click="documentIssuance(0)" size="small">{{ $t('quXiaoFaFang') }}</el-button>
                 </el-form-item>
                 <!-- <span>审核模式:{{ auditTypeItem.auditType }}</span> -->
+                 <el-form-item v-if="currentClick == '2-1' && user.companyId == 8484 && (user.roleName =='超级管理员' || user.roleName =='系统管理员')">
+                  <el-button @click="showSyncFeiShuDialog = true" size="small">同步飞书报销单</el-button>
+                </el-form-item>
+                
               </div>
             </el-form>
           </div>
@@ -488,13 +495,13 @@
                   <span style="font-size:12px;">{{scope.row.denyReason}}</span>
                 </template>
               </el-table-column> -->
-            <el-table-column prop="sendState" :label="$t('faFangZhuangTai')" width="80" v-if="permissions.costExpenseRelease">
+            <el-table-column prop="sendState" :label="$t('faFangZhuangTai')" width="80" v-if="permissions.costExpenseRelease&& user.companyId != 8484">
               <template slot-scope="scope">
                 <span :style="`color: ${scope.row.sendState == 0 ? '#FFA500' : ''}`">{{ scope.row.sendState == 1 ? $t('yiFaFang') :
                   '未发放' }}</span>
               </template>
             </el-table-column>
-            <el-table-column prop="payWayName" :label="$t('zhiFuFangShi')" width="80" v-if="permissions.costExpenseRelease">
+            <el-table-column prop="payWayName" :label="$t('zhiFuFangShi')" width="80" v-if="permissions.costExpenseRelease&& user.companyId != 8484">
             </el-table-column>
             <el-table-column fixed="right" :label="$t('operation')" :width="isAuditList ? 220 : 160">
               <template slot-scope="scope">
@@ -1115,7 +1122,7 @@
           <div class="conter-border" v-for="item, index in ParticularsList.invoiceList" :key="index">
             <div class="detail-item" v-if="auditTypeItem.auditType != 1">
               <span class="detail-item-title"> <span class="printBox">{{ $t('other.project') }}</span> </span>
-              <span class="detail-item-content">
+              <span class="detail-item-content" :style="item.projectName.indexOf('未关联')> -1?'color:red;':''">
                 {{ item.projectName }}
                 <!-- <el-select size="small" v-if="!flg" v-model="item.projectId" :placeholder="$t('other.project')"
                   style="width: 130px">
@@ -1247,6 +1254,19 @@
         <el-button type="primary" @click="addEditorOwner()" :disabled="!equipmentOwnerValue.equipmentOwner">确 定</el-button>
       </span>
     </el-dialog>
+
+    <el-dialog title="同步飞书费用报销单据" :visible.sync="showSyncFeiShuDialog" width="400px" :before-close="handleClose">
+      <div>
+        <span>单据月份</span>
+        <el-date-picker size="small" v-model="syncYmonth" type="month" style=" width: 145px"
+          value-format="yyyy-MM" :placeholder="$t('optiondate')">
+        </el-date-picker>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="showSyncFeiShuDialog = false;">{{ $t('quXiao') }}</el-button>
+        <el-button type="primary" :loading="isSyncing" @click="startSync()">开始同步</el-button>
+      </span>
+    </el-dialog>
   </section>
 </template>
 
@@ -1267,6 +1287,10 @@ export default {
   props: {},
   data() {
     return {
+      noProjectFlag: false,
+      syncYmonth: null,
+      isSyncing: false,
+      showSyncFeiShuDialog: false,
       modifyRemarkVisible: false,
       themeColor: getThemeColor(),
       pdfIcons: require('@/assets/image/pdfIcon.png'),
@@ -1432,6 +1456,28 @@ export default {
     }
   },
   methods: {
+    startSync() {
+      if (!this.syncYmonth) {
+        this.$message({ message: '请选择需要同步的月份', type: 'error'})
+        return;
+      }
+      this.isSyncing = true;
+      this.http.post('/feishu-info/refreshExpenseSheet', {
+          ymonth: this.syncYmonth,
+          companyId: this.user.companyId
+        },
+        res => {
+          this.isSyncing = false;
+          this.showSyncFeiShuDialog = false;
+          if (res.code == 'ok') {
+            this.getList();
+          } else {
+            this.$message({ message: res.msg, type: 'error'})
+          }
+        }, err => {
+          this.$message({ message: err, type: 'error' })
+        })
+    },
     updateOwner(row) {
       if(!row) {
         this.equipmentOwnerValue = { equipmentOwner: '' }
@@ -2223,6 +2269,9 @@ export default {
       if (this.permissions.costExpenseRelease) {
         param.sendState = this.sendState
       }
+      if (this.user.companyId == 8484) {
+        param.noProjectFlag = this.noProjectFlag;
+      }
       this.list = [];
       // this.total = 0;
       this.http.post('/expense-sheet/list', param,

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

@@ -390,7 +390,7 @@
                     <el-button v-if="(permissions.projectManagement || user.id==scope.row.creatorId) && user.timeType.mainProjectState != '1' && !user.timeType.hideSubproject" size="mini"  @click="subProject(scope.row)">{{ $t('lable.subproject') }}</el-button>
                     <el-button size="mini" v-if="permissions.projectParticipator || permissions.projectManagement || user.id==scope.row.inchargerId || user.id==scope.row.creatorId" type="primary" @click="handleAdd(scope.$index, scope.row)">{{ $t('bian-ji') }}</el-button>
                     <!-- <el-button v-if="permissions.projectManagement || user.id==scope.row.creatorId" size="mini"  @click="deletePro(scope.$index, scope.row)">删除</el-button> -->
-                    <el-dropdown class="customdropdown" split-button size="mini" @click="finishPro(scope.row)" v-if="user.id!=scope.row.inchargerId && (permissions.projectManagement || user.id==scope.row.creatorId || user.id==scope.row.inchargerId) && scope.row.status == 1" placement="bottom-start">
+                    <el-dropdown class="customdropdown" split-button size="mini" @click="finishPro(scope.row)" v-if="(permissions.projectManagement || user.id==scope.row.creatorId) && scope.row.status == 1" placement="bottom-start">
                         {{ $t('wan-cheng') }} 
                         <el-dropdown-menu slot="dropdown" class="customdropdown_menu">
                             <el-button size="mini"  @click="cancelPro(scope.row)" class="customdropdown_menu_btn">{{ $t('btn.undo') }}</el-button><br>

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

@@ -2155,6 +2155,7 @@
                                     message: '同步成功',
                                     type: "success"
                                 });
+                                this.getTaskGroup();
                             } else {
                                 this.$message({
                                     message: res.msg,

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

@@ -3386,6 +3386,29 @@ export default {
             this.$set(that.depForm, 'otherManagerIds', [])
           }
           that.depTitle = this.$t('editorialdepartment');
+          that.http.post('/department/get',{
+                id: that.depData.id,
+              },
+              (res) => {
+                if (res.data != null) {
+                  if (res.data.managerId != null&&this.users.filter(u=>u.id == res.data.managerId).length == 0) {
+                    //主要负责人不存在
+                    this.$set(that.depForm, 'managerId', null);
+                    that.$message({
+                      message: '主要负责人已离职,请重新选择并设置',
+                      type: "warning",
+                    });
+                  }
+                } 
+              },
+              (error) => {
+                that.$message({
+                  message: error,
+                  type: "error",
+                });
+              }
+            );
+
           //获取部门的配置
           if (that.user.timeType.type == 2 && that.user.timeType.multiWorktime == 1) {
             that.http.post('/department-enable/get',{

BIN
fhKeeper/formulahousekeeper/webttkuaiban/src/main/resources/static/download/timesheet.apk