Files
playwrite/services/material_extractor.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

167 lines
5.4 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 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()