Compare commits

...

10 Commits

Author SHA1 Message Date
Misaka_Company
a78afe383f fix: adjust footer detection logic in excel converter
Changed footer detection to look ahead at the next row instead of the current row, and simplified to only check for '制单人'.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-05 08:39:18 +08:00
Misaka_Company
c176063bfa feat: add material matching logic for discrete material plan cleaner
- Add db/materials_to_delete.py for querying materials to delete by manager
- Add manager_name parameter to DiscreteMaterialPlanCleaner
- Implement material keyword matching logic in process_order
- Update file paths from orderID.txt to ProductionID.txt

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-23 16:50:14 +08:00
Misaka_Company
902ff9b831 refactor: use database for production order queries
- Add db/production_order_query.py component for querying production orders
- Replace file-based orderID.txt with database-driven approach
- Read ProductionID.txt (总排号) and query [26年压力表合同数据] table
- Update both extraction and cleaning scripts to use new component
- Change parameter: order_id_file → production_id_file

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-23 16:06:40 +08:00
Misaka_Company
1e2eed76e2 chore: remove obsolete login.py
The login functionality has been moved to utils/auth.py, and the old login.py file is no longer used.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-23 15:17:12 +08:00
Misaka_Company
60b6fecbb3 chore: move scripts to tools directory
Move analyze_excel.py, excel_to_markdown.py, and locator_helper.py
into the tools/ subdirectory to improve project organization.
2026-01-23 15:12:33 +08:00
Misaka_Company
bbf84b7f8c docs: update project structure and add database documentation
- Add tests/ directory to .gitignore
- Document database connectivity with pyodbc and config
- Add project structure and file organization guidelines
- Update installation steps to use requirements.txt
- Document new dependencies: pandas, openpyxl, pyodbc
2026-01-23 15:00:55 +08:00
Misaka_Company
4b0a882b69 refactor: improve material code input selection
Replace the class-based locator strategy with a text-based filter using regex to accurately identify the material code input field. This change targets elements matching "材料编码" followed by 11 digits, increasing reliability. Also uncommented page2.pause() to assist with debugging the iteration loop.
2026-01-23 14:30:35 +08:00
Misaka_Company
ebd53b168e refactor: improve locator precision and data extraction
Refine element selection by introducing a specific parent container `.card-table-side-box` to enhance locator accuracy. Replace the previous text-parsing approach for material codes with direct input value retrieval. Additionally, add support for extracting cumulative pending and outbound quantities to improve data completeness.
2026-01-23 13:52:52 +08:00
Misaka_Company
cf88800444 feat: Check preparation status before cleaning data
Added logic to extract and validate the "Material Preparation Status" (备料状态) before modifying discrete material plans.

The cleanup process now only proceeds if the status is "审批通过" (Approval Passed). Orders with other statuses are skipped to prevent errors during modification. This change also includes code cleanup by removing commented-out debug statements.
2026-01-23 11:11:22 +08:00
Misaka_Company
acd19025d4 Stop tracking orderID.txt 2026-01-22 18:17:40 +08:00
22 changed files with 665 additions and 691 deletions

6
.gitignore vendored
View File

@@ -13,4 +13,8 @@ tmpclaude-*
*workspace* *workspace*
*.png *.png
data/ data/
orderID.txt orderID.txt
ProductionID.txt
# 测试脚本
tests/

View File

@@ -12,8 +12,8 @@ This is a Python automation project using Playwright to interact with a Chinese
# Activate the virtual environment (Windows) # Activate the virtual environment (Windows)
.venv\Scripts\activate .venv\Scripts\activate
# Install dependencies # Install all dependencies
pip install playwright pip install -r requirements.txt
# Install Playwright browsers # Install Playwright browsers
playwright install chromium playwright install chromium
@@ -23,17 +23,53 @@ playwright install chromium
```bash ```bash
# Run the main test script # Run the main test script
python test_playwright.py python tests/test_playwright.py
# Run the record script # Run the record script
python record.py python record.py
# Run database query test
python tests/test_db_query.py
``` ```
## Key Dependencies ## Key Dependencies
- **playwright==1.57.0**: Browser automation framework - **playwright==1.57.0**: Browser automation framework
- **pyodbc**: SQL Server database connectivity
- **pandas**: Data processing and Excel file handling
- **openpyxl**: Excel file operations
- Uses synchronous API (`playwright.sync_api`) - Uses synchronous API (`playwright.sync_api`)
## Project Structure
```
playwrite/
├── config/ # Configuration files (database, etc.)
├── db/ # Database connection components
├── data/ # Data files (excluded from git)
├── docs/ # Documentation (e.g., DEPENDENCIES.md)
├── tests/ # Test scripts (excluded from git)
├── requirements.txt # Python dependencies
├── .gitignore # Git ignore rules
└── CLAUDE.md # This file
```
## File Organization Rules
**IMPORTANT**: When creating test scripts, always place them in the `tests/` folder:
- Test scripts should be prefixed with `test_`
- All files in `tests/` are excluded from git tracking
- Examples: `tests/test_playwright.py`, `tests/test_db_query.py`
**Configuration Management**:
- Place configuration files in `config/` folder
- Database credentials and settings go in `config/database_config.py`
**Data Storage**:
- Use `data/` folder for temporary data files
- This folder is excluded from git tracking
## Architecture ## Architecture
### Nested Iframe Structure ### Nested Iframe Structure
@@ -80,6 +116,35 @@ with page.expect_popup() as popup_info:
new_page = popup_info.value new_page = popup_info.value
``` ```
### Database Connection
The project uses `pyodbc` to connect to SQL Server for data queries:
```python
from db.connection import get_connection
# Using context manager
with get_connection() as db:
results = db.execute_query("SELECT * FROM table")
# Connection automatically closed
# Using the query helper
from db.connection import query_production_orders
results = query_production_orders(['ID1', 'ID2', 'ID3'])
```
Database configuration is stored in `config/database_config.py`:
```python
SQL_SERVER_CONFIG = {
'driver': 'ODBC Driver 18 for SQL Server',
'server': '192.168.110.114',
'database': 'CompanyDB',
'username': 'peng',
'password': 'Cqbld123456.',
'TrustServerCertificate': 'yes'
}
```
## Common Utilities ## Common Utilities
### `get_input_by_label(frame, label_text)` ### `get_input_by_label(frame, label_text)`

