Files
playwrite/utils/discrete_material_cleaner.py
Misaka_Company 5024384c73 refactor: restructure main_clean.py with dependency injection and interfaces
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>
2026-02-10 14:17:37 +08:00

191 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
离散备料计划清理器 - 重构版
使用依赖注入、接口抽象、职责分离设计
"""
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()