Procházet zdrojové kódy

Merge branch 'master' of http://47.100.37.243:10191/wutt/manHourHousekeeper

QuYueTing před 1 měsícem
rodič
revize
2f266ee65c

+ 11 - 1
fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/controller/TaskController.java

@@ -204,6 +204,7 @@ public class TaskController {
         WxCorpInfo wxCorpInfo = wxCorpInfoService.getOne(new QueryWrapper<WxCorpInfo>().eq("company_id", user.getCompanyId()));
         int isInsert=0;
         boolean shouldResetAuditStatus = false;
+        boolean shouldUpdatetaskDailyAllocate = false;
         List<TaskType> typeList = taskTypeMapper.selectList(new QueryWrapper<TaskType>().eq("company_id", user.getCompanyId()));
         TaskType taskType = typeList.stream().filter(t->t.getId()==task.getTaskPlanType()).findAny().orElse(null);
         if (taskType == null){
@@ -502,6 +503,9 @@ public class TaskController {
             }
 
         }
+        if (task.getTaskStatus()>=2){
+            shouldUpdatetaskDailyAllocate=true;
+        }
         boolean saved = taskService.saveOrUpdate(task);
         //新增成功,给第一审核人发送信息提醒
         if (saved&&isInsert==1){
@@ -531,7 +535,8 @@ public class TaskController {
                 allocateArrayList.forEach(a->a.setTaskId(task.getId()));
                 taskDailyAllocateService.saveBatch(allocateArrayList);
             }
-        } else if (saved && shouldResetAuditStatus) {
+        }
+        else if (saved && shouldResetAuditStatus) {
             msgRecepientList=new ArrayList<>();
             //给第一审核人发送信息提醒
             log.info("撤销/驳回后小组长修改,给第一审核人发送信息提醒");
@@ -556,6 +561,11 @@ public class TaskController {
                 taskDailyAllocateService.remove(new QueryWrapper<TaskDailyAllocate>().eq("task_id",task.getId()));
                 taskDailyAllocateService.saveBatch(allocateArrayList);
             }
+        } else if (saved&&shouldUpdatetaskDailyAllocate) {
+            if (!allocateArrayList.isEmpty()) {
+                taskDailyAllocateService.remove(new QueryWrapper<TaskDailyAllocate>().eq("task_id",task.getId()));
+                taskDailyAllocateService.saveBatch(allocateArrayList);
+            }
         }
 
         if (task.getExecutorId() == null) {

+ 2 - 1
fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/mapper/TaskDailyAllocateMapper.java

@@ -2,6 +2,7 @@ package com.management.platform.mapper;
 
 import com.management.platform.entity.TaskDailyAllocate;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
 
 import java.time.LocalDateTime;
 import java.util.ArrayList;
@@ -17,7 +18,7 @@ import java.util.ArrayList;
 public interface TaskDailyAllocateMapper extends BaseMapper<TaskDailyAllocate> {
 
 
-    ArrayList<TaskDailyAllocate> getUserTaskTimeList(Integer taskId, String userId, LocalDateTime stateTime, LocalDateTime endTime);
+    ArrayList<TaskDailyAllocate> getUserTaskTimeList(@Param("taskId") Integer taskId,@Param("userId") String userId, @Param("startTime") LocalDateTime stateTime,@Param("endTime") LocalDateTime endTime);
 
 
     int getConflict(TaskDailyAllocate taskDailyAllocate);

+ 1 - 0
fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/mapper/TaskMapper.java

@@ -95,6 +95,7 @@ public interface TaskMapper extends BaseMapper<Task> {
     Integer getMyAuditTaskCount(@Param(Constants.WRAPPER) Wrapper wrapper,String userId);
 
     Integer getUserConflitTaskCount(String userId, Integer taskId, LocalDateTime startDate, LocalDateTime endDate);
+    List<Task> getTaskConflitList(String userId, Integer taskId, LocalDateTime startDate, LocalDateTime endDate);
 
     List getUserTaskTimeList(Integer taskId, String userId, LocalDateTime startDate, LocalDateTime endDate);
 }

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

@@ -13928,7 +13928,9 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
                     StringBuilder content= new StringBuilder();
                     double sum = gantExportVoList.stream().mapToDouble(GantExportVo::getTotalWorkHour).sum();
                     double remain = 7.5 - sum;
-                    content.append("共").append(sum).append("h(剩余工时").append(remain).append("h)\n");
+                    DecimalFormat df = new DecimalFormat("#0.0");
+                    String formattedRemain = df.format(remain);
+                    content.append("共").append(sum).append("h(剩余工时").append(formattedRemain).append("h)\n");
 
                     for (GantExportVo exportVo : gantExportVoList) {
                         String projectName = exportVo.getProjectName();

+ 160 - 20
fhKeeper/formulahousekeeper/management-platform-mld/src/main/java/com/management/platform/service/impl/TaskServiceImpl.java

@@ -44,6 +44,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -60,6 +61,10 @@ import java.util.stream.Collectors;
 @Slf4j
 public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService {
 
+    // 定义午休时间段(12:00-13:00)
+    private static final LocalTime LUNCH_START = LocalTime.of(12, 0);
+    private static final LocalTime LUNCH_END = LocalTime.of(13, 0);
+
     @Value(value = "${upload.path}")
     private String path;
     @Resource
@@ -749,12 +754,6 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                             task.setName(taskContentCell.getStringCellValue());
                         }
 
-//                    XSSFCell planHoursCell = row.getCell(9);//计划工时
-//                    if(planHoursCell!=null){
-//                        planHoursCell.setCellType(CellType.STRING);
-//                        task.setPlanHours(Double.parseDouble(planHoursCell.getStringCellValue()));
-//                    }
-
                         XSSFCell priorityCell = row.getCell(6);//优先级
                         if (priorityCell != null) {
                             priorityCell.setCellType(CellType.STRING);
@@ -815,6 +814,23 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                         task.setEndDate(endDate);
                     }
 
+                    // 每天的工作开始和结束时间
+                    LocalTime workStartTime = LocalTime.of(9, 0);
+                    LocalTime workEndTime = LocalTime.of(17, 30);
+                    double dailyWorkHours = 7.5;
+                    double totalHours = calculateTotalWorkHours(task.getStartDate(), task.getEndDate(), workStartTime, workEndTime);
+                    task.setPlanHours(totalHours);
+
+                    // 计算工作日数
+                    int adjustedWorkDays = (int) (totalHours / dailyWorkHours);
+
+                    // 检查余数并进行调整
+                    if (totalHours % dailyWorkHours >= 1) {
+                        adjustedWorkDays += 1; // 如果余数大于等于1,则加1
+                    }
+                    task.setPlannedDays(adjustedWorkDays);
+
+
                     XSSFCell executorCell = row.getCell(10);//执行人
                     if (executorCell != null) {
                         executorCell.setCellType(CellType.STRING);
@@ -861,16 +877,30 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                         time2 = endDate.toLocalTime();
                         taskDailyAllocate.setEndTime(time2);
                     }
-
                     if (date1==null||date2==null|| !date1.isEqual(date2)){
                         throw new Exception("第" + (rowIndex + 1) + "行,每日开始时间和每日截止时间日期不一致");
                     }else {
                         taskDailyAllocate.setAllocateDate(date1);
                     }
+                    LocalDate endDateLocal = task.getEndDate().toLocalDate();
+                    LocalDate startDateLocal = task.getStartDate().toLocalDate();
+                    if (date1.isBefore(startDateLocal)||date1.isAfter(endDateLocal)){
+                        throw new Exception("第" + (rowIndex + 1) + "行,每日分配日期不在计划的开始时间和截止时间范围内");
+                    }
 
-                    double hoursDifference = Duration.between(time1, time2).getSeconds() / 3600.0;
+                    if (time1.isAfter(time2)){
+                        throw new Exception("第" + (rowIndex + 1) + "行,每日分配日期的开始时间不能晚于截止时间");
+                    }
+                    double hoursDifference = calculateEffectiveHours(time1, time2);
                     taskDailyAllocate.setWorkHour(hoursDifference);
-                    taskDailyAllocate.setOverWorkHour(hoursDifference-7.5);
+
+                    LocalTime localZao = LocalTime.of(9, 0);
+                    LocalTime localWan = LocalTime.of(17, 30);
+                    // 计算时间差(保持正负)
+                    // 计算 time1 到 9:00 和 17:30 的时间差(小时)
+                    double zao = Math.max(0.0, calculateRoundedHourDifference(time1, localZao));
+                    double wan = Math.max(0.0, calculateRoundedHourDifference(localWan,time2 ));
+                    taskDailyAllocate.setOverWorkHour(zao + wan);
 
                     task.setCompanyId(companyId);
                     task.setCreaterId(userId);
@@ -879,6 +909,7 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                     task.setCreateDate(LocalDate.now());
                     task.setIndate(LocalDateTime.now());
                     task.setTaskStatus(TaskController.STATUS_FIRST_CHECK);
+                    task.setIsTaskPlan(1);
                     taskMapper.insert(task);
                     initTaskId = task.getId();
                     initProjectId=task.getProjectId();
@@ -908,10 +939,12 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                         taskExecutor.setExecutorId(user.getId());
                         taskExecutor.setExecutorName(user.getName());
                         taskExecutor.setExecutorColor(user.getColor());
-                        taskExecutor.setPlanHours(task.getPlanHours());
+                        taskExecutor.setPlanHours(hoursDifference);
+                        initworkHourExector=hoursDifference;
                         taskExecutor.setProjectId(task.getProjectId());
 
                         Integer count = taskMapper.getUserConflitTaskCount(user.getId(), task.getId(), task.getStartDate(), task.getEndDate());
+//                        List<Task> taskConflitList = taskMapper.getTaskConflitList(user.getId(), task.getId(), task.getStartDate(), task.getEndDate());
                         if (count > 0) {
                             throw new Exception("第" + (rowIndex + 1) + "行,执行人" + user.getName() + "在其他任务上有时间冲突");
                         }
@@ -1017,16 +1050,28 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                         time2 = endDate.toLocalTime();
                         taskDailyAllocate.setEndTime(time2);
                     }
-
                     if (date1==null||date2==null|| !date1.isEqual(date2)){
                         throw new Exception("第" + (rowIndex + 1) + "行,每日开始时间和每日截止时间日期不一致");
                     }else {
                         taskDailyAllocate.setAllocateDate(date1);
                     }
+                    LocalDate endDateLocal = initEndDate.toLocalDate();
+                    LocalDate startDateLocal = initStartDate.toLocalDate();
+                    if (date1.isBefore(startDateLocal)||date1.isAfter(endDateLocal)){
+                        throw new Exception("第" + (rowIndex + 1) + "行,每日分配日期不在计划的开始时间和截止时间范围内");
+                    }
 
-                    double hoursDifference = Duration.between(time1, time2).getSeconds() / 3600.0;
+                    if (time1.isAfter(time2)){
+                        throw new Exception("第" + (rowIndex + 1) + "行,每日分配日期的开始时间不能晚于截止时间");
+                    }
+                    double hoursDifference = calculateEffectiveHours(time1, time2);
                     taskDailyAllocate.setWorkHour(hoursDifference);
-                    taskDailyAllocate.setOverWorkHour(hoursDifference-7.5);
+                    LocalTime localZao = LocalTime.of(9, 0);
+                    LocalTime localWan = LocalTime.of(17, 30);
+                    double zao = Math.max(0.0, calculateRoundedHourDifference(time1, localZao));
+                    double wan = Math.max(0.0, calculateRoundedHourDifference(localWan,time2 ));
+                    taskDailyAllocate.setOverWorkHour(zao + wan);
+
 
                     //如果executorStr==initUserStr,taskExcutor执行人就不用保存
                     if(executorStr.equals(initUserStr)){
@@ -1039,16 +1084,17 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                         taskDailyAllocate.setUserId(first.get().getId());
                         taskDailyAllocateService.save(taskDailyAllocate);
                         initUserStr=executorStr;
-                        initExectorIds+=first.get().getId();
-                        initExectorColors+=first.get().getColor();
-                        initExectorNames+=first.get().getName();
+                        initExectorIds = getString(initExectorIds, first.get().getId());
+                        initExectorColors=getString(initExectorColors, first.get().getColor());
+                        initExectorNames=getString(initExectorNames, first.get().getName());
                         initworkHourExector+=hoursDifference;
                         taskMapper.update(null,new UpdateWrapper<Task>().set("executor_id",initExectorIds)
                                 .set("executor_color",initExectorColors).set("executor_name",initExectorNames)
                                 .eq("id",initTaskId));
                         taskExecutorService.update(null,new UpdateWrapper<TaskExecutor>().eq("task_id",initTaskId).eq("executor_id",first.get().getId()).set("plan_hours",initworkHourExector));
 
-                    }else {
+                    }
+                    else {
 
                         initworkHourExector=hoursDifference;
                         String[] strings = executorStr.split(",");
@@ -1087,9 +1133,9 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
                         taskDailyAllocate.setUserId(first.get().getId());
 
                         initUserStr=executorStr;
-                        initExectorIds+=first.get().getId();
-                        initExectorColors+=first.get().getColor();
-                        initExectorNames+=first.get().getName();
+                        initExectorIds = getString(initExectorIds, first.get().getId());
+                        initExectorColors=getString(initExectorColors, first.get().getColor());
+                        initExectorNames=getString(initExectorNames, first.get().getName());
 
                         taskExecutorService.save(taskExecutor);
                         taskDailyAllocateService.save(taskDailyAllocate);
@@ -1121,6 +1167,17 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
         return httpRespMsg;
     }
 
+    private static String getString(String initString, String newStr) {
+        if (StringUtils.isEmpty(initString)){
+            initString = newStr;
+        }else {
+            if (!initString.contains(newStr)) {
+                initString += "," + newStr;
+            }
+        }
+        return initString;
+    }
+
     @Override
     public HttpRespMsg delete(TaskGroup item) {
         HttpRespMsg msg = new HttpRespMsg();
@@ -1852,4 +1909,87 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements Ta
         }
         return list;
     }
+
+    //任务计算得到计划工时
+    //计算时间
+    public static double calculateTotalWorkHours(LocalDateTime start, LocalDateTime end, LocalTime workStart, LocalTime workEnd) {
+        double totalHours = 0.0;
+
+        // 如果开始时间在结束时间之后,返回 0
+        if (start.isAfter(end)) {
+            return totalHours;
+        }
+
+        // 获取开始日期和结束日期
+        LocalDateTime current = start;
+
+        while (current.toLocalDate().isBefore(end.toLocalDate()) || current.toLocalDate().isEqual(end.toLocalDate())) {
+            // 计算当天的工作开始和结束时间
+            LocalDateTime workStartDateTime = current.with(workStart);
+            LocalDateTime workEndDateTime = current.with(workEnd);
+
+            // 计算当天的有效工作时间
+            LocalDateTime actualStart = current.isBefore(workStartDateTime) ? workStartDateTime : current;
+            LocalDateTime actualEnd = end.isBefore(workEndDateTime) ? end : workEndDateTime;
+
+            // 检查是否跨越中午休息时间
+            LocalDateTime lunchStart = current.toLocalDate().atTime(12, 0);
+            LocalDateTime lunchEnd = current.toLocalDate().atTime(13, 0);
+
+            // 计算当天的工时
+            if (actualStart.isBefore(actualEnd)) {
+                // 计算工时
+                long hours = ChronoUnit.HOURS.between(actualStart, actualEnd);
+                long minutes = ChronoUnit.MINUTES.between(actualStart, actualEnd) % 60;
+                totalHours += hours + (minutes / 60.0);
+
+                // 检查是否跨越中午休息时间并减去1小时
+                if (!(actualEnd.isBefore(lunchStart) || actualStart.isAfter(lunchEnd))) {
+                    totalHours -= 1; // 减去1小时
+                }
+            }
+
+            // 移动到下一天
+            current = current.toLocalDate().plusDays(1).atStartOfDay();
+        }
+
+        return totalHours;
+    }
+    //每日分配计算得到工时
+    public static double calculateEffectiveHours(LocalTime time1, LocalTime time2) {
+        if (time1.isAfter(time2)) {
+            throw new IllegalArgumentException("time1 不能晚于 time2");
+        }
+
+        // 1. 如果时间范围完全不涉及午休时间,直接计算
+        if (time2.isBefore(LUNCH_START) || time1.isAfter(LUNCH_END)) {
+            return Duration.between(time1, time2).getSeconds() / 3600.0;
+        }
+
+        // 2. 如果时间范围完全包含午休时间,减去 1 小时
+        if (time1.isBefore(LUNCH_START) && time2.isAfter(LUNCH_END)) {
+            return (Duration.between(time1, time2).getSeconds() / 3600.0) - 1;
+        }
+
+        // 3. 处理部分重叠的情况
+        double effectiveHours = 0;
+
+        // 3.1 计算午休前的有效时间
+        if (time1.isBefore(LUNCH_START)) {
+            effectiveHours += Duration.between(time1, LUNCH_START).getSeconds() / 3600.0;
+        }
+
+        // 3.2 计算午休后的有效时间
+        if (time2.isAfter(LUNCH_END)) {
+            effectiveHours += Duration.between(LUNCH_END, time2).getSeconds() / 3600.0;
+        }
+
+        return effectiveHours;
+    }
+
+    // 辅助方法:计算两个 LocalTime 的时间差(保留一位小数)
+    private static double calculateRoundedHourDifference(LocalTime start, LocalTime end) {
+        double hours = Duration.between(start, end).toMinutes() / 60.0;
+        return Math.round(hours * 10) / 10.0;
+    }
 }

+ 8 - 0
fhKeeper/formulahousekeeper/management-platform-mld/src/main/resources/mapper/TaskMapper.xml

@@ -888,5 +888,13 @@
         </if>
         and task.start_date &lt;= #{endDate} and task.end_date &gt;= #{startDate}
     </select>
+    <select id="getTaskConflitList" resultType="com.management.platform.entity.Task">
+        SELECT task.* FROM task_executor LEFT JOIN task ON task.id = task_executor.`task_id`
+        WHERE task_executor.`executor_id` = #{userId} and task.task_status &lt;&gt; 2
+        <if test="taskId != null">
+            and task.id &lt;&gt; #{taskId}
+        </if>
+        and task.start_date &lt;= #{endDate} and task.end_date &gt;= #{startDate}
+    </select>
 
 </mapper>

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

@@ -32,9 +32,9 @@ public class FinanceMonthlyWorktimeController {
     }
 
     @RequestMapping("/getByMonth")
-    public HttpRespMsg getByMonth(Integer companyId, String ymonth, @RequestParam(required = false, defaultValue = "0" ) Integer reGenerate,HttpServletRequest request) {
+    public HttpRespMsg getByMonth(Integer companyId, String ymonth, @RequestParam(required = false, defaultValue = "0" ) Integer reGenerate,Integer isItAWorkOrder, HttpServletRequest request) {
         try {
-            return financeMonthlyWorktimeService.getByMonth(companyId, ymonth,reGenerate, request);
+            return financeMonthlyWorktimeService.getByMonth(companyId, ymonth,reGenerate,isItAWorkOrder ,request);
         } catch (Exception e) {
             e.printStackTrace();
             HttpRespMsg msg = new HttpRespMsg();
@@ -43,6 +43,11 @@ public class FinanceMonthlyWorktimeController {
         }
     }
 
+    @RequestMapping("/exportByMonth")
+    public HttpRespMsg exportByMonth(Integer companyId, String ymonth, @RequestParam(required = false, defaultValue = "0" ) Integer reGenerate,Integer isItAWorkOrder,HttpServletRequest request) throws Exception {
+       return financeMonthlyWorktimeService.exportByMonth(companyId, ymonth,reGenerate,isItAWorkOrder ,request);
+    }
+
     @RequestMapping("/setTimesheetDate")
     public HttpRespMsg setTimesheetDate(Integer id, String timesheetDate, HttpServletRequest request) {
         return financeMonthlyWorktimeService.setTimesheetDate(id, timesheetDate,request);

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

@@ -19,11 +19,13 @@ public interface FinanceMonthlyWorktimeService extends IService<FinanceMonthlyWo
 
     HttpRespMsg send(String fmwId, String timesheetDate, HttpServletRequest request);
 
-    HttpRespMsg getByMonth(Integer companyId, String ymonth, Integer reGenerate, HttpServletRequest request) throws Exception;
+    HttpRespMsg getByMonth(Integer companyId, String ymonth, Integer reGenerate, Integer isItAWorkOrder, HttpServletRequest request) throws Exception;
 
     HttpRespMsg setTimesheetDate(Integer id, String timesheetDate, HttpServletRequest request);
 
     HttpRespMsg changeWorktime(FmwDetail detail, HttpServletRequest request);
 
     HttpRespMsg setStatusFinal(Integer id, HttpServletRequest request);
+
+    HttpRespMsg exportByMonth(Integer companyId, String ymonth, Integer reGenerate, Integer isItAWorkOrder, HttpServletRequest request) throws Exception;
 }

+ 196 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/FinanceMonthlyWorktimeServiceImpl.java

@@ -11,7 +11,13 @@ import com.management.platform.service.FinanceMonthlyWorktimeService;
 import com.management.platform.service.FmwDetailService;
 import com.management.platform.task.DataCollectTask;
 import com.management.platform.util.HttpRespMsg;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.http.*;
 import org.springframework.stereotype.Service;
@@ -21,6 +27,8 @@ import org.springframework.web.client.RestTemplate;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
+import java.io.FileOutputStream;
+import java.text.DecimalFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -36,6 +44,7 @@ import java.util.stream.Collectors;
  * @since 2025-04-16
  */
 @Service
+@Slf4j
 public class FinanceMonthlyWorktimeServiceImpl extends ServiceImpl<FinanceMonthlyWorktimeMapper, FinanceMonthlyWorktime> implements FinanceMonthlyWorktimeService {
     public static final String TIME_TYPE_COMPOSE = "组装工时(车间)";
     public static final String TIME_TYPE_REPAIR = "维修工时(车间)";
@@ -75,6 +84,9 @@ public class FinanceMonthlyWorktimeServiceImpl extends ServiceImpl<FinanceMonthl
     @Resource
     private ErpOrderInfoMapper erpOrderInfoMapper;
 
+    @Value(value = "${upload.path}")
+    private String uploadPath;
+
     @Override
     public HttpRespMsg send(String fmwId, String timesheetDate, HttpServletRequest request) {
         HttpRespMsg httpRespMsg = new HttpRespMsg();
@@ -145,7 +157,7 @@ public class FinanceMonthlyWorktimeServiceImpl extends ServiceImpl<FinanceMonthl
 
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public HttpRespMsg getByMonth(Integer companyId, String ymonth, Integer reGenerate, HttpServletRequest request) throws Exception {
+    public HttpRespMsg getByMonth(Integer companyId, String ymonth, Integer reGenerate, Integer isItAWorkOrder, HttpServletRequest request) throws Exception {
         //获取该月份的数据,如果没有自动生成
         HttpRespMsg httpRespMsg = new HttpRespMsg();
         String token = request.getHeader("token");
@@ -333,7 +345,13 @@ public class FinanceMonthlyWorktimeServiceImpl extends ServiceImpl<FinanceMonthl
             }
         }
         //查询数据
-        List<FmwDetail> details = fmwDetailMapper.selectList(new QueryWrapper<FmwDetail>().eq("fmw_id", financeMonthlyWorktime.getId()));
+        QueryWrapper<FmwDetail> detailQueryWrapper = new QueryWrapper<FmwDetail>().eq("fmw_id", financeMonthlyWorktime.getId());
+        if (isItAWorkOrder!=null&&isItAWorkOrder==2){
+            detailQueryWrapper.isNotNull("extra_field4");//有工单号的数据
+        } else if (isItAWorkOrder != null && isItAWorkOrder == 3) {
+            detailQueryWrapper.isNull("extra_field4");//非工单则工单号为空的数据
+        }
+        List<FmwDetail> details = fmwDetailMapper.selectList(detailQueryWrapper);
         financeMonthlyWorktime.setDetailList(details);
         httpRespMsg.data = financeMonthlyWorktime;
         return httpRespMsg;
@@ -385,4 +403,180 @@ public class FinanceMonthlyWorktimeServiceImpl extends ServiceImpl<FinanceMonthl
         financeMonthlyWorktimeMapper.updateById(financeMonthlyWorktime);
         return new HttpRespMsg();
     }
+
+    @Override
+    @Transactional
+    public HttpRespMsg exportByMonth(Integer companyId, String ymonth, Integer reGenerate, Integer isItAWorkOrder, HttpServletRequest request) throws Exception {
+        //获取该月份的数据,如果没有自动生成
+        HttpRespMsg httpRespMsg = new HttpRespMsg();
+        HttpRespMsg respMsg = getByMonth(companyId, ymonth, reGenerate, isItAWorkOrder, request);
+        FinanceMonthlyWorktime financeMonthlyWorktime = (FinanceMonthlyWorktime) respMsg.getData();
+        List<FmwDetail> detailList = financeMonthlyWorktime.getDetailList();
+
+        // 1. 创建工作簿和工作表
+        Workbook workbook = new XSSFWorkbook();
+        Sheet sheet = workbook.createSheet("工程工时表");
+
+        // 2. 创建样式
+        CellStyle headerStyle = createHeaderStyle(workbook);
+        CellStyle dataStyle = createDataStyle(workbook);
+
+        // 3. 创建多级表头
+        // 第一行表头(主标题)
+        Row headerRow1 = sheet.createRow(0);
+        String[] mainHeaders = {
+                "项目号", "工单号", "行号", "车间组装/维修工时(h)","","","",
+                "项目工时(h)", "协助工时(h)", "公摊工时(h)", "工时合计(h)"
+        };
+
+        // 第二行表头(子标题)
+        Row headerRow2 = sheet.createRow(1);
+        String[] subHeaders = {
+                "", "", "", "组装工时(h)", "维修工时(h)", "调试工时(h)", "等料工时(h)",
+                "", "", "", ""
+        };
+
+        // 设置主标题
+        for (int i = 0; i < mainHeaders.length; i++) {
+            Cell cell = headerRow1.createCell(i);
+            cell.setCellValue(mainHeaders[i]);
+            cell.setCellStyle(headerStyle);
+            // 合并"车间组装/维修工时(h)"下方的4个单元格
+            if (i == 3) {
+                sheet.addMergedRegion(new CellRangeAddress(0, 0, 3, 6));
+            }
+        }
+
+        // 设置子标题
+        for (int i = 0; i < subHeaders.length; i++) {
+            Cell cell = headerRow2.createCell(i);
+            cell.setCellValue(subHeaders[i]);
+            cell.setCellStyle(headerStyle);
+        }
+
+        for (int i = 0; i < 3; i++) {
+            sheet.addMergedRegion(new CellRangeAddress(0, 1, i, i));
+        }
+
+        for (int i = 7; i < 11; i++) {
+            sheet.addMergedRegion(new CellRangeAddress(0, 1, i, i));
+        }
+
+        // 5. 自动调整列宽
+        for (int i = 0; i < mainHeaders.length ; i++) {
+            sheet.setColumnWidth(i, 18 * 256); // 0表示第一列
+        }
+        if (!detailList.isEmpty()) {
+            for (int i = 0; i < detailList.size(); i++) {
+                Row row = sheet.createRow(i + 2);
+                Cell cell1 = row.createCell(0);
+                FmwDetail fmwDetail = detailList.get(i);
+                cell1.setCellValue(StringUtils.isEmpty(fmwDetail.getProjectCode())?"":fmwDetail.getProjectCode());
+                cell1.setCellStyle(dataStyle);
+
+                // 2. 工单号
+                Cell cell2 = row.createCell(1);
+                cell2.setCellValue(StringUtils.isEmpty(fmwDetail.getExtraField4()) ? "" : fmwDetail.getExtraField4());
+                cell2.setCellStyle(dataStyle);
+
+                // 3. 行号
+                Cell cell3 = row.createCell(2);
+                cell3.setCellValue(StringUtils.isEmpty(fmwDetail.getExtraField5()) ? "" : fmwDetail.getExtraField5());
+                cell3.setCellStyle(dataStyle);
+
+                // 4. 车间组装/维修工时(子分类)
+                // 4.1 组装工时
+                Cell cell4 = row.createCell(3);
+                cell4.setCellValue(fmwDetail.getComposeTime() == null ? 0 : fmwDetail.getComposeTime());
+                cell4.setCellStyle(dataStyle);
+
+                // 4.2 维修工时
+                Cell cell5 = row.createCell(4);
+                cell5.setCellValue(fmwDetail.getRepairTime() == null ? 0 : fmwDetail.getRepairTime());
+                cell5.setCellStyle(dataStyle);
+
+                // 4.3 调试工时
+                Cell cell6 = row.createCell(5);
+                cell6.setCellValue(fmwDetail.getDebugTime() == null ? 0 : fmwDetail.getDebugTime());
+                cell6.setCellStyle(dataStyle);
+
+                // 4.4 等料工时
+                Cell cell7 = row.createCell(6);
+                cell7.setCellValue(fmwDetail.getWaitingTime() == null ? 0 : fmwDetail.getWaitingTime());
+                cell7.setCellStyle(dataStyle);
+
+                // 5. 项目工时
+                Cell cell8 = row.createCell(7);
+                double v1 = fmwDetail.getCleanTime() == null ? 0 : fmwDetail.getCleanTime();
+                double v2 = fmwDetail.getBustripTime() == null ? 0 : fmwDetail.getBustripTime();
+                double sum = v1 + v2;
+                DecimalFormat df = new DecimalFormat("#.#");
+                double roundedSum = Double.parseDouble(df.format(sum));
+                cell8.setCellValue(roundedSum);
+                cell8.setCellStyle(dataStyle);
+
+                // 6. 协助工时
+                Cell cell9 = row.createCell(8);
+                cell9.setCellValue(fmwDetail.getAssistTime() == null ? 0 : fmwDetail.getAssistTime());
+                cell9.setCellStyle(dataStyle);
+
+                // 7. 公摊工时
+                Cell cell10 = row.createCell(9);
+                cell10.setCellValue(fmwDetail.getPublicTime() == null ? 0 : fmwDetail.getPublicTime());
+                cell10.setCellStyle(dataStyle);
+
+                // 8. 工时合计(计算字段)
+                double totalHours =
+                        (fmwDetail.getRepairTime() != null ? fmwDetail.getRepairTime() : 0)
+                        + (fmwDetail.getDebugTime() != null ? fmwDetail.getDebugTime() : 0)
+                        + (fmwDetail.getWaitingTime() != null ? fmwDetail.getWaitingTime() : 0)
+                        + (fmwDetail.getAssistTime() != null ? fmwDetail.getAssistTime() : 0)
+                        + (fmwDetail.getPublicTime() != null ? fmwDetail.getPublicTime() : 0)
+                        + (fmwDetail.getComposeTime() != null ? fmwDetail.getComposeTime() : 0);
+
+                Cell cell11 = row.createCell(10);
+                cell11.setCellValue(totalHours);
+                cell11.setCellStyle(dataStyle);
+
+            }
+        }
+
+        long l = System.currentTimeMillis();
+        // 6. 写入文件
+        try (FileOutputStream outputStream = new FileOutputStream(uploadPath+"月度财务工时表"+l+".xlsx")) {
+            workbook.write(outputStream);
+        }
+        workbook.close();
+        log.info("Excel文件已生成!");
+        httpRespMsg.data="/upload/"+"月度财务工时表"+l+".xlsx";
+        return httpRespMsg;
+    }
+
+    private static CellStyle createHeaderStyle(Workbook workbook) {
+        CellStyle style = workbook.createCellStyle();
+        style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        Font font = workbook.createFont();
+        font.setBold(true);
+        style.setFont(font);
+        style.setAlignment(HorizontalAlignment.CENTER);
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        return style;
+    }
+
+    private static CellStyle createDataStyle(Workbook workbook) {
+        CellStyle style = workbook.createCellStyle();
+        style.setBorderBottom(BorderStyle.THIN);
+        style.setBorderTop(BorderStyle.THIN);
+        style.setBorderLeft(BorderStyle.THIN);
+        style.setBorderRight(BorderStyle.THIN);
+        style.setAlignment(HorizontalAlignment.CENTER);
+        DataFormat format = workbook.createDataFormat();
+        style.setDataFormat(format.getFormat("0.0"));
+        return style;
+    }
 }