1
config/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Config package

View File

@@ -0,0 +1,9 @@
# ================= SQL Server 配置 =================
SQL_SERVER_CONFIG = {
'driver': 'ODBC Driver 18 for SQL Server',
'server': '192.168.110.114',
'database': 'CompanyDB',
'username': 'peng',
'password': 'Cqbld123456.',
'TrustServerCertificate': 'yes'
}

View File

@@ -1,23 +0,0 @@
"""
Excel 转换工具使用示例
"""
from utils.excel_converter import ExcelConverter
def main():
# 创建转换器verbose=True 打印详细日志)
converter = ExcelConverter(verbose=True)
# 转换 Excel 文件
input_file = "data/离散备料计划打印模版-布莱迪.xlsx"
output_file = "data/离散备料计划打印模版-布莱迪_转换.xlsx"
# 执行转换
df = converter.convert(input_file, output_file)
print(f"\n转换完成!")
print(f"数据形状: {df.shape}")
if __name__ == "__main__":
main()

1
db/__init__.py Normal file
View File

@@ -0,0 +1 @@
# Database package

184
db/connection.py Normal file
View File

@@ -0,0 +1,184 @@
"""
SQL Server 数据库连接组件
提供数据库连接和查询接口
"""
import pyodbc
from typing import List, Dict, Any, Optional
import sys
import os
# 添加项目根目录到 sys.path
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if project_root not in sys.path:
sys.path.insert(0, project_root)
from config.database_config import SQL_SERVER_CONFIG
class DatabaseConnection:
"""SQL Server 数据库连接类"""
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""
初始化数据库连接
Args:
config: 数据库配置字典,默认使用 SQL_SERVER_CONFIG
"""
self.config = config or SQL_SERVER_CONFIG
self.connection = None
def connect(self) -> pyodbc.Connection:
"""
建立数据库连接
Returns:
pyodbc.Connection: 数据库连接对象
"""
if self.connection is not None:
return self.connection
# 构建连接字符串
conn_str = (
f"DRIVER={{{self.config['driver']}}};"
f"SERVER={self.config['server']};"
f"DATABASE={self.config['database']};"
f"UID={self.config['username']};"
f"PWD={self.config['password']};"
f"TrustServerCertificate={self.config['TrustServerCertificate']};"
)
try:
self.connection = pyodbc.connect(conn_str)
print(f"成功连接到数据库: {self.config['server']}/{self.config['database']}")
return self.connection
except pyodbc.Error as e:
print(f"数据库连接失败: {e}")
raise
def disconnect(self):
"""关闭数据库连接"""
if self.connection:
self.connection.close()
self.connection = None
print("数据库连接已关闭")
def execute_query(self, sql: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]:
"""
执行查询语句并返回结果
Args:
sql: SQL 查询语句
params: 查询参数(可选)
Returns:
List[Dict[str, Any]]: 查询结果列表,每个元素为一行数据的字典
"""
if not self.connection:
self.connect()
cursor = self.connection.cursor()
try:
if params:
cursor.execute(sql, params)
else:
cursor.execute(sql)
# 获取列名
columns = [column[0] for column in cursor.description]
# 将结果转换为字典列表
results = []
for row in cursor.fetchall():
results.append(dict(zip(columns, row)))
return results
except pyodbc.Error as e:
print(f"查询执行失败: {e}")
raise
finally:
cursor.close()
def execute_update(self, sql: str, params: Optional[tuple] = None) -> int:
"""
执行更新/插入/删除语句
Args:
sql: SQL 语句
params: 参数(可选)
Returns:
int: 受影响的行数
"""
if not self.connection:
self.connect()
cursor = self.connection.cursor()
try:
if params:
cursor.execute(sql, params)
else:
cursor.execute(sql)
self.connection.commit()
return cursor.rowcount
except pyodbc.Error as e:
self.connection.rollback()
print(f"执行失败,已回滚: {e}")
raise
finally:
cursor.close()
def __enter__(self):
"""支持 with 语句的上下文管理器入口"""
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""支持 with 语句的上下文管理器出口"""
self.disconnect()
# 便捷函数
def query_production_orders(总排号_list: List[str]) -> List[Dict[str, Any]]:
"""
根据总排号列表查询生产订单号
Args:
总排号_list: 总排号列表
Returns:
List[Dict[str, Any]]: 查询结果
"""
db = DatabaseConnection()
# 构建占位符字符串
placeholders = ','.join(['?' for _ in 总排号_list])
sql = f"""
SELECT [总排号], [生产订单号], [序号], [订单号], [客户名称], [产品型号]
FROM [productionContractData].[26年压力表合同数据]
WHERE [总排号] IN ({placeholders})
ORDER BY [序号]
"""
try:
results = db.execute_query(sql, tuple(总排号_list))
return results
finally:
db.disconnect()
def get_connection() -> DatabaseConnection:
"""
获取数据库连接实例
Returns:
DatabaseConnection: 数据库连接对象
"""
return DatabaseConnection()

28
db/materials_to_delete.py Normal file
View File

@@ -0,0 +1,28 @@
"""
待删除物料查询组件
从数据库查询指定负责人需要删除的物料名称
"""
from db.connection import get_connection
def get_materials_to_delete(manager_name):
"""
根据负责人名称查询待删除物料名称列表
Args:
manager_name: 负责人姓名
Returns:
物料名称列表(关键字)
"""
query = """
SELECT [MaterialName]
FROM [dbo].[MaterialsToBeDeleted]
WHERE [ManagerName] = ?
"""
with get_connection() as conn:
results = conn.execute_query(query, (manager_name,))
# 提取物料名称并去除空值
material_names = [row['MaterialName'] for row in results if row['MaterialName']]
return material_names

View File

