Commit 61c9550cc2688cef09c76cb98fd01b22a1465692
Merge remote-tracking branch 'origin/master' into dev
Showing
19 changed files
with
1086 additions
and
6 deletions
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 { | @@ -37,7 +37,7 @@ public class ApiSignatureAspect { | ||
| 37 | 37 | ||
| 38 | private final ApiSignatureRedisDAO signatureRedisDAO; | 38 | private final ApiSignatureRedisDAO signatureRedisDAO; |
| 39 | 39 | ||
| 40 | - @Before("@annotation(signature)") | 40 | + @Before("@within(signature)") |
| 41 | public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { | 41 | public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) { |
| 42 | // 1. 验证通过,直接结束 | 42 | // 1. 验证通过,直接结束 |
| 43 | if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { | 43 | if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) { |
| @@ -171,4 +171,4 @@ public class ApiSignatureAspect { | @@ -171,4 +171,4 @@ public class ApiSignatureAspect { | ||
| 171 | return sortedMap; | 171 | return sortedMap; |
| 172 | } | 172 | } |
| 173 | 173 | ||
| 174 | -} | ||
| 175 | \ No newline at end of file | 174 | \ No newline at end of file |
| 175 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/AppHomepageSummaryController.java
| @@ -12,6 +12,7 @@ import jakarta.validation.Valid; | @@ -12,6 +12,7 @@ import jakarta.validation.Valid; | ||
| 12 | import org.springframework.web.bind.annotation.*; | 12 | import org.springframework.web.bind.annotation.*; |
| 13 | 13 | ||
| 14 | import java.util.List; | 14 | import java.util.List; |
| 15 | +import java.util.Map; | ||
| 15 | 16 | ||
| 16 | import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; | 17 | import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; |
| 17 | 18 | ||
| @@ -52,4 +53,16 @@ public class AppHomepageSummaryController { | @@ -52,4 +53,16 @@ public class AppHomepageSummaryController { | ||
| 52 | public CommonResult<AppWorkOrderSummaryRspVo> iWorkOrderSummary(@Valid @RequestBody AppWorkOrderSummaryReqVo req) { | 53 | public CommonResult<AppWorkOrderSummaryRspVo> iWorkOrderSummary(@Valid @RequestBody AppWorkOrderSummaryReqVo req) { |
| 53 | return success(homepageSummaryService.iWorkOrderSummary(req)); | 54 | return success(homepageSummaryService.iWorkOrderSummary(req)); |
| 54 | } | 55 | } |
| 56 | + | ||
| 57 | + @PostMapping("/taskDetailsWithDistance") | ||
| 58 | + @Operation(summary = "任务待办(按距离排序 + 班组进度)") | ||
| 59 | + public CommonResult<TaskDistanceSortResultVO> queryTaskDetailsWithDistance(@Valid @RequestBody AppTaskDistanceSortReqVO req) { | ||
| 60 | + return success(homepageSummaryService.queryTaskDetailsWithDistance(req)); | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + @PostMapping("/taskDetailsWithSmartSort") | ||
| 64 | + @Operation(summary = "任务待办(智能排序:距离 + 个人效率加权)") | ||
| 65 | + public CommonResult<TaskDistanceSortResultVO> queryTaskDetailsWithSmartSort(@Valid @RequestBody AppTaskSmartSortReqVO req) { | ||
| 66 | + return success(homepageSummaryService.queryTaskDetailsWithSmartSort(req)); | ||
| 67 | + } | ||
| 55 | } | 68 | } |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskDistanceSortReqVO.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; | ||
| 2 | + | ||
| 3 | +import com.zteits.urbanops.framework.common.pojo.PageParam; | ||
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 5 | +import jakarta.validation.constraints.NotNull; | ||
| 6 | +import lombok.Data; | ||
| 7 | +import lombok.EqualsAndHashCode; | ||
| 8 | + | ||
| 9 | +import java.math.BigDecimal; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * app - 任务距离排序 请求 VO | ||
| 13 | + * | ||
| 14 | + * @author | ||
| 15 | + */ | ||
| 16 | +@Data | ||
| 17 | +@EqualsAndHashCode(callSuper = true) | ||
| 18 | +public class AppTaskDistanceSortReqVO extends PageParam { | ||
| 19 | + | ||
| 20 | + @Schema(description = "查询类型:1-待办,2-已办", example = "1") | ||
| 21 | + @NotNull | ||
| 22 | + public Integer queryType; | ||
| 23 | + | ||
| 24 | + @Schema(description = "当前经度", example = "116.397428") | ||
| 25 | + @NotNull(message = "当前经度不能为空") | ||
| 26 | + private BigDecimal currentLon; | ||
| 27 | + | ||
| 28 | + @Schema(description = "当前纬度", example = "39.909204") | ||
| 29 | + @NotNull(message = "当前纬度不能为空") | ||
| 30 | + private BigDecimal currentLat; | ||
| 31 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/AppTaskSmartSortReqVO.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; | ||
| 2 | + | ||
| 3 | +import com.zteits.urbanops.framework.common.pojo.PageParam; | ||
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 5 | +import jakarta.validation.constraints.NotNull; | ||
| 6 | +import lombok.Data; | ||
| 7 | +import lombok.EqualsAndHashCode; | ||
| 8 | + | ||
| 9 | +import java.math.BigDecimal; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * app - 任务智能排序 请求 VO | ||
| 13 | + * | ||
| 14 | + * @author | ||
| 15 | + */ | ||
| 16 | +@Data | ||
| 17 | +@EqualsAndHashCode(callSuper = true) | ||
| 18 | +public class AppTaskSmartSortReqVO extends PageParam { | ||
| 19 | + | ||
| 20 | + @Schema(description = "查询类型:1-待办,2-已办", example = "1") | ||
| 21 | + @NotNull | ||
| 22 | + private Integer queryType; | ||
| 23 | + | ||
| 24 | + @Schema(description = "当前经度", example = "116.397428") | ||
| 25 | + @NotNull(message = "当前经度不能为空") | ||
| 26 | + private BigDecimal currentLon; | ||
| 27 | + | ||
| 28 | + @Schema(description = "当前纬度", example = "39.909204") | ||
| 29 | + @NotNull(message = "当前纬度不能为空") | ||
| 30 | + private BigDecimal currentLat; | ||
| 31 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortRespVO.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | +import lombok.EqualsAndHashCode; | ||
| 5 | + | ||
| 6 | +import java.math.BigDecimal; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * app - 任务距离排序 单个任务响应 VO | ||
| 10 | + * | ||
| 11 | + * @author | ||
| 12 | + */ | ||
| 13 | +@Data | ||
| 14 | +@EqualsAndHashCode(callSuper = true) | ||
| 15 | +public class TaskDistanceSortRespVO extends TaskDetailsRspVo { | ||
| 16 | + | ||
| 17 | + /** 任务类型:inspection / maintain / workorder */ | ||
| 18 | + private String taskType; | ||
| 19 | + | ||
| 20 | + /** 任务点经度 */ | ||
| 21 | + private BigDecimal taskLon; | ||
| 22 | + | ||
| 23 | + /** 任务点纬度 */ | ||
| 24 | + private BigDecimal taskLat; | ||
| 25 | + | ||
| 26 | + /** 从上一点到本任务的距离(米),无 GPS 则为 null */ | ||
| 27 | + private Integer distance; | ||
| 28 | + | ||
| 29 | + /** 从起点累计距离(米) */ | ||
| 30 | + private Integer cumulativeDistance; | ||
| 31 | + | ||
| 32 | + /** 预估步行时间(分钟) */ | ||
| 33 | + private Integer estimatedWalkingMinutes; | ||
| 34 | + | ||
| 35 | + /** 预估三轮车时间(分钟) */ | ||
| 36 | + private Integer estimatedTricycleMinutes; | ||
| 37 | + | ||
| 38 | + /** 排序序号:1, 2, 3... */ | ||
| 39 | + private Integer sortOrder; | ||
| 40 | + | ||
| 41 | + /** 任务子类型:plan_type_id 或 order_type,用于效率矩阵匹配 */ | ||
| 42 | + private String taskSubType; | ||
| 43 | + | ||
| 44 | + /** 距离得分 [0, 1] */ | ||
| 45 | + private Double distanceScore; | ||
| 46 | + | ||
| 47 | + /** 效率得分(已截断) */ | ||
| 48 | + private Double efficiencyScore; | ||
| 49 | + | ||
| 50 | + /** 综合得分 */ | ||
| 51 | + private Double compositeScore; | ||
| 52 | + | ||
| 53 | + /** 用户历史平均完成时长(分钟) */ | ||
| 54 | + private BigDecimal userAvgMinutes; | ||
| 55 | + | ||
| 56 | + /** 班组平均完成时长(分钟) */ | ||
| 57 | + private BigDecimal teamAvgMinutes; | ||
| 58 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortResultVO.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +import java.util.List; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * app - 任务距离排序 顶层返回 VO | ||
| 9 | + * | ||
| 10 | + * @author | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +public class TaskDistanceSortResultVO { | ||
| 14 | + | ||
| 15 | + /** 排序后的任务列表 */ | ||
| 16 | + private List<TaskDistanceSortRespVO> sortedTasks; | ||
| 17 | + | ||
| 18 | + /** 班组内进度统计 */ | ||
| 19 | + private TeamProgressVO teamProgress; | ||
| 20 | + | ||
| 21 | + /** 预估总距离(米) */ | ||
| 22 | + private Integer estimatedTotalDistance; | ||
| 23 | + | ||
| 24 | + /** 预估总步行时间(分钟) */ | ||
| 25 | + private Integer estimatedTotalWalkingMinutes; | ||
| 26 | + | ||
| 27 | + /** 预估总三轮车时间(分钟) */ | ||
| 28 | + private Integer estimatedTotalTricycleMinutes; | ||
| 29 | + | ||
| 30 | + /** 总待办任务数 */ | ||
| 31 | + private Integer totalPendingTasks; | ||
| 32 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TeamProgressVO.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.controller.app.homepage.vo; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * app - 班组内进度统计 VO | ||
| 7 | + * | ||
| 8 | + * @author | ||
| 9 | + */ | ||
| 10 | +@Data | ||
| 11 | +public class TeamProgressVO { | ||
| 12 | + | ||
| 13 | + /** 当前用户今日已完成数 */ | ||
| 14 | + private Integer userCompletedCount; | ||
| 15 | + | ||
| 16 | + /** 班组均值 */ | ||
| 17 | + private Double teamAverage; | ||
| 18 | + | ||
| 19 | + /** 班组内最高 */ | ||
| 20 | + private Integer teamMax; | ||
| 21 | + | ||
| 22 | + /** 班组总人数 */ | ||
| 23 | + private Integer teamMemberCount; | ||
| 24 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/UserTaskEfficiencyDO.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.dal.dataobject; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.annotation.KeySequence; | ||
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 6 | +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO; | ||
| 7 | +import lombok.*; | ||
| 8 | + | ||
| 9 | +import java.math.BigDecimal; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 用户任务效率预计算 DO | ||
| 13 | + * | ||
| 14 | + * @author | ||
| 15 | + */ | ||
| 16 | +@TableName("garden_user_task_efficiency") | ||
| 17 | +@KeySequence("garden_user_task_efficiency_seq") | ||
| 18 | +@Data | ||
| 19 | +@EqualsAndHashCode(callSuper = true) | ||
| 20 | +@ToString(callSuper = true) | ||
| 21 | +@Builder | ||
| 22 | +@NoArgsConstructor | ||
| 23 | +@AllArgsConstructor | ||
| 24 | +public class UserTaskEfficiencyDO extends BaseDO { | ||
| 25 | + | ||
| 26 | + @TableId | ||
| 27 | + private Long id; | ||
| 28 | + | ||
| 29 | + /** 用户ID */ | ||
| 30 | + private Long userId; | ||
| 31 | + | ||
| 32 | + /** 部门ID(班组) */ | ||
| 33 | + private Long deptId; | ||
| 34 | + | ||
| 35 | + /** 任务大类:inspection / maintain / workorder */ | ||
| 36 | + private String taskCategory; | ||
| 37 | + | ||
| 38 | + /** 任务类型(细粒度):plan_type_id 或 order_type,ALL 表示大类汇总 */ | ||
| 39 | + private String taskType; | ||
| 40 | + | ||
| 41 | + /** 6个自然月内完成任务数 */ | ||
| 42 | + private Integer completedCount; | ||
| 43 | + | ||
| 44 | + /** 总耗时(分钟) */ | ||
| 45 | + private Long totalMinutes; | ||
| 46 | + | ||
| 47 | + /** 个人平均完成时长(分钟) */ | ||
| 48 | + private BigDecimal avgMinutes; | ||
| 49 | + | ||
| 50 | + /** 班组平均完成时长(分钟) */ | ||
| 51 | + private BigDecimal teamAvgMinutes; | ||
| 52 | + | ||
| 53 | + /** 效率比 = teamAvg / userAvg */ | ||
| 54 | + private BigDecimal efficiencyRatio; | ||
| 55 | + | ||
| 56 | + /** 统计周期 yyyy-MM */ | ||
| 57 | + private String statPeriod; | ||
| 58 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/UserTaskEfficiencyMapper.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.dal.mysql; | ||
| 2 | + | ||
| 3 | +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; | ||
| 4 | +import com.zteits.urbanops.module.garden.dal.dataobject.UserTaskEfficiencyDO; | ||
| 5 | +import org.apache.ibatis.annotations.Mapper; | ||
| 6 | +import org.apache.ibatis.annotations.Param; | ||
| 7 | + | ||
| 8 | +import java.util.List; | ||
| 9 | +import java.util.Map; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 用户任务效率 Mapper | ||
| 13 | + * | ||
| 14 | + * @author | ||
| 15 | + */ | ||
| 16 | +@Mapper | ||
| 17 | +public interface UserTaskEfficiencyMapper extends BaseMapperX<UserTaskEfficiencyDO> { | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * 根据用户和统计周期查询效率数据 | ||
| 21 | + */ | ||
| 22 | + List<UserTaskEfficiencyDO> selectByUserIdAndPeriod(@Param("userId") Long userId, | ||
| 23 | + @Param("statPeriod") String statPeriod); | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 查询最近一条有数据的统计周期 | ||
| 27 | + */ | ||
| 28 | + String selectLatestPeriod(@Param("userId") Long userId); | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 删除指定统计周期的数据(幂等重跑) | ||
| 32 | + */ | ||
| 33 | + int deleteByPeriod(@Param("statPeriod") String statPeriod); | ||
| 34 | + | ||
| 35 | + /** 巡查计划效率统计 */ | ||
| 36 | + List<Map<String, Object>> statInspectionEfficiency(@Param("beginTime") String beginTime); | ||
| 37 | + | ||
| 38 | + /** 养护计划效率统计 */ | ||
| 39 | + List<Map<String, Object>> statMaintainEfficiency(@Param("beginTime") String beginTime); | ||
| 40 | + | ||
| 41 | + /** 工单效率统计 */ | ||
| 42 | + List<Map<String, Object>> statWorkOrderEfficiency(@Param("beginTime") String beginTime); | ||
| 43 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/homepage/HomepageSummaryMapper.java
| @@ -10,6 +10,7 @@ import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppWorkOrder | @@ -10,6 +10,7 @@ import com.zteits.urbanops.module.garden.controller.app.homepage.vo.AppWorkOrder | ||
| 10 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.CommonTaskStatusVo; | 10 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.CommonTaskStatusVo; |
| 11 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo; | 11 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskCompletionStatusRspVo; |
| 12 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo; | 12 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo; |
| 13 | +import com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDistanceSortRespVO; | ||
| 13 | import com.zteits.urbanops.module.garden.dal.dataobject.departmentcost.DepartmentCostDO; | 14 | import com.zteits.urbanops.module.garden.dal.dataobject.departmentcost.DepartmentCostDO; |
| 14 | import com.zteits.urbanops.module.garden.dal.dataobject.taskstatistics.TaskStatisticsDO; | 15 | import com.zteits.urbanops.module.garden.dal.dataobject.taskstatistics.TaskStatisticsDO; |
| 15 | import org.apache.ibatis.annotations.Mapper; | 16 | import org.apache.ibatis.annotations.Mapper; |
| @@ -216,4 +217,17 @@ public interface HomepageSummaryMapper extends BaseMapperX<DepartmentCostDO> { | @@ -216,4 +217,17 @@ public interface HomepageSummaryMapper extends BaseMapperX<DepartmentCostDO> { | ||
| 216 | * @Return | 217 | * @Return |
| 217 | */ | 218 | */ |
| 218 | List<AppWorkOrderSummaryVo> countWorkOrder(@Param("queryType") String queryType,@Param("userId") Long userId, @Param("beginTime") String beginTime, @Param("endTime") String endTime); | 219 | List<AppWorkOrderSummaryVo> countWorkOrder(@Param("queryType") String queryType,@Param("userId") Long userId, @Param("beginTime") String beginTime, @Param("endTime") String endTime); |
| 220 | + | ||
| 221 | + /** | ||
| 222 | + * 查询今日待办任务(含 GPS 坐标),三表 UNION | ||
| 223 | + */ | ||
| 224 | + List<TaskDistanceSortRespVO> queryPendingTaskDetailsWithGps(@Param("roleIds") List<Long> roleIds, | ||
| 225 | + @Param("userId") Long userId, | ||
| 226 | + @Param("deptId") Long deptId, | ||
| 227 | + @Param("currentDate") LocalDateTime currentDate); | ||
| 228 | + | ||
| 229 | + /** | ||
| 230 | + * 统计今日同班组(同 dept_id)成员完成任务数 | ||
| 231 | + */ | ||
| 232 | + List<Map<String, Object>> countTeamCompletedToday(@Param("deptId") Long deptId); | ||
| 219 | } | 233 | } |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/CommonConstants.java
| @@ -26,4 +26,7 @@ public interface CommonConstants { | @@ -26,4 +26,7 @@ public interface CommonConstants { | ||
| 26 | 26 | ||
| 27 | /*积分来源 01:任务反馈*/ | 27 | /*积分来源 01:任务反馈*/ |
| 28 | public static final String UNIT_INTEGRAL_SOURCE_TYPE_01 = "01"; | 28 | public static final String UNIT_INTEGRAL_SOURCE_TYPE_01 = "01"; |
| 29 | + | ||
| 30 | + /*全域督察员角色标识*/ | ||
| 31 | + public static final String INSPECTOR_ROLE_KEY = "inspector"; | ||
| 29 | } | 32 | } |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/job/TaskEfficiencyCalculateJob.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.job; | ||
| 2 | + | ||
| 3 | +import com.zteits.urbanops.module.garden.dal.dataobject.UserTaskEfficiencyDO; | ||
| 4 | +import com.zteits.urbanops.module.garden.dal.mysql.UserTaskEfficiencyMapper; | ||
| 5 | +import jakarta.annotation.Resource; | ||
| 6 | +import lombok.extern.slf4j.Slf4j; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | + | ||
| 9 | +import java.math.BigDecimal; | ||
| 10 | +import java.math.RoundingMode; | ||
| 11 | +import java.time.LocalDate; | ||
| 12 | +import java.time.LocalDateTime; | ||
| 13 | +import java.time.format.DateTimeFormatter; | ||
| 14 | +import java.util.*; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * 用户任务效率预计算定时任务 | ||
| 18 | + * 每天凌晨 2:00 执行,统计最近 6 个自然月的用户任务完成效率 | ||
| 19 | + * | ||
| 20 | + * @author | ||
| 21 | + */ | ||
| 22 | +@Slf4j | ||
| 23 | +@Component | ||
| 24 | +public class TaskEfficiencyCalculateJob { | ||
| 25 | + | ||
| 26 | + @Resource | ||
| 27 | + private UserTaskEfficiencyMapper userTaskEfficiencyMapper; | ||
| 28 | + | ||
| 29 | + public void execute() { | ||
| 30 | + String statPeriod = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")); | ||
| 31 | + log.info("TaskEfficiencyCalculateJob start, statPeriod={}", statPeriod); | ||
| 32 | + try { | ||
| 33 | + // 1. 删除本月旧数据 | ||
| 34 | + userTaskEfficiencyMapper.deleteByPeriod(statPeriod); | ||
| 35 | + | ||
| 36 | + // 2. 6个月窗口 | ||
| 37 | + String beginTime = LocalDate.now().minusMonths(6).withDayOfMonth(1) + " 00:00:00"; | ||
| 38 | + | ||
| 39 | + // 3. 三类任务分别统计 | ||
| 40 | + List<UserTaskEfficiencyDO> all = new ArrayList<>(); | ||
| 41 | + all.addAll(mapRows(userTaskEfficiencyMapper.statInspectionEfficiency(beginTime), statPeriod)); | ||
| 42 | + all.addAll(mapRows(userTaskEfficiencyMapper.statMaintainEfficiency(beginTime), statPeriod)); | ||
| 43 | + all.addAll(mapRows(userTaskEfficiencyMapper.statWorkOrderEfficiency(beginTime), statPeriod)); | ||
| 44 | + | ||
| 45 | + if (all.isEmpty()) { | ||
| 46 | + log.info("TaskEfficiencyCalculateJob done, no data"); | ||
| 47 | + return; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + // 4. 计算班组均值 | ||
| 51 | + calcTeamAverage(all); | ||
| 52 | + | ||
| 53 | + // 5. 计算效率比 | ||
| 54 | + for (UserTaskEfficiencyDO e : all) { | ||
| 55 | + e.setEfficiencyRatio(calcRatio(e.getAvgMinutes(), e.getTeamAvgMinutes())); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + // 6. 生成大类汇总(ALL) | ||
| 59 | + all.addAll(buildCategorySummary(all, statPeriod)); | ||
| 60 | + | ||
| 61 | + // 7. 批量写入 | ||
| 62 | + LocalDateTime now = LocalDateTime.now(); | ||
| 63 | + for (UserTaskEfficiencyDO e : all) { | ||
| 64 | + e.setCreator("job"); | ||
| 65 | + e.setUpdater("job"); | ||
| 66 | + e.setCreateTime(now); | ||
| 67 | + e.setUpdateTime(now); | ||
| 68 | + userTaskEfficiencyMapper.insert(e); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + log.info("TaskEfficiencyCalculateJob done, total={}", all.size()); | ||
| 72 | + } catch (Exception e) { | ||
| 73 | + log.error("TaskEfficiencyCalculateJob error", e); | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + private List<UserTaskEfficiencyDO> mapRows(List<Map<String, Object>> rows, String statPeriod) { | ||
| 78 | + List<UserTaskEfficiencyDO> result = new ArrayList<>(); | ||
| 79 | + if (rows == null || rows.isEmpty()) return result; | ||
| 80 | + for (Map<String, Object> row : rows) { | ||
| 81 | + UserTaskEfficiencyDO e = new UserTaskEfficiencyDO(); | ||
| 82 | + e.setUserId(toLong(row.get("user_id"))); | ||
| 83 | + e.setDeptId(toLong(row.get("dept_id"))); | ||
| 84 | + e.setTaskCategory((String) row.get("task_category")); | ||
| 85 | + e.setTaskType(String.valueOf(row.get("task_type"))); | ||
| 86 | + e.setCompletedCount(toInt(row.get("cnt"))); | ||
| 87 | + e.setTotalMinutes(toLong(row.get("total_min"))); | ||
| 88 | + e.setAvgMinutes(toDecimal(row.get("avg_min"))); | ||
| 89 | + e.setStatPeriod(statPeriod); | ||
| 90 | + result.add(e); | ||
| 91 | + } | ||
| 92 | + return result; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + private void calcTeamAverage(List<UserTaskEfficiencyDO> all) { | ||
| 96 | + Map<String, List<UserTaskEfficiencyDO>> groups = new LinkedHashMap<>(); | ||
| 97 | + for (UserTaskEfficiencyDO e : all) { | ||
| 98 | + String key = e.getDeptId() + "_" + e.getTaskCategory() + "_" + e.getTaskType(); | ||
| 99 | + groups.computeIfAbsent(key, k -> new ArrayList<>()).add(e); | ||
| 100 | + } | ||
| 101 | + for (List<UserTaskEfficiencyDO> group : groups.values()) { | ||
| 102 | + long totalMin = 0; | ||
| 103 | + int totalCnt = 0; | ||
| 104 | + for (UserTaskEfficiencyDO e : group) { | ||
| 105 | + totalMin += e.getTotalMinutes() != null ? e.getTotalMinutes() : 0; | ||
| 106 | + totalCnt += e.getCompletedCount() != null ? e.getCompletedCount() : 0; | ||
| 107 | + } | ||
| 108 | + BigDecimal teamAvg = totalCnt > 0 | ||
| 109 | + ? BigDecimal.valueOf(totalMin).divide(BigDecimal.valueOf(totalCnt), 2, RoundingMode.HALF_UP) | ||
| 110 | + : BigDecimal.ZERO; | ||
| 111 | + for (UserTaskEfficiencyDO e : group) { | ||
| 112 | + e.setTeamAvgMinutes(teamAvg); | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + private BigDecimal calcRatio(BigDecimal userAvg, BigDecimal teamAvg) { | ||
| 118 | + if (userAvg == null || teamAvg == null | ||
| 119 | + || userAvg.compareTo(BigDecimal.ZERO) <= 0 | ||
| 120 | + || teamAvg.compareTo(BigDecimal.ZERO) <= 0) { | ||
| 121 | + return BigDecimal.ONE; | ||
| 122 | + } | ||
| 123 | + BigDecimal ratio = teamAvg.divide(userAvg, 4, RoundingMode.HALF_UP); | ||
| 124 | + if (ratio.compareTo(new BigDecimal("3.0")) > 0) return new BigDecimal("3.0"); | ||
| 125 | + if (ratio.compareTo(new BigDecimal("0.3")) < 0) return new BigDecimal("0.3"); | ||
| 126 | + return ratio; | ||
| 127 | + } | ||
| 128 | + | ||
| 129 | + private List<UserTaskEfficiencyDO> buildCategorySummary(List<UserTaskEfficiencyDO> all, String statPeriod) { | ||
| 130 | + Map<String, List<UserTaskEfficiencyDO>> catMap = new LinkedHashMap<>(); | ||
| 131 | + for (UserTaskEfficiencyDO e : all) { | ||
| 132 | + String key = e.getUserId() + "_" + e.getDeptId() + "_" + e.getTaskCategory(); | ||
| 133 | + catMap.computeIfAbsent(key, k -> new ArrayList<>()).add(e); | ||
| 134 | + } | ||
| 135 | + List<UserTaskEfficiencyDO> summaries = new ArrayList<>(); | ||
| 136 | + for (List<UserTaskEfficiencyDO> group : catMap.values()) { | ||
| 137 | + UserTaskEfficiencyDO s = new UserTaskEfficiencyDO(); | ||
| 138 | + s.setUserId(group.get(0).getUserId()); | ||
| 139 | + s.setDeptId(group.get(0).getDeptId()); | ||
| 140 | + s.setTaskCategory(group.get(0).getTaskCategory()); | ||
| 141 | + s.setTaskType("ALL"); | ||
| 142 | + s.setStatPeriod(statPeriod); | ||
| 143 | + long totalMin = 0; | ||
| 144 | + int totalCnt = 0; | ||
| 145 | + for (UserTaskEfficiencyDO e : group) { | ||
| 146 | + totalMin += e.getTotalMinutes() != null ? e.getTotalMinutes() : 0; | ||
| 147 | + totalCnt += e.getCompletedCount() != null ? e.getCompletedCount() : 0; | ||
| 148 | + } | ||
| 149 | + s.setCompletedCount(totalCnt); | ||
| 150 | + s.setTotalMinutes(totalMin); | ||
| 151 | + s.setAvgMinutes(totalCnt > 0 | ||
| 152 | + ? BigDecimal.valueOf(totalMin).divide(BigDecimal.valueOf(totalCnt), 2, RoundingMode.HALF_UP) | ||
| 153 | + : BigDecimal.ZERO); | ||
| 154 | + s.setTeamAvgMinutes(group.get(0).getTeamAvgMinutes()); | ||
| 155 | + s.setEfficiencyRatio(calcRatio(s.getAvgMinutes(), s.getTeamAvgMinutes())); | ||
| 156 | + summaries.add(s); | ||
| 157 | + } | ||
| 158 | + return summaries; | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + private Long toLong(Object o) { | ||
| 162 | + if (o == null) return 0L; | ||
| 163 | + return o instanceof Number ? ((Number) o).longValue() : Long.parseLong(o.toString()); | ||
| 164 | + } | ||
| 165 | + private Integer toInt(Object o) { | ||
| 166 | + if (o == null) return 0; | ||
| 167 | + return o instanceof Number ? ((Number) o).intValue() : Integer.parseInt(o.toString()); | ||
| 168 | + } | ||
| 169 | + private BigDecimal toDecimal(Object o) { | ||
| 170 | + if (o == null) return BigDecimal.ZERO; | ||
| 171 | + return o instanceof BigDecimal ? (BigDecimal) o : new BigDecimal(o.toString()); | ||
| 172 | + } | ||
| 173 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryService.java
| @@ -4,6 +4,7 @@ import com.zteits.urbanops.framework.common.pojo.PageResult; | @@ -4,6 +4,7 @@ import com.zteits.urbanops.framework.common.pojo.PageResult; | ||
| 4 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*; | 4 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*; |
| 5 | 5 | ||
| 6 | import java.util.List; | 6 | import java.util.List; |
| 7 | +import java.util.Map; | ||
| 7 | 8 | ||
| 8 | /** | 9 | /** |
| 9 | * @Classname HomepageSummaryService | 10 | * @Classname HomepageSummaryService |
| @@ -48,4 +49,16 @@ public interface HomepageSummaryService { | @@ -48,4 +49,16 @@ public interface HomepageSummaryService { | ||
| 48 | AppWorkOrderSummaryRspVo iWorkOrderSummary(AppWorkOrderSummaryReqVo req); | 49 | AppWorkOrderSummaryRspVo iWorkOrderSummary(AppWorkOrderSummaryReqVo req); |
| 49 | 50 | ||
| 50 | 51 | ||
| 52 | + /** | ||
| 53 | + * 任务待办(按距离排序 + 班组进度) | ||
| 54 | + * | ||
| 55 | + * @param req 请求参数(当前 GPS 坐标) | ||
| 56 | + * @return 排序后的任务列表 + 班组进度统计 | ||
| 57 | + */ | ||
| 58 | + TaskDistanceSortResultVO queryTaskDetailsWithDistance(AppTaskDistanceSortReqVO req); | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 任务待办(智能排序:距离 + 个人效率加权) | ||
| 62 | + */ | ||
| 63 | + TaskDistanceSortResultVO queryTaskDetailsWithSmartSort(AppTaskSmartSortReqVO req); | ||
| 51 | } | 64 | } |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java
| @@ -5,27 +5,38 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | @@ -5,27 +5,38 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||
| 5 | import com.baomidou.mybatisplus.core.metadata.IPage; | 5 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 6 | import com.esotericsoftware.minlog.Log; | 6 | import com.esotericsoftware.minlog.Log; |
| 7 | import com.zteits.urbanops.framework.common.pojo.PageResult; | 7 | import com.zteits.urbanops.framework.common.pojo.PageResult; |
| 8 | +import com.zteits.urbanops.framework.common.util.date.DateUtils; | ||
| 8 | import com.zteits.urbanops.framework.common.util.date.LocalDateTimeUtils; | 9 | import com.zteits.urbanops.framework.common.util.date.LocalDateTimeUtils; |
| 10 | +import com.zteits.urbanops.framework.mybatis.core.query.QueryWrapperX; | ||
| 9 | import com.zteits.urbanops.framework.mybatis.core.util.MyBatisUtils; | 11 | import com.zteits.urbanops.framework.mybatis.core.util.MyBatisUtils; |
| 10 | import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; | 12 | import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; |
| 13 | +import com.zteits.urbanops.framework.common.biz.system.dict.dto.DictDataRespDTO; | ||
| 11 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*; | 14 | import com.zteits.urbanops.module.garden.controller.app.homepage.vo.*; |
| 15 | +import com.zteits.urbanops.module.garden.dal.dataobject.UserTaskEfficiencyDO; | ||
| 16 | +import com.zteits.urbanops.module.garden.enums.CommonConstants; | ||
| 12 | import com.zteits.urbanops.module.garden.dal.dataobject.taskstatistics.TaskStatisticsDO; | 17 | import com.zteits.urbanops.module.garden.dal.dataobject.taskstatistics.TaskStatisticsDO; |
| 18 | +import com.zteits.urbanops.module.garden.dal.mysql.UserTaskEfficiencyMapper; | ||
| 13 | import com.zteits.urbanops.module.garden.dal.mysql.homepage.HomepageSummaryMapper; | 19 | import com.zteits.urbanops.module.garden.dal.mysql.homepage.HomepageSummaryMapper; |
| 14 | import com.zteits.urbanops.module.garden.dal.mysql.taskstatistics.TaskStatisticsMapper; | 20 | import com.zteits.urbanops.module.garden.dal.mysql.taskstatistics.TaskStatisticsMapper; |
| 21 | +import com.zteits.urbanops.module.garden.util.GpsDistanceUtil; | ||
| 15 | import com.zteits.urbanops.module.system.api.dept.DeptApi; | 22 | import com.zteits.urbanops.module.system.api.dept.DeptApi; |
| 23 | +import com.zteits.urbanops.module.system.api.dict.DictDataApi; | ||
| 16 | import com.zteits.urbanops.module.system.api.permission.RoleApi; | 24 | import com.zteits.urbanops.module.system.api.permission.RoleApi; |
| 17 | import com.zteits.urbanops.module.system.api.user.AdminUserApi; | 25 | import com.zteits.urbanops.module.system.api.user.AdminUserApi; |
| 18 | -import com.zteits.urbanops.module.system.enums.common.CommonConstants; | 26 | +import com.zteits.urbanops.module.system.api.user.dto.AdminUserRespDTO; |
| 27 | +import com.zteits.urbanops.module.system.enums.DictTypeConstants; | ||
| 19 | import jakarta.annotation.Resource; | 28 | import jakarta.annotation.Resource; |
| 20 | import org.apache.commons.collections4.CollectionUtils; | 29 | import org.apache.commons.collections4.CollectionUtils; |
| 21 | import org.apache.commons.lang3.StringUtils; | 30 | import org.apache.commons.lang3.StringUtils; |
| 22 | import org.springframework.stereotype.Service; | 31 | import org.springframework.stereotype.Service; |
| 23 | import org.springframework.transaction.annotation.Transactional; | 32 | import org.springframework.transaction.annotation.Transactional; |
| 24 | 33 | ||
| 34 | +import java.math.BigDecimal; | ||
| 25 | import java.text.DateFormat; | 35 | import java.text.DateFormat; |
| 26 | import java.text.ParseException; | 36 | import java.text.ParseException; |
| 27 | import java.text.SimpleDateFormat; | 37 | import java.text.SimpleDateFormat; |
| 28 | import java.time.LocalDateTime; | 38 | import java.time.LocalDateTime; |
| 39 | +import java.time.LocalTime; | ||
| 29 | import java.time.format.DateTimeFormatter; | 40 | import java.time.format.DateTimeFormatter; |
| 30 | import java.util.*; | 41 | import java.util.*; |
| 31 | import java.util.stream.Collectors; | 42 | import java.util.stream.Collectors; |
| @@ -58,6 +69,12 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ | @@ -58,6 +69,12 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ | ||
| 58 | @Resource | 69 | @Resource |
| 59 | private DeptApi deptApi; | 70 | private DeptApi deptApi; |
| 60 | 71 | ||
| 72 | + @Resource | ||
| 73 | + private DictDataApi dictDataApi; | ||
| 74 | + | ||
| 75 | + @Resource | ||
| 76 | + private UserTaskEfficiencyMapper userTaskEfficiencyMapper; | ||
| 77 | + | ||
| 61 | @Override | 78 | @Override |
| 62 | public List<TaskCompletionStatusRspVo> taskCompletionSummary(AppHomePageSummaryReqVO req) { | 79 | public List<TaskCompletionStatusRspVo> taskCompletionSummary(AppHomePageSummaryReqVO req) { |
| 63 | Long userId = getLoginUserId(); | 80 | Long userId = getLoginUserId(); |
| @@ -408,4 +425,360 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ | @@ -408,4 +425,360 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{ | ||
| 408 | 425 | ||
| 409 | return days; | 426 | return days; |
| 410 | } | 427 | } |
| 428 | + | ||
| 429 | + @Override | ||
| 430 | + public TaskDistanceSortResultVO queryTaskDetailsWithDistance(AppTaskDistanceSortReqVO req) { | ||
| 431 | + // 1. 获取当前登录用户信息 | ||
| 432 | + Long userId = SecurityFrameworkUtils.getLoginUserId(); | ||
| 433 | + Long deptId = SecurityFrameworkUtils.getDeptId(); | ||
| 434 | + List<Long> roleIds = roleApi.getRoleIdsByUserId(userId); | ||
| 435 | + | ||
| 436 | + // 2. 根据 queryType 分流 | ||
| 437 | + List<TaskDistanceSortRespVO> sortedTasks; | ||
| 438 | + if (req.getQueryType() != null && req.getQueryType() == 2) { | ||
| 439 | + // ========== 已办:查询已完成任务(无 GPS 距离排序) ========== | ||
| 440 | + sortedTasks = queryCompletedTasksWithoutGps(userId); | ||
| 441 | + } else { | ||
| 442 | + // ========== 待办:查询待办任务 + GPS 贪心最近邻排序 ========== | ||
| 443 | + sortedTasks = queryPendingTasksWithGpsSort(req, userId, deptId, roleIds); | ||
| 444 | + } | ||
| 445 | + | ||
| 446 | + // 3. 查询班组进度 | ||
| 447 | + TeamProgressVO teamProgress = buildTeamProgress(userId, deptId); | ||
| 448 | + | ||
| 449 | + // 4. 组装返回结果 | ||
| 450 | + TaskDistanceSortResultVO result = new TaskDistanceSortResultVO(); | ||
| 451 | + result.setSortedTasks(sortedTasks); | ||
| 452 | + result.setTeamProgress(teamProgress); | ||
| 453 | + result.setTotalPendingTasks(sortedTasks.size()); | ||
| 454 | + int totalDist = 0; | ||
| 455 | + int totalWalkMin = 0; | ||
| 456 | + int totalTriMin = 0; | ||
| 457 | + for (TaskDistanceSortRespVO task : sortedTasks) { | ||
| 458 | + if (task.getDistance() != null) { | ||
| 459 | + totalDist += task.getDistance(); | ||
| 460 | + totalWalkMin += (task.getEstimatedWalkingMinutes() != null ? task.getEstimatedWalkingMinutes() : 0); | ||
| 461 | + totalTriMin += (task.getEstimatedTricycleMinutes() != null ? task.getEstimatedTricycleMinutes() : 0); | ||
| 462 | + } | ||
| 463 | + } | ||
| 464 | + result.setEstimatedTotalDistance(totalDist); | ||
| 465 | + result.setEstimatedTotalWalkingMinutes(totalWalkMin); | ||
| 466 | + result.setEstimatedTotalTricycleMinutes(totalTriMin); | ||
| 467 | + | ||
| 468 | + return result; | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + /** | ||
| 472 | + * 已办任务查询(无 GPS 排序) | ||
| 473 | + */ | ||
| 474 | + private List<TaskDistanceSortRespVO> queryCompletedTasksWithoutGps(Long userId) { | ||
| 475 | + IPage<TaskDetailsRspVo> page = MyBatisUtils.buildPage(new AppHomePageSummaryPageReqVO()); | ||
| 476 | + // 设置大分页以获取全部已办任务 | ||
| 477 | + page.setSize(Integer.MAX_VALUE); | ||
| 478 | + IPage<TaskDetailsRspVo> mpPage = homepageSummaryMapper.queryCompletedTaskDetails(page, userId); | ||
| 479 | + List<TaskDistanceSortRespVO> result = new ArrayList<>(); | ||
| 480 | + if (mpPage != null && CollectionUtils.isNotEmpty(mpPage.getRecords())) { | ||
| 481 | + int i = 0; | ||
| 482 | + for (TaskDetailsRspVo vo : mpPage.getRecords()) { | ||
| 483 | + TaskDistanceSortRespVO sortVo = new TaskDistanceSortRespVO(); | ||
| 484 | + sortVo.setTaskName(vo.getTaskName()); | ||
| 485 | + sortVo.setBusiDateTime(vo.getBusiDateTime()); | ||
| 486 | + sortVo.setPressingType(vo.getPressingType()); | ||
| 487 | + sortVo.setSortOrder(++i); | ||
| 488 | + sortVo.setDistance(null); | ||
| 489 | + sortVo.setCumulativeDistance(null); | ||
| 490 | + sortVo.setEstimatedWalkingMinutes(null); | ||
| 491 | + sortVo.setEstimatedTricycleMinutes(null); | ||
| 492 | + result.add(sortVo); | ||
| 493 | + } | ||
| 494 | + } | ||
| 495 | + return result; | ||
| 496 | + } | ||
| 497 | + | ||
| 498 | + /** | ||
| 499 | + * 待办任务查询 + GPS 贪心最近邻排序 | ||
| 500 | + */ | ||
| 501 | + private List<TaskDistanceSortRespVO> queryPendingTasksWithGpsSort(AppTaskDistanceSortReqVO req, | ||
| 502 | + Long userId, Long deptId, | ||
| 503 | + List<Long> roleIds) { | ||
| 504 | + // 从字典表读取速度配置 | ||
| 505 | + double walkingSpeedKmh = 4.0; | ||
| 506 | + double tricycleSpeedKmh = 15.0; | ||
| 507 | + try { | ||
| 508 | + List<DictDataRespDTO> speedConfigs = dictDataApi.getDictDataList(DictTypeConstants.TRAVEL_SPEED_CONFIG); | ||
| 509 | + if (CollectionUtils.isNotEmpty(speedConfigs)) { | ||
| 510 | + for (DictDataRespDTO config : speedConfigs) { | ||
| 511 | + if ("步行速度(km/h)".equals(config.getLabel())) { | ||
| 512 | + walkingSpeedKmh = Double.parseDouble(config.getValue()); | ||
| 513 | + } else if ("三轮车速度(km/h)".equals(config.getLabel())) { | ||
| 514 | + tricycleSpeedKmh = Double.parseDouble(config.getValue()); | ||
| 515 | + } | ||
| 516 | + } | ||
| 517 | + } | ||
| 518 | + } catch (Exception e) { | ||
| 519 | + Log.warn("读取出行速度字典配置失败,使用默认值", e); | ||
| 520 | + } | ||
| 521 | + double walkingSpeedMperMin = walkingSpeedKmh * 1000 / 60; | ||
| 522 | + double tricycleSpeedMperMin = tricycleSpeedKmh * 1000 / 60; | ||
| 523 | + | ||
| 524 | + // 查询今日待办任务(含 GPS 坐标) | ||
| 525 | + LocalDateTime now = LocalDateTime.now(); | ||
| 526 | + List<TaskDistanceSortRespVO> allTasks = homepageSummaryMapper | ||
| 527 | + .queryPendingTaskDetailsWithGps(roleIds, userId, deptId, now); | ||
| 528 | + | ||
| 529 | + // 分离有 GPS 和无 GPS 任务 | ||
| 530 | + List<TaskDistanceSortRespVO> tasksWithGps = new ArrayList<>(); | ||
| 531 | + List<TaskDistanceSortRespVO> tasksWithoutGps = new ArrayList<>(); | ||
| 532 | + if (CollectionUtils.isNotEmpty(allTasks)) { | ||
| 533 | + for (TaskDistanceSortRespVO task : allTasks) { | ||
| 534 | + if (task.getTaskLat() != null && task.getTaskLon() != null) { | ||
| 535 | + tasksWithGps.add(task); | ||
| 536 | + } else { | ||
| 537 | + tasksWithoutGps.add(task); | ||
| 538 | + } | ||
| 539 | + } | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + // 贪心最近邻排序 | ||
| 543 | + List<TaskDistanceSortRespVO> sortedTasks = new ArrayList<>(); | ||
| 544 | + double currentLat = req.getCurrentLat().doubleValue(); | ||
| 545 | + double currentLon = req.getCurrentLon().doubleValue(); | ||
| 546 | + int cumulativeDistance = 0; | ||
| 547 | + int sortOrder = 0; | ||
| 548 | + | ||
| 549 | + List<TaskDistanceSortRespVO> remaining = new ArrayList<>(tasksWithGps); | ||
| 550 | + while (!remaining.isEmpty()) { | ||
| 551 | + TaskDistanceSortRespVO nearest = null; | ||
| 552 | + double minDistance = Double.MAX_VALUE; | ||
| 553 | + int nearestIndex = -1; | ||
| 554 | + for (int i = 0; i < remaining.size(); i++) { | ||
| 555 | + TaskDistanceSortRespVO task = remaining.get(i); | ||
| 556 | + double dist = GpsDistanceUtil.haversineDistance( | ||
| 557 | + currentLat, currentLon, | ||
| 558 | + task.getTaskLat().doubleValue(), task.getTaskLon().doubleValue()); | ||
| 559 | + if (dist < minDistance) { | ||
| 560 | + minDistance = dist; | ||
| 561 | + nearest = task; | ||
| 562 | + nearestIndex = i; | ||
| 563 | + } | ||
| 564 | + } | ||
| 565 | + sortOrder++; | ||
| 566 | + int distMeters = (int) Math.round(minDistance); | ||
| 567 | + cumulativeDistance += distMeters; | ||
| 568 | + nearest.setSortOrder(sortOrder); | ||
| 569 | + nearest.setDistance(distMeters); | ||
| 570 | + nearest.setCumulativeDistance(cumulativeDistance); | ||
| 571 | + nearest.setEstimatedWalkingMinutes((int) Math.ceil(distMeters / walkingSpeedMperMin)); | ||
| 572 | + nearest.setEstimatedTricycleMinutes((int) Math.ceil(distMeters / tricycleSpeedMperMin)); | ||
| 573 | + sortedTasks.add(nearest); | ||
| 574 | + | ||
| 575 | + currentLat = nearest.getTaskLat().doubleValue(); | ||
| 576 | + currentLon = nearest.getTaskLon().doubleValue(); | ||
| 577 | + remaining.remove(nearestIndex); | ||
| 578 | + } | ||
| 579 | + | ||
| 580 | + // 无 GPS 任务追加到末尾 | ||
| 581 | + if (CollectionUtils.isNotEmpty(tasksWithoutGps)) { | ||
| 582 | + for (TaskDistanceSortRespVO task : tasksWithoutGps) { | ||
| 583 | + sortOrder++; | ||
| 584 | + task.setSortOrder(sortOrder); | ||
| 585 | + task.setDistance(null); | ||
| 586 | + task.setCumulativeDistance(null); | ||
| 587 | + task.setEstimatedWalkingMinutes(null); | ||
| 588 | + task.setEstimatedTricycleMinutes(null); | ||
| 589 | + sortedTasks.add(task); | ||
| 590 | + } | ||
| 591 | + } | ||
| 592 | + return sortedTasks; | ||
| 593 | + } | ||
| 594 | + | ||
| 595 | + /** | ||
| 596 | + * 构建班组进度统计 | ||
| 597 | + */ | ||
| 598 | + private TeamProgressVO buildTeamProgress(Long userId, Long deptId) { | ||
| 599 | + TeamProgressVO teamProgress = new TeamProgressVO(); | ||
| 600 | + teamProgress.setUserCompletedCount(0); | ||
| 601 | + teamProgress.setTeamAverage(0.0); | ||
| 602 | + teamProgress.setTeamMax(0); | ||
| 603 | + teamProgress.setTeamMemberCount(0); | ||
| 604 | + try { | ||
| 605 | + List<Map<String, Object>> teamData = homepageSummaryMapper.countTeamCompletedToday(deptId); | ||
| 606 | + if (CollectionUtils.isNotEmpty(teamData)) { | ||
| 607 | + int totalCompleted = 0; | ||
| 608 | + int maxCompleted = 0; | ||
| 609 | + for (Map<String, Object> row : teamData) { | ||
| 610 | + Object uidObj = row.get("userId"); | ||
| 611 | + Object cntObj = row.get("completedCount"); | ||
| 612 | + int count = cntObj != null ? ((Number) cntObj).intValue() : 0; | ||
| 613 | + totalCompleted += count; | ||
| 614 | + if (count > maxCompleted) { | ||
| 615 | + maxCompleted = count; | ||
| 616 | + } | ||
| 617 | + if (uidObj != null && userId.equals(((Number) uidObj).longValue())) { | ||
| 618 | + teamProgress.setUserCompletedCount(count); | ||
| 619 | + } | ||
| 620 | + } | ||
| 621 | + int memberCount = teamData.size(); | ||
| 622 | + teamProgress.setTeamMemberCount(memberCount); | ||
| 623 | + teamProgress.setTeamAverage(memberCount > 0 | ||
| 624 | + ? Math.round(totalCompleted * 10.0 / memberCount) / 10.0 : 0.0); | ||
| 625 | + teamProgress.setTeamMax(maxCompleted); | ||
| 626 | + } | ||
| 627 | + } catch (Exception e) { | ||
| 628 | + Log.warn("查询班组完成统计失败", e); | ||
| 629 | + } | ||
| 630 | + return teamProgress; | ||
| 631 | + } | ||
| 632 | + | ||
| 633 | + @Override | ||
| 634 | + public TaskDistanceSortResultVO queryTaskDetailsWithSmartSort(AppTaskSmartSortReqVO req) { | ||
| 635 | + Long userId = SecurityFrameworkUtils.getLoginUserId(); | ||
| 636 | + Long deptId = SecurityFrameworkUtils.getDeptId(); | ||
| 637 | + List<Long> roleIds = roleApi.getRoleIdsByUserId(userId); | ||
| 638 | + | ||
| 639 | + // 1. 从字典读取配置 | ||
| 640 | + double distWeight = 0.6, effWeight = 0.4, threshold = 2000; | ||
| 641 | + double clampMin = 0.3, clampMax = 3.0; | ||
| 642 | + try { | ||
| 643 | + List<DictDataRespDTO> configs = dictDataApi.getDictDataList(DictTypeConstants.TASK_SORT_WEIGHT_CONFIG); | ||
| 644 | + if (CollectionUtils.isNotEmpty(configs)) { | ||
| 645 | + for (DictDataRespDTO c : configs) { | ||
| 646 | + if ("距离权重".equals(c.getLabel())) distWeight = Double.parseDouble(c.getValue()); | ||
| 647 | + else if ("效率权重".equals(c.getLabel())) effWeight = Double.parseDouble(c.getValue()); | ||
| 648 | + else if ("效率比上限".equals(c.getLabel())) clampMax = Double.parseDouble(c.getValue()); | ||
| 649 | + else if ("效率比下限".equals(c.getLabel())) clampMin = Double.parseDouble(c.getValue()); | ||
| 650 | + } | ||
| 651 | + } | ||
| 652 | + List<DictDataRespDTO> speedConfigs = dictDataApi.getDictDataList(DictTypeConstants.TRAVEL_SPEED_CONFIG); | ||
| 653 | + if (CollectionUtils.isNotEmpty(speedConfigs)) { | ||
| 654 | + for (DictDataRespDTO c : speedConfigs) { | ||
| 655 | + if ("步行可达距离阈值(m)".equals(c.getLabel())) threshold = Double.parseDouble(c.getValue()); | ||
| 656 | + } | ||
| 657 | + } | ||
| 658 | + } catch (Exception e) { | ||
| 659 | + Log.warn("读取智能排序配置失败", e); | ||
| 660 | + } | ||
| 661 | + | ||
| 662 | + // 2. 查询今日待办任务 | ||
| 663 | + List<TaskDistanceSortRespVO> allTasks = homepageSummaryMapper | ||
| 664 | + .queryPendingTaskDetailsWithGps(roleIds, userId, deptId, LocalDateTime.now()); | ||
| 665 | + | ||
| 666 | + // 3. 查询效率矩阵 | ||
| 667 | + Map<String, BigDecimal> effMap = loadEfficiencyMap(userId); | ||
| 668 | + | ||
| 669 | + // 4. 为每个任务打分 | ||
| 670 | + List<TaskDistanceSortRespVO> tasksWithGps = new ArrayList<>(); | ||
| 671 | + List<TaskDistanceSortRespVO> tasksWithoutGps = new ArrayList<>(); | ||
| 672 | + if (CollectionUtils.isNotEmpty(allTasks)) { | ||
| 673 | + for (TaskDistanceSortRespVO task : allTasks) { | ||
| 674 | + if (task.getTaskLat() != null && task.getTaskLon() != null) { | ||
| 675 | + double dist = GpsDistanceUtil.haversineDistance( | ||
| 676 | + req.getCurrentLat().doubleValue(), req.getCurrentLon().doubleValue(), | ||
| 677 | + task.getTaskLat().doubleValue(), task.getTaskLon().doubleValue()); | ||
| 678 | + double distScore = Math.max(0, 1 - dist / threshold); | ||
| 679 | + task.setDistanceScore(Math.round(distScore * 100.0) / 100.0); | ||
| 680 | + | ||
| 681 | + BigDecimal effRatio = lookupEfficiency(effMap, task.getTaskType(), task.getTaskSubType()); | ||
| 682 | + double effScore = clamp(effRatio.doubleValue(), clampMin, clampMax); | ||
| 683 | + task.setEfficiencyScore(Math.round(effScore * 100.0) / 100.0); | ||
| 684 | + | ||
| 685 | + task.setCompositeScore(Math.round((distWeight * distScore + effWeight * effScore) * 100.0) / 100.0); | ||
| 686 | + tasksWithGps.add(task); | ||
| 687 | + } else { | ||
| 688 | + BigDecimal effRatio = lookupEfficiency(effMap, task.getTaskType(), task.getTaskSubType()); | ||
| 689 | + double effScore = clamp(effRatio.doubleValue(), clampMin, clampMax); | ||
| 690 | + task.setDistanceScore(0.0); | ||
| 691 | + task.setEfficiencyScore(Math.round(effScore * 100.0) / 100.0); | ||
| 692 | + task.setCompositeScore(Math.round((effWeight * effScore) * 100.0) / 100.0); | ||
| 693 | + tasksWithoutGps.add(task); | ||
| 694 | + } | ||
| 695 | + } | ||
| 696 | + } | ||
| 697 | + | ||
| 698 | + // 5. 排序:有GPS按综合分降序,无GPS追加末尾按效率分降序 | ||
| 699 | + tasksWithGps.sort((a, b) -> Double.compare( | ||
| 700 | + b.getCompositeScore() != null ? b.getCompositeScore() : 0, | ||
| 701 | + a.getCompositeScore() != null ? a.getCompositeScore() : 0)); | ||
| 702 | + tasksWithoutGps.sort((a, b) -> Double.compare( | ||
| 703 | + b.getEfficiencyScore() != null ? b.getEfficiencyScore() : 0, | ||
| 704 | + a.getEfficiencyScore() != null ? a.getEfficiencyScore() : 0)); | ||
| 705 | + | ||
| 706 | + List<TaskDistanceSortRespVO> sortedTasks = new ArrayList<>(); | ||
| 707 | + int order = 0; | ||
| 708 | + for (TaskDistanceSortRespVO t : tasksWithGps) { | ||
| 709 | + t.setSortOrder(++order); | ||
| 710 | + int distMeters = (int) Math.round(GpsDistanceUtil.haversineDistance( | ||
| 711 | + req.getCurrentLat().doubleValue(), req.getCurrentLon().doubleValue(), | ||
| 712 | + t.getTaskLat().doubleValue(), t.getTaskLon().doubleValue())); | ||
| 713 | + t.setDistance(distMeters); | ||
| 714 | + sortedTasks.add(t); | ||
| 715 | + } | ||
| 716 | + for (TaskDistanceSortRespVO t : tasksWithoutGps) { | ||
| 717 | + t.setSortOrder(++order); | ||
| 718 | + t.setDistance(null); | ||
| 719 | + sortedTasks.add(t); | ||
| 720 | + } | ||
| 721 | + | ||
| 722 | + // 6. 班组进度 | ||
| 723 | + TeamProgressVO teamProgress = buildTeamProgress(userId, deptId); | ||
| 724 | + | ||
| 725 | + // 7. 组装结果 | ||
| 726 | + TaskDistanceSortResultVO result = new TaskDistanceSortResultVO(); | ||
| 727 | + result.setSortedTasks(sortedTasks); | ||
| 728 | + result.setTeamProgress(teamProgress); | ||
| 729 | + result.setTotalPendingTasks(sortedTasks.size()); | ||
| 730 | + int totalDist = 0; | ||
| 731 | + for (TaskDistanceSortRespVO t : sortedTasks) { | ||
| 732 | + if (t.getDistance() != null) totalDist += t.getDistance(); | ||
| 733 | + } | ||
| 734 | + result.setEstimatedTotalDistance(totalDist); | ||
| 735 | + return result; | ||
| 736 | + } | ||
| 737 | + | ||
| 738 | + /** | ||
| 739 | + * 加载当前用户当月效率矩阵 | ||
| 740 | + */ | ||
| 741 | + private Map<String, BigDecimal> loadEfficiencyMap(Long userId) { | ||
| 742 | + Map<String, BigDecimal> map = new LinkedHashMap<>(); | ||
| 743 | + try { | ||
| 744 | + String statPeriod = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM")); | ||
| 745 | + List<UserTaskEfficiencyDO> list = userTaskEfficiencyMapper.selectByUserIdAndPeriod(userId, statPeriod); | ||
| 746 | + if (CollectionUtils.isEmpty(list)) { | ||
| 747 | + // fallback: 查最近一个周期的数据 | ||
| 748 | + String latestPeriod = userTaskEfficiencyMapper.selectLatestPeriod(userId); | ||
| 749 | + if (latestPeriod != null) { | ||
| 750 | + list = userTaskEfficiencyMapper.selectByUserIdAndPeriod(userId, latestPeriod); | ||
| 751 | + } | ||
| 752 | + } | ||
| 753 | + if (CollectionUtils.isNotEmpty(list)) { | ||
| 754 | + for (UserTaskEfficiencyDO e : list) { | ||
| 755 | + String key = e.getTaskCategory() + "_" + e.getTaskType(); | ||
| 756 | + map.put(key, e.getEfficiencyRatio() != null ? e.getEfficiencyRatio() : BigDecimal.ONE); | ||
| 757 | + } | ||
| 758 | + } | ||
| 759 | + } catch (Exception e) { | ||
| 760 | + Log.warn("加载效率矩阵失败", e); | ||
| 761 | + } | ||
| 762 | + return map; | ||
| 763 | + } | ||
| 764 | + | ||
| 765 | + /** | ||
| 766 | + * 从效率矩阵查找,先精确匹配再 fallback 大类 | ||
| 767 | + */ | ||
| 768 | + private BigDecimal lookupEfficiency(Map<String, BigDecimal> map, String category, String subType) { | ||
| 769 | + // 精确匹配 | ||
| 770 | + String exactKey = category + "_" + subType; | ||
| 771 | + if (map.containsKey(exactKey)) return map.get(exactKey); | ||
| 772 | + // fallback: 大类汇总 | ||
| 773 | + String fallbackKey = category + "_ALL"; | ||
| 774 | + if (map.containsKey(fallbackKey)) return map.get(fallbackKey); | ||
| 775 | + // 无数据:中性 | ||
| 776 | + return BigDecimal.ONE; | ||
| 777 | + } | ||
| 778 | + | ||
| 779 | + private double clamp(double val, double min, double max) { | ||
| 780 | + if (val > max) return max; | ||
| 781 | + if (val < min) return min; | ||
| 782 | + return val; | ||
| 783 | + } | ||
| 411 | } | 784 | } |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/util/GpsDistanceUtil.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.util; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * GPS 距离计算工具类 | ||
| 5 | + * | ||
| 6 | + * @author | ||
| 7 | + */ | ||
| 8 | +public class GpsDistanceUtil { | ||
| 9 | + | ||
| 10 | + /** 地球半径(米) */ | ||
| 11 | + private static final double EARTH_RADIUS_M = 6_371_000; | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * Haversine 公式计算两点之间的球面距离 | ||
| 15 | + * | ||
| 16 | + * @param lat1 点1纬度 | ||
| 17 | + * @param lon1 点1经度 | ||
| 18 | + * @param lat2 点2纬度 | ||
| 19 | + * @param lon2 点2经度 | ||
| 20 | + * @return 两点之间的距离(米) | ||
| 21 | + */ | ||
| 22 | + public static double haversineDistance(double lat1, double lon1, | ||
| 23 | + double lat2, double lon2) { | ||
| 24 | + double dLat = Math.toRadians(lat2 - lat1); | ||
| 25 | + double dLon = Math.toRadians(lon2 - lon1); | ||
| 26 | + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) | ||
| 27 | + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) | ||
| 28 | + * Math.sin(dLon / 2) * Math.sin(dLon / 2); | ||
| 29 | + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); | ||
| 30 | + return EARTH_RADIUS_M * c; | ||
| 31 | + } | ||
| 32 | +} |
urbanops-module-garden/src/main/resources/mapper/UserTaskEfficiencyMapper.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||
| 3 | +<mapper namespace="com.zteits.urbanops.module.garden.dal.mysql.UserTaskEfficiencyMapper"> | ||
| 4 | + | ||
| 5 | + <select id="selectByUserIdAndPeriod" resultType="com.zteits.urbanops.module.garden.dal.dataobject.UserTaskEfficiencyDO"> | ||
| 6 | + SELECT * FROM garden_user_task_efficiency | ||
| 7 | + WHERE user_id = #{userId} | ||
| 8 | + AND stat_period = #{statPeriod} | ||
| 9 | + AND deleted = 0 | ||
| 10 | + </select> | ||
| 11 | + | ||
| 12 | + <select id="selectLatestPeriod" resultType="java.lang.String"> | ||
| 13 | + SELECT stat_period FROM garden_user_task_efficiency | ||
| 14 | + WHERE user_id = #{userId} | ||
| 15 | + AND deleted = 0 | ||
| 16 | + ORDER BY stat_period DESC | ||
| 17 | + LIMIT 1 | ||
| 18 | + </select> | ||
| 19 | + | ||
| 20 | + <delete id="deleteByPeriod"> | ||
| 21 | + DELETE FROM garden_user_task_efficiency | ||
| 22 | + WHERE stat_period = #{statPeriod} | ||
| 23 | + </delete> | ||
| 24 | + | ||
| 25 | + <!-- 巡查计划效率统计 --> | ||
| 26 | + <select id="statInspectionEfficiency" resultType="java.util.Map"> | ||
| 27 | + SELECT c.user_id, u.dept_id, 'inspection' AS task_category, | ||
| 28 | + p.plan_type_id AS task_type, COUNT(*) AS cnt, | ||
| 29 | + SUM(TIMESTAMPDIFF(MINUTE, d.begin_time, c.finish_time)) AS total_min, | ||
| 30 | + AVG(TIMESTAMPDIFF(MINUTE, d.begin_time, c.finish_time)) AS avg_min | ||
| 31 | + FROM garden_inspection_plan_commit c | ||
| 32 | + INNER JOIN garden_inspection_plan_detail d ON c.batch_no = d.batch_no AND c.user_id = d.user_id | ||
| 33 | + INNER JOIN garden_inspection_plan p ON c.batch_no = p.batch_no | ||
| 34 | + INNER JOIN system_users u ON c.user_id = u.id | ||
| 35 | + WHERE c.finish_time >= #{beginTime} | ||
| 36 | + AND c.finish_time IS NOT NULL | ||
| 37 | + AND d.begin_time IS NOT NULL | ||
| 38 | + AND TIMESTAMPDIFF(MINUTE, d.begin_time, c.finish_time) BETWEEN 1 AND 1440 | ||
| 39 | + GROUP BY c.user_id, u.dept_id, p.plan_type_id | ||
| 40 | + </select> | ||
| 41 | + | ||
| 42 | + <!-- 养护计划效率统计 --> | ||
| 43 | + <select id="statMaintainEfficiency" resultType="java.util.Map"> | ||
| 44 | + SELECT c.user_id, u.dept_id, 'maintain' AS task_category, | ||
| 45 | + p.plan_type_id AS task_type, COUNT(*) AS cnt, | ||
| 46 | + SUM(TIMESTAMPDIFF(MINUTE, d.begin_time, c.finish_time)) AS total_min, | ||
| 47 | + AVG(TIMESTAMPDIFF(MINUTE, d.begin_time, c.finish_time)) AS avg_min | ||
| 48 | + FROM garden_maintain_plan_commit c | ||
| 49 | + INNER JOIN garden_maintain_plan_detail d ON c.batch_no = d.batch_no AND c.user_id = d.user_id | ||
| 50 | + INNER JOIN garden_maintain_plan p ON c.batch_no = p.batch_no | ||
| 51 | + INNER JOIN system_users u ON c.user_id = u.id | ||
| 52 | + WHERE c.finish_time >= #{beginTime} | ||
| 53 | + AND c.finish_time IS NOT NULL | ||
| 54 | + AND d.begin_time IS NOT NULL | ||
| 55 | + AND TIMESTAMPDIFF(MINUTE, d.begin_time, c.finish_time) BETWEEN 1 AND 1440 | ||
| 56 | + GROUP BY c.user_id, u.dept_id, p.plan_type_id | ||
| 57 | + </select> | ||
| 58 | + | ||
| 59 | + <!-- 工单效率统计 --> | ||
| 60 | + <select id="statWorkOrderEfficiency" resultType="java.util.Map"> | ||
| 61 | + SELECT t.ASSIGNEE_ AS user_id, u.dept_id, 'workorder' AS task_category, | ||
| 62 | + m.order_type AS task_type, COUNT(*) AS cnt, | ||
| 63 | + SUM(TIMESTAMPDIFF(MINUTE, t.START_TIME_, t.END_TIME_)) AS total_min, | ||
| 64 | + AVG(TIMESTAMPDIFF(MINUTE, t.START_TIME_, t.END_TIME_)) AS avg_min | ||
| 65 | + FROM act_hi_taskinst t | ||
| 66 | + LEFT JOIN workorder_main_info m ON t.PROC_INST_ID_ = m.process_instance_id AND m.deleted = 0 | ||
| 67 | + INNER JOIN system_users u ON t.ASSIGNEE_ = u.id | ||
| 68 | + WHERE t.END_TIME_ >= #{beginTime} | ||
| 69 | + AND t.state_ = 'completed' | ||
| 70 | + AND t.START_TIME_ IS NOT NULL AND t.END_TIME_ IS NOT NULL | ||
| 71 | + AND m.order_name IS NOT NULL | ||
| 72 | + AND TIMESTAMPDIFF(MINUTE, t.START_TIME_, t.END_TIME_) BETWEEN 1 AND 1440 | ||
| 73 | + GROUP BY t.ASSIGNEE_, u.dept_id, m.order_type | ||
| 74 | + </select> | ||
| 75 | + | ||
| 76 | +</mapper> |
urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml
| @@ -615,5 +615,111 @@ | @@ -615,5 +615,111 @@ | ||
| 615 | GROUP BY w.worker_company_id, d.name | 615 | GROUP BY w.worker_company_id, d.name |
| 616 | </select> | 616 | </select> |
| 617 | 617 | ||
| 618 | + <!-- 查询今日待办任务(含 GPS 坐标) --> | ||
| 619 | + <select id="queryPendingTaskDetailsWithGps" resultType="com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDistanceSortRespVO"> | ||
| 620 | + SELECT taskName, busiDateTime, pressingType, taskLat, taskLon, taskType, taskSubType | ||
| 621 | + FROM ( | ||
| 622 | + SELECT | ||
| 623 | + a.plan_name AS taskName, | ||
| 624 | + c.begin_time AS busiDateTime, | ||
| 625 | + NULL AS pressingType, | ||
| 626 | + (CAST(r.starting_latitude AS DECIMAL(18,10)) | ||
| 627 | + + CAST(r.end_latitude AS DECIMAL(18,10))) / 2 AS taskLat, | ||
| 628 | + (CAST(r.starting_longitude AS DECIMAL(18,10)) | ||
| 629 | + + CAST(r.end_longitude AS DECIMAL(18,10))) / 2 AS taskLon, | ||
| 630 | + 'inspection' AS taskType, | ||
| 631 | + a.plan_type_id AS taskSubType | ||
| 632 | + FROM garden_inspection_plan a | ||
| 633 | + INNER JOIN garden_inspection_plan_role b ON a.batch_no = b.batch_no | ||
| 634 | + INNER JOIN garden_inspection_plan_detail c ON a.batch_no = c.batch_no | ||
| 635 | + LEFT JOIN garden_road r ON a.road_id = r.id AND r.deleted = 0 | ||
| 636 | + WHERE a.deleted = 0 | ||
| 637 | + AND c.finish_state = 1 | ||
| 638 | + AND c.begin_time <= #{currentDate} | ||
| 639 | + AND c.end_time >= #{currentDate} | ||
| 640 | + AND a.dept_id = #{deptId} | ||
| 641 | + AND b.role_id IN | ||
| 642 | + <foreach collection='roleIds' item='roleId' open='(' close=')' separator=','> | ||
| 643 | + #{roleId} | ||
| 644 | + </foreach> | ||
| 645 | + AND r.starting_latitude IS NOT NULL AND r.starting_latitude != '' | ||
| 646 | + | ||
| 647 | + UNION ALL | ||
| 648 | + | ||
| 649 | + SELECT | ||
| 650 | + a.plan_name AS taskName, | ||
| 651 | + c.begin_time AS busiDateTime, | ||
| 652 | + NULL AS pressingType, | ||
| 653 | + (CAST(r.starting_latitude AS DECIMAL(18,10)) | ||
| 654 | + + CAST(r.end_latitude AS DECIMAL(18,10))) / 2 AS taskLat, | ||
| 655 | + (CAST(r.starting_longitude AS DECIMAL(18,10)) | ||
| 656 | + + CAST(r.end_longitude AS DECIMAL(18,10))) / 2 AS taskLon, | ||
| 657 | + 'maintain' AS taskType, | ||
| 658 | + a.plan_type_id AS taskSubType | ||
| 659 | + FROM garden_maintain_plan a | ||
| 660 | + INNER JOIN garden_maintain_plan_role b ON a.batch_no = b.batch_no | ||
| 661 | + INNER JOIN garden_maintain_plan_detail c ON a.batch_no = c.batch_no | ||
| 662 | + LEFT JOIN garden_road r ON a.road_id = r.id AND r.deleted = 0 | ||
| 663 | + WHERE a.deleted = 0 | ||
| 664 | + AND c.finish_state = 1 | ||
| 665 | + AND c.begin_time <= #{currentDate} | ||
| 666 | + AND c.end_time >= #{currentDate} | ||
| 667 | + AND a.dept_id = #{deptId} | ||
| 668 | + AND b.role_id IN | ||
| 669 | + <foreach collection='roleIds' item='roleId' open='(' close=')' separator=','> | ||
| 670 | + #{roleId} | ||
| 671 | + </foreach> | ||
| 672 | + AND r.starting_latitude IS NOT NULL AND r.starting_latitude != '' | ||
| 673 | + | ||
| 674 | + UNION ALL | ||
| 675 | + | ||
| 676 | + SELECT | ||
| 677 | + m.order_name AS taskName, | ||
| 678 | + m.create_time AS busiDateTime, | ||
| 679 | + m.pressing_type AS pressingType, | ||
| 680 | + m.lat AS taskLat, | ||
| 681 | + m.lon AS taskLon, | ||
| 682 | + 'workorder' AS taskType, | ||
| 683 | + m.order_type AS taskSubType | ||
| 684 | + FROM act_ru_task t | ||
| 685 | + LEFT JOIN workorder_main_info m | ||
| 686 | + ON t.PROC_INST_ID_ = m.process_instance_id AND m.deleted = 0 | ||
| 687 | + WHERE t.SUSPENSION_STATE_ = 1 | ||
| 688 | + AND m.order_name IS NOT NULL | ||
| 689 | + AND t.ASSIGNEE_ = #{userId} | ||
| 690 | + ) t | ||
| 691 | + </select> | ||
| 692 | + | ||
| 693 | + <select id="countTeamCompletedToday" resultType="java.util.Map"> | ||
| 694 | + SELECT | ||
| 695 | + u.id AS userId, | ||
| 696 | + u.nickname AS userName, | ||
| 697 | + COALESCE(insp.cnt, 0) + COALESCE(mt.cnt, 0) | ||
| 698 | + + COALESCE(wo.cnt, 0) AS completedCount | ||
| 699 | + FROM system_users u | ||
| 700 | + LEFT JOIN ( | ||
| 701 | + SELECT user_id, COUNT(*) AS cnt | ||
| 702 | + FROM garden_inspection_plan_commit | ||
| 703 | + WHERE DATE(finish_time) = CURDATE() | ||
| 704 | + GROUP BY user_id | ||
| 705 | + ) insp ON u.id = insp.user_id | ||
| 706 | + LEFT JOIN ( | ||
| 707 | + SELECT user_id, COUNT(*) AS cnt | ||
| 708 | + FROM garden_maintain_plan_commit | ||
| 709 | + WHERE DATE(finish_time) = CURDATE() | ||
| 710 | + GROUP BY user_id | ||
| 711 | + ) mt ON u.id = mt.user_id | ||
| 712 | + LEFT JOIN ( | ||
| 713 | + SELECT t.ASSIGNEE_ AS user_id, COUNT(DISTINCT m.order_no) AS cnt | ||
| 714 | + FROM act_hi_taskinst t | ||
| 715 | + LEFT JOIN workorder_main_info m | ||
| 716 | + ON t.PROC_INST_ID_ = m.process_instance_id AND m.deleted = 0 | ||
| 717 | + WHERE t.state_ = 'completed' | ||
| 718 | + AND DATE(t.END_TIME_) = CURDATE() | ||
| 719 | + GROUP BY t.ASSIGNEE_ | ||
| 720 | + ) wo ON u.id = wo.user_id | ||
| 721 | + WHERE u.dept_id = #{deptId} | ||
| 722 | + AND u.deleted = 0 | ||
| 723 | + </select> | ||
| 618 | 724 | ||
| 619 | </mapper> | 725 | </mapper> |
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/DictTypeConstants.java
| @@ -32,8 +32,8 @@ public interface DictTypeConstants { | @@ -32,8 +32,8 @@ public interface DictTypeConstants { | ||
| 32 | 32 | ||
| 33 | String SYSTEM_IS_INNER = "system_is_inner";//是否是内部员工 | 33 | String SYSTEM_IS_INNER = "system_is_inner";//是否是内部员工 |
| 34 | 34 | ||
| 35 | + String TRAVEL_SPEED_CONFIG = "travel_speed_config"; // 出行速度配置 | ||
| 35 | 36 | ||
| 36 | - | ||
| 37 | - | 37 | + String TASK_SORT_WEIGHT_CONFIG = "task_sort_weight_config"; // 智能排序权重配置 |
| 38 | 38 | ||
| 39 | } | 39 | } |
urbanops-server/src/main/resources/application.yaml
| @@ -271,7 +271,7 @@ urbanops: | @@ -271,7 +271,7 @@ urbanops: | ||
| 271 | - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 | 271 | - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 |
| 272 | security: | 272 | security: |
| 273 | permit-all_urls: | 273 | permit-all_urls: |
| 274 | - - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 | 274 | + - /admin-api/mp/open/** |
| 275 | - /admin-api/garden/device-user/** | 275 | - /admin-api/garden/device-user/** |
| 276 | api-encrypt: | 276 | api-encrypt: |
| 277 | enable: true # 是否开启 API 加密 | 277 | enable: true # 是否开启 API 加密 |