feat: add database-driven material validation with multi-mode support

This commit enhances the material validation functionality to support
database-driven workflows alongside the existing Excel-based approach.

## New Features

### DAO Layer
- Add ProductionContractDataDAO for querying production contract data
- Add MaterialsToBeDeletedDAO with full CRUD operations
- Enhance DiscreteMaterialPlanDAO with query_all(), query_by_source_numbers(),
  and get_unique_material_names() methods

### Validation Modes
Support for 4 validation modes in MaterialValidationTab:
1. Database Full - Query all materials from DiscreteMaterialPlanData
2. Database Filtered - Query by ProductionID.txt file
3. Excel Existing - Validate from existing Excel file
4. Excel Full - Complete workflow with ERP extraction

### Configuration
- Add ValidationConfig dataclass with data_source, batch_size, match_mode,
  enable_crud_operations, and default_manager fields
- Update ConfigLoader to support validation configuration
- Add validation settings section in SettingsTab GUI

### Query Chain
Implementation of full query chain:
ProductionID.txt (总排号) → productionContractData (生产订单号) →
DiscreteMaterialPlanData (SourceNumber) → MaterialName →
MaterialsToBeDeleted comparison

## Backward Compatibility
All existing Excel-based validation methods remain unchanged, ensuring
no breaking changes for existing workflows.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-06 13:08:31 +08:00
parent 53a1e33e45
commit 2096a51f55
9 changed files with 976 additions and 57 deletions

View File

@@ -1,6 +1,10 @@
"""
物料状态校验工具
校验订单中的物料状态,匹配待删除物料
支持两种数据源:
1. Excel 文件(原有方式)
2. 数据库驱动(新增方式)
"""
import os
@@ -8,6 +12,9 @@ import pandas as pd
from typing import List, Dict, Any
from utils.离散备料计划维护数据提取 import DiscreteMaterialPlanExtractor
from db.materials_to_delete import get_all_materials_to_delete
from db.production_contract_data_dao import ProductionContractDataDAO
from db.discrete_material_plan_dao import DiscreteMaterialPlanDAO
from db.materials_to_be_deleted_dao import MaterialsToBeDeletedDAO
class MaterialStatusValidator:
@@ -195,3 +202,147 @@ class MaterialStatusValidator:
self._print(f" 未匹配: {len(results) - matched_count}")
return output_file
# ==================== DATABASE-DRIVEN VALIDATION ====================
def _read_production_ids(self, production_id_file: str) -> List[str]:
"""
读取 ProductionID.txt 文件
Args:
production_id_file: ProductionID.txt 文件路径
Returns:
List[str]: 总排号列表
"""
with open(production_id_file, 'r', encoding='utf-8') as f:
production_ids = [line.strip() for line in f if line.strip()]
return production_ids
def _get_source_numbers_from_production_ids(
self, production_ids: List[str]
) -> List[str]:
"""
通过 ProductionID 查询获取 SourceNumber 列表
查询链路:
ProductionID (总排号) -> productionContractData.26年压力表合同数据.生产订单号
Args:
production_ids: 总排号列表
Returns:
List[str]: 生产订单号列表
"""
self._print(f"[INFO] 正在查询 {len(production_ids)} 个总排号对应的生产订单号...")
contract_dao = ProductionContractDataDAO()
source_numbers = contract_dao.get_source_numbers_by_总排号(production_ids)
self._print(f"[INFO] 找到 {len(source_numbers)} 个唯一的生产订单号")
return source_numbers
def _get_material_names_from_db(
self, source_numbers: List[str] = None
) -> List[str]:
"""
从数据库获取材料名称,可选按 SourceNumber 过滤
Args:
source_numbers: 可选的 SourceNumber 列表进行过滤
Returns:
List[str]: 唯一材料名称列表
"""
if source_numbers is None or not source_numbers:
self._print("[INFO] 查询所有材料的名称...")
else:
self._print(f"[INFO] 查询 {len(source_numbers)} 个生产订单对应的材料名称...")
dao = DiscreteMaterialPlanDAO()
material_names = dao.get_unique_material_names(source_numbers)
self._print(f"[INFO] 找到 {len(material_names)} 个唯一材料名称")
return material_names
def validate_from_database(
self,
production_id_file: str = None,
full_table: bool = False,
output_file: str = None
) -> str:
"""
使用数据库作为数据源执行校验
支持两种模式:
1. 全表校验 (full_table=True): 查询整个 DiscreteMaterialPlanData 表
2. ProductionID 过滤校验 (production_id_file 指定): 基于 ProductionID.txt 文件过滤
查询链路模式2:
ProductionID.txt (总排号)
-> productionContractData.26年压力表合同数据.生产订单号 (SourceNumber)
-> DiscreteMaterialPlanData.SourceNumber
-> DiscreteMaterialPlanData.MaterialName
-> 对比 MaterialsToBeDeleted.MaterialName
Args:
production_id_file: ProductionID.txt 路径模式2
full_table: 是否全表校验模式1
output_file: 输出文件路径
Returns:
输出文件路径
"""
# 设置默认输出路径
if output_file is None:
output_file = "D:/python/playwrite/data/物料状态校验结果.xlsx"
self._print("=" * 60)
self._print("使用数据库数据源执行校验...")
# 根据模式获取材料名称
if full_table:
self._print("\n模式: 全表校验")
self._print("[INFO] 查询 DiscreteMaterialPlanData 表中的所有材料...")
material_names = self._get_material_names_from_db(None)
elif production_id_file:
self._print("\n模式: ProductionID 过滤校验")
self._print(f"[INFO] 读取 ProductionID 文件: {production_id_file}")
# 1. 读取 ProductionID.txt
production_ids = self._read_production_ids(production_id_file)
self._print(f"[INFO] 读取到 {len(production_ids)} 个总排号")
# 2. 查询获取 SourceNumbers
source_numbers = self._get_source_numbers_from_production_ids(production_ids)
# 3. 获取材料名称
material_names = self._get_material_names_from_db(source_numbers)
else:
raise ValueError(
"必须指定 full_table=True 或提供 production_id_file 参数"
)
# 从数据库获取待删除物料
self._print("\n从数据库获取待删除物料...")
db_materials = get_all_materials_to_delete()
self._print(f"获取到 {len(db_materials)} 条待删除物料记录")
# 匹配物料
self._print("\n匹配物料...")
results = self.match_materials(material_names, db_materials)
# 输出结果
self._print("\n输出结果...")
result_df = pd.DataFrame(results)
result_df.to_excel(output_file, index=False)
self._print(f"结果已保存: {output_file}")
# 打印统计信息
matched_count = sum(1 for r in results if r["匹配状态"] == "匹配成功")
self._print(f"\n统计信息:")
self._print(f" 总材料数: {len(results)}")
self._print(f" 匹配成功: {matched_count}")
self._print(f" 未匹配: {len(results) - matched_count}")
return output_file