@@ -0,0 +1,50 @@
"""
生产订单号查询组件
从 ProductionID.txt 读取总排号,查询数据库获取生产订单号
"""
from db.connection import get_connection
def read_production_ids(file_path):
"""
读取 ProductionID.txt 文件,获取总排号列表
Args:
file_path: ProductionID.txt 文件路径
Returns:
总排号列表
"""
with open(file_path, 'r', encoding='utf-8') as f:
# 去除空白行和空格
production_ids = [line.strip() for line in f if line.strip()]
return production_ids
def query_production_order_numbers(production_ids):
"""
根据总排号列表,从数据库查询生产订单号
Args:
production_ids: 总排号列表
Returns:
生产订单号列表
"""
if not production_ids:
return []
# 构建 IN 子句的占位符
placeholders = ','.join(['?' for _ in production_ids])
query = f"""
SELECT [生产订单号]
FROM [productionContractData].[26年压力表合同数据]
WHERE [总排号] IN ({placeholders})
"""
with get_connection() as conn:
results = conn.execute_query(query, tuple(production_ids))
# 提取生产订单号并去除空值
production_order_numbers = [row['生产订单号'] for row in results if row['生产订单号']]
return production_order_numbers

169
docs/DEPENDENCIES.md Normal file
View File

@@ -0,0 +1,169 @@
# 项目依赖说明
## Python 版本要求
- Python >= 3.8
## 依赖安装
```bash
# 激活虚拟环境 (Windows)
.venv\Scripts\activate
# 安装所有依赖
pip install -r requirements.txt
# 安装 Playwright 浏览器
playwright install chromium
```
## 核心依赖
### 1. playwright==1.57.0
**用途**: 浏览器自动化框架
**功能模块**:
- `playwright.sync_api` - 同步 API
- 浏览器启动与页面操作
- 元素定位器 (Locator)
- 弹窗处理 (expect_popup)
- 嵌套 iframe 处理
**使用文件**:
- `test_playwright.py` - 主要自动化脚本
- `record.py` - 录制脚本
**重要**: 安装后需运行 `playwright install chromium` 下载浏览器
---
### 2. pyodbc>=5.0.0
**用途**: SQL Server 数据库连接
**功能模块**:
- 数据库连接管理
- SQL 查询执行
- 事务处理
**使用文件**:
- `db/connection.py` - 数据库连接组件
- `test_db_query.py` - 数据库查询测试脚本
**注意**: Windows 系统通常已预装 ODBC 驱动,如遇连接问题请安装 [ODBC Driver 18 for SQL Server](https://learn.microsoft.com/zh-cn/sql/connect/odbc/download-odbc-driver-for-sql-server)
---
### 3. pandas>=2.0.0
**用途**: 数据处理与分析
**功能模块**:
- DataFrame 数据结构
- Excel 文件读写
- 数据清洗与转换
**使用文件**:
- `excel_to_markdown.py` - Excel 转 Markdown
- `analyze_excel.py` - Excel 数据分析
---
### 4. openpyxl>=3.1.0
**用途**: Excel 文件操作
**功能模块**:
- 读取 `.xlsx` 文件
- 工作表操作
- 单元格读取
**使用文件**:
- `excel_to_markdown.py`
- `analyze_excel.py`
---
### 5. numpy>=1.24.0
**用途**: 数值计算
**说明**: pandas 的依赖包,自动安装
---
### 6. python-dateutil>=2.8.0
**用途**: 日期时间处理
**说明**: pandas 的依赖包,自动安装
---
### 7. pytz>=2023.0
**用途**: 时区处理
**说明**: pandas 的依赖包,自动安装
---
## 传递依赖
以下包由核心依赖自动安装,无需手动指定:
| 包名 | 版本 | 被依赖包 |
|------|------|----------|
| greenlet | 3.3.0 | playwright |
| pyee | 13.0.0 | playwright |
| typing_extensions | 4.15.0 | 多个包 |
| et_xmlfile | 2.0.0 | openpyxl |
| six | 1.17.0 | python-dateutil |
| tzdata | 2025.3 | pandas |
---
## 按功能分类
### 浏览器自动化
```
playwright==1.57.0
```
### 数据库
```
pyodbc>=5.0.0
```
### Excel/数据处理
```
pandas>=2.0.0
openpyxl>=3.1.0
numpy>=1.24.0
```
---
## 当前环境已安装包
```
Package Version
----------------- -----------
et_xmlfile 2.0.0
greenlet 3.3.0
numpy 2.2.6
openpyxl 3.1.5
pandas 2.3.3
pip 25.3
playwright 1.57.0
pyee 13.0.0
python-dateutil 2.9.0.post0
pytz 2025.2
setuptools 65.5.0
six 1.17.0
typing_extensions 4.15.0
tzdata 2025.3
```
**注意**: `pyodbc` 尚未安装,运行数据库相关脚本前请先安装。

View File

@@ -1,65 +0,0 @@
"""
登录模块 - 负责用友BIP系统的登录操作
"""
from playwright.sync_api import Playwright, Browser, BrowserContext, Page, Frame
def login(
playwright: Playwright,
username: str,
password: str,
url: str = "https://68.11.34.30:8082/yonbip/resources/uap/rbac/login/main/index.html",
headless: bool = False,
ignore_https_errors: bool = True
) -> tuple[Browser, BrowserContext, Page, Frame]:
"""
登录用友BIP系统
参数:
playwright: Playwright实例
username: 用户名
password: 密码
url: 登录页面URL
headless: 是否使用无头模式
ignore_https_errors: 是否忽略HTTPS错误
返回:
tuple: (browser, context, page, main_frame)
- browser: 浏览器实例
- context: 浏览器上下文
- page: 页面对象
- main_frame: 登录后的主iframe (forwardFrame)
"""
# 启动浏览器
browser = playwright.chromium.launch(headless=headless)
# 创建上下文
context = browser.new_context(ignore_https_errors=ignore_https_errors)
# 创建页面
page = context.new_page()
# 导航到登录页面
page.goto(url)
# 提取主 iframe (forwardFrame)
main_frame = page.locator("#forwardFrame").content_frame
# 填写用户名
main_frame.get_by_role("textbox", name="用户名").fill(username)
# 填写密码
main_frame.get_by_role("textbox", name="密码").fill(password)
# 点击登录按钮
main_frame.get_by_role("button", name="登录").click()
# 处理强制登录弹窗(判断是否存在)
confirm_btn = main_frame.get_by_role("button", name="确定")
if confirm_btn.count() > 0:
confirm_btn.click()
print("强制登录")
else:
print("正常登录")
return browser, context, page, main_frame

View File

@@ -15,13 +15,12 @@ def main():
) )
# 设置文件路径 # 设置文件路径
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt") order_id_file = os.path.join(os.path.dirname(__file__), "ProductionID.txt")
output_file = r"D:/python/playwrite/data/离散备料计划维护_合并.xlsx" output_file = r"D:/python/playwrite/data/离散备料计划维护_合并.xlsx"
# 执行提取 # 执行提取
extractor.extract(order_id_file, output_file) extractor.extract(order_id_file, output_file)
input("按回车退出...")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -10,12 +10,13 @@ def main():
cleaner = DiscreteMaterialPlanCleaner( cleaner = DiscreteMaterialPlanCleaner(
username="BLDpengqiangqiang", username="BLDpengqiangqiang",
password="Cqbld123456.", password="Cqbld123456.",
headless=False, manager_name="彭羽",
headless=True,
verbose=True verbose=True
) )
# 设置文件路径 # 设置文件路径
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt") order_id_file = os.path.join(os.path.dirname(__file__), "ProductionID.txt")
# 执行清理 # 执行清理
cleaner.clean(order_id_file) cleaner.clean(order_id_file)

