|
|
|
@ -1,8 +1,7 @@ |
|
|
|
import xmlrpc.client |
|
|
|
import httpx |
|
|
|
import time |
|
|
|
import time |
|
|
|
import sys |
|
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
# Odoo 连接配置 |
|
|
|
|
|
|
|
ODOO_URL = "http://192.168.31.41:32000" |
|
|
|
ODOO_URL = "http://192.168.31.41:32000" |
|
|
|
DB_NAME = "quantify" |
|
|
|
DB_NAME = "quantify" |
|
|
|
USERNAME = "rpc" |
|
|
|
USERNAME = "rpc" |
|
|
|
@ -16,75 +15,91 @@ class OdooClient: |
|
|
|
self.username = username |
|
|
|
self.username = username |
|
|
|
self.password = password |
|
|
|
self.password = password |
|
|
|
self.uid = None |
|
|
|
self.uid = None |
|
|
|
self.models = None |
|
|
|
self.client = None |
|
|
|
self._connect() |
|
|
|
self._connect() |
|
|
|
|
|
|
|
|
|
|
|
def _connect(self): |
|
|
|
def _connect(self): |
|
|
|
"""建立 XML-RPC 连接""" |
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
common = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/common") |
|
|
|
self.client = httpx.Client(timeout=30.0) |
|
|
|
self.uid = common.authenticate(self.db, self.username, self.password, {}) |
|
|
|
|
|
|
|
|
|
|
|
payload = { |
|
|
|
|
|
|
|
"jsonrpc": "2.0", |
|
|
|
|
|
|
|
"method": "call", |
|
|
|
|
|
|
|
"params": { |
|
|
|
|
|
|
|
"service": "common", |
|
|
|
|
|
|
|
"method": "login", |
|
|
|
|
|
|
|
"args": [self.db, self.username, self.password] |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
"id": 1 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response = self.client.post(f"{self.url}/jsonrpc", json=payload) |
|
|
|
|
|
|
|
result = response.json() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "error" in result: |
|
|
|
|
|
|
|
raise Exception(f"登录失败: {result['error']}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.uid = result.get("result") |
|
|
|
if not self.uid: |
|
|
|
if not self.uid: |
|
|
|
raise Exception("认证失败,请检查用户名和密码") |
|
|
|
raise Exception("认证失败,请检查用户名和密码") |
|
|
|
self.models = xmlrpc.client.ServerProxy(f"{self.url}/xmlrpc/2/object") |
|
|
|
|
|
|
|
print(f"[INFO] 连接成功,UID: {self.uid}") |
|
|
|
print(f"[INFO] 连接成功,UID: {self.uid}") |
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
print(f"[ERROR] 连接失败: {e}") |
|
|
|
print(f"[ERROR] 连接失败: {e}") |
|
|
|
sys.exit(1) |
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _execute(self, model, method, args=None, kwargs=None): |
|
|
|
|
|
|
|
if args is None: |
|
|
|
|
|
|
|
args = [] |
|
|
|
|
|
|
|
if kwargs is None: |
|
|
|
|
|
|
|
kwargs = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
payload = { |
|
|
|
|
|
|
|
"jsonrpc": "2.0", |
|
|
|
|
|
|
|
"method": "call", |
|
|
|
|
|
|
|
"params": { |
|
|
|
|
|
|
|
"service": "object", |
|
|
|
|
|
|
|
"method": "execute_kw", |
|
|
|
|
|
|
|
"args": [self.db, self.uid, self.password, model, method, args, kwargs] |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
"id": 2 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
response = self.client.post(f"{self.url}/jsonrpc", json=payload) |
|
|
|
|
|
|
|
result = response.json() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "error" in result: |
|
|
|
|
|
|
|
raise Exception(f"RPC错误: {result['error']}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result.get("result") |
|
|
|
|
|
|
|
|
|
|
|
def search_draft_records(self): |
|
|
|
def search_draft_records(self): |
|
|
|
"""搜索所有状态为 draft 的 alpha.datasets 记录""" |
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
ids = self.models.execute_kw( |
|
|
|
ids = self._execute('alpha.datasets', 'search', [[('status', '=', 'draft')]]) |
|
|
|
self.db, self.uid, self.password, |
|
|
|
|
|
|
|
'alpha.datasets', 'search', |
|
|
|
|
|
|
|
[[('status', '=', 'draft')]] |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
print(f"[INFO] 找到 {len(ids)} 条 draft 记录: {ids}") |
|
|
|
print(f"[INFO] 找到 {len(ids)} 条 draft 记录: {ids}") |
|
|
|
return ids |
|
|
|
return ids |
|
|
|
except xmlrpc.client.Fault as e: |
|
|
|
|
|
|
|
print(f"[ERROR] XML-RPC Fault 搜索记录失败: {e.faultCode} - {e.faultString}") |
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
print(f"[ERROR] 搜索 draft 记录失败: {e}") |
|
|
|
print(f"[ERROR] 搜索 draft 记录失败: {e}") |
|
|
|
return [] |
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
def get_record_status(self, record_id): |
|
|
|
def get_record_status(self, record_id): |
|
|
|
"""获取单条记录的状态""" |
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
records = self.models.execute_kw( |
|
|
|
records = self._execute('alpha.datasets', 'read', [[record_id], ['status', 'result_message', 'datasets_id', 'name']]) |
|
|
|
self.db, self.uid, self.password, |
|
|
|
|
|
|
|
'alpha.datasets', 'read', |
|
|
|
|
|
|
|
[[record_id], ['status', 'result_message', 'datasets_id', 'name']] |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
if records: |
|
|
|
if records: |
|
|
|
return records[0] |
|
|
|
return records[0] |
|
|
|
return None |
|
|
|
return None |
|
|
|
except xmlrpc.client.Fault as e: |
|
|
|
|
|
|
|
print(f"[ERROR] XML-RPC Fault 获取记录 {record_id} 状态失败: {e.faultCode} - {e.faultString}") |
|
|
|
|
|
|
|
return None |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
print(f"[ERROR] 获取记录 {record_id} 状态失败: {e}") |
|
|
|
print(f"[ERROR] 获取记录 {record_id} 状态失败: {e}") |
|
|
|
return None |
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
def trigger_btn_get_datasets(self, record_id): |
|
|
|
def trigger_btn_get_datasets(self, record_id): |
|
|
|
""" |
|
|
|
|
|
|
|
调用 btn_get_datasets 方法 |
|
|
|
|
|
|
|
返回: (success, message) |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
result = self.models.execute_kw( |
|
|
|
result = self._execute('alpha.datasets', 'btn_get_datasets', [[record_id]]) |
|
|
|
self.db, self.uid, self.password, |
|
|
|
|
|
|
|
'alpha.datasets', 'btn_get_datasets', |
|
|
|
|
|
|
|
[[record_id]] |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 处理不同的返回值 |
|
|
|
|
|
|
|
if result is True: |
|
|
|
if result is True: |
|
|
|
print(f"[INFO] 记录 {record_id} 的 btn_get_datasets 调用成功") |
|
|
|
print(f"[INFO] 记录 {record_id} 的 btn_get_datasets 调用成功") |
|
|
|
return True, "调用成功" |
|
|
|
return True, "调用成功" |
|
|
|
elif isinstance(result, dict) and result.get('type') == 'ir.actions.client': |
|
|
|
elif isinstance(result, dict) and result.get('type') == 'ir.actions.client': |
|
|
|
# 返回的是 notification 字典 |
|
|
|
|
|
|
|
message = result.get('params', {}).get('message', '') |
|
|
|
message = result.get('params', {}).get('message', '') |
|
|
|
msg_type = result.get('params', {}).get('type', 'info') |
|
|
|
msg_type = result.get('params', {}).get('type', 'info') |
|
|
|
if msg_type == 'danger': |
|
|
|
if msg_type == 'danger': |
|
|
|
@ -97,20 +112,12 @@ class OdooClient: |
|
|
|
print(f"[INFO] 记录 {record_id} 的 btn_get_datasets 调用完成,返回: {result}") |
|
|
|
print(f"[INFO] 记录 {record_id} 的 btn_get_datasets 调用完成,返回: {result}") |
|
|
|
return True, "调用成功" |
|
|
|
return True, "调用成功" |
|
|
|
|
|
|
|
|
|
|
|
except xmlrpc.client.Fault as e: |
|
|
|
|
|
|
|
error_msg = f"XML-RPC Fault: {e.faultCode} - {e.faultString}" |
|
|
|
|
|
|
|
print(f"[ERROR] 调用记录 {record_id} 的 btn_get_datasets 失败: {error_msg}") |
|
|
|
|
|
|
|
return False, error_msg |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
error_msg = f"Exception: {str(e)}" |
|
|
|
error_msg = f"Exception: {str(e)}" |
|
|
|
print(f"[ERROR] 调用记录 {record_id} 的 btn_get_datasets 失败: {error_msg}") |
|
|
|
print(f"[ERROR] 调用记录 {record_id} 的 btn_get_datasets 失败: {error_msg}") |
|
|
|
return False, error_msg |
|
|
|
return False, error_msg |
|
|
|
|
|
|
|
|
|
|
|
def wait_for_status_change(self, record_id, check_interval=5, max_wait_minutes=30): |
|
|
|
def wait_for_status_change(self, record_id, check_interval=5, max_wait_minutes=30): |
|
|
|
""" |
|
|
|
|
|
|
|
轮询等待记录状态变为 done 或 failed |
|
|
|
|
|
|
|
返回: (status, result_message) |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
max_attempts = (max_wait_minutes * 60) // check_interval |
|
|
|
max_attempts = (max_wait_minutes * 60) // check_interval |
|
|
|
attempt = 0 |
|
|
|
attempt = 0 |
|
|
|
|
|
|
|
|
|
|
|
@ -139,22 +146,18 @@ class OdooClient: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
|
def main(): |
|
|
|
"""主函数""" |
|
|
|
|
|
|
|
print("=" * 60) |
|
|
|
print("=" * 60) |
|
|
|
print("Odoo 批量处理 draft 数据集脚本") |
|
|
|
print("Odoo 批量处理 draft 数据集脚本") |
|
|
|
print("=" * 60) |
|
|
|
print("=" * 60) |
|
|
|
|
|
|
|
|
|
|
|
# 初始化客户端 |
|
|
|
|
|
|
|
client = OdooClient(ODOO_URL, DB_NAME, USERNAME, PASSWORD) |
|
|
|
client = OdooClient(ODOO_URL, DB_NAME, USERNAME, PASSWORD) |
|
|
|
|
|
|
|
|
|
|
|
# 统计信息 |
|
|
|
|
|
|
|
total_processed = 0 |
|
|
|
total_processed = 0 |
|
|
|
success_count = 0 |
|
|
|
success_count = 0 |
|
|
|
failed_count = 0 |
|
|
|
failed_count = 0 |
|
|
|
timeout_count = 0 |
|
|
|
timeout_count = 0 |
|
|
|
|
|
|
|
|
|
|
|
while True: |
|
|
|
while True: |
|
|
|
# 搜索所有 draft 记录 |
|
|
|
|
|
|
|
draft_ids = client.search_draft_records() |
|
|
|
draft_ids = client.search_draft_records() |
|
|
|
|
|
|
|
|
|
|
|
if not draft_ids: |
|
|
|
if not draft_ids: |
|
|
|
@ -166,7 +169,6 @@ def main(): |
|
|
|
for record_id in draft_ids: |
|
|
|
for record_id in draft_ids: |
|
|
|
print(f"\n--- 处理记录 ID: {record_id} ---") |
|
|
|
print(f"\n--- 处理记录 ID: {record_id} ---") |
|
|
|
|
|
|
|
|
|
|
|
# 再次确认状态(防止在搜索后状态被改变) |
|
|
|
|
|
|
|
current_record = client.get_record_status(record_id) |
|
|
|
current_record = client.get_record_status(record_id) |
|
|
|
if not current_record: |
|
|
|
if not current_record: |
|
|
|
print(f"[ERROR] 无法获取记录 {record_id} 的信息,跳过") |
|
|
|
print(f"[ERROR] 无法获取记录 {record_id} 的信息,跳过") |
|
|
|
@ -184,7 +186,6 @@ def main(): |
|
|
|
print(f"[INFO] 数据集名称: {name}") |
|
|
|
print(f"[INFO] 数据集名称: {name}") |
|
|
|
print(f"[INFO] 数据集 ID: {datasets_id}") |
|
|
|
print(f"[INFO] 数据集 ID: {datasets_id}") |
|
|
|
|
|
|
|
|
|
|
|
# 触发 btn_get_datasets |
|
|
|
|
|
|
|
success, message = client.trigger_btn_get_datasets(record_id) |
|
|
|
success, message = client.trigger_btn_get_datasets(record_id) |
|
|
|
|
|
|
|
|
|
|
|
if not success: |
|
|
|
if not success: |
|
|
|
@ -192,21 +193,15 @@ def main(): |
|
|
|
failed_count += 1 |
|
|
|
failed_count += 1 |
|
|
|
total_processed += 1 |
|
|
|
total_processed += 1 |
|
|
|
|
|
|
|
|
|
|
|
# 更新记录状态为 failed(如果触发失败) |
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
client.models.execute_kw( |
|
|
|
client._execute('alpha.datasets', 'write', [[record_id], { |
|
|
|
client.db, client.uid, client.password, |
|
|
|
|
|
|
|
'alpha.datasets', 'write', |
|
|
|
|
|
|
|
[[record_id], { |
|
|
|
|
|
|
|
'status': 'failed', |
|
|
|
'status': 'failed', |
|
|
|
'result_message': f'Trigger failed: {message}' |
|
|
|
'result_message': f'Trigger failed: {message}' |
|
|
|
}] |
|
|
|
}]) |
|
|
|
) |
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
print(f"[WARN] 无法更新记录状态: {e}") |
|
|
|
print(f"[WARN] 无法更新记录状态: {e}") |
|
|
|
continue |
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
# 等待状态变化 |
|
|
|
|
|
|
|
status, result_msg = client.wait_for_status_change(record_id) |
|
|
|
status, result_msg = client.wait_for_status_change(record_id) |
|
|
|
|
|
|
|
|
|
|
|
if status == 'done': |
|
|
|
if status == 'done': |
|
|
|
@ -225,10 +220,8 @@ def main(): |
|
|
|
|
|
|
|
|
|
|
|
total_processed += 1 |
|
|
|
total_processed += 1 |
|
|
|
|
|
|
|
|
|
|
|
# 短暂延迟,避免请求过快 |
|
|
|
|
|
|
|
time.sleep(1) |
|
|
|
time.sleep(1) |
|
|
|
|
|
|
|
|
|
|
|
# 输出统计 |
|
|
|
|
|
|
|
print("\n" + "=" * 60) |
|
|
|
print("\n" + "=" * 60) |
|
|
|
print("处理完成统计:") |
|
|
|
print("处理完成统计:") |
|
|
|
print(f" 总处理记录数: {total_processed}") |
|
|
|
print(f" 总处理记录数: {total_processed}") |
|
|
|
|