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>
This commit is contained in:
Misaka_Company
2026-02-10 14:17:37 +08:00
parent 21f5827cd3
commit 5024384c73
16 changed files with 1661 additions and 346 deletions

View File

@@ -0,0 +1,166 @@
"""
物料信息提取服务实现
实现从页面提取物料信息的具体逻辑
"""
import re
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from interfaces.i_material_extractor import IMaterialExtractor
from config.ui_constants import UIConstants
from models.material_info import MaterialInfo
class MaterialExtractor(IMaterialExtractor):
"""物料信息提取实现"""
def __init__(self, config: UIConstants, verbose: bool = True):
"""
初始化物料提取器
Args:
config: UI 配置
verbose: 是否打印详细日志
"""
self.config = config
self.verbose = verbose
def _print(self, *args, **kwargs):
"""打印日志(如果 verbose=True"""
if self.verbose:
print(*args, **kwargs)
def extract_detail_count(self, frame) -> int:
"""
提取详细信息数量
Args:
frame: 目标 iframe
Returns:
详细信息数量
"""
detail_element = frame.get_by_text(re.compile(r"^详细信息 \(\d+\)$"))
detail_text = detail_element.inner_text()
match = re.search(r"详细信息 \((\d+)\)", detail_text)
if match:
detail_count = int(match.group(1))
self._print(f"详细信息数量: {detail_count}")
return detail_count
return 0
def extract_detail_status(self, frame) -> str:
"""
提取备料状态
Args:
frame: 目标 iframe
Returns:
备料状态文本
"""
detail_element = frame.get_by_text(re.compile(r"^备料状态:.+$"))
detail_text = detail_element.inner_text().replace("\n", "")
match = re.search(r"^备料状态:(.+)$", detail_text)
if match:
detail_status = match.group(1)
self._print(f"备料状态: {detail_status}")
return detail_status
return ""
def extract_materials(self, frame, count: int):
"""
提取所有物料信息
Args:
frame: 目标 iframe
count: 物料数量
Returns:
物料信息列表
"""
materials = []
# 获取展开后的父容器
child_form = frame.locator(self.config.selectors.CARD_TABLE_SIDE_BOX)
child_form.wait_for(state="visible", timeout=self.config.timeouts.FORM_VISIBLE)
self._print(f"父容器 {self.config.selectors.CARD_TABLE_SIDE_BOX} 已找到")
for i in range(count):
serial_number = i + 1
id_label_locator = child_form.get_by_text("序号 " + str(serial_number))
id_label_locator.wait_for(
state="visible", timeout=self.config.timeouts.SERIAL_VISIBLE
)
self._print(f"处理 {id_label_locator.inner_text()}")
# 提取物料信息
code = self._extract_material_code(child_form)
name = self._extract_material_name(child_form)
pending_quantity = self._extract_pending_quantity(child_form)
shipped_quantity = self._extract_shipped_quantity(child_form)
material = MaterialInfo(
serial_number=serial_number,
code=code,
name=name,
pending_quantity=pending_quantity,
shipped_quantity=shipped_quantity,
)
materials.append(material)
# 导航到下一个物料
self._navigate_to_next_material(child_form, i, count)
return materials
def _extract_material_code(self, child_form) -> str:
"""提取物料编码"""
input_box = (
child_form.locator("div")
.filter(has_text=re.compile(r"^材料编码\d{11}$", re.MULTILINE))
.locator("input")
.first
)
code = input_box.input_value()
self._print(f"材料编码: {code}")
return code
def _extract_material_name(self, child_form) -> str:
"""提取物料名称"""
input_box = (
child_form.locator("div")
.filter(has_text=re.compile(r"^材料名称$"))
.locator("input[type='text']")
)
name = input_box.input_value()
self._print(f"材料名称: {name}")
return name
def _extract_pending_quantity(self, child_form) -> str:
"""提取累计待发数量"""
input_box = (
child_form.locator("div")
.filter(has_text=re.compile(r"^累计待发数量$"))
.locator("input[type='text']")
)
quantity = input_box.input_value()
self._print(f"累计待发数量: {quantity}")
return quantity
def _extract_shipped_quantity(self, child_form) -> str:
"""提取累计出库数量"""
input_box = (
child_form.locator("div")
.filter(has_text=re.compile(r"^累计出库数量$"))
.locator("input[type='text']")
)
quantity = input_box.input_value()
self._print(f"累计出库数量: {quantity}")
return quantity
def _navigate_to_next_material(self, child_form, current_index: int, total_count: int):
"""导航到下一个物料"""
if current_index != total_count - 1:
child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(2).click()
else:
child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(4).click()