View File

@@ -1,222 +0,0 @@
SC70202510110001
SC70202510110002
SC70202510110003
SC70202510110004
SC70202510110005
SC70202510110006
SC70202510110007
SC70202510110008
SC70202510110009
SC70202510110010
SC70202510110011
SC70202510110012
SC70202510110013
SC70202510110014
SC70202510110015
SC70202510110016
SC70202510110017
SC70202510110018
SC70202510110019
SC70202510110020
SC70202510110021
SC70202510110022
SC70202510110023
SC70202510110024
SC70202510110025
SC70202510110026
SC70202510110027
SC70202510110028
SC70202510110029
SC70202510110030
SC70202510110031
SC70202510110032
SC70202510110033
SC70202510110034
SC70202510110035
SC70202510110036
SC70202510110037
SC70202510110038
SC70202510110039
SC70202510110040
SC70202510110041
SC70202510110042
SC70202510110043
SC70202510110044
SC70202510110045
SC70202510110046
SC70202510110047
SC70202510110048
SC70202510110049
SC70202510110050
SC70202510110051
SC70202510110052
SC70202510110053
SC70202510110054
SC70202510110055
SC70202510110056
SC70202510110057
SC70202510110058
SC70202510110059
SC70202510110060
SC70202510110061
SC70202510110062
SC70202510110063
SC70202510110064
SC70202510110065
SC70202510110066
SC70202510110067
SC70202510110068
SC70202510110069
SC70202510110070
SC70202510110071
SC70202510110072
SC70202510110073
SC70202510110074
SC70202510110075
SC70202510110076
SC70202510110077
SC70202510110078
SC70202510110079
SC70202510110080
SC70202510110081
SC70202510110082
SC70202510110083
SC70202510110084
SC70202510110085
SC70202510110086
SC70202510110087
SC70202510110088
SC70202510110089
SC70202510110090
SC70202510110091
SC70202510110092
SC70202510110093
SC70202510110094
SC70202510110095
SC70202510110096
SC70202510110097
SC70202510110098
SC70202510110099
SC70202510110100
SC70202510110101
SC70202510110102
SC70202510110103
SC70202510110104
SC70202510110105
SC70202510110106
SC70202510110107
SC70202510110108
SC70202510110109
SC70202510110110
SC70202510110111
SC70202510110112
SC70202510110113
SC70202510110114
SC70202510110115
SC70202510110116
SC70202510110117
SC70202510110118
SC70202510110119
SC70202510110120
SC70202510110121
SC70202510110122
SC70202510110123
SC70202510110124
SC70202510110125
SC70202510110126
SC70202510110127
SC70202510110128
SC70202510110129
SC70202510110130
SC70202510110131
SC70202510110132
SC70202510110133
SC70202510110134
SC70202510110135
SC70202510110136
SC70202510110137
SC70202510110138
SC70202510110139
SC70202510110140
SC70202510110141
SC70202510110142
SC70202510110143
SC70202510110144
SC70202510110145
SC70202510110146
SC70202510110147
SC70202510110148
SC70202510110149
SC70202510110150
SC70202510110151
SC70202510110152
SC70202510110153
SC70202510110154
SC70202510110155
SC70202510110156
SC70202510110157
SC70202510110158
SC70202510110159
SC70202510110160
SC70202510110161
SC70202510110162
SC70202510110163
SC70202510110164
SC70202510110165
SC70202510110166
SC70202510110167
SC70202510110168
SC70202510110169
SC70202510110170
SC70202510110171
SC70202510110172
SC70202510110173
SC70202510110174
SC70202510110175
SC70202510110176
SC70202510110177
SC70202510110178
SC70202510110179
SC70202510110180
SC70202510110181
SC70202510110182
SC70202510110183
SC70202510110184
SC70202510110185
SC70202510110186
SC70202510110187
SC70202510110188
SC70202510110189
SC70202510110190
SC70202510110191
SC70202510110192
SC70202510110193
SC70202510110194
SC70202510110195
SC70202510110196
SC70202510110197
SC70202510110198
SC70202510110199
SC70202510110200
SC70202510110201
SC70202510110202
SC70202510110203
SC70202510110204
SC70202510110205
SC70202510110206
SC70202510110207
SC70202510110208
SC70202510110209
SC70202510110210
SC70202510110211
SC70202510110212
SC70202510110213
SC70202510110214
SC70202510110215
SC70202510110216
SC70202510110217
SC70202510110218
SC70202510110219
SC70202510110220
SC70202510110221
SC70202510110222

19
requirements.txt Normal file
View File

@@ -0,0 +1,19 @@
# ===========================
# Playwright Python Automation
# ===========================
# Python 3.8+ required
# --- Browser Automation ---
playwright==1.57.0
# --- Database ---
pyodbc>=5.0.0
# --- Excel/Data Processing ---
pandas>=2.0.0
openpyxl>=3.1.0
numpy>=1.24.0
# --- System Utilities (installed via pip) ---
python-dateutil>=2.8.0
pytz>=2023.0

View File

