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, )