Commit 28be17376aa39d5b78709e248fc89709ef434822

Authored by 王彪总
1 parent 35590a5b

feat(garden): 新增任务距离排序和智能排序功能

- 实现任务按距离排序和班组进度统计功能
- 添加任务智能排序(距离+个人效率加权)功能
- 新增GPS距离计算工具类和相关数据传输对象
- 添加出行速度配置和智能排序权重配置字典类型
- 实现用户任务效率预计算定时任务
- 优化API签名切面注解使用方式
- 添加微信公众号开放平台回调接口免登录配置
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDetailsRspVo.java
... ... @@ -17,9 +17,13 @@ public class TaskDetailsRspVo {
17 17 private String taskName;
18 18 /*创建时间或完成时间*/
19 19 private LocalDateTime busiDateTime;
20   - /**
21   - * 紧急程度:1:特急;2:紧急;3:一般
22   - */
  20 + /*紧急程度:1:特急;2:紧急;3:一般*/
23 21 private Integer pressingType;
  22 + /*计划编码*/
  23 + private String planNo;
  24 + /*批次号*/
  25 + private String batchNo;
  26 + /*任务类型:inspection/maintain/workorder*/
  27 + private String taskType;
24 28  
25 29 }
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/app/homepage/vo/TaskDistanceSortRespVO.java
... ... @@ -14,9 +14,6 @@ import java.math.BigDecimal;
14 14 @EqualsAndHashCode(callSuper = true)
15 15 public class TaskDistanceSortRespVO extends TaskDetailsRspVo {
16 16  
17   - /** 任务类型:inspection / maintain / workorder */
18   - private String taskType;
19   -
20 17 /** 任务点经度 */
21 18 private BigDecimal taskLon;
22 19  
... ... @@ -47,6 +44,9 @@ public class TaskDistanceSortRespVO extends TaskDetailsRspVo {
47 44 /** 效率得分(已截断) */
48 45 private Double efficiencyScore;
49 46  
  47 + /** 员工历史任务完成效率 %,100=班组均值,大于100比班组快,小于100比班组慢 */
  48 + private Integer efficiencyPercent;
  49 +
50 50 /** 综合得分 */
51 51 private Double compositeScore;
52 52  
... ...
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/homepage/HomepageSummaryServiceImpl.java
... ... @@ -3,6 +3,7 @@ package com.zteits.urbanops.module.garden.service.homepage;
3 3 import cn.hutool.core.bean.BeanUtil;
4 4 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
5 5 import com.baomidou.mybatisplus.core.metadata.IPage;
  6 +import com.alibaba.fastjson.JSON;
6 7 import com.esotericsoftware.minlog.Log;
7 8 import com.zteits.urbanops.framework.common.pojo.PageResult;
8 9 import com.zteits.urbanops.framework.common.util.date.DateUtils;
... ... @@ -465,6 +466,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
465 466 result.setEstimatedTotalWalkingMinutes(totalWalkMin);
466 467 result.setEstimatedTotalTricycleMinutes(totalTriMin);
467 468  
  469 + Log.info("返回App参数: " + JSON.toJSONString(result));
468 470 return result;
469 471 }
470 472  
... ... @@ -480,10 +482,15 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
480 482 if (mpPage != null && CollectionUtils.isNotEmpty(mpPage.getRecords())) {
481 483 int i = 0;
482 484 for (TaskDetailsRspVo vo : mpPage.getRecords()) {
  485 + // 跳过工单,仅返回巡查/养护
  486 + if ("workorder".equals(vo.getTaskType())) continue;
483 487 TaskDistanceSortRespVO sortVo = new TaskDistanceSortRespVO();
484 488 sortVo.setTaskName(vo.getTaskName());
485 489 sortVo.setBusiDateTime(vo.getBusiDateTime());
486 490 sortVo.setPressingType(vo.getPressingType());
  491 + sortVo.setPlanNo(vo.getPlanNo());
  492 + sortVo.setBatchNo(vo.getBatchNo());
  493 + sortVo.setTaskType(vo.getTaskType());
487 494 sortVo.setSortOrder(++i);
488 495 sortVo.setDistance(null);
489 496 sortVo.setCumulativeDistance(null);
... ... @@ -492,6 +499,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
492 499 result.add(sortVo);
493 500 }
494 501 }
  502 + Log.info("返回App参数: " + JSON.toJSONString(result));
495 503 return result;
496 504 }
497 505  
... ... @@ -620,8 +628,9 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
620 628 }
621 629 int memberCount = teamData.size();
622 630 teamProgress.setTeamMemberCount(memberCount);
623   - teamProgress.setTeamAverage(memberCount > 0
624   - ? Math.round(totalCompleted * 10.0 / memberCount) / 10.0 : 0.0);
  631 + double avg = memberCount > 0 ? Math.round(totalCompleted * 10.0 / memberCount) / 10.0 : 0.0;
  632 + if (avg == 0.0 && totalCompleted > 0) avg = 0.1;
  633 + teamProgress.setTeamAverage(avg);