@@ -1,318 +0,0 @@
import re
import time
from playwright.sync_api import Playwright, sync_playwright, expect, TimeoutError as PWTimeoutError
import pdb
def click_button_until_disappear(frame, button_name="保存提交", max_attempts=None, max_duration=None):
"""
持续点击按钮直到按钮消失。
包含业务逻辑:点击前检查【生产部门】是否为空,为空则自动回填数据。
"""
click_count = 0
start_time = time.time()
# 定义加载控件的定位器 (基于传入的 frame)
loading_locator = frame.locator("div").filter(has_text="加载中").nth(1)
print(f"开始监控'{button_name}'按钮 (含自动补全逻辑)...")
while True:
try:
# --- 1. 检查退出条件 ---
if max_attempts and click_count >= max_attempts:
print(f"已达到最大点击次数{max_attempts}次,停止操作")
break
if max_duration and (time.time() - start_time) >= max_duration:
elapsed = time.time() - start_time
print(f"已达到最大持续时间{max_duration}秒,停止操作")
break
# --- 2. 检查按钮状态 ---
button = frame.get_by_role("button", name=button_name)
# 如果按钮已经消失,任务完成
if button.count() == 0:
elapsed = time.time() - start_time
print(f"SUCCESS: '{button_name}'按钮已消失")
print(f"共点击{click_count}次,总耗时{elapsed:.1f}")
return {
"success": True,
"click_count": click_count,
"elapsed_time": elapsed
}
# ==========================================
# 插入业务逻辑:检查并回填数据
# ==========================================
try:
# 1. 获取生产部门输入框
dep_input = get_input_by_label(frame, "生产部门")
if dep_input:
current_val = dep_input.input_value()
# 检查是否为空 (strip去除空白字符)
if current_val != "压力表车间" and current_val != "CQ030101":
print(f" [补全] 检测到生产部门为空,开始自动填充...")
# A. 填入生产部门
dep_input.fill("压力表车间")
print(" [补全] -> 生产部门已填入: CQ030101")
# B. 填入订单类型 (仅在需要补全生产部门时才操作这个,根据你的需求逻辑)
type_input = get_input_by_label(frame, "订单类型")
if type_input:
type_input.fill("55C2-Cxx-02")
print(" [补全] -> 订单类型已填入: 55C2-Cxx-02")
else:
print(" [警告] 无法找到'订单类型'输入框")
# 稍微等待一下填入值的生效(可选)
time.sleep(0.5)
else:
print(f" [信息] 生产部门已有值: {current_val},跳过补全")
else:
print(" [警告] 无法找到'生产部门'输入框,跳过检查")
except Exception as logic_e:
print(f" [异常] 数据补全逻辑出错 (不影响继续点击): {logic_e}")
# ==========================================
# --- 3. 执行点击 ---
button.click()
click_count += 1
print(f"[{time.strftime('%H:%M:%S')}] 第{click_count}次点击'{button_name}'")
# --- 4. 智能等待加载逻辑 ---
# 4.1 缓冲等待点击后UI可能需要几秒钟才会弹出遮罩
# print(" - 等待遮罩层响应...")
time.sleep(2)
# 4.2 检测并等待遮罩消失
try:
# 尝试等待遮罩出现 (给它3秒的时间被检测到)
if loading_locator.is_visible(timeout=3000):
print(" - 检测到'加载中',正在等待数据载入...")
# 只要出现了,就无限等待它消失 (timeout=0)
loading_locator.wait_for(state="hidden", timeout=0)
print(" - 数据载入完毕")
else:
# 如果3秒内没检测到 visible可能是数据极少瞬间加载完了
pass
except Exception as e:
# 忽略定位超时等非致命错误
pass
# 稍微休息一下
time.sleep(0.5)
except Exception as e:
elapsed = time.time() - start_time
print(f"操作异常: {e}")
return {
"success": False,
"click_count": click_count,
"error": str(e)
}
def get_input_by_label(frame, label_text: str, label_locator=None):
"""
通过标签文本获取对应的输入框对象
参数:
frame: iframe对象
label_text: 标签文本(用于查找和日志输出)
label_locator: 可选已经定位好的标签locator。如果为None则函数内部查找
返回:
成功返回输入框的locator对象
失败返回None
"""
try:
# 如果没有传入label_locator则根据label_text查找
if label_locator is None:
label_locator = frame.locator("div").filter(has_text=re.compile(f"^{label_text}$")).first
print(f"找到{label_text}标签")
# 向上找到包含标签和输入框的共同父容器
parent_container = label_locator.locator("..") # 父元素
# 在父容器中查找输入框
input_box = parent_container.locator("input").first
# 如果父元素中没有,再向上一层
if input_box.count() == 0:
print(f"{label_text}: 在父元素中未找到,向上一层查找...")
grandparent = parent_container.locator("..") # 祖父元素
input_box = grandparent.locator("input").first
# 验证是否找到输入框
if input_box.count() > 0:
input_box.wait_for(state="visible", timeout=5000)
current_value = input_box.input_value()
print(f"{label_text}: {current_value}")
print(input_box.count())
return input_box
else:
print(f"{label_text}: 在父容器中找不到输入框")
return None
except Exception as e:
print(f"{label_text}失败: {e}")
return None
def run(playwright: Playwright) -> None:
browser = playwright.chromium.launch(headless=False)
context = browser.new_context(ignore_https_errors=True)
page = context.new_page()
# 1. 登录主页面
try:
page.goto("https://68.11.34.30:8082/yonbip/resources/uap/rbac/login/main/index.html")
# 提取主 iframe
main_frame = page.locator("#forwardFrame").content_frame
main_frame.get_by_role("textbox", name="用户名").fill("BLDpengqiangqiang")
main_frame.get_by_role("textbox", name="密码").fill("Cqbld123456.")
main_frame.get_by_role("button", name="登录").click()
# 2. 处理强制登录弹窗(判断是否存在)
confirm_btn = main_frame.get_by_role("button", name="确定")
try:
# 短暂等待检测弹窗
if confirm_btn.is_visible(timeout=3000):
confirm_btn.click()
print("强制登录")
else:
print("正常登录")
except:
print("正常登录 (未检测到弹窗)")
# 3. 点击打开“补货安排”
main_frame.locator("i").first.click() # 假设是某个新建按钮
with page.expect_popup() as page1_info:
main_frame.get_by_title("补货安排", exact=True).click()
page1 = page1_info.value
# 4. 新页面:等待页面加载完成 + 提取嵌套 iframe
print("Page1 已打开,正在等待内层 iframe 加载...")
page1.wait_for_load_state("domcontentloaded")
# 提取外层 forwardFrame
outer_frame = page1.locator("#forwardFrame").content_frame
# 关键:等待内层 #mainiframe 出现并加载
inner_frame_locator = outer_frame.locator("#mainiframe")
inner_frame_locator.wait_for(state="visible", timeout=15000)
inner_frame = inner_frame_locator.content_frame
print("Page1 内层 iframe 已加载完成")
# 填写查询条件
textbox = inner_frame.get_by_role("textbox", name="期初标识≠")
textbox.fill("")
inner_frame.locator("#rc_select_0").fill("关闭")
textbox = inner_frame.get_by_role("textbox", name="排产号")
textbox.fill("R")
inner_frame.get_by_role("textbox", name="单据日期开始日期").fill("2025-12-28")
inner_frame.get_by_role("textbox", name="单据日期结束日期").fill("2025-12-31")
# inner_frame.get_by_text("今日").click()
# inner_frame.get_by_text("今日").click()
# 点击查询按钮
inner_frame.locator(".iconfont.icon-chaxun").click()
# 勾选所有合同
inner_frame.get_by_role("row", name="序号").get_by_label("").check()
# 获取订单合计数量
summary_locator = inner_frame.get_by_text(re.compile(r"合计:\s*\d+\s*行"))
try:
summary_text = summary_locator.inner_text(timeout=5000)
match = re.search(r"\d+", summary_text)
if match:
row_count = int(match.group())
print(f"当前总行数:{row_count}")
else:
print("未匹配到行数")
except:
print("未找到合计行")
# 进入 Page2
inner_frame.get_by_role("button").filter(has_text="补货安排").hover()
inner_frame.get_by_text("生产订单").click()
inner_frame.get_by_role("textbox", name="工厂").fill("10010705")
with page1.expect_popup(timeout=60000) as page2_info:
inner_frame.get_by_role("button", name="确定(Y)").click()
page2 = page2_info.value
# --- Page2 加载逻辑优化 ---
print("Page2 已打开,正在初始化...")
page2.wait_for_load_state("domcontentloaded")
outer_frame_p2 = page2.locator("#forwardFrame").content_frame
inner_frame_locator_p2 = outer_frame_p2.locator("#mainiframe")
inner_frame_locator_p2.wait_for(state="visible", timeout=15000)
inner_frame_p2 = inner_frame_locator_p2.content_frame
print("Page2 基础框架已就绪")
# 1. 按照建议等待3秒让加载遮罩有机会出现
print("等待 7 秒,检测加载遮罩是否出现...")
time.sleep(7)
# 2. 定义加载控件定位器
# 注意:这里直接基于 inner_frame_p2 构建,对应你的 path: ...locator("#mainiframe").content_frame.locator("div")...
loading_locator = inner_frame_p2.locator("div").filter(has_text="加载中").nth(1)
try:
# 尝试等待加载控件变为 visible。
# 给它 3秒 的检测时间,如果这 3秒 内出现了,说明页面正在加载数据。
# 如果没出现,触发 TimeoutError进入 except 块,说明数据量很少,可能瞬间加载完了。
loading_locator.wait_for(state="visible", timeout=3000)
print("检测到'加载中'控件,开始等待数据加载(时间不定)...")
# 3. 关键:等待控件消失 (state="hidden" 或 "detached")
# timeout=0 表示无限等待,防止数据量大加载时间过长导致报错
loading_locator.wait_for(state="hidden", timeout=0)
print(">>> 页面加载完毕!(加载控件已消失)")
except PWTimeoutError:
print(">>> 页面加载完毕!(未检测到'加载中'控件,可能已快速完成)")
except Exception as e:
print(f">>> 页面加载状态判断异常: {e},默认视为加载完毕")
# --- 这里可以继续 Page2 的后续操作 ---
input_box = get_input_by_label(inner_frame_p2, "生产部门")
input_box = get_input_by_label(inner_frame_p2, "订单类型")
input_box = get_input_by_label(inner_frame_p2, "数量")
print("\n>>> 开始执行连续点击操作 <<<")
# 这里的 inner_frame_p2 是你在 Page2 提取出来的内层 iframe
result = click_button_until_disappear(
frame=inner_frame_p2,
button_name="保存提交", # 修改为你实际要点击的按钮名字,比如 "下一页" 或 "加载更多"
max_attempts=500 # 防止死循环,设置一个最大上限
)
print("进入调试模式,你现在可以在终端输入 Python 代码来测试定位器...")
pdb.set_trace() # <--- 程序会在这里卡住,控制权交给终端
print("最终结果:", result)
input("操作完成,按回车关闭...")
finally:
context.close()
browser.close()
with sync_playwright() as playwright:
run(playwright)

