diff --git a/config/config_task2.ini b/config/config_task2.ini index fdff294..cda961e 100644 --- a/config/config_task2.ini +++ b/config/config_task2.ini @@ -10,7 +10,7 @@ docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEY [Schedule] # 同步频率(分钟) -sync_interval = 1 +sync_interval = 30 [wework] # 企业微信应用ID diff --git a/config/token_cache.json b/config/token_cache.json index 4f2fa75..12cb6ce 100644 --- a/config/token_cache.json +++ b/config/token_cache.json @@ -1,4 +1,4 @@ { - "access_token": "LsTV1Z6Ye4h2c77s1oKx4zasisAn4QiTbqXmZRUYchZRzp6Q_igpVWp22oEhv8rTHuYyB1LKfqhLjQ9qvgwU13fZns6t-aNBDMw00BBf76V2XqoKKXPHNQZGKQly8RH9NgFMoqfYiKeDnoCdbYnvRjgn8oYoc0AvnCu_qU9TdP8XJgLO01-inoLGSEdTLZnWXf90uf4RztU8xH1a7nuM1p31w58AT7iV3h8Uv9aPOe8", - "fetch_time": 1767954012.568217 + "access_token": "9-OFdr2nbGo-1sSAYBujyvTtP5v88W0MjLSy3BzHayMEw0tX0tQ8qsbYXshNdHURsLqjXh4dVaRs2HIxvy2gXbWglBg90YtJU_3Yxfz-EXkCUT6Yyt1U1Z6ojUzWkwG6esUX9rWndHWK7hdXusZj5vpHBqVyQpY1Pi3eVKpB28puC_7IXsgbUaW5-JenDP3C7AnAs_x7jbsECXD8Y2XjZymhBKEwf_jZH-iK1Ojnvxk", + "fetch_time": 1768214302.441097 } \ No newline at end of file diff --git a/src2/smartsheet_sync.py b/src2/smartsheet_sync.py index 83dc6ca..475b1f5 100644 --- a/src2/smartsheet_sync.py +++ b/src2/smartsheet_sync.py @@ -227,27 +227,25 @@ class SmartSheetSync: # 直接使用任务一的 update_records 方法(已添加debug=1) return self.api.update_records(sheet_id, update_records) - def get_records_with_tapd_link(self, sheet_id: str) -> List[Dict]: + def get_records_with_tapd_link(self, sheet_id: str, + all_records: List[Dict] = None) -> List[Dict]: """ - 获取所有包含TAPD链接的记录 + 获取所有包含TAPD链接的新记录(同步状态为空) Args: sheet_id: 子表ID + all_records: 可选,已获取的所有记录列表,避免重复获取 Returns: - List[Dict]: 包含TAPD链接的记录列表,每条记录包含: - - record: 原始记录对象 - - record_id: 记录ID - - tapd_link: TAPD链接 - - story_id: 解析出的需求单号(如果解析成功) - - parse_success: 链接解析是否成功 - - parse_error: 解析失败的错误信息 + List[Dict]: 包含TAPD链接的记录列表 """ - print(f"正在获取包含TAPD链接的记录...") + print(f"正在获取包含TAPD链接的新记录...") + + if all_records is None: + all_records = self.get_all_records(sheet_id) - all_records = self.get_all_records(sheet_id) records_with_link = [] - skipped_synced_count = 0 # 统计跳过的已同步记录数 + skipped_synced_count = 0 for record in all_records: tapd_link = self.extract_tapd_link(record) @@ -313,6 +311,68 @@ class SmartSheetSync: FIELD_PLAN: self.api.get_field_value_by_title(record, FIELD_PLAN), } + def get_synced_records_for_update(self, sheet_id: str, + terminal_statuses: List[str], + all_records: List[Dict] = None) -> List[Dict]: + """ + 获取需要持续同步的已同步记录 + + 筛选条件: + - 同步状态 = "成功" + - TAPD状态 不在终态列表中 + + Args: + sheet_id: 子表ID + terminal_statuses: 终态列表(如 ['已完成', '取消']) + all_records: 可选,已获取的所有记录列表,避免重复获取 + + Returns: + List[Dict]: 需要持续同步的记录列表 + """ + print(f"正在获取需要持续同步的记录...") + + if all_records is None: + all_records = self.get_all_records(sheet_id) + records_for_update = [] + skipped_terminal_count = 0 + + for record in all_records: + # 检查同步状态是否为"成功" + sync_status = self.api.get_field_value_by_title(record, FIELD_SYNC_STATUS) + if sync_status != "成功": + continue + + # 检查TAPD链接是否存在 + tapd_link = self.extract_tapd_link(record) + if not tapd_link: + continue + + # 检查TAPD状态是否为终态 + tapd_status = self.api.get_field_value_by_title(record, FIELD_TAPD_STATUS) + if tapd_status in terminal_statuses: + skipped_terminal_count += 1 + continue + + # 解析链接获取story_id + success, result, link_type = parse_tapd_link(tapd_link) + if not success: + continue + + record_info = { + "record": record, + "record_id": record.get('record_id', ''), + "tapd_link": tapd_link, + "story_id": result, + "current_status": tapd_status, + } + records_for_update.append(record_info) + + print(f" ✓ 找到 {len(records_for_update)} 条需要持续同步的记录") + if skipped_terminal_count > 0: + print(f" - 跳过终态记录: {skipped_terminal_count} 条") + + return records_for_update + def process_sheet(api: SmartSheetSync, sheet_id: str, sheet_title: str) -> Dict: """ diff --git a/src2/sync_service.py b/src2/sync_service.py index 1326b88..fd7f64e 100644 --- a/src2/sync_service.py +++ b/src2/sync_service.py @@ -20,7 +20,7 @@ sys.path.insert(0, str(project_root)) from src.token_manager import TokenManager from src2.config import Task2ConfigManager from src2.logger import get_task2_logger -from src2.tapd_api import TAPDStoryApi +from src2.tapd_api import TAPDStoryApi, TERMINAL_STATUSES from src2.smartsheet_sync import SmartSheetSync, REQUIRED_FIELDS @@ -181,65 +181,76 @@ class SyncService: print(f" ⚠ 跳过此子表: {sheet_result['skip_reason']}") return sheet_result - # 2. 获取包含TAPD链接的记录 - records_with_link = self.smartsheet.get_records_with_tapd_link(sheet_id) + # 2. 获取所有记录(只获取一次,供新记录同步和持续同步共用) + print(f"正在获取所有记录...") + all_records = self.smartsheet.get_all_records(sheet_id) + + # 3. 获取包含TAPD链接的新记录(同步状态为空) + records_with_link = self.smartsheet.get_records_with_tapd_link( + sheet_id, all_records=all_records + ) sheet_result["records_with_link"] = len(records_with_link) - if not records_with_link: - print(f" ℹ 没有包含TAPD链接的记录") - return sheet_result + # 4. 处理新记录 + if records_with_link: + print(f"\n--- 新记录同步 ---") + success_records = [] # 成功同步的记录 + failed_record_ids = [] # 失败的记录ID列表 - # 3. 处理每条记录 - success_records = [] # 成功同步的记录(包含业务字段 + 同步状态) - failed_record_ids = [] # 失败的记录ID列表 + for record_info in records_with_link: + record_result = self._process_record(record_info) + sheet_result["details"].append(record_result) - for record_info in records_with_link: - record_result = self._process_record(record_info) - sheet_result["details"].append(record_result) + if record_result["success"]: + sheet_result["records_synced"] += 1 - if record_result["success"]: - sheet_result["records_synced"] += 1 + if record_result["update_record"]: + success_records.append(record_result["update_record"]) + sheet_result["records_updated"] += 1 + else: + sheet_result["records_failed"] += 1 + failed_record_ids.append(record_info["record_id"]) - if record_result["update_record"]: - success_records.append(record_result["update_record"]) - sheet_result["records_updated"] += 1 - else: - sheet_result["records_failed"] += 1 - failed_record_ids.append(record_info["record_id"]) + # 收集失败记录的详细信息用于推送 + failed_record = { + "sheet_title": sheet_title, + "record_id": record_info["record_id"], + "tapd_link": record_info.get("tapd_link", "(无链接)"), + "error_message": record_result.get("error_message", "未知错误") + } + sheet_result["failed_records"].append(failed_record) - # 收集失败记录的详细信息用于推送 - failed_record = { - "sheet_title": sheet_title, - "record_id": record_info["record_id"], - "tapd_link": record_info.get("tapd_link", "(无链接)"), - "error_message": record_result.get("error_message", "未知错误") - } - sheet_result["failed_records"].append(failed_record) + # 4. 批量回写成功记录 + if success_records: + print(f"\n正在回写 {len(success_records)} 条成功记录...") + try: + self.smartsheet.batch_update_records(sheet_id, success_records) + print(f" ✓ 成功记录回写完成") + except Exception as e: + print(f" ✗ 成功记录回写失败: {e}") - # 4. 批量回写成功记录(业务字段 + 同步状态=成功) - if success_records: - print(f"\n正在回写 {len(success_records)} 条成功记录...") - try: - self.smartsheet.batch_update_records(sheet_id, success_records) - print(f" ✓ 成功记录回写完成") - except Exception as e: - print(f" ✗ 成功记录回写失败: {e}") + # 5. 批量回写失败记录 + if failed_record_ids: + print(f"\n正在回写 {len(failed_record_ids)} 条失败记录的状态...") + try: + failed_updates = [ + self.smartsheet.build_update_record( + record_id=record_id, + sync_status="失败" + ) + for record_id in failed_record_ids + ] + self.smartsheet.batch_update_records(sheet_id, failed_updates) + print(f" ✓ 失败记录状态回写完成") + except Exception as e: + print(f" ✗ 失败记录状态回写失败: {e}") + else: + print(f" ℹ 没有新记录需要同步") - # 5. 批量回写失败记录(只写入同步状态=失败) - if failed_record_ids: - print(f"\n正在回写 {len(failed_record_ids)} 条失败记录的状态...") - try: - failed_updates = [ - self.smartsheet.build_update_record( - record_id=record_id, - sync_status="失败" - ) - for record_id in failed_record_ids - ] - self.smartsheet.batch_update_records(sheet_id, failed_updates) - print(f" ✓ 失败记录状态回写完成") - except Exception as e: - print(f" ✗ 失败记录状态回写失败: {e}") + # 7. 持续同步:更新已同步记录的最新状态 + print(f"\n--- 持续同步 ---") + update_result = self._process_synced_records_update(sheet_id, all_records=all_records) + sheet_result["continuous_sync"] = update_result sheet_result["total_records"] = len(records_with_link) @@ -371,6 +382,82 @@ class SyncService: return False + def _process_synced_records_update(self, sheet_id: str, + all_records: List[Dict] = None) -> Dict[str, Any]: + """ + 处理已同步记录的状态更新(持续同步) + + Args: + sheet_id: 子表ID + all_records: 可选,已获取的所有记录列表 + + Returns: + Dict: 更新结果统计 + """ + result = { + "checked": 0, + "updated": 0, + "failed": 0, + } + + # 获取需要持续同步的记录 + records = self.smartsheet.get_synced_records_for_update( + sheet_id, TERMINAL_STATUSES, all_records=all_records + ) + + if not records: + print(f" ℹ 没有需要持续同步的记录") + return result + + result["checked"] = len(records) + updates = [] + + for record_info in records: + try: + # 查询TAPD最新状态 + story_info = self.tapd_api.get_story(record_info["story_id"]) + + # 提取最新字段值 + new_status = story_info.get('status', '') + new_owner = story_info.get('owner', '') + new_begin = story_info.get('begin', '') + new_due = story_info.get('due', '') + plan_id = story_info.get('custom_plan_field_1', '') + new_plan = self.tapd_api.map_plan_id_to_name(plan_id) + + # 获取当前值并比较 + current = self.smartsheet.get_current_field_values(record_info["record"]) + needs_update = self._check_needs_update( + current, new_status, new_owner, new_begin, new_due, new_plan + ) + + if needs_update: + update_record = self.smartsheet.build_update_record( + record_id=record_info["record_id"], + status=new_status, + owner=new_owner, + begin_date=new_begin, + due_date=new_due, + plan=new_plan + ) + updates.append(update_record) + + except Exception as e: + result["failed"] += 1 + if self.test_mode: + print(f" ✗ 记录 {record_info['record_id']} 更新失败: {e}") + + # 批量更新 + if updates: + print(f" 正在更新 {len(updates)} 条记录...") + self.smartsheet.batch_update_records(sheet_id, updates) + result["updated"] = len(updates) + print(f" ✓ 持续同步更新完成") + else: + print(f" ✓ 所有记录状态均未变化") + + return result + def _send_failure_notification(self, failed_records: List[Dict]) -> None: """ 发送同步失败通知 diff --git a/src2/tapd_api.py b/src2/tapd_api.py index cddeaa8..66fe749 100644 --- a/src2/tapd_api.py +++ b/src2/tapd_api.py @@ -28,6 +28,9 @@ STATUS_MAPPING = { "status_13": "待评审", } +# 需求终态列表(这些状态不需要持续同步) +TERMINAL_STATUSES = ['已完成', '取消'] + def map_status(status_code: str) -> str: """