From 5618a89bbf8c57aff6768a9239ca35bd29aab7cf Mon Sep 17 00:00:00 2001 From: 王彪总 Date: Thu, 2 Jul 2026 16:24:55 +0800 Subject: [PATCH] feat(garden): 新增任务距离排序和智能排序功能 --- urbanops-framework/urbanops-spring-boot-starter-protection/src/main/java/com/zteits/urbanops/framework/signature/core/aop/ApiSignatureAspect.java | 4 ++-- urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/AppHomepageSummaryController.java | 18 +++++++++++++++++- urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskDistanceSortReqVO.java | 31 +++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskSmartSortReqVO.java | 31 +++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortRespVO.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortResultVO.java | 32 ++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TeamProgressVO.java | 24 ++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/UserTaskEfficiencyDO.java | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/UserTaskEfficiencyMapper.java | 43 +++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/homepage/HomepageSummaryMapper.java | 15 ++++++++++++++- urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/job/TaskEfficiencyCalculateJob.java | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryService.java | 21 ++++++++++++++++++++- urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java | 375 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GpsDistanceUtil.java | 32 ++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/resources/mapper/UserTaskEfficiencyMapper.xml | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java | 4 ++-- urbanops-server/src/main/resources/application.yaml | 2 +- 18 files changed, 1089 insertions(+), 14 deletions(-) create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskDistanceSortReqVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskSmartSortReqVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortRespVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortResultVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TeamProgressVO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/UserTaskEfficiencyDO.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/UserTaskEfficiencyMapper.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/job/TaskEfficiencyCalculateJob.java create mode 100644 urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GpsDistanceUtil.java create mode 100644 urbanops-module-garden/src/main/resources/mapper/UserTaskEfficiencyMapper.xml diff --git a/urbanops-framework/urbanops-spring-boot-starter-protection/src/main/java/com/zteits/urbanops/framework/signature/core/aop/ApiSignatureAspect.java b/urbanops-framework/urbanops-spring-boot-starter-protection/src/main/java/com/zteits/urbanops/framework/signature/core/aop/ApiSignatureAspect.java index a1c30ee..d5deed2 100644 --- a/urbanops-framework/urbanops-spring-boot-starter-protection/src/main/java/com/zteits/urbanops/framework/signature/core/aop/ApiSignatureAspect.java +++ b/urbanops-framework/urbanops-spring-boot-starter-protection/src/main/java/com/zteits/urbanops/framework/signature/core/aop/ApiSignatureAspect.java @@ -37,7 +37,7 @@ public class ApiSignatureAspect { private final ApiSignatureRedisDAO signatureRedisDAO; - @Before("@annotation(signature)") + @Before("@within(signature)") public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { // 1. 验证通过,直接结束 if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { @@ -171,4 +171,4 @@ public class ApiSignatureAspect { return sortedMap; } -} \ No newline at end of file +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/AppHomepageSummaryController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/AppHomepageSummaryController.java index dc2fcf6..61bf7be 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/AppHomepageSummaryController.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/AppHomepageSummaryController.java @@ -2,7 +2,10 @@ package com.zteits.urbanops.module.garden.controller.app.homepage; import com.zteits.urbanops.framework.common.pojo.CommonResult; import com.zteits.urbanops.framework.common.pojo.PageResult; -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryPageReqVO; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryReqVO; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo; import com.zteits.urbanops.module.garden.service.homepage.HomepageSummaryService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -12,6 +15,7 @@ import jakarta.validation.Valid; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; @@ -52,4 +56,16 @@ public class AppHomepageSummaryController { public CommonResult iWorkOrderSummary(@Valid @RequestBody AppWorkOrderSummaryReqVo req) { return success(homepageSummaryService.iWorkOrderSummary(req)); } + + @PostMapping("/taskDetailsWithDistance") + @Operation(summary = "任务待办(按距离排序 + 班组进度)") + public CommonResult queryTaskDetailsWithDistance(@Valid @RequestBody AppTaskDistanceSortReqVO req) { + return success(homepageSummaryService.queryTaskDetailsWithDistance(req)); + } + + @PostMapping("/taskDetailsWithSmartSort") + @Operation(summary = "任务待办(智能排序:距离 + 个人效率加权)") + public CommonResult queryTaskDetailsWithSmartSort(@Valid @RequestBody AppTaskSmartSortReqVO req) { + return success(homepageSummaryService.queryTaskDetailsWithSmartSort(req)); + } } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskDistanceSortReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskDistanceSortReqVO.java new file mode 100644 index 0000000..4ff790a --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskDistanceSortReqVO.java @@ -0,0 +1,31 @@ +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; + +import com.zteits.urbanops.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * app - 任务距离排序 请求 VO + * + * @author + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AppTaskDistanceSortReqVO extends PageParam { + + @Schema(description = "查询类型:1-待办,2-已办", example = "1") + @NotNull + public Integer queryType; + + @Schema(description = "当前经度", example = "116.397428") + @NotNull(message = "当前经度不能为空") + private BigDecimal currentLon; + + @Schema(description = "当前纬度", example = "39.909204") + @NotNull(message = "当前纬度不能为空") + private BigDecimal currentLat; +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskSmartSortReqVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskSmartSortReqVO.java new file mode 100644 index 0000000..1c75f3f --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskSmartSortReqVO.java @@ -0,0 +1,31 @@ +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; + +import com.zteits.urbanops.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * app - 任务智能排序 请求 VO + * + * @author + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AppTaskSmartSortReqVO extends PageParam { + + @Schema(description = "查询类型:1-待办,2-已办", example = "1") + @NotNull + private Integer queryType; + + @Schema(description = "当前经度", example = "116.397428") + @NotNull(message = "当前经度不能为空") + private BigDecimal currentLon; + + @Schema(description = "当前纬度", example = "39.909204") + @NotNull(message = "当前纬度不能为空") + private BigDecimal currentLat; +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortRespVO.java new file mode 100644 index 0000000..85777f8 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortRespVO.java @@ -0,0 +1,58 @@ +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * app - 任务距离排序 单个任务响应 VO + * + * @author + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TaskDistanceSortRespVO extends TaskDetailsRspVo { + + /** 任务类型:inspection / maintain / workorder */ + private String taskType; + + /** 任务点经度 */ + private BigDecimal taskLon; + + /** 任务点纬度 */ + private BigDecimal taskLat; + + /** 从上一点到本任务的距离(米),无 GPS 则为 null */ + private Integer distance; + + /** 从起点累计距离(米) */ + private Integer cumulativeDistance; + + /** 预估步行时间(分钟) */ + private Integer estimatedWalkingMinutes; + + /** 预估三轮车时间(分钟) */ + private Integer estimatedTricycleMinutes; + + /** 排序序号:1, 2, 3... */ + private Integer sortOrder; + + /** 任务子类型:plan_type_id 或 order_type,用于效率矩阵匹配 */ + private String taskSubType; + + /** 距离得分 [0, 1] */ + private Double distanceScore; + + /** 效率得分(已截断) */ + private Double efficiencyScore; + + /** 综合得分 */ + private Double compositeScore; + + /** 用户历史平均完成时长(分钟) */ + private BigDecimal userAvgMinutes; + + /** 班组平均完成时长(分钟) */ + private BigDecimal teamAvgMinutes; +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortResultVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortResultVO.java new file mode 100644 index 0000000..26831d2 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortResultVO.java @@ -0,0 +1,32 @@ +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; + +import lombok.Data; + +import java.util.List; + +/** + * app - 任务距离排序 顶层返回 VO + * + * @author + */ +@Data +public class TaskDistanceSortResultVO { + + /** 排序后的任务列表 */ + private List sortedTasks; + + /** 班组内进度统计 */ + private TeamProgressVO teamProgress; + + /** 预估总距离(米) */ + private Integer estimatedTotalDistance; + + /** 预估总步行时间(分钟) */ + private Integer estimatedTotalWalkingMinutes; + + /** 预估总三轮车时间(分钟) */ + private Integer estimatedTotalTricycleMinutes; + + /** 总待办任务数 */ + private Integer totalPendingTasks; +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TeamProgressVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TeamProgressVO.java new file mode 100644 index 0000000..44b31f5 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TeamProgressVO.java @@ -0,0 +1,24 @@ +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; + +import lombok.Data; + +/** + * app - 班组内进度统计 VO + * + * @author + */ +@Data +public class TeamProgressVO { + + /** 当前用户今日已完成数 */ + private Integer userCompletedCount; + + /** 班组均值 */ + private Double teamAverage; + + /** 班组内最高 */ + private Integer teamMax; + + /** 班组总人数 */ + private Integer teamMemberCount; +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/UserTaskEfficiencyDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/UserTaskEfficiencyDO.java new file mode 100644 index 0000000..4800a99 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/UserTaskEfficiencyDO.java @@ -0,0 +1,58 @@ +package com.zteits.urbanops.module.garden.dal.dataobject; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.math.BigDecimal; + +/** + * 用户任务效率预计算 DO + * + * @author + */ +@TableName("garden_user_task_efficiency") +@KeySequence("garden_user_task_efficiency_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserTaskEfficiencyDO extends BaseDO { + + @TableId + private Long id; + + /** 用户ID */ + private Long userId; + + /** 部门ID(班组) */ + private Long deptId; + + /** 任务大类:inspection / maintain / workorder */ + private String taskCategory; + + /** 任务类型(细粒度):plan_type_id 或 order_type,ALL 表示大类汇总 */ + private String taskType; + + /** 6个自然月内完成任务数 */ + private Integer completedCount; + + /** 总耗时(分钟) */ + private Long totalMinutes; + + /** 个人平均完成时长(分钟) */ + private BigDecimal avgMinutes; + + /** 班组平均完成时长(分钟) */ + private BigDecimal teamAvgMinutes; + + /** 效率比 = teamAvg / userAvg */ + private BigDecimal efficiencyRatio; + + /** 统计周期 yyyy-MM */ + private String statPeriod; +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/UserTaskEfficiencyMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/UserTaskEfficiencyMapper.java new file mode 100644 index 0000000..6c3f04d --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/UserTaskEfficiencyMapper.java @@ -0,0 +1,43 @@ +package com.zteits.urbanops.module.garden.dal.mysql; + +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; +import com.zteits.urbanops.module.garden.dal.dataobject.UserTaskEfficiencyDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +/** + * 用户任务效率 Mapper + * + * @author + */ +@Mapper +public interface UserTaskEfficiencyMapper extends BaseMapperX { + + /** + * 根据用户和统计周期查询效率数据 + */ + List selectByUserIdAndPeriod(@Param("userId") Long userId, + @Param("statPeriod") String statPeriod); + + /** + * 查询最近一条有数据的统计周期 + */ + String selectLatestPeriod(@Param("userId") Long userId); + + /** + * 删除指定统计周期的数据(幂等重跑) + */ + int deleteByPeriod(@Param("statPeriod") String statPeriod); + + /** 巡查计划效率统计 */ + List> statInspectionEfficiency(@Param("beginTime") String beginTime); + + /** 养护计划效率统计 */ + List> statMaintainEfficiency(@Param("beginTime") String beginTime); + + /** 工单效率统计 */ + List> statWorkOrderEfficiency(@Param("beginTime") String beginTime); +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/homepage/HomepageSummaryMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/homepage/HomepageSummaryMapper.java index 40d10be..4e44181 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/homepage/HomepageSummaryMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/homepage/HomepageSummaryMapper.java @@ -6,10 +6,10 @@ import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.CommitTask import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.CommitWorkOrderRespVo; import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.CommitWorkOrderSourceRespVo; import com.zteits.urbanops.module.garden.controller.admin.homepage.vo.WorkOrderTrendRespVo; -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppWorkOrderSummaryVo; import com.zteits.urbanops.module.garden.controller.app.homepage.vo.CommonTaskStatusVo; import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo; import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDistanceSortRespVO; import com.zteits.urbanops.module.garden.dal.dataobject.departmentcost.DepartmentCostDO; import com.zteits.urbanops.module.garden.dal.dataobject.taskstatistics.TaskStatisticsDO; import org.apache.ibatis.annotations.Mapper; @@ -216,4 +216,17 @@ public interface HomepageSummaryMapper extends BaseMapperX { * @Return */ List countWorkOrder(@Param("queryType") String queryType,@Param("userId") Long userId, @Param("beginTime") String beginTime, @Param("endTime") String endTime); + + /** + * 查询今日待办任务(含 GPS 坐标),三表 UNION + */ + List queryPendingTaskDetailsWithGps(@Param("roleIds") List roleIds, + @Param("userId") Long userId, + @Param("deptId") Long deptId, + @Param("currentDate") LocalDateTime currentDate); + + /** + * 统计今日同班组(同 dept_id)成员完成任务数 + */ + List> countTeamCompletedToday(@Param("deptId") Long deptId); } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/job/TaskEfficiencyCalculateJob.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/job/TaskEfficiencyCalculateJob.java new file mode 100644 index 0000000..e8364eb --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/job/TaskEfficiencyCalculateJob.java @@ -0,0 +1,173 @@ +package com.zteits.urbanops.module.garden.job; + +import com.zteits.urbanops.module.garden.dal.dataobject.UserTaskEfficiencyDO; +import com.zteits.urbanops.module.garden.dal.mysql.UserTaskEfficiencyMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * 用户任务效率预计算定时任务 + * 每天凌晨 2:00 执行,统计最近 6 个自然月的用户任务完成效率 + * + * @author + */ +@Slf4j +@Component +public class TaskEfficiencyCalculateJob { + + @Resource + private UserTaskEfficiencyMapper userTaskEfficiencyMapper; + + public void execute() { + String statPeriod = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")); + log.info("TaskEfficiencyCalculateJob start, statPeriod={}", statPeriod); + try { + // 1. 删除本月旧数据 + userTaskEfficiencyMapper.deleteByPeriod(statPeriod); + + // 2. 6个月窗口 + String beginTime = LocalDate.now().minusMonths(6).withDayOfMonth(1) + " 00:00:00"; + + // 3. 三类任务分别统计 + List all = new ArrayList<>(); + all.addAll(mapRows(userTaskEfficiencyMapper.statInspectionEfficiency(beginTime), statPeriod)); + all.addAll(mapRows(userTaskEfficiencyMapper.statMaintainEfficiency(beginTime), statPeriod)); + all.addAll(mapRows(userTaskEfficiencyMapper.statWorkOrderEfficiency(beginTime), statPeriod)); + + if (all.isEmpty()) { + log.info("TaskEfficiencyCalculateJob done, no data"); + return; + } + + // 4. 计算班组均值 + calcTeamAverage(all); + + // 5. 计算效率比 + for (UserTaskEfficiencyDO e : all) { + e.setEfficiencyRatio(calcRatio(e.getAvgMinutes(), e.getTeamAvgMinutes())); + } + + // 6. 生成大类汇总(ALL) + all.addAll(buildCategorySummary(all, statPeriod)); + + // 7. 批量写入 + LocalDateTime now = LocalDateTime.now(); + for (UserTaskEfficiencyDO e : all) { + e.setCreator("job"); + e.setUpdater("job"); + e.setCreateTime(now); + e.setUpdateTime(now); + userTaskEfficiencyMapper.insert(e); + } + + log.info("TaskEfficiencyCalculateJob done, total={}", all.size()); + } catch (Exception e) { + log.error("TaskEfficiencyCalculateJob error", e); + } + } + + private List mapRows(List> rows, String statPeriod) { + List result = new ArrayList<>(); + if (rows == null || rows.isEmpty()) return result; + for (Map row : rows) { + UserTaskEfficiencyDO e = new UserTaskEfficiencyDO(); + e.setUserId(toLong(row.get("user_id"))); + e.setDeptId(toLong(row.get("dept_id"))); + e.setTaskCategory((String) row.get("task_category")); + e.setTaskType(String.valueOf(row.get("task_type"))); + e.setCompletedCount(toInt(row.get("cnt"))); + e.setTotalMinutes(toLong(row.get("total_min"))); + e.setAvgMinutes(toDecimal(row.get("avg_min"))); + e.setStatPeriod(statPeriod); + result.add(e); + } + return result; + } + + private void calcTeamAverage(List all) { + Map> groups = new LinkedHashMap<>(); + for (UserTaskEfficiencyDO e : all) { + String key = e.getDeptId() + "_" + e.getTaskCategory() + "_" + e.getTaskType(); + groups.computeIfAbsent(key, k -> new ArrayList<>()).add(e); + } + for (List group : groups.values()) { + long totalMin = 0; + int totalCnt = 0; + for (UserTaskEfficiencyDO e : group) { + totalMin += e.getTotalMinutes() != null ? e.getTotalMinutes() : 0; + totalCnt += e.getCompletedCount() != null ? e.getCompletedCount() : 0; + } + BigDecimal teamAvg = totalCnt > 0 + ? BigDecimal.valueOf(totalMin).divide(BigDecimal.valueOf(totalCnt), 2, RoundingMode.HALF_UP) + : BigDecimal.ZERO; + for (UserTaskEfficiencyDO e : group) { + e.setTeamAvgMinutes(teamAvg); + } + } + } + + private BigDecimal calcRatio(BigDecimal userAvg, BigDecimal teamAvg) { + if (userAvg == null || teamAvg == null + || userAvg.compareTo(BigDecimal.ZERO) <= 0 + || teamAvg.compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ONE; + } + BigDecimal ratio = teamAvg.divide(userAvg, 4, RoundingMode.HALF_UP); + if (ratio.compareTo(new BigDecimal("3.0")) > 0) return new BigDecimal("3.0"); + if (ratio.compareTo(new BigDecimal("0.3")) < 0) return new BigDecimal("0.3"); + return ratio; + } + + private List buildCategorySummary(List all, String statPeriod) { + Map> catMap = new LinkedHashMap<>(); + for (UserTaskEfficiencyDO e : all) { + String key = e.getUserId() + "_" + e.getDeptId() + "_" + e.getTaskCategory(); + catMap.computeIfAbsent(key, k -> new ArrayList<>()).add(e); + } + List summaries = new ArrayList<>(); + for (List group : catMap.values()) { + UserTaskEfficiencyDO s = new UserTaskEfficiencyDO(); + s.setUserId(group.get(0).getUserId()); + s.setDeptId(group.get(0).getDeptId()); + s.setTaskCategory(group.get(0).getTaskCategory()); + s.setTaskType("ALL"); + s.setStatPeriod(statPeriod); + long totalMin = 0; + int totalCnt = 0; + for (UserTaskEfficiencyDO e : group) { + totalMin += e.getTotalMinutes() != null ? e.getTotalMinutes() : 0; + totalCnt += e.getCompletedCount() != null ? e.getCompletedCount() : 0; + } + s.setCompletedCount(totalCnt); + s.setTotalMinutes(totalMin); + s.setAvgMinutes(totalCnt > 0 + ? BigDecimal.valueOf(totalMin).divide(BigDecimal.valueOf(totalCnt), 2, RoundingMode.HALF_UP) + : BigDecimal.ZERO); + s.setTeamAvgMinutes(group.get(0).getTeamAvgMinutes()); + s.setEfficiencyRatio(calcRatio(s.getAvgMinutes(), s.getTeamAvgMinutes())); + summaries.add(s); + } + return summaries; + } + + private Long toLong(Object o) { + if (o == null) return 0L; + return o instanceof Number ? ((Number) o).longValue() : Long.parseLong(o.toString()); + } + private Integer toInt(Object o) { + if (o == null) return 0; + return o instanceof Number ? ((Number) o).intValue() : Integer.parseInt(o.toString()); + } + private BigDecimal toDecimal(Object o) { + if (o == null) return BigDecimal.ZERO; + return o instanceof BigDecimal ? (BigDecimal) o : new BigDecimal(o.toString()); + } +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryService.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryService.java index c535741..9312433 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryService.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryService.java @@ -1,9 +1,16 @@ package com.zteits.urbanops.module.garden.service.homepage; import com.zteits.urbanops.framework.common.pojo.PageResult; -import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryPageReqVO; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppHomePageSummaryReqVO; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppTaskDistanceSortReqVO; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppTaskSmartSortReqVO; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo; +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDistanceSortResultVO; import java.util.List; +import java.util.Map; /** * @Classname HomepageSummaryService @@ -48,4 +55,16 @@ public interface HomepageSummaryService { AppWorkOrderSummaryRspVo iWorkOrderSummary(AppWorkOrderSummaryReqVo req); + /** + * 任务待办(按距离排序 + 班组进度) + * + * @param req 请求参数(当前 GPS 坐标) + * @return 排序后的任务列表 + 班组进度统计 + */ + TaskDistanceSortResultVO queryTaskDetailsWithDistance(AppTaskDistanceSortReqVO req); + + /** + * 任务待办(智能排序:距离 + 个人效率加权) + */ + TaskDistanceSortResultVO queryTaskDetailsWithSmartSort(AppTaskSmartSortReqVO req); } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java index 4b8daff..a86fdba 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java @@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.esotericsoftware.minlog.Log; import com.zteits.urbanops.framework.common.pojo.PageResult; +import com.zteits.urbanops.framework.common.util.date.DateUtils; import com.zteits.urbanops.framework.common.util.date.LocalDateTimeUtils; +import com.zteits.urbanops.framework.mybatis.core.query.QueryWrapperX; import com.zteits.urbanops.framework.mybatis.core.util.MyBatisUtils; import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*; @@ -15,7 +17,7 @@ import com.zteits.urbanops.module.garden.dal.mysql.taskstatistics.TaskStatistics import com.zteits.urbanops.module.system.api.dept.DeptApi; import com.zteits.urbanops.module.system.api.permission.RoleApi; import com.zteits.urbanops.module.system.api.user.AdminUserApi; -import com.zteits.urbanops.module.system.enums.common.CommonConstants; +import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -26,12 +28,11 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; -import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - /** * @Classname HomepageSummaryServiceImpl * @Description 首页统计实现 @@ -57,9 +58,15 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ @Resource private DeptApi deptApi; + @Resource + private DictDataApi dictDataApi; + + @Resource + private UserTaskEfficiencyMapper userTaskEfficiencyMapper; + @Override public List taskCompletionSummary(AppHomePageSummaryReqVO req) { - Long userId = getLoginUserId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); //巡查任务和养护任务已完成,未完成数量统计 //返回数据初始化 List days = getDays(req.getBeginTime(), req.getEndTime()); @@ -105,7 +112,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ @Override public PageResult queryTaskDetails(AppHomePageSummaryPageReqVO req) { - Long userId = getLoginUserId(); + Long userId = SecurityFrameworkUtils.getLoginUserId(); IPage mpPage = MyBatisUtils.buildPage(req); if (req.getQueryType() == 2) { //已完成 @@ -113,7 +120,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ } else { //待办 Long deptId = SecurityFrameworkUtils.getDeptId(); - List roleIds = roleApi.getRoleIdsByUserId(getLoginUserId()); + List roleIds = roleApi.getRoleIdsByUserId(SecurityFrameworkUtils.getLoginUserId()); mpPage = homepageSummaryMapper.queryPendingTaskDetails(mpPage, roleIds, userId, deptId, LocalDateTime.now()); } return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); @@ -401,4 +408,360 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ return days; } + + @Override + public TaskDistanceSortResultVO queryTaskDetailsWithDistance(AppTaskDistanceSortReqVO req) { + // 1. 获取当前登录用户信息 + Long userId = SecurityFrameworkUtils.getLoginUserId(); + Long deptId = SecurityFrameworkUtils.getDeptId(); + List roleIds = roleApi.getRoleIdsByUserId(userId); + + // 2. 根据 queryType 分流 + List sortedTasks; + if (req.getQueryType() != null && req.getQueryType() == 2) { + // ========== 已办:查询已完成任务(无 GPS 距离排序) ========== + sortedTasks = queryCompletedTasksWithoutGps(userId); + } else { + // ========== 待办:查询待办任务 + GPS 贪心最近邻排序 ========== + sortedTasks = queryPendingTasksWithGpsSort(req, userId, deptId, roleIds); + } + + // 3. 查询班组进度 + TeamProgressVO teamProgress = buildTeamProgress(userId, deptId); + + // 4. 组装返回结果 + TaskDistanceSortResultVO result = new TaskDistanceSortResultVO(); + result.setSortedTasks(sortedTasks); + result.setTeamProgress(teamProgress); + result.setTotalPendingTasks(sortedTasks.size()); + int totalDist = 0; + int totalWalkMin = 0; + int totalTriMin = 0; + for (TaskDistanceSortRespVO task : sortedTasks) { + if (task.getDistance() != null) { + totalDist += task.getDistance(); + totalWalkMin += (task.getEstimatedWalkingMinutes() != null ? task.getEstimatedWalkingMinutes() : 0); + totalTriMin += (task.getEstimatedTricycleMinutes() != null ? task.getEstimatedTricycleMinutes() : 0); + } + } + result.setEstimatedTotalDistance(totalDist); + result.setEstimatedTotalWalkingMinutes(totalWalkMin); + result.setEstimatedTotalTricycleMinutes(totalTriMin); + + return result; + } + + /** + * 已办任务查询(无 GPS 排序) + */ + private List queryCompletedTasksWithoutGps(Long userId) { + IPage page = MyBatisUtils.buildPage(new AppHomePageSummaryPageReqVO()); + // 设置大分页以获取全部已办任务 + page.setSize(Integer.MAX_VALUE); + IPage mpPage = homepageSummaryMapper.queryCompletedTaskDetails(page, userId); + List result = new ArrayList<>(); + if (mpPage != null && CollectionUtils.isNotEmpty(mpPage.getRecords())) { + int i = 0; + for (TaskDetailsRspVo vo : mpPage.getRecords()) { + TaskDistanceSortRespVO sortVo = new TaskDistanceSortRespVO(); + sortVo.setTaskName(vo.getTaskName()); + sortVo.setBusiDateTime(vo.getBusiDateTime()); + sortVo.setPressingType(vo.getPressingType()); + sortVo.setSortOrder(++i); + sortVo.setDistance(null); + sortVo.setCumulativeDistance(null); + sortVo.setEstimatedWalkingMinutes(null); + sortVo.setEstimatedTricycleMinutes(null); + result.add(sortVo); + } + } + return result; + } + + /** + * 待办任务查询 + GPS 贪心最近邻排序 + */ + private List queryPendingTasksWithGpsSort(AppTaskDistanceSortReqVO req, + Long userId, Long deptId, + List roleIds) { + // 从字典表读取速度配置 + double walkingSpeedKmh = 4.0; + double tricycleSpeedKmh = 15.0; + try { + List speedConfigs = dictDataApi.getDictDataList(DictTypeConstants.TRAVEL_SPEED_CONFIG); + if (CollectionUtils.isNotEmpty(speedConfigs)) { + for (DictDataRespDTO config : speedConfigs) { + if ("步行速度(km/h)".equals(config.getLabel())) { + walkingSpeedKmh = Double.parseDouble(config.getValue()); + } else if ("三轮车速度(km/h)".equals(config.getLabel())) { + tricycleSpeedKmh = Double.parseDouble(config.getValue()); + } + } + } + } catch (Exception e) { + Log.warn("读取出行速度字典配置失败,使用默认值", e); + } + double walkingSpeedMperMin = walkingSpeedKmh * 1000 / 60; + double tricycleSpeedMperMin = tricycleSpeedKmh * 1000 / 60; + + // 查询今日待办任务(含 GPS 坐标) + LocalDateTime now = LocalDateTime.now(); + List allTasks = homepageSummaryMapper + .queryPendingTaskDetailsWithGps(roleIds, userId, deptId, now); + + // 分离有 GPS 和无 GPS 任务 + List tasksWithGps = new ArrayList<>(); + List tasksWithoutGps = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(allTasks)) { + for (TaskDistanceSortRespVO task : allTasks) { + if (task.getTaskLat() != null && task.getTaskLon() != null) { + tasksWithGps.add(task); + } else { + tasksWithoutGps.add(task); + } + } + } + + // 贪心最近邻排序 + List sortedTasks = new ArrayList<>(); + double currentLat = req.getCurrentLat().doubleValue(); + double currentLon = req.getCurrentLon().doubleValue(); + int cumulativeDistance = 0; + int sortOrder = 0; + + List remaining = new ArrayList<>(tasksWithGps); + while (!remaining.isEmpty()) { + TaskDistanceSortRespVO nearest = null; + double minDistance = Double.MAX_VALUE; + int nearestIndex = -1; + for (int i = 0; i < remaining.size(); i++) { + TaskDistanceSortRespVO task = remaining.get(i); + double dist = GpsDistanceUtil.haversineDistance( + currentLat, currentLon, + task.getTaskLat().doubleValue(), task.getTaskLon().doubleValue()); + if (dist < minDistance) { + minDistance = dist; + nearest = task; + nearestIndex = i; + } + } + sortOrder++; + int distMeters = (int) Math.round(minDistance); + cumulativeDistance += distMeters; + nearest.setSortOrder(sortOrder); + nearest.setDistance(distMeters); + nearest.setCumulativeDistance(cumulativeDistance); + nearest.setEstimatedWalkingMinutes((int) Math.ceil(distMeters / walkingSpeedMperMin)); + nearest.setEstimatedTricycleMinutes((int) Math.ceil(distMeters / tricycleSpeedMperMin)); + sortedTasks.add(nearest); + + currentLat = nearest.getTaskLat().doubleValue(); + currentLon = nearest.getTaskLon().doubleValue(); + remaining.remove(nearestIndex); + } + + // 无 GPS 任务追加到末尾 + if (CollectionUtils.isNotEmpty(tasksWithoutGps)) { + for (TaskDistanceSortRespVO task : tasksWithoutGps) { + sortOrder++; + task.setSortOrder(sortOrder); + task.setDistance(null); + task.setCumulativeDistance(null); + task.setEstimatedWalkingMinutes(null); + task.setEstimatedTricycleMinutes(null); + sortedTasks.add(task); + } + } + return sortedTasks; + } + + /** + * 构建班组进度统计 + */ + private TeamProgressVO buildTeamProgress(Long userId, Long deptId) { + TeamProgressVO teamProgress = new TeamProgressVO(); + teamProgress.setUserCompletedCount(0); + teamProgress.setTeamAverage(0.0); + teamProgress.setTeamMax(0); + teamProgress.setTeamMemberCount(0); + try { + List> teamData = homepageSummaryMapper.countTeamCompletedToday(deptId); + if (CollectionUtils.isNotEmpty(teamData)) { + int totalCompleted = 0; + int maxCompleted = 0; + for (Map row : teamData) { + Object uidObj = row.get("userId"); + Object cntObj = row.get("completedCount"); + int count = cntObj != null ? ((Number) cntObj).intValue() : 0; + totalCompleted += count; + if (count > maxCompleted) { + maxCompleted = count; + } + if (uidObj != null && userId.equals(((Number) uidObj).longValue())) { + teamProgress.setUserCompletedCount(count); + } + } + int memberCount = teamData.size(); + teamProgress.setTeamMemberCount(memberCount); + teamProgress.setTeamAverage(memberCount > 0 + ? Math.round(totalCompleted * 10.0 / memberCount) / 10.0 : 0.0); + teamProgress.setTeamMax(maxCompleted); + } + } catch (Exception e) { + Log.warn("查询班组完成统计失败", e); + } + return teamProgress; + } + + @Override + public TaskDistanceSortResultVO queryTaskDetailsWithSmartSort(AppTaskSmartSortReqVO req) { + Long userId = SecurityFrameworkUtils.getLoginUserId(); + Long deptId = SecurityFrameworkUtils.getDeptId(); + List roleIds = roleApi.getRoleIdsByUserId(userId); + + // 1. 从字典读取配置 + double distWeight = 0.6, effWeight = 0.4, threshold = 2000; + double clampMin = 0.3, clampMax = 3.0; + try { + List configs = dictDataApi.getDictDataList(DictTypeConstants.TASK_SORT_WEIGHT_CONFIG); + if (CollectionUtils.isNotEmpty(configs)) { + for (DictDataRespDTO c : configs) { + if ("距离权重".equals(c.getLabel())) distWeight = Double.parseDouble(c.getValue()); + else if ("效率权重".equals(c.getLabel())) effWeight = Double.parseDouble(c.getValue()); + else if ("效率比上限".equals(c.getLabel())) clampMax = Double.parseDouble(c.getValue()); + else if ("效率比下限".equals(c.getLabel())) clampMin = Double.parseDouble(c.getValue()); + } + } + List speedConfigs = dictDataApi.getDictDataList(DictTypeConstants.TRAVEL_SPEED_CONFIG); + if (CollectionUtils.isNotEmpty(speedConfigs)) { + for (DictDataRespDTO c : speedConfigs) { + if ("步行可达距离阈值(m)".equals(c.getLabel())) threshold = Double.parseDouble(c.getValue()); + } + } + } catch (Exception e) { + Log.warn("读取智能排序配置失败", e); + } + + // 2. 查询今日待办任务 + List allTasks = homepageSummaryMapper + .queryPendingTaskDetailsWithGps(roleIds, userId, deptId, LocalDateTime.now()); + + // 3. 查询效率矩阵 + Map effMap = loadEfficiencyMap(userId); + + // 4. 为每个任务打分 + List tasksWithGps = new ArrayList<>(); + List tasksWithoutGps = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(allTasks)) { + for (TaskDistanceSortRespVO task : allTasks) { + if (task.getTaskLat() != null && task.getTaskLon() != null) { + double dist = GpsDistanceUtil.haversineDistance( + req.getCurrentLat().doubleValue(), req.getCurrentLon().doubleValue(), + task.getTaskLat().doubleValue(), task.getTaskLon().doubleValue()); + double distScore = Math.max(0, 1 - dist / threshold); + task.setDistanceScore(Math.round(distScore * 100.0) / 100.0); + + BigDecimal effRatio = lookupEfficiency(effMap, task.getTaskType(), task.getTaskSubType()); + double effScore = clamp(effRatio.doubleValue(), clampMin, clampMax); + task.setEfficiencyScore(Math.round(effScore * 100.0) / 100.0); + + task.setCompositeScore(Math.round((distWeight * distScore + effWeight * effScore) * 100.0) / 100.0); + tasksWithGps.add(task); + } else { + BigDecimal effRatio = lookupEfficiency(effMap, task.getTaskType(), task.getTaskSubType()); + double effScore = clamp(effRatio.doubleValue(), clampMin, clampMax); + task.setDistanceScore(0.0); + task.setEfficiencyScore(Math.round(effScore * 100.0) / 100.0); + task.setCompositeScore(Math.round((effWeight * effScore) * 100.0) / 100.0); + tasksWithoutGps.add(task); + } + } + } + + // 5. 排序:有GPS按综合分降序,无GPS追加末尾按效率分降序 + tasksWithGps.sort((a, b) -> Double.compare( + b.getCompositeScore() != null ? b.getCompositeScore() : 0, + a.getCompositeScore() != null ? a.getCompositeScore() : 0)); + tasksWithoutGps.sort((a, b) -> Double.compare( + b.getEfficiencyScore() != null ? b.getEfficiencyScore() : 0, + a.getEfficiencyScore() != null ? a.getEfficiencyScore() : 0)); + + List sortedTasks = new ArrayList<>(); + int order = 0; + for (TaskDistanceSortRespVO t : tasksWithGps) { + t.setSortOrder(++order); + int distMeters = (int) Math.round(GpsDistanceUtil.haversineDistance( + req.getCurrentLat().doubleValue(), req.getCurrentLon().doubleValue(), + t.getTaskLat().doubleValue(), t.getTaskLon().doubleValue())); + t.setDistance(distMeters); + sortedTasks.add(t); + } + for (TaskDistanceSortRespVO t : tasksWithoutGps) { + t.setSortOrder(++order); + t.setDistance(null); + sortedTasks.add(t); + } + + // 6. 班组进度 + TeamProgressVO teamProgress = buildTeamProgress(userId, deptId); + + // 7. 组装结果 + TaskDistanceSortResultVO result = new TaskDistanceSortResultVO(); + result.setSortedTasks(sortedTasks); + result.setTeamProgress(teamProgress); + result.setTotalPendingTasks(sortedTasks.size()); + int totalDist = 0; + for (TaskDistanceSortRespVO t : sortedTasks) { + if (t.getDistance() != null) totalDist += t.getDistance(); + } + result.setEstimatedTotalDistance(totalDist); + return result; + } + + /** + * 加载当前用户当月效率矩阵 + */ + private Map loadEfficiencyMap(Long userId) { + Map map = new LinkedHashMap<>(); + try { + String statPeriod = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM")); + List list = userTaskEfficiencyMapper.selectByUserIdAndPeriod(userId, statPeriod); + if (CollectionUtils.isEmpty(list)) { + // fallback: 查最近一个周期的数据 + String latestPeriod = userTaskEfficiencyMapper.selectLatestPeriod(userId); + if (latestPeriod != null) { + list = userTaskEfficiencyMapper.selectByUserIdAndPeriod(userId, latestPeriod); + } + } + if (CollectionUtils.isNotEmpty(list)) { + for (UserTaskEfficiencyDO e : list) { + String key = e.getTaskCategory() + "_" + e.getTaskType(); + map.put(key, e.getEfficiencyRatio() != null ? e.getEfficiencyRatio() : BigDecimal.ONE); + } + } + } catch (Exception e) { + Log.warn("加载效率矩阵失败", e); + } + return map; + } + + /** + * 从效率矩阵查找,先精确匹配再 fallback 大类 + */ + private BigDecimal lookupEfficiency(Map map, String category, String subType) { + // 精确匹配 + String exactKey = category + "_" + subType; + if (map.containsKey(exactKey)) return map.get(exactKey); + // fallback: 大类汇总 + String fallbackKey = category + "_ALL"; + if (map.containsKey(fallbackKey)) return map.get(fallbackKey); + // 无数据:中性 + return BigDecimal.ONE; + } + + private double clamp(double val, double min, double max) { + if (val > max) return max; + if (val < min) return min; + return val; + } } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GpsDistanceUtil.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GpsDistanceUtil.java new file mode 100644 index 0000000..2dde886 --- /dev/null +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GpsDistanceUtil.java @@ -0,0 +1,32 @@ +package com.zteits.urbanops.module.garden.util; + +/** + * GPS 距离计算工具类 + * + * @author + */ +public class GpsDistanceUtil { + + /** 地球半径(米) */ + private static final double EARTH_RADIUS_M = 6_371_000; + + /** + * Haversine 公式计算两点之间的球面距离 + * + * @param lat1 点1纬度 + * @param lon1 点1经度 + * @param lat2 点2纬度 + * @param lon2 点2经度 + * @return 两点之间的距离(米) + */ + public static double haversineDistance(double lat1, double lon1, + double lat2, double lon2) { + double dLat = Math.toRadians(lat2 - lat1); + double dLon = Math.toRadians(lon2 - lon1); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) + * Math.sin(dLon / 2) * Math.sin(dLon / 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return EARTH_RADIUS_M * c; + } +} diff --git a/urbanops-module-garden/src/main/resources/mapper/UserTaskEfficiencyMapper.xml b/urbanops-module-garden/src/main/resources/mapper/UserTaskEfficiencyMapper.xml new file mode 100644 index 0000000..c14493a --- /dev/null +++ b/urbanops-module-garden/src/main/resources/mapper/UserTaskEfficiencyMapper.xml @@ -0,0 +1,76 @@ + + + + + + + + + + DELETE FROM garden_user_task_efficiency + WHERE stat_period = #{statPeriod} + + + + + + + + + + + + diff --git a/urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml b/urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml index b42e5a6..ae58663 100644 --- a/urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml +++ b/urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml @@ -615,5 +615,111 @@ GROUP BY w.worker_company_id, d.name + + + + diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java index 006c12a..a539820 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java @@ -32,8 +32,8 @@ public interface DictTypeConstants { String SYSTEM_IS_INNER = "system_is_inner";//是否是内部员工 + String TRAVEL_SPEED_CONFIG = "travel_speed_config"; // 出行速度配置 - - + String TASK_SORT_WEIGHT_CONFIG = "task_sort_weight_config"; // 智能排序权重配置 } diff --git a/urbanops-server/src/main/resources/application.yaml b/urbanops-server/src/main/resources/application.yaml index e5f485b..66d32b9 100644 --- a/urbanops-server/src/main/resources/application.yaml +++ b/urbanops-server/src/main/resources/application.yaml @@ -271,7 +271,7 @@ urbanops: - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 security: permit-all_urls: - - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 + - /admin-api/mp/open/** - /admin-api/garden/device-user/** api-encrypt: enable: true # 是否开启 API 加密 -- libgit2 0.21.4