diff --git a/.gitignore b/.gitignore
index 6f39f13..4024dcd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,12 @@ tests/
config/user_settings.json
nul
+# 主脚本(包含用户特定配置)
+main.py
+main_clean.py
+main_ui.py
+record.py
+
# 环境变量
.env
.env.local
\ No newline at end of file
diff --git a/db/materials_to_delete.py b/db/materials_to_delete.py
index 0aa4b3c..2d7e86a 100644
--- a/db/materials_to_delete.py
+++ b/db/materials_to_delete.py
@@ -1,6 +1,6 @@
"""
待删除物料查询组件
-从数据库查询指定负责人需要删除的物料名称
+从数据库查询指定负责人需要删除的物料编码
"""
from typing import List, Dict, Any
@@ -9,25 +9,25 @@ from db.connection import get_connection
def get_materials_to_delete(manager_name):
"""
- 根据负责人名称查询待删除物料名称列表
+ 根据负责人名称查询待删除物料编码列表
Args:
manager_name: 负责人姓名
Returns:
- 物料名称列表(关键字)
+ 物料编码列表
"""
query = """
- SELECT [MaterialName]
- FROM [dbo].[MaterialsTypeToBeDeleted]
+ SELECT [MaterialCode]
+ 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
+ # 提取物料编码并去除空值
+ material_codes = [row["MaterialCode"] for row in results if row["MaterialCode"]]
+ return material_codes
def get_all_materials_to_delete() -> List[Dict[str, Any]]:
@@ -35,15 +35,40 @@ def get_all_materials_to_delete() -> List[Dict[str, Any]]:
获取所有待删除物料记录
Returns:
- List[Dict[str, Any]]: 包含MaterialName和ManagerName的记录列表
+ List[Dict[str, Any]]: 包含MaterialCode和ManagerName的记录列表
"""
query = """
- SELECT [MaterialName], [ManagerName]
- FROM [dbo].[MaterialsTypeToBeDeleted]
- WHERE [MaterialName] IS NOT NULL
- ORDER BY [ManagerName], [MaterialName]
+ SELECT [MaterialCode], [ManagerName]
+ FROM [dbo].[MaterialsToBeDeleted]
+ WHERE [MaterialCode] IS NOT NULL
+ ORDER BY [ManagerName], [MaterialCode]
"""
with get_connection() as conn:
results = conn.execute_query(query)
return results
+
+
+def should_delete_material(manager_name: str, material_code: str) -> bool:
+ """
+ 检查指定物料编码是否需要删除
+
+ 直接在数据库层面查询,避免将大量数据加载到内存
+
+ Args:
+ manager_name: 负责人姓名
+ material_code: 物料编码
+
+ Returns:
+ bool: 如果物料需要删除返回 True,否则返回 False
+ """
+ query = """
+ SELECT COUNT(*) as count
+ FROM [dbo].[MaterialsToBeDeleted]
+ WHERE [ManagerName] = ?
+ AND [MaterialCode] = ?
+ """
+
+ with get_connection() as conn:
+ results = conn.execute_query(query, (manager_name, material_code))
+ return results[0]["count"] > 0 if results else False
diff --git a/docs/MAIN_CLEAN_FLOW.md b/docs/MAIN_CLEAN_FLOW.md
new file mode 100644
index 0000000..7f42a0b
--- /dev/null
+++ b/docs/MAIN_CLEAN_FLOW.md
@@ -0,0 +1,345 @@
+# main_clean.py 流程说明
+
+## 概述
+
+`main_clean.py` 是离散备料计划维护数据清理工具的主程序入口。该工具通过 Playwright 自动化登录用友 BIP 系统,读取生产订单号,然后逐个清理订单中不需要的物料数据。
+
+---
+
+## 系统架构图
+
+```mermaid
+flowchart TB
+ Start([开始]) --> Init[初始化 DiscreteMaterialPlanCleaner]
+
+ Init --> QueryDB[(查询数据库
获取待删除物料列表)]
+
+ QueryDB --> Login[登录用友BIP系统]
+
+ Login --> OpenMenu[点击功能菜单]
+ OpenMenu --> OpenPage[打开离散生产订单维护页面]
+
+ OpenPage --> SetupQuery[设置查询界面]
+ SetupQuery --> ReadFile[(读取ProductionID.txt)]
+ ReadFile --> GetOrders[(查询数据库
获取生产订单号)]
+
+ GetOrders --> LoopStart{遍历订单}
+
+ LoopStart --> ProcessOrder[处理单个订单]
+ ProcessOrder --> QueryOrder[查询订单详情]
+ QueryOrder --> OpenPlanPage[打开备料计划页面]
+ OpenPlanPage --> WaitLoad[等待页面加载完成]
+
+ WaitLoad --> CheckData{检查数据状态}
+
+ CheckData -->|detail_count = 0| SkipOrder[跳过该订单]
+ CheckData -->|status != 审批通过| SkipOrder
+ CheckData -->|detail_count > 0
status = 审批通过| ClickModify[点击修改按钮]
+
+ ClickModify --> ExpandForm[展开详细信息表单]
+ ExpandForm --> LoopMaterials{遍历物料}
+
+ LoopMaterials --> GetMaterial[获取物料信息]
+ GetMaterial --> CheckDelete{需要删除?}
+
+ CheckDelete -->|是| MarkDelete[标记待删除]
+ CheckDelete -->|否| KeepMaterial[保留物料]
+
+ MarkDelete --> NextMaterial{下一个物料?}
+ KeepMaterial --> NextMaterial
+
+ NextMaterial -->|是| LoopMaterials
+ NextMaterial -->|否| ClosePlanPage[关闭备料计划页面]
+
+ SkipOrder --> ClosePlanPage
+ ClosePlanPage --> NextOrder{下一个订单?}
+
+ NextOrder -->|是| LoopStart
+ NextOrder -->|否| Logout[注销账号]
+
+ Logout --> CloseBrowser[关闭浏览器]
+ CloseBrowser --> End([结束])
+
+ style Init fill:#e1f5ff
+ style QueryDB fill:#fff4e1
+ style Login fill:#e1f5ff
+ style End fill:#ffe1e1
+ style CheckDelete fill:#fff4e1
+ style CheckData fill:#fff4e1
+```
+
+---
+
+## 详细流程说明
+
+### 1. 初始化阶段
+
+```mermaid
+flowchart LR
+ A[main函数] --> B[创建 DiscreteMaterialPlanCleaner 实例]
+ B --> C[配置参数:
username, password,
manager_name, headless, verbose]
+
+ style A fill:#e1f5ff
+ style B fill:#e1f5ff
+ style C fill:#e1f5ff
+```
+
+**代码位置**: `main_clean.py:9-24`
+
+```python
+cleaner = DiscreteMaterialPlanCleaner(
+ username="BLDpengqiangqiang",
+ password="Cqbld123456.",
+ manager_name="彭羽",
+ headless=False,
+ verbose=True,
+)
+```
+
+---
+
+### 2. 数据准备阶段
+
+```mermaid
+flowchart TB
+ A[clean.clean方法] --> B[查询数据库
MaterialsToBeDeleted表]
+ B --> C[获取指定负责人的
待删除物料编码列表]
+
+ style A fill:#fff4e1
+ style B fill:#fff4e1
+ style C fill:#fff4e1
+```
+
+**代码位置**: `utils/离散备料计划维护数据清理.py:292-304`
+
+| 模块 | 功能 | 文件 |
+|------|------|------|
+| `get_materials_to_delete()` | 查询待删除物料编码 | `db/materials_to_delete.py` |
+
+---
+
+### 3. 登录与导航
+
+```mermaid
+sequenceDiagram
+ participant Main as main_clean.py
+ participant Auth as auth.py
+ participant Browser as Chromium Browser
+ participant ERP as 用友BIP系统
+
+ Main->>Auth: login(playwright, username, password)
+ Auth->>Browser: launch(headless=False)
+ Auth->>Browser: new_context()
+ Auth->>Browser: new_page()
+ Auth->>ERP: goto(登录页面URL)
+ Auth->>ERP: 填写用户名和密码
+ Auth->>ERP: 点击登录按钮
+ alt 有强制登录弹窗
+ ERP-->>Auth: 显示弹窗
+ Auth->>ERP: 点击确定
+ end
+ Auth-->>Main: 返回 (browser, context, page, main_frame)
+```
+
+**代码位置**: `utils/auth.py:8-72`
+
+---
+
+### 4. 设置查询界面
+
+```mermaid
+flowchart TB
+ A[点击图标按钮] --> B[选择"订单号查询"]
+ B --> C[点击"全部"标签]
+ C --> D{填充显示条数}
+ D --> E{成功?}
+ E -->|否| F[重试
最多3次]
+ F --> D
+ E -->|是| G[继续]
+
+ style D fill:#fff4e1
+ style E fill:#fff4e1
+ style F fill:#ffe1e1
+```
+
+**代码位置**: `utils/离散备料计划维护数据清理.py:263-290`
+
+---
+
+### 5. 获取生产订单号
+
+```mermaid
+flowchart LR
+ A[read_production_ids] --> B[读取ProductionID.txt]
+ B --> C[解析总排号列表]
+ C --> D[query_production_order_numbers]
+ D --> E[查询数据库
获取生产订单号]
+
+ style A fill:#fff4e1
+ style D fill:#fff4e1
+```
+
+**代码位置**: `utils/离散备料计划维护数据清理.py:41-59`
+
+| 函数 | 功能 | 数据库表 |
+|------|------|----------|
+| `read_production_ids()` | 读取总排号文件 | 文件输入 |
+| `query_production_order_numbers()` | 查询生产订单号 | ProductionOrder |
+
+---
+
+### 6. 订单处理流程
+
+```mermaid
+flowchart TB
+ Start([开始处理订单]) --> FillInput[填充生产订单号]
+ FillInput --> ClickQuery[点击查询按钮]
+ ClickQuery --> WaitLoad[等待加载完成]
+
+ WaitLoad --> ClickMore[点击"更多"按钮]
+ ClickMore --> OpenPlan[打开"备料计划"页面]
+
+ OpenPlan --> GetIframe[获取嵌套iframe]
+ GetIframe --> WaitPlanLoad[等待备料计划页面加载]
+
+ WaitPlanLoad --> ExtractInfo[提取信息:
- 编码
- 详细信息数量
- 备料状态]
+
+ ExtractInfo --> CheckStatus{状态检查}
+
+ style Start fill:#e1f5ff
+ style CheckStatus fill:#fff4e1
+```
+
+**代码位置**: `utils/离散备料计划维护数据清理.py:61-167`
+
+---
+
+### 7. 物料清理流程
+
+```mermaid
+flowchart TB
+ Check{detail_count > 0
&
status = 审批通过} -->|否| Skip[跳过处理]
+ Check -->|是| ClickModify[点击修改按钮]
+
+ ClickModify --> WaitSave[等待保存按钮可见]
+ WaitSave --> Expand[点击展开按钮]
+ Expand --> GetForm[获取表单容器]
+
+ GetForm --> LoopStart{遍历物料}
+
+ LoopStart --> GetSerial[定位序号]
+ GetSerial --> GetCode[获取材料编码]
+ GetCode --> GetName[获取材料名称]
+ GetName --> GetQty1[获取累计待发数量]
+ GetQty1 --> GetQty2[获取累计出库数量]
+
+ GetQty2 --> Match{物料编码
在待删除列表中?}
+
+ Match -->|精确匹配| Delete[标记待删除]
+ Match -->|未匹配| Keep[保留]
+
+ Delete --> Next{下一个?}
+ Keep --> Next
+
+ Next -->|是| LoopStart
+ Next -->|否| Done([完成])
+
+ style Check fill:#fff4e1
+ style Match fill:#fff4e1
+ style Done fill:#e1f5ff
+```
+
+**代码位置**: `utils/离散备料计划维护数据清理.py:169-261`
+
+---
+
+## 关键数据结构
+
+### DiscreteMaterialPlanCleaner 类
+
+```mermaid
+classDiagram
+ class DiscreteMaterialPlanCleaner {
+ +str username
+ +str password
+ +str manager_name
+ +bool headless
+ +bool verbose
+ +_print(args)
+ +get_production_order_numbers(file)
+ +process_order(frame, order_id, ...)
+ +setup_query_interface(frame)
+ +clean(file, debug_mode, ...)
+ }
+
+ class auth {
+ +login(playwright, username, password)
+ +logout(main_frame, verbose)
+ +close_session(browser, context)
+ }
+
+ class materials_to_delete {
+ +get_materials_to_delete(manager_name)
+ +get_all_materials_to_delete()
+ }
+
+ class production_order_query {
+ +read_production_ids(file)
+ +query_production_order_numbers(ids)
+ }
+
+ DiscreteMaterialPlanCleaner --> auth
+ DiscreteMaterialPlanCleaner --> materials_to_delete
+ DiscreteMaterialPlanCleaner --> production_order_query
+```
+
+---
+
+## 配置参数说明
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `username` | str | - | 登录用户名 |
+| `password` | str | - | 登录密码 |
+| `manager_name` | str | - | 负责人姓名(用于查询待删除物料) |
+| `headless` | bool | False | 是否无头模式运行浏览器 |
+| `verbose` | bool | True | 是否打印详细日志 |
+
+---
+
+## 文件依赖关系
+
+```mermaid
+graph TD
+ A[main_clean.py] --> B[utils/离散备料计划维护数据清理.py]
+ B --> C[utils/auth.py]
+ B --> D[db/production_order_query.py]
+ B --> E[db/materials_to_delete.py]
+ B --> F[db/connection.py]
+
+ style A fill:#e1f5ff
+ style B fill:#e1f5ff
+```
+
+---
+
+## 执行入口
+
+```bash
+# 直接运行
+python main_clean.py
+
+# 或在虚拟环境中运行
+.venv\Scripts\activate
+python main_clean.py
+```
+
+---
+
+## 注意事项
+
+1. **浏览器模式**: 默认使用非无头模式 (`headless=False`),便于调试观察
+2. **日志输出**: 启用详细日志 (`verbose=True`),打印每一步操作
+3. **数据库连接**: 使用上下文管理器自动管理数据库连接
+4. **异常处理**: 加载超时会自动重试或跳过
+5. **调试模式**: 支持在指定订单处暂停调试
diff --git a/main.py b/main.py
deleted file mode 100644
index 50f4934..0000000
--- a/main.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""
-主程序 - 使用离散备料计划维护数据提取工具
-"""
-
-import os
-from utils.离散备料计划维护数据提取 import DiscreteMaterialPlanExtractor
-
-
-def main():
- # 创建提取器
- extractor = DiscreteMaterialPlanExtractor(
- username="BLDpengqiangqiang",
- password="Cqbld123456.",
- headless=True,
- verbose=True,
- )
-
- # 设置文件路径
- order_id_file = os.path.join(os.path.dirname(__file__), "ProductionID.txt")
- output_file = r"D:/python/playwrite/data/离散备料计划维护_合并.xlsx"
-
- # 执行提取
- extractor.extract(order_id_file, output_file)
-
-
-if __name__ == "__main__":
- main()
diff --git a/main_clean.py b/main_clean.py
deleted file mode 100644
index 7019789..0000000
--- a/main_clean.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""
-主程序 - 使用离散备料计划维护数据清理工具
-"""
-
-import os
-from utils.离散备料计划维护数据清理 import DiscreteMaterialPlanCleaner
-
-
-def main():
- # 创建清理器
- cleaner = DiscreteMaterialPlanCleaner(
- username="BLDpengqiangqiang",
- password="Cqbld123456.",
- manager_name="彭羽",
- headless=True,
- verbose=True,
- )
-
- # 设置文件路径
- order_id_file = os.path.join(os.path.dirname(__file__), "ProductionID.txt")
-
- # 执行清理
- cleaner.clean(order_id_file)
-
-
-if __name__ == "__main__":
- main()
diff --git a/main_ui.py b/main_ui.py
deleted file mode 100644
index a883168..0000000
--- a/main_ui.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-ERP 自动化工具 - GUI 版本
-
-这是一个基于 Tkinter 的图形用户界面应用程序,
-用于自动化 ERP 系统(用友 YonBIP)的数据提取、校验和查询任务。
-
-运行方式:
- python main_ui.py
-
-功能:
- - 数据提取:从 ERP 系统提取备料计划数据
- - 物料校验:校验物料状态并匹配待删除物料
- - 数据查询:查询生产订单号等信息
- - 设置管理:管理系统配置
- - 用户认证:基于用户权限的数据访问控制
-"""
-
-import tkinter as tk
-from tkinter import messagebox
-from gui.main_window import MainWindow
-from gui.login_dialog import LoginDialog
-from auth.session_manager import SessionManager
-
-
-def main():
- """主函数"""
- # 创建根窗口
- root = tk.Tk()
-
- # 显示登录对话框
- login_dialog = LoginDialog(root)
- credentials = login_dialog.get_credentials()
-
- if not credentials:
- # 用户取消登录或关闭对话框
- root.destroy()
- return
-
- username, password = credentials
-
- # 认证并初始化会话
- session_manager = SessionManager.get_instance()
-
- if not session_manager.login(username, password):
- messagebox.showerror("登录失败", "用户名或密码错误")
- root.destroy()
- return
-
- # 创建主窗口(传入 session_manager)
- app = MainWindow(root, session_manager)
-
- # 启动主事件循环
- root.mainloop()
-
-
-if __name__ == "__main__":
- main()
diff --git a/utils/离散备料计划维护数据清理.py b/utils/离散备料计划维护数据清理.py
index f446819..52362ab 100644
--- a/utils/离散备料计划维护数据清理.py
+++ b/utils/离散备料计划维护数据清理.py
@@ -64,7 +64,7 @@ class DiscreteMaterialPlanCleaner:
order_id,
order_index,
page1,
- materials_to_delete=None,
+ manager_name=None,
debug_mode=False,
debug_order=None,
):
@@ -75,12 +75,12 @@ class DiscreteMaterialPlanCleaner:
order_id: 订单号
order_index: 订单索引
page1: 页面对象
- materials_to_delete: 待删除物料关键字列表
+ manager_name: 负责人姓名(用于查询待删除物料)
debug_mode: 是否启用调试模式
debug_order: 调试订单号
"""
- if materials_to_delete is None:
- materials_to_delete = []
+ if manager_name is None:
+ manager_name = self.manager_name
from playwright.sync_api import TimeoutError
import re
import time
@@ -180,22 +180,23 @@ class DiscreteMaterialPlanCleaner:
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):
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()} ")
- # 获取材料编码(通过文本定位,取第一个input)
+ # 获取材料编码(用于精确匹配)
input_box = (
child_form.locator("div")
.filter(has_text=re.compile(r"^材料编码\d{11}$", re.MULTILINE))
.locator("input")
.first
)
- self._print(f"材料编码:{input_box.input_value()}")
+ material_code = input_box.input_value()
+ self._print(f"材料编码:{material_code}")
- # 获取材料名称
+ # 获取材料名称(仅用于日志)
input_box = (
child_form.locator("div")
.filter(has_text=re.compile(r"^材料名称$"))
@@ -220,22 +221,17 @@ class DiscreteMaterialPlanCleaner:
)
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
+ # 检查是否需要清理该物料(直接数据库查询)
+ from db.materials_to_delete import should_delete_material
+ should_delete = should_delete_material(manager_name, material_code)
if should_delete:
self._print(
- f">>> 需要清理:材料名称【{material_name}】匹配关键字【{matched_keyword}】"
+ f">>> 需要清理:材料名称【{material_name}】材料编码【{material_code}】"
)
# TODO: 执行删除操作
else:
- self._print(f"保留:材料名称【{material_name}】无需清理")
+ self._print(f"保留:材料名称【{material_name}】材料编码【{material_code}】无需清理")
if id != detail_count - 1:
child_form.get_by_role("button").filter(
@@ -298,10 +294,7 @@ class DiscreteMaterialPlanCleaner:
debug_mode: 是否启用调试模式
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)} 个待删除物料关键字")
+ self._print(f"使用负责人 [{self.manager_name}] 进行数据清理")
with sync_playwright() as playwright:
# 调用登录模块
@@ -348,7 +341,7 @@ class DiscreteMaterialPlanCleaner:
order_id,
order_index,
page1,
- materials_to_delete,
+ self.manager_name,
debug_mode=debug_mode,
debug_order=debug_order,
)