You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
15 KiB
420 lines
15 KiB
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) |