import xmlrpc.client import os import sys import time from datetime import datetime, timedelta # Odoo 连接配置 ODOO_URL = "http://192.168.31.41:32000" DB_NAME = "quantify" USERNAME = "rpc" PASSWORD = "aaaAAA111" class OdooClient: def __init__(self, url, db, username, password): self.url = url self.db = db self.username = username self.password = password self.uid = None self.models = None self._connect() def _connect(self): """建立 XML-RPC 连接""" try: common = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/common") self.uid = common.authenticate(self.db, self.username, self.password, {}) if not self.uid: raise Exception("认证失败,请检查用户名和密码") self.models = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/object") print(f"[INFO] 连接成功,UID: {self.uid}") except Exception as e: print(f"[ERROR] 连接失败: {e}") sys.exit(1) def get_today_date_str(self): """获取今天的日期字符串 (YYYY-MM-DD)""" return datetime.now().strftime('%Y-%m-%d') def get_today_start(self): """获取今天的开始时间 (YYYY-MM-DD 00:00:00)""" return datetime.now().strftime('%Y-%m-%d 00:00:00') def get_today_end(self): """获取今天的结束时间 (YYYY-MM-DD 23:59:59)""" return datetime.now().strftime('%Y-%m-%d 23:59:59') def get_tomorrow_start(self): """获取明天的开始时间 (YYYY-MM-DD 00:00:00)""" tomorrow = datetime.now() + timedelta(days=1) return tomorrow.strftime('%Y-%m-%d 00:00:00') # ==================== 步骤1: 清理历史数据 ==================== def cancel_old_non_terminal_records(self): """ 步骤1: 取消非今天的非终态记录 将状态不是 done/failed/cancel 且创建日期 < 今天的记录设为 cancel """ today_start = self.get_today_start() # 搜索条件:状态不是终态,且创建日期小于今天开始时间 domain = [ ('status', 'not in', ['done', 'failed', 'cancel']), ('create_date', '<', today_start) ] try: record_ids = self.models.execute_kw( self.db, self.uid, self.password, 'alpha.idea', 'search', [domain] ) if not record_ids: print(f"[INFO] 没有需要取消的历史记录") return 0 print(f"[INFO] 找到 {len(record_ids)} 条历史记录需要取消(创建日期 < {self.get_today_date_str()})") # 批量更新状态为 cancel self.models.execute_kw( self.db, self.uid, self.password, 'alpha.idea', 'write', [record_ids, {'status': 'cancel'}] ) print(f"[INFO] 成功将 {len(record_ids)} 条历史记录状态更新为 cancel") return len(record_ids) except xmlrpc.client.Fault as e: print(f"[ERROR] XML-RPC Fault 取消记录失败: {e.faultCode} - {e.faultString}") return 0 except Exception as e: print(f"[ERROR] 取消记录失败: {e}") return 0 # ==================== 通用方法: 获取指定状态的记录 ==================== def fetch_records_by_status(self, status, fields=None): """ 获取今天指定状态的记录 :param status: 状态值,如 'draft', 'fetched_data', 'generated_prompt' :param fields: 需要读取的字段列表,默认读取 ['id', 'name', 'status'] """ today_start = self.get_today_start() tomorrow_start = self.get_tomorrow_start() if fields is None: fields = ['id', 'name', 'status'] # 使用日期范围查询当天的记录 domain = [ ('create_date', '>=', today_start), ('create_date', '<', tomorrow_start), ('status', '=', status) ] try: record_ids = self.models.execute_kw( self.db, self.uid, self.password, 'alpha.idea', 'search', [domain] ) if not record_ids: print(f"[INFO] 没有找到今天的 {status} 状态记录") return [] print(f"[INFO] 找到 {len(record_ids)} 条今天的 {status} 状态记录") records = self.models.execute_kw( self.db, self.uid, self.password, 'alpha.idea', 'read', [record_ids, fields] ) return records except xmlrpc.client.Fault as e: print(f"[ERROR] XML-RPC Fault 获取 {status} 记录失败: {e.faultCode} - {e.faultString}") return [] except Exception as e: print(f"[ERROR] 获取 {status} 记录失败: {e}") return [] # ==================== 通用方法: 调用按钮并等待状态变化 ==================== def call_button_and_wait(self, record_id, button_name, target_statuses, check_interval=5, max_wait_minutes=30): """ 调用指定按钮并等待状态变为目标状态 :param record_id: 记录ID :param button_name: 按钮方法名,如 'btn_check_and_fetch_data' :param target_statuses: 目标状态列表,如 ['generated_prompt', 'failed'] :param check_interval: 检查间隔(秒) :param max_wait_minutes: 最大等待时间(分钟) :return: (最终状态, 结果消息) """ # 调用按钮 try: result = self.models.execute_kw( self.db, self.uid, self.password, 'alpha.idea', button_name, [[record_id]] ) print(f"[INFO] 记录 {record_id} 的 {button_name} 已调用") except xmlrpc.client.Fault as e: error_msg = f"XML-RPC Fault: {e.faultCode} - {e.faultString}" print(f"[ERROR] 调用 {button_name} 失败: {error_msg}") return 'failed', error_msg except Exception as e: error_msg = f"Exception: {str(e)}" print(f"[ERROR] 调用 {button_name} 失败: {error_msg}") return 'failed', error_msg # 等待状态变化 return self.wait_for_status_change(record_id, target_statuses, check_interval, max_wait_minutes) def wait_for_status_change(self, record_id, target_statuses, check_interval=5, max_wait_minutes=30): """ 轮询等待记录状态变为目标状态之一 :return: (current_status, result_message) """ max_attempts = (max_wait_minutes * 60) // check_interval attempt = 0 # 获取初始状态 record = self.get_record_status(record_id) if not record: return 'unknown', '无法获取记录状态' current_status = record.get('status') print(f"[INFO] 记录 {record_id} 初始状态: {current_status}") while attempt < max_attempts: if current_status in target_statuses: print(f"[INFO] 记录 {record_id} 状态变为目标状态: {current_status}") return current_status, record.get('result_message', '') print(f"[INFO] 记录 {record_id} 当前状态: {current_status},等待 {check_interval} 秒后重试...") time.sleep(check_interval) attempt += 1 record = self.get_record_status(record_id) if record: current_status = record.get('status') else: print(f"[WARN] 无法获取记录 {record_id} 状态") print(f"[WARN] 记录 {record_id} 等待超时 ({max_wait_minutes} 分钟)") return current_status, '等待超时' def get_record_status(self, record_id): """获取单条记录的状态和 result_message""" try: records = self.models.execute_kw( self.db, self.uid, self.password, 'alpha.idea', 'read', [[record_id], ['status', 'result_message', 'name']] ) if records: return records[0] return None except Exception as e: print(f"[ERROR] 获取记录 {record_id} 状态失败: {e}") return None # ==================== 步骤2: 处理 draft 记录 ==================== def process_draft_records(self): """ 步骤2: 处理今天的 draft 状态记录 调用 btn_check_and_fetch_data,等待状态变为 fetched_data 或 failed """ print("\n[步骤2] 处理今天的 draft 记录...") print("-" * 40) draft_records = self.fetch_records_by_status('draft') if not draft_records: print("[INFO] 没有需要处理的 draft 记录") return 0 success_count = 0 failed_count = 0 for record in draft_records: record_id = record.get('id') name = record.get('name') print(f"\n处理 draft 记录: {name} (ID: {record_id})") # 调用 btn_check_and_fetch_data,等待状态变为 fetched_data 或 failed target_statuses = ['fetched_data', 'failed', 'cancel', 'done'] final_status, message = self.call_button_and_wait( record_id, 'btn_check_and_fetch_data', target_statuses, check_interval=5, max_wait_minutes=30 ) if final_status == 'fetched_data': print(f"[SUCCESS] 记录 {record_id} 成功变为 fetched_data 状态") success_count += 1 else: print(f"[FAILED] 记录 {record_id} 最终状态为 {final_status},失败原因: {message}") failed_count += 1 print(f"\n[步骤2完成] 成功: {success_count}, 失败: {failed_count}") return success_count # ==================== 步骤3: 处理 fetched_data 记录 ==================== def process_fetched_data_records(self): """ 步骤3: 处理今天的 fetched_data 状态记录 调用 btn_generate_final_prompt,等待状态变为 generated_prompt 或 failed """ print("\n[步骤3] 处理今天的 fetched_data 记录...") print("-" * 40) fetched_records = self.fetch_records_by_status('fetched_data') if not fetched_records: print("[INFO] 没有需要处理的 fetched_data 记录") return 0 success_count = 0 failed_count = 0 for record in fetched_records: record_id = record.get('id') name = record.get('name') print(f"\n处理 fetched_data 记录: {name} (ID: {record_id})") # 调用 btn_generate_final_prompt,等待状态变为 generated_prompt 或 failed target_statuses = ['generated_prompt', 'failed', 'cancel', 'done'] final_status, message = self.call_button_and_wait( record_id, 'btn_generate_final_prompt', target_statuses, check_interval=5, max_wait_minutes=30 ) if final_status == 'generated_prompt': print(f"[SUCCESS] 记录 {record_id} 成功变为 generated_prompt 状态") success_count += 1 else: print(f"[FAILED] 记录 {record_id} 最终状态为 {final_status},失败原因: {message}") failed_count += 1 print(f"\n[步骤3完成] 成功: {success_count}, 失败: {failed_count}") return success_count # ==================== 步骤4: 下载 final_prompt ==================== def download_final_prompts(self): """ 步骤4: 获取今天的 generated_prompt 状态记录,下载 final_prompt 到本地文件 """ print("\n[步骤4] 下载今天的 final_prompt...") print("-" * 40) generated_records = self.fetch_records_by_status('generated_prompt', ['id', 'name', 'final_prompt']) if not generated_records: print("[INFO] 没有需要下载的 generated_prompt 记录") return 0 # 创建当天日期文件夹 today_folder = self.get_today_date_str() output_dir = os.path.join(os.getcwd(), today_folder) print(f"[INFO] 文件将保存到: {output_dir}") success_count = 0 failed_count = 0 for record in generated_records: record_id = record.get('id') name = record.get('name') final_prompt = record.get('final_prompt', '') print(f"\n处理记录: {name} (ID: {record_id})") # 检查 final_prompt 是否为空 if not final_prompt or not final_prompt.strip(): print(f"[WARN] 记录 {name} 的 final_prompt 为空,跳过") failed_count += 1 continue # 保存文件 if self.save_prompt_to_md(name, final_prompt, output_dir): success_count += 1 else: failed_count += 1 print(f"\n[步骤4完成] 成功保存: {success_count}, 失败: {failed_count}") return success_count def save_prompt_to_md(self, name, final_prompt, output_dir): """将 final_prompt 保存为 Markdown 文件""" try: os.makedirs(output_dir, exist_ok=True) filename = f"{name}.md" filepath = os.path.join(output_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: f.write(final_prompt) print(f"[SUCCESS] 已保存文件: {filepath}") return True except Exception as e: print(f"[ERROR] 保存文件失败 {name}.md: {e}") return False def main(): """主函数""" print("=" * 60) print("Odoo Alpha Idea 处理脚本") print(f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("=" * 60) # 初始化客户端 client = OdooClient(ODOO_URL, DB_NAME, USERNAME, PASSWORD) # 步骤1: 清理历史数据(创建日期 < 今天的非终态记录设为 cancel) print("\n[步骤1] 清理历史数据...") print("-" * 40) canceled_count = client.cancel_old_non_terminal_records() print(f"[步骤1完成] 已取消 {canceled_count} 条历史记录") # 步骤2: 处理今天的 draft 记录 draft_success = client.process_draft_records() # 步骤3: 处理今天的 fetched_data 记录 fetched_success = client.process_fetched_data_records() # 步骤4: 下载今天的 final_prompt downloaded_count = client.download_final_prompts() # 输出统计 print("\n" + "=" * 60) print("处理完成统计:") print(f" 步骤1 - 取消的历史记录: {canceled_count}") print(f" 步骤2 - 处理的 draft 记录: {draft_success}") print(f" 步骤3 - 处理的 fetched_data 记录: {fetched_success}") print(f" 步骤4 - 下载的 prompt 文件: {downloaded_count}") print(f" 文件保存路径: {os.path.join(os.getcwd(), client.get_today_date_str())}") print("=" * 60) if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n[INFO] 用户中断脚本") sys.exit(0) except Exception as e: print(f"[ERROR] 脚本异常: {e}") sys.exit(1)