ProgressCalculatorService.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. <?php
  2. namespace App\Service;
  3. use App\Model\Item;
  4. use App\Model\ItemNode;
  5. use App\Model\ItemNodeMission;
  6. class ProgressCalculatorService extends Service
  7. {
  8. // 定义常量,方便调用
  9. const TYPE_MISSION = 'mission';
  10. const TYPE_NODE = 'node';
  11. const TYPE_ITEM = 'item';
  12. /**
  13. * 统一进度计算入口
  14. * @param string $type 发生变更的层级类型: mission, node, item
  15. * @param int $id 对应层级的主键 ID
  16. */
  17. public static function calculate($type, $id) {
  18. if (empty($id)) return;
  19. switch ($type) {
  20. case self::TYPE_MISSION:
  21. // 1. 如果是任务变了,找到未删除的任务,进而拿到它所属的节点 ID
  22. $mission = ItemNodeMission::where('id', $id)
  23. ->where('del_time', 0)
  24. ->first();
  25. if ($mission) {
  26. if($mission->state == ItemNodeMission::TYPE_THREE){
  27. $mission->progress = 100.0;
  28. $mission->save();
  29. }
  30. self::handleNodeProgress($mission->item_node_id);
  31. }
  32. break;
  33. case self::TYPE_NODE:
  34. // 2. 如果是节点变了,直接计算该节点
  35. self::handleNodeProgress($id);
  36. break;
  37. case self::TYPE_ITEM:
  38. // 3. 如果是项目层级触发,直接计算项目
  39. self::handleItemProgress($id);
  40. break;
  41. }
  42. }
  43. /**
  44. * 核心逻辑:计算节点进度并自动向上触发项目计算(私有,不对外)
  45. */
  46. private static function handleNodeProgress($nodeId) {
  47. // 严格限制:只有未删除的节点才参与计算
  48. $node = ItemNode::where('id', $nodeId)
  49. ->where('del_time', 0)
  50. ->first();
  51. if (!$node) return;
  52. // 查找该节点下所有未删除的任务
  53. $missions = ItemNodeMission::where('item_node_id', $nodeId)
  54. ->where('del_time', 0)
  55. ->get();
  56. if ($missions->isEmpty()) {
  57. // 无任务,进度由自身状态决定
  58. $node->progress = ($node->state == ItemNodeMission::TYPE_THREE) ? 100.00 : 0.00;
  59. } else {
  60. $totalWeight = $missions->sum('mission_weight');
  61. if ($totalWeight <= 0) {
  62. // 如果建了任务但权重全是 0,按任务个数平均分
  63. $completedCount = $missions->where('state', 3)->count();
  64. $node->progress = round(($completedCount / $missions->count()) * 100, 2);
  65. } else {
  66. // 已完成(state = 3)的任务权重和
  67. $completedWeight = $missions->where('state', 3)->sum('mission_weight');
  68. $node->progress = round(($completedWeight / $totalWeight) * 100, 2);
  69. }
  70. }
  71. $node->save();
  72. // 【自动冒泡】节点算完,自动去算它所属的项目
  73. self::handleItemProgress($node->item_id);
  74. }
  75. /**
  76. * 核心逻辑:计算项目进度(私有,不对外)
  77. */
  78. private static function handleItemProgress($itemId) {
  79. // 严格限制:只有未删除的项目才参与计算
  80. $item = Item::where('id', $itemId)
  81. ->where('del_time', 0)
  82. ->first();
  83. if (!$item) return;
  84. // 查找该项目下所有未删除的节点
  85. $nodes = ItemNode::where('item_id', $itemId)
  86. ->where('del_time', 0)
  87. ->get();
  88. if ($nodes->isEmpty()) {
  89. // 无节点,进度由自身状态决定
  90. $item->progress = ($item->state == ItemNode::TYPE_THREE) ? 100.00 : 0.00;;
  91. $item->save();
  92. return;
  93. }
  94. $totalWeight = $nodes->sum('node_weight');
  95. if ($totalWeight <= 0) {
  96. // 如果有节点但权重全写了 0,按节点个数平均分配
  97. $currentSum = 0;
  98. foreach ($nodes as $node) {
  99. $currentSum += ($node->progress / 100);
  100. }
  101. $item->progress = round(($currentSum / $nodes->count()) * 100, 2);
  102. } else {
  103. // 采用复合算法:SUM(节点权重 * 节点自身的 progress) / 总权重
  104. $currentSum = 0;
  105. foreach ($nodes as $node) {
  106. $currentSum += $node->node_weight * ($node->progress / 100);
  107. }
  108. $item->progress = round(($currentSum / $totalWeight) * 100, 2);
  109. }
  110. $item->save();
  111. }
  112. }