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>
167 lines
5.4 KiB
Python
167 lines
5.4 KiB
Python
"""
|
||
物料信息提取服务实现
|
||
实现从页面提取物料信息的具体逻辑
|
||
"""
|
||
|
||
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()
|