Refactor discrete material cleaner to improve maintainability and testability: - Introduce dependency injection pattern for service components - Add interfaces (IPageNavigator, IMaterialExtractor, IDeletionChecker) - Extract PageNavigator service for page navigation logic - Extract MaterialExtractor service for data extraction - Extract DatabaseDeletionChecker service for deletion logic - Add MaterialInfo data model for structured data - Centralize configuration (CleanerConfig, UIConstants) - Implement separation of concerns across services layer Also includes comprehensive execution mechanism documentation. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
191 lines
6.6 KiB
Python
191 lines
6.6 KiB
Python
"""
|
||
离散备料计划清理器 - 重构版
|
||
使用依赖注入、接口抽象、职责分离设计
|
||
"""
|
||
|
||
import time
|
||
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
|
||
from interfaces.i_page_navigator import IPageNavigator
|
||
from interfaces.i_material_extractor import IMaterialExtractor
|
||
from interfaces.i_deletion_checker import IDeletionChecker
|
||
from config.cleaner_config import CleanerConfig
|
||
from config.ui_constants import UIConstants
|
||
from utils.auth import login, logout
|
||
from db.production_order_query import (
|
||
read_production_ids,
|
||
query_production_order_numbers,
|
||
)
|
||
|
||
|
||
class DiscreteMaterialCleaner:
|
||
"""离散备料计划清理器 - 重构版"""
|
||
|
||
def __init__(
|
||
self,
|
||
config: CleanerConfig,
|
||
navigator: IPageNavigator,
|
||
extractor: IMaterialExtractor,
|
||
deletion_checker: IDeletionChecker,
|
||
):
|
||
"""
|
||
初始化清理器
|
||
|
||
Args:
|
||
config: 清理器配置
|
||
navigator: 页面导航器
|
||
extractor: 物料信息提取器
|
||
deletion_checker: 删除判断器
|
||
"""
|
||
self.config = config
|
||
self.navigator = navigator
|
||
self.extractor = extractor
|
||
self.checker = deletion_checker
|
||
|
||
def _print(self, *args, **kwargs):
|
||
"""打印日志(如果 verbose=True)"""
|
||
if self.config.verbose:
|
||
print(*args, **kwargs)
|
||
|
||
def clean(self, production_id_file: str) -> None:
|
||
"""
|
||
执行清理流程
|
||
|
||
Args:
|
||
production_id_file: ProductionID.txt 文件路径
|
||
"""
|
||
self._print(f"使用负责人 [{self.config.manager_name}] 进行数据清理")
|
||
|
||
with sync_playwright() as playwright:
|
||
# 1. 初始化浏览器和页面
|
||
browser, context, page, main_frame = self._initialize_browser(playwright)
|
||
|
||
# 2. 导航到目标页面
|
||
page1, main_frame, inner_frame = self.navigator.navigate_to_main_page(page)
|
||
self.navigator.setup_query_interface(inner_frame)
|
||
|
||
# 3. 获取订单列表
|
||
order_ids = self._get_order_ids(production_id_file)
|
||
|
||
# 4. 处理每个订单
|
||
for order_index, order_id in enumerate(order_ids):
|
||
self._process_order(inner_frame, order_id, order_index, page1)
|
||
|
||
# 5. 清理
|
||
self._cleanup(main_frame, context, browser)
|
||
|
||
def _initialize_browser(self, playwright):
|
||
"""初始化浏览器和登录"""
|
||
self._print("=" * 80)
|
||
self._print("开始执行离散备料计划维护数据清理")
|
||
self._print("=" * 80)
|
||
|
||
return login(
|
||
playwright=playwright,
|
||
username=self.config.username,
|
||
password=self.config.password,
|
||
headless=self.config.headless,
|
||
ignore_https_errors=True,
|
||
)
|
||
|
||
def _get_order_ids(self, production_id_file: str):
|
||
"""获取订单 ID 列表"""
|
||
# 读取总排号
|
||
production_ids = read_production_ids(production_id_file)
|
||
self._print(f"从文件读取到 {len(production_ids)} 个总排号")
|
||
|
||
# 查询数据库获取生产订单号
|
||
order_ids = query_production_order_numbers(production_ids)
|
||
self._print(f"查询到 {len(order_ids)} 个生产订单号")
|
||
|
||
return order_ids
|
||
|
||
def _process_order(self, inner_frame, order_id: str, order_index: int, page1):
|
||
"""处理单个订单"""
|
||
self._print(
|
||
f"\n=== 开始处理第 {order_index + 1} 个订单,订单号: {order_id} ==="
|
||
)
|
||
|
||
# 1. 查询订单
|
||
self._query_order(inner_frame, order_id, order_index)
|
||
|
||
# 2. 调试模式暂停
|
||
if (
|
||
self.config.debug_mode
|
||
and (self.config.debug_order is None or order_index == self.config.debug_order)
|
||
):
|
||
self._print(f"=== 调试暂停:第 {order_index + 1} 个订单 ===")
|
||
page1.pause()
|
||
|
||
# 3. 导航到备料计划页面
|
||
page2, plan_frame = self.navigator.navigate_to_material_plan_page(page1)
|
||
|
||
# 4. 提取订单信息
|
||
detail_count = self.extractor.extract_detail_count(plan_frame)
|
||
detail_status = self.extractor.extract_detail_status(plan_frame)
|
||
|
||
# 5. 检查是否需要处理
|
||
if not self._should_process_order(detail_count, detail_status, order_index):
|
||
page2.close()
|
||
return
|
||
|
||
# 6. 进入编辑模式并展开
|
||
self._enter_edit_mode(plan_frame)
|
||
|
||
# 7. 提取并处理物料
|
||
materials = self.extractor.extract_materials(plan_frame, detail_count)
|
||
for material in materials:
|
||
material.should_delete = self.checker.should_delete(material)
|
||
# TODO: 执行实际的删除操作
|
||
|
||
page2.close()
|
||
time.sleep(1)
|
||
|
||
def _query_order(self, inner_frame, order_id: str, order_index: int):
|
||
"""查询单个订单"""
|
||
# 清空文本框
|
||
textbox = inner_frame.get_by_role("textbox", name="生产订单号")
|
||
textbox.fill("")
|
||
|
||
# 填充订单号
|
||
textbox.fill(order_id)
|
||
|
||
# 点击查询
|
||
from config.ui_constants import Selectors
|
||
|
||
inner_frame.locator(Selectors.SEARCH_BTN).click()
|
||
self._print(f"第 {order_index + 1} 个订单查询完成,等待加载结果...")
|
||
|
||
# 等待加载完成
|
||
self.navigator.wait_for_page_loaded(inner_frame)
|
||
self._print(f"第 {order_index + 1} 个订单加载完成,开始清理数据...")
|
||
|
||
def _should_process_order(
|
||
self, detail_count: int, detail_status: str, order_index: int
|
||
) -> bool:
|
||
"""判断订单是否需要处理"""
|
||
if detail_count == 0:
|
||
self._print(f"第 {order_index + 1} 个订单无数据需要清理,跳过...")
|
||
return False
|
||
elif detail_status != "审批通过":
|
||
self._print(f"第 {order_index + 1} 个订单备料状态: {detail_status}")
|
||
return False
|
||
return True
|
||
|
||
def _enter_edit_mode(self, frame):
|
||
"""进入编辑模式并展开物料列表"""
|
||
from config.ui_constants import Selectors
|
||
|
||
frame.get_by_role("button", name=Selectors.MODIFY_BUTTON).click()
|
||
save_button_locator = frame.get_by_role("button", name=Selectors.SAVE_BUTTON)
|
||
save_button_locator.wait_for(state="visible", timeout=10000)
|
||
|
||
frame.get_by_text(Selectors.EXPAND_BUTTON).first.click()
|
||
|
||
def _cleanup(self, main_frame, context, browser):
|
||
"""清理资源"""
|
||
self._print("\n开始执行账号注销...")
|
||
logout(main_frame, verbose=self.config.verbose)
|
||
self._print(f"\n=== 全部完成 ===")
|
||
context.close()
|
||
browser.close()
|