View File

@@ -159,7 +159,7 @@ class ExcelConverter:
data_row = table_row + 1 data_row = table_row + 1
while data_row < len(all_rows) and all_rows[data_row]: while data_row < len(all_rows) and all_rows[data_row]:
# 检查是否是页脚信息(制单人、打印人) # 检查是否是页脚信息(制单人、打印人)
if all_rows[data_row][0] and ('制单人' in str(all_rows[data_row][0]) or '打印人' in str(all_rows[data_row][0])): if all_rows[data_row+1][0] and '制单人' in str(all_rows[data_row+1][0]) :
# 解析页脚信息 # 解析页脚信息
self._parse_header_row(all_rows[data_row], footer_info) self._parse_header_row(all_rows[data_row], footer_info)
# 检查下一行是否也是页脚信息 # 检查下一行是否也是页脚信息

View File

@@ -7,6 +7,7 @@ import pandas as pd
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
from utils.excel_converter import ExcelConverter from utils.excel_converter import ExcelConverter
from utils.auth import login, logout from utils.auth import login, logout
from db.production_order_query import read_production_ids, query_production_order_numbers
class DiscreteMaterialPlanExtractor: class DiscreteMaterialPlanExtractor:
@@ -33,11 +34,24 @@ class DiscreteMaterialPlanExtractor:
if self.verbose: if self.verbose:
print(*args, **kwargs) print(*args, **kwargs)
def read_order_ids(self, file_path): def get_production_order_numbers(self, production_id_file):
"""读取订单号文件""" """
with open(file_path, 'r', encoding='utf-8') as f: 读取总排号文件并查询数据库获取生产订单号
# 去除空白行和空格
order_ids = [line.strip() for line in f if line.strip()] Args:
production_id_file: ProductionID.txt 文件路径
Returns:
生产订单号列表
"""
# 读取总排号
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 return order_ids
def group_order_ids(self, order_ids, group_size=100): def group_order_ids(self, order_ids, group_size=100):
@@ -171,14 +185,14 @@ class DiscreteMaterialPlanExtractor:
if attempt == max_retries - 1: if attempt == max_retries - 1:
self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...") self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...")
def extract(self, order_id_file, data_dir="D:/python/playwrite/data", def extract(self, production_id_file, data_dir="D:/python/playwrite/data",
output_file="D:/python/playwrite/data/离散备料计划维护_合并.xlsx", output_file="D:/python/playwrite/data/离散备料计划维护_合并.xlsx",
debug_mode=False, debug_batch=None): debug_mode=False, debug_batch=None):
""" """
执行完整的数据提取流程 执行完整的数据提取流程
Args: Args:
order_id_file: 订单号文件路径 production_id_file: ProductionID.txt 文件路径
data_dir: 数据保存目录 data_dir: 数据保存目录
output_file: 最终输出文件路径 output_file: 最终输出文件路径
debug_mode: 是否启用调试模式 debug_mode: 是否启用调试模式
@@ -219,9 +233,8 @@ class DiscreteMaterialPlanExtractor:
# 设置查询界面 # 设置查询界面
self.setup_query_interface(inner_frame) self.setup_query_interface(inner_frame)
# 读取订单号文件 # 读取总排号并查询生产订单号
order_ids = self.read_order_ids(order_id_file) order_ids = self.get_production_order_numbers(production_id_file)
self._print(f"共读取到 {len(order_ids)} 个订单号")
# 按批次下载 # 按批次下载
downloaded_files = [] downloaded_files = []
@@ -264,10 +277,10 @@ def main():
verbose=True verbose=True
) )
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt") production_id_file = os.path.join(os.path.dirname(__file__), "productionID.txt")
output_file = "D:/python/playwrite/data/离散备料计划维护_合并.xlsx" output_file = "D:/python/playwrite/data/离散备料计划维护_合并.xlsx"
extractor.extract(order_id_file, output_file) extractor.extract(production_id_file, output_file)
input("按回车退出...") input("按回车退出...")