625 634 teamProgress.setTeamMax(maxCompleted);
626 635 }
627 636 } catch (Exception e) {
... ... @@ -643,7 +652,8 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
643 652 result.setSortedTasks(sortedTasks);
644 653 result.setTeamProgress(teamProgress);
645 654 result.setTotalPendingTasks(sortedTasks.size());
646   - return result;
  655 + Log.info("返回App参数: " + JSON.toJSONString(result));
  656 + return result;
647 657 }
648 658  
649 659 List<Long> roleIds = roleApi.getRoleIdsByUserId(userId);
... ... @@ -676,31 +686,34 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
676 686 .queryPendingTaskDetailsWithGps(roleIds, userId, deptId, LocalDateTime.now());
677 687  
678 688 // 3. 查询效率矩阵
679   - Map<String, BigDecimal> effMap = loadEfficiencyMap(userId);
  689 + Map<String, UserTaskEfficiencyDO> effMap = loadEfficiencyMap(userId);
680 690  
681 691 // 4. 为每个任务打分
682 692 List<TaskDistanceSortRespVO> tasksWithGps = new ArrayList<>();
683 693 List<TaskDistanceSortRespVO> tasksWithoutGps = new ArrayList<>();
684 694 if (CollectionUtils.isNotEmpty(allTasks)) {
685 695 for (TaskDistanceSortRespVO task : allTasks) {
  696 + UserTaskEfficiencyDO effDO = lookupEfficiency(effMap, task.getTaskType(), task.getTaskSubType());
  697 + BigDecimal effRatio = (effDO != null && effDO.getEfficiencyRatio() != null)
  698 + ? effDO.getEfficiencyRatio() : BigDecimal.ONE;
  699 + double effScore = clamp(effRatio.doubleValue(), clampMin, clampMax);
  700 + task.setEfficiencyScore(Math.round(effScore * 100.0) / 100.0);
  701 + task.setEfficiencyPercent((int) Math.round(effRatio.doubleValue() * 100));
  702 + if (effDO != null) {
  703 + task.setUserAvgMinutes(effDO.getAvgMinutes());
  704 + task.setTeamAvgMinutes(effDO.getTeamAvgMinutes());
  705 + }
  706 +
686 707 if (task.getTaskLat() != null && task.getTaskLon() != null) {
687 708 double dist = GpsDistanceUtil.haversineDistance(
688 709 req.getCurrentLat().doubleValue(), req.getCurrentLon().doubleValue(),
689 710 task.getTaskLat().doubleValue(), task.getTaskLon().doubleValue());
690 711 double distScore = Math.max(0, 1 - dist / threshold);
691 712 task.setDistanceScore(Math.round(distScore * 100.0) / 100.0);
692   -
693   - BigDecimal effRatio = lookupEfficiency(effMap, task.getTaskType(), task.getTaskSubType());
694   - double effScore = clamp(effRatio.doubleValue(), clampMin, clampMax);
695   - task.setEfficiencyScore(Math.round(effScore * 100.0) / 100.0);
696   -
697 713 task.setCompositeScore(Math.round((distWeight * distScore + effWeight * effScore) * 100.0) / 100.0);
698 714 tasksWithGps.add(task);
699 715 } else {
700   - BigDecimal effRatio = lookupEfficiency(effMap, task.getTaskType(), task.getTaskSubType());
701   - double effScore = clamp(effRatio.doubleValue(), clampMin, clampMax);
702 716 task.setDistanceScore(0.0);
703   - task.setEfficiencyScore(Math.round(effScore * 100.0) / 100.0);
704 717 task.setCompositeScore(Math.round((effWeight * effScore) * 100.0) / 100.0);
705 718 tasksWithoutGps.add(task);
706 719 }
... ... @@ -744,19 +757,19 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
744 757 if (t.getDistance() != null) totalDist += t.getDistance();
745 758 }
746 759 result.setEstimatedTotalDistance(totalDist);
  760 + Log.info("返回App参数: " + JSON.toJSONString(result));
747 761 return result;
748 762 }
749 763  
750 764 /**
751 765 * 加载当前用户当月效率矩阵
752 766 */
753   - private Map<String, BigDecimal> loadEfficiencyMap(Long userId) {
754   - Map<String, BigDecimal> map = new LinkedHashMap<>();
  767 + private Map<String, UserTaskEfficiencyDO> loadEfficiencyMap(Long userId) {
  768 + Map<String, UserTaskEfficiencyDO> map = new LinkedHashMap<>();
755 769 try {
756 770 String statPeriod = java.time.LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM"));
757 771 List<UserTaskEfficiencyDO> list = userTaskEfficiencyMapper.selectByUserIdAndPeriod(userId, statPeriod);
758 772 if (CollectionUtils.isEmpty(list)) {
759   - // fallback: 查最近一个周期的数据
760 773 String latestPeriod = userTaskEfficiencyMapper.selectLatestPeriod(userId);
761 774 if (latestPeriod != null) {
762 775 list = userTaskEfficiencyMapper.selectByUserIdAndPeriod(userId, latestPeriod);
... ... @@ -765,7 +778,7 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
765 778 if (CollectionUtils.isNotEmpty(list)) {
766 779 for (UserTaskEfficiencyDO e : list) {
767 780 String key = e.getTaskCategory() + "_" + e.getTaskType();
768   - map.put(key, e.getEfficiencyRatio() != null ? e.getEfficiencyRatio() : BigDecimal.ONE);
  781 + map.put(key, e);
769 782 }
770 783 }
771 784 } catch (Exception e) {
... ... @@ -774,18 +787,12 @@ public class HomepageSummaryServiceImpl implements HomepageSummaryService{
774 787 return map;
775 788 }
776 789  
777   - /**
778   - * 从效率矩阵查找,先精确匹配再 fallback 大类
779   - */
780   - private BigDecimal lookupEfficiency(Map<String, BigDecimal> map, String category, String subType) {
781   - // 精确匹配
  790 + private UserTaskEfficiencyDO lookupEfficiency(Map<String, UserTaskEfficiencyDO> map, String category, String subType) {
782 791 String exactKey = category + "_" + subType;
783 792 if (map.containsKey(exactKey)) return map.get(exactKey);
784   - // fallback: 大类汇总
785 793 String fallbackKey = category + "_ALL";
786 794 if (map.containsKey(fallbackKey)) return map.get(fallbackKey);
787   - // 无数据:中性
788   - return BigDecimal.ONE;
  795 + return null;
789 796 }
790 797  
791 798 private double clamp(double val, double min, double max) {
... ...
urbanops-module-garden/src/main/resources/mapper/homepage/HomepageSummaryMapper.xml
... ... @@ -376,26 +376,29 @@
376 376 <!--已完成任务详情-->
377 377 <select id="queryCompletedTaskDetails" resultType="com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDetailsRspVo" >
378 378  
379   - SELECT taskName, busiDateTime, pressingType
  379 + SELECT taskName, busiDateTime, pressingType, taskType, planNo, batchNo
380 380 FROM (
381 381 -- 巡查已办理
382   - SELECT B.plan_name as taskName,A.finish_time as busiDateTime, null as pressingType
  382 + SELECT B.plan_name as taskName,A.finish_time as busiDateTime, null as pressingType,
  383 + 'inspection' as taskType, a.plan_no as planNo, a.batch_no as batchNo
383 384 from garden_inspection_plan_commit a , garden_inspection_plan b
384 385 where a.batch_no=b.batch_no
385 386 and user_id = #{userId}
386 387 union all
387 388 -- 养护已办理
388   - SELECT b.plan_name as taskName,a.finish_time as busiDateTime, null as pressingType
  389 + SELECT b.plan_name as taskName,a.finish_time as busiDateTime, null as pressingType,
  390 + 'maintain' as taskType, a.plan_no as planNo, a.batch_no as batchNo
389 391 from garden_maintain_plan_commit a , garden_maintain_plan b
390 392 where a.batch_no=b.batch_no
391 393 and user_id = #{userId}
392 394 union all
393 395 -- 工单已办
394 396 SELECT
395   - res.order_name as taskName, res.create_time as busiDateTime, res.pressing_type as pressingType
  397 + res.order_name as taskName, res.create_time as busiDateTime, res.pressing_type as pressingType,
  398 + 'workorder' as taskType, res.order_no as planNo, null as batchNo
396 399 FROM (
397 400 SELECT
398   - m.order_name,m.create_time, m.pressing_type,
  401 + m.order_name,m.create_time, m.pressing_type, m.order_no,
399 402 ROW_NUMBER() OVER (
400 403 PARTITION BY m.order_no
401 404 ORDER BY t.START_TIME_ DESC, t.ID_ DESC
... ... @@ -617,7 +620,7 @@
617 620  
618 621 <!-- 查询今日待办任务(含 GPS 坐标) -->
619 622 <select id="queryPendingTaskDetailsWithGps" resultType="com.zteits.urbanops.module.garden.controller.app.homepage.vo.TaskDistanceSortRespVO">
620   - SELECT taskName, busiDateTime, pressingType, taskLat, taskLon, taskType, taskSubType
  623 + SELECT taskName, busiDateTime, pressingType, taskLat, taskLon, taskType, taskSubType, planNo, batchNo
621 624 FROM (
622 625 SELECT
623 626 a.plan_name AS taskName,
... ... @@ -628,7 +631,9 @@
628 631 (CAST(r.starting_longitude AS DECIMAL(18,10))
629 632 + CAST(r.end_longitude AS DECIMAL(18,10))) / 2 AS taskLon,
630 633 'inspection' AS taskType,
631   - a.plan_type_id AS taskSubType
  634 + a.plan_type_id AS taskSubType,
  635 + c.plan_no AS planNo,
  636 + c.batch_no AS batchNo
632 637 FROM garden_inspection_plan a
633 638 INNER JOIN garden_inspection_plan_role b ON a.batch_no = b.batch_no
634 639 INNER JOIN garden_inspection_plan_detail c ON a.batch_no = c.batch_no
... ... @@ -655,7 +660,9 @@
655 660 (CAST(r.starting_longitude AS DECIMAL(18,10))
656 661 + CAST(r.end_longitude AS DECIMAL(18,10))) / 2 AS taskLon,
657 662 'maintain' AS taskType,
658   - a.plan_type_id AS taskSubType
  663 + a.plan_type_id AS taskSubType,
  664 + c.plan_no AS planNo,
  665 + c.batch_no AS batchNo
659 666 FROM garden_maintain_plan a
660 667 INNER JOIN garden_maintain_plan_role b ON a.batch_no = b.batch_no
661 668 INNER JOIN garden_maintain_plan_detail c ON a.batch_no = c.batch_no
... ... @@ -670,23 +677,6 @@
670 677 #{roleId}
671 678 </foreach>
672 679 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 680 ) t
691 681 </select>
692 682  
... ...