feat: enhance TreeNode and WeekWorkPackageLogger to support rejection status tracking

This commit is contained in:
MarioYang 2026-01-28 09:32:53 +08:00
parent 8d1e1e7163
commit 5a3824a85f
1 changed files with 49 additions and 20 deletions

View File

@ -65,7 +65,7 @@ class InvalidDateRangeError(WorkPackageLogError):
class TreeNode:
"""Represents a node in the hierarchy tree"""
def __init__(self, node_id: str, name: str, node_type: str, hours: float = 0.0, entries: List = None, completion: str = None):
def __init__(self, node_id: str, name: str, node_type: str, hours: float = 0.0, entries: List = None, completion: str = None, logger = None):
self.id = node_id
self.name = name
self.type = node_type # 'project' or 'work_package'
@ -75,6 +75,7 @@ class TreeNode:
self.parent = None
self._raw_completion = completion # API原始完成度資料
self._calculated_completion = None # 快取計算後的完成度
self.logger = logger # 用於檢查駁回狀態
def add_child(self, child):
child.parent = self
@ -87,6 +88,13 @@ class TreeNode:
total += child.get_total_hours()
return total
def _is_rejected(self) -> bool:
"""檢查此節點是否為駁回狀態"""
if self.type != 'work_package' or not self.logger:
return False
return self.logger._is_work_package_rejected(self.id)
def get_completion(self, visited_nodes: set = None) -> str:
"""
Recursively compute the completion percentage for this node.
@ -138,20 +146,15 @@ class TreeNode:
self._calculated_completion = "100%"
return "100%"
# 2. 檢查是否有設定完成度
if self._raw_completion and self._raw_completion != "no-info":
self._calculated_completion = self._raw_completion
return self._raw_completion
# 3. 如果是no-info則根據子項目計算
if not self.children:
# 無子項目最終層給0%
self._calculated_completion = "0%"
return "0%"
else:
# 有子項目,計算所有子項目完成度的平均值
# 2. 如果有子項目,需要重新計算(跳過駁回的子項目)
if self.children:
# 有子項目,計算所有子項目完成度的平均值(跳過駁回的子項目)
child_completions = []
for child in self.children:
# 跳過駁回狀態的子項目
if child._is_rejected():
continue
child_completion_str = child.get_completion(visited_nodes)
# 將完成度字串轉換為數值
@ -168,10 +171,18 @@ class TreeNode:
if child_completions:
avg_completion = sum(child_completions) / len(child_completions)
self._calculated_completion = f"{avg_completion:.0f}%"
else:
self._calculated_completion = "0%"
return self._calculated_completion
return self._calculated_completion
# 如果所有子項目都被駁回則使用原始完成度或0%
# 3. 檢查是否有設定完成度(沒有子項目或所有子項目都被駁回時使用)
# 如果設定為 0%,視為未設定,仍會嘗試從子項目計算
if self._raw_completion and self._raw_completion != "no-info" and self._raw_completion != "0%":
self._calculated_completion = self._raw_completion
return self._raw_completion
# 4. 沒有任何完成度資訊給0%
self._calculated_completion = "0%"
return "0%"
def to_dict(self) -> Dict:
"""Convert tree node to dictionary for JSON serialization"""
@ -421,6 +432,24 @@ class WeekWorkPackageLogger:
return {}
def _is_work_package_rejected(self, work_package_id: str) -> bool:
"""檢查 work package 是否為駁回狀態"""
wp_data = self._get_work_package_details(work_package_id)
if not wp_data:
return False
# 檢查狀態是否包含駁回關鍵字
status_link = wp_data.get('_links', {}).get('status')
if status_link and status_link.get('href'):
status_data = self._get_status_details(status_link['href'])
if status_data:
status_name = status_data.get('name', '').lower()
if any(keyword in status_name for keyword in REJECTION_KEYWORDS):
return True
return False
def _get_work_package_completion(self, work_package_id: str) -> str:
"""獲取work package的原始完成度資訊"""
wp_data = self._get_work_package_details(work_package_id)
@ -503,7 +532,7 @@ class WeekWorkPackageLogger:
wp_completion = self._get_work_package_completion(work_package_id)
# 創建節點
node = TreeNode(work_package_id, wp_name, 'work_package', node_hours, entries, wp_completion)
node = TreeNode(work_package_id, wp_name, 'work_package', node_hours, entries, wp_completion, self)
wp_nodes[work_package_id] = node
# 獲取所有子項目
@ -643,7 +672,7 @@ class WeekWorkPackageLogger:
for proj_id, proj_name in project_path:
if proj_id not in project_trees:
project_trees[proj_id] = TreeNode(proj_id, proj_name, 'project')
project_trees[proj_id] = TreeNode(proj_id, proj_name, 'project', logger=self)
if current_project_node is None:
# This is the root project in the path
@ -657,7 +686,7 @@ class WeekWorkPackageLogger:
# If no project path found (no hierarchy support), create a simple node
if not project_path and project_id:
if project_id not in project_trees:
project_trees[project_id] = TreeNode(project_id, f"Project-{project_id}", 'project')
project_trees[project_id] = TreeNode(project_id, f"Project-{project_id}", 'project', logger=self)
current_project_node = project_trees[project_id]
# 建構完整的 work package 層級結構