View File

@@ -5,23 +5,27 @@
import os import os
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
from utils.auth import login, logout from utils.auth import login, logout
from db.production_order_query import read_production_ids, query_production_order_numbers
from db.materials_to_delete import get_materials_to_delete
class DiscreteMaterialPlanCleaner: class DiscreteMaterialPlanCleaner:
"""离散备料计划维护数据清理器""" """离散备料计划维护数据清理器"""
def __init__(self, username, password, headless=False, verbose=True): def __init__(self, username, password, manager_name, headless=False, verbose=True):
""" """
初始化清理器 初始化清理器
Args: Args:
username: 登录用户名 username: 登录用户名
password: 登录密码 password: 登录密码
manager_name: 负责人姓名
headless: 是否无头模式运行 headless: 是否无头模式运行
verbose: 是否打印详细日志 verbose: 是否打印详细日志
""" """
self.username = username self.username = username
self.password = password self.password = password
self.manager_name = manager_name
self.headless = headless self.headless = headless
self.verbose = verbose self.verbose = verbose
@@ -30,14 +34,27 @@ class DiscreteMaterialPlanCleaner:
if self.verbose: if self.verbose:
print(*args, **kwargs) print(*args, **kwargs)
def read_order_ids(self, file_path): def get_production_order_numbers(self, production_id_file):
"""读取订单号文件""" """
with open(file_path, 'r', encoding='utf-8') as f: 读取总排号文件并查询数据库获取生产订单号
# 去除空白行和空格
order_ids = [line.strip() for line in f if line.strip()] Args:
production_id_file: ProductionID.txt 文件路径
Returns:
生产订单号列表
"""
# 读取总排号
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 return order_ids
def process_order(self, inner_frame, order_id, order_index, page1, debug_mode=False, debug_order=None): def process_order(self, inner_frame, order_id, order_index, page1, materials_to_delete=None, debug_mode=False, debug_order=None):
"""清理单个订单的数据 """清理单个订单的数据
Args: Args:
@@ -45,9 +62,12 @@ class DiscreteMaterialPlanCleaner:
order_id: 订单号 order_id: 订单号
order_index: 订单索引 order_index: 订单索引
page1: 页面对象 page1: 页面对象
materials_to_delete: 待删除物料关键字列表
debug_mode: 是否启用调试模式 debug_mode: 是否启用调试模式
debug_order: 调试订单号 debug_order: 调试订单号
""" """
if materials_to_delete is None:
materials_to_delete = []
from playwright.sync_api import TimeoutError from playwright.sync_api import TimeoutError
import re import re
import time import time
@@ -124,52 +144,86 @@ class DiscreteMaterialPlanCleaner:
detail_count = int(match.group(1)) detail_count = int(match.group(1))
self._print(f"详细信息数量: {detail_count}") self._print(f"详细信息数量: {detail_count}")
# 提取"备料状态"信息
if detail_count > 0: detail_element = inner_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}")
#page2.pause()
if detail_count > 0 and detail_status == "审批通过":
inner_frame.get_by_role("button", name="修改").click() inner_frame.get_by_role("button", name="修改").click()
save_button_locator = inner_frame.get_by_role("button", name="保存") save_button_locator = inner_frame.get_by_role("button", name="保存")
save_button_locator.wait_for(state="visible", timeout=10000) save_button_locator.wait_for(state="visible", timeout=10000)
inner_frame.get_by_text("展开").first.click() inner_frame.get_by_text("展开").first.click()
# 获取展开后的父容器,基于它定位子元素更加精确
# 父元素 class="card-table-side-box undefined"
child_form = inner_frame.locator(".card-table-side-box")
# 等待父容器变为可见
child_form.wait_for(state="visible", timeout=5000)
self._print(f"父容器 .card-table-side-box 已找到")
page2.pause() page2.pause()
for id in range(detail_count): for id in range(detail_count):
id_lable_locator= inner_frame.get_by_text("序号 " + str(id + 1)).wait_for(state="visible", timeout=10000) id_lable_locator = child_form.get_by_text("序号 " + str(id + 1))
id_lable_locator.wait_for(state="visible", timeout=10000)
self._print(f"处理 {id_lable_locator.inner_text()} ")
#page2.pause() # 获取材料编码通过文本定位取第一个input
# 提取"展开"页面中的内容 input_box = child_form.locator("div").filter(has_text=re.compile(r"^材料编码\d{11}$", re.MULTILINE)).locator("input").first
detail_element = inner_frame.get_by_text(re.compile(r"^材料编码\d+$")) self._print(f"材料编码:{input_box.input_value()}")
#移除detail_text中的换行符
detail_text = detail_element.inner_text().replace("\n", "")
# 使用正则表达式提取括号中的数字
match = re.search(r"材料编码(\d+)", detail_text)
if match:
match_result = int(match.group(1))
self._print(f"材料编码:{match_result}")
# 获取材料名称
input_box = child_form.locator("div").filter(has_text=re.compile(r"^材料名称$")).locator("input[type='text']")
material_name = input_box.input_value()
self._print(f"材料名称:{material_name}")
# 获取累计待发数量
input_box = child_form.locator("div").filter(has_text=re.compile(r"^累计待发数量$")).locator("input[type='text']")
self._print(f"累计待发数量:{input_box.input_value()}")
input_box = inner_frame.locator("div").filter(has_text=re.compile(r"^材料名称$")).nth(2).locator("input[type='text']") # 获取累计出库数量
self._print(f"材料名称:{input_box.input_value()}") input_box = child_form.locator("div").filter(has_text=re.compile(r"^累计出库数量$")).locator("input[type='text']")
self._print(f"累计出库数量:{input_box.input_value()}")
# 检查是否需要清理该物料
should_delete = False
matched_keyword = None
for keyword in materials_to_delete:
if keyword in material_name:
should_delete = True
matched_keyword = keyword
break
if should_delete:
self._print(f">>> 需要清理:材料名称【{material_name}】匹配关键字【{matched_keyword}")
# TODO: 执行删除操作
else:
self._print(f"保留:材料名称【{material_name}】无需清理")
if id != detail_count - 1: if id != detail_count - 1:
inner_frame.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(2).click() child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(2).click()
else: else:
inner_frame.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(4).click() child_form.get_by_role("button").filter(has_text=re.compile(r"^$")).nth(4).click()
#page2.pause() #page2.pause()
elif detail_count == 0:
pass
else:
self._print(f"{order_index + 1} 个订单无数据需要清理,跳过...") self._print(f"{order_index + 1} 个订单无数据需要清理,跳过...")
page2.close() page2.close()
return return
elif detail_status != "审批通过":
self._print(f"{order_index + 1} 个订单备料状态: {detail_status}")
page2.close()
return
@@ -205,15 +259,20 @@ class DiscreteMaterialPlanCleaner:
if attempt == max_retries - 1: if attempt == max_retries - 1:
self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...") self._print(f"警告: {max_retries} 次尝试后仍未成功填充,继续执行...")
def clean(self, order_id_file, debug_mode=False, debug_order=None): def clean(self, production_id_file, debug_mode=False, debug_order=None):
""" """
执行完整的数据清理流程 执行完整的数据清理流程
Args: Args:
order_id_file: 订单号文件路径 production_id_file: ProductionID.txt 文件路径
debug_mode: 是否启用调试模式 debug_mode: 是否启用调试模式
debug_order: 调试订单索引 debug_order: 调试订单索引
""" """
# 获取待删除物料列表
self._print(f"正在查询负责人 [{self.manager_name}] 的待删除物料列表...")
materials_to_delete = get_materials_to_delete(self.manager_name)
self._print(f"查询到 {len(materials_to_delete)} 个待删除物料关键字")
with sync_playwright() as playwright: with sync_playwright() as playwright:
# 调用登录模块 # 调用登录模块
browser, context, page, main_frame = login( browser, context, page, main_frame = login(
@@ -246,15 +305,14 @@ class DiscreteMaterialPlanCleaner:
# 设置查询界面 # 设置查询界面
self.setup_query_interface(inner_frame) self.setup_query_interface(inner_frame)
# 读取订单号文件 # 读取总排号并查询生产订单号
order_ids = self.read_order_ids(order_id_file) order_ids = self.get_production_order_numbers(production_id_file)
self._print(f"共读取到 {len(order_ids)} 个订单号")
# 按订单清理 # 按订单清理
for order_index, order_id in enumerate(order_ids): for order_index, order_id in enumerate(order_ids):
self._print(f"\n=== 开始处理第 {order_index + 1} 个订单,订单号: {order_id} ===") self._print(f"\n=== 开始处理第 {order_index + 1} 个订单,订单号: {order_id} ===")
self.process_order( self.process_order(
inner_frame, order_id, order_index, page1, inner_frame, order_id, order_index, page1, materials_to_delete,
debug_mode=debug_mode, debug_order=debug_order debug_mode=debug_mode, debug_order=debug_order
) )
@@ -274,13 +332,14 @@ def main():
cleaner = DiscreteMaterialPlanCleaner( cleaner = DiscreteMaterialPlanCleaner(
username="BLDpengqiangqiang", username="BLDpengqiangqiang",
password="Cqbld123456.", password="Cqbld123456.",
manager_name="彭羽",
headless=False, headless=False,
verbose=True verbose=True
) )
order_id_file = os.path.join(os.path.dirname(__file__), "orderID.txt") production_id_file = os.path.join(os.path.dirname(__file__), "productionID.txt")
cleaner.clean(order_id_file) cleaner.clean(production_id_file)
input("按回车退出...") input("按回车退出...")