ProgressCalculatorService.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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. self::handleNodeProgress($mission->item_node_id);
  27. }
  28. break;
  29. case self::TYPE_NODE:
  30. // 2. 如果是节点变了,直接计算该节点
  31. self::handleNodeProgress($id);
  32. break;
  33. case self::TYPE_ITEM:
  34. // 3. 如果是项目层级触发,直接计算项目
  35. self::handleItemProgress($id);
  36. break;
  37. }
  38. }
  39. /**
  40. * 核心逻辑:计算节点进度并自动向上触发项目计算(私有,不对外)
  41. */
  42. private static function handleNodeProgress($nodeId) {
  43. // 严格限制:只有未删除的节点才参与计算
  44. $node = ItemNode::where('id', $nodeId)
  45. ->where('del_time', 0)
  46. ->first();
  47. if (!$node) return;
  48. // 查找该节点下所有未删除的任务
  49. $missions = ItemNodeMission::where('item_node_id', $nodeId)
  50. ->where('del_time', 0)
  51. ->get();
  52. if ($missions->isEmpty()) {
  53. // 无任务,进度由自身状态决定
  54. $node->progress = ($node->state == ItemNodeMission::TYPE_THREE) ? 100.00 : 0.00;
  55. } else {
  56. $totalWeight = $missions->sum('mission_weight');
  57. if ($totalWeight <= 0) {
  58. // 如果建了任务但权重全是 0,按任务个数平均分
  59. $completedCount = $missions->where('state', 3)->count();
  60. $node->progress = round(($completedCount / $missions->count()) * 100, 2);
  61. } else {
  62. // 已完成(state = 3)的任务权重和
  63. $completedWeight = $missions->where('state', 3)->sum('mission_weight');
  64. $node->progress = round(($completedWeight / $totalWeight) * 100, 2);
  65. }
  66. }
  67. $node->save();
  68. // 【自动冒泡】节点算完,自动去算它所属的项目
  69. self::handleItemProgress($node->item_id);
  70. }
  71. /**
  72. * 核心逻辑:计算项目进度(私有,不对外)
  73. */
  74. private static function handleItemProgress($itemId) {
  75. // 严格限制:只有未删除的项目才参与计算
  76. $item = Item::where('id', $itemId)
  77. ->where('del_time', 0)
  78. ->first();
  79. if (!$item) return;
  80. // 查找该项目下所有未删除的节点
  81. $nodes = ItemNode::where('item_id', $itemId)
  82. ->where('del_time', 0)
  83. ->get();
  84. if ($nodes->isEmpty()) {
  85. // 无节点,进度由自身状态决定
  86. $item->progress = ($item->state == ItemNode::TYPE_THREE) ? 100.00 : 0.00;;
  87. $item->save();
  88. return;
  89. }
  90. $totalWeight = $nodes->sum('node_weight');
  91. if ($totalWeight <= 0) {
  92. // 如果有节点但权重全写了 0,按节点个数平均分配
  93. $currentSum = 0;
  94. foreach ($nodes as $node) {
  95. $currentSum += ($node->progress / 100);
  96. }
  97. $item->progress = round(($currentSum / $nodes->count()) * 100, 2);
  98. } else {
  99. // 采用复合算法:SUM(节点权重 * 节点自身的 progress) / 总权重
  100. $currentSum = 0;
  101. foreach ($nodes as $node) {
  102. $currentSum += $node->node_weight * ($node->progress / 100);
  103. }
  104. $item->progress = round(($currentSum / $totalWeight) * 100, 2);
  105. }
  106. $item->save();
  107. }
  108. }