diff --git a/.claude/skills/week_work_package_log/work_package_logger.py b/.claude/skills/week_work_package_log/work_package_logger.py index 6d91d30..695502f 100644 --- a/.claude/skills/week_work_package_log/work_package_logger.py +++ b/.claude/skills/week_work_package_log/work_package_logger.py @@ -453,6 +453,69 @@ class WeekWorkPackageLogger: # 3. 若都沒有則填 no-info return NO_INFO + def _get_work_package_children(self, work_package_id: str) -> List[Dict]: + """獲取 work package 的所有直接子項目""" + try: + # 使用 OpenProject API 獲取子項目 + params = { + 'filters': json.dumps([{ + 'parent': { + 'operator': '=', + 'values': [work_package_id] + } + }]) + } + + response = requests.get( + self.work_packages_endpoint, + params=params, + auth=self._get_auth(), + timeout=30 + ) + + if response.status_code == 200: + data = response.json() + return data.get('_embedded', {}).get('elements', []) + else: + return [] + + except Exception as e: + print(f"⚠ Warning: Could not fetch children for work package {work_package_id}: {e}") + return [] + + def _build_complete_work_package_tree(self, work_package_id: str, wp_nodes: Dict, time_entries_by_wp: Dict) -> TreeNode: + """遞歸建構完整的 work package 樹狀結構(包含所有子項目)""" + if work_package_id in wp_nodes: + return wp_nodes[work_package_id] + + # 獲取 work package 詳細資料 + wp_data = self._get_work_package_details(work_package_id) + if not wp_data: + return None + + wp_name = wp_data.get('subject', f'WP-{work_package_id}') + + # 檢查是否有時間記錄 + entries = time_entries_by_wp.get(work_package_id, []) + node_hours = sum(entry['hours'] for entry in entries) + + # 獲取完成度 + wp_completion = self._get_work_package_completion(work_package_id) + + # 創建節點 + node = TreeNode(work_package_id, wp_name, 'work_package', node_hours, entries, wp_completion) + wp_nodes[work_package_id] = node + + # 獲取所有子項目 + children_data = self._get_work_package_children(work_package_id) + for child_data in children_data: + child_id = str(child_data.get('id')) + child_node = self._build_complete_work_package_tree(child_id, wp_nodes, time_entries_by_wp) + if child_node: + node.add_child(child_node) + + return node + def _get_project_completion(self, project_id: str) -> str: """Project不計算完成度""" return None @@ -597,41 +660,23 @@ class WeekWorkPackageLogger: project_trees[project_id] = TreeNode(project_id, f"Project-{project_id}", 'project') current_project_node = project_trees[project_id] - # Build work package hierarchy for this project - # current_project_node now points to the actual project where time was logged + # 建構完整的 work package 層級結構 wp_nodes = {} + root_wp_nodes = set() - for wp_id, entries in wp_entries.items(): + # 首先找出所有根 work package(通過檢查父子關係) + for wp_id in wp_entries.keys(): wp_path = self._get_work_package_path(wp_id) - - # Calculate total hours for this work package - total_hours = sum(entry['hours'] for entry in entries) - - # Build work package hierarchy - current_wp_node = None - - for wp_id_in_path, wp_name in wp_path: - if wp_id_in_path not in wp_nodes: - # Only attach entries to the actual work package (not parent work packages) - node_entries = entries if wp_id_in_path == wp_id else [] - node_hours = total_hours if wp_id_in_path == wp_id else 0 - wp_completion = self._get_work_package_completion(wp_id_in_path) - wp_nodes[wp_id_in_path] = TreeNode(wp_id_in_path, wp_name, 'work_package', node_hours, node_entries, wp_completion) - - if current_wp_node is None: - current_wp_node = wp_nodes[wp_id_in_path] - else: - if wp_nodes[wp_id_in_path] not in current_wp_node.children: - current_wp_node.add_child(wp_nodes[wp_id_in_path]) - current_wp_node = wp_nodes[wp_id_in_path] - - # Attach work package hierarchy to the project where time was actually logged - if wp_path and current_project_node: + if wp_path: root_wp_id = wp_path[0][0] - if root_wp_id in wp_nodes: - root_wp_node = wp_nodes[root_wp_id] - if root_wp_node not in current_project_node.children: - current_project_node.add_child(root_wp_node) + root_wp_nodes.add(root_wp_id) + + # 為每個根 work package 建構完整樹狀結構 + for root_wp_id in root_wp_nodes: + if current_project_node: + root_wp_node = self._build_complete_work_package_tree(root_wp_id, wp_nodes, wp_entries) + if root_wp_node and root_wp_node not in current_project_node.children: + current_project_node.add_child(root_wp_node) return project_trees