Add optional database persistence feature that automatically saves extracted discrete material plan data to SQL Server. Users can enable this feature in the settings tab. Changes: - Add enable_db_persistence flag to ExtractionConfig (default: disabled) - Create DiscreteMaterialPlanDAO for database operations with REPLACE pattern - Update progress tracking to include database persistence stage (90-100%) - Add database persistence checkbox in settings UI - Remove verbose logging checkbox from data extraction UI (config-only now) - Update extraction workflow to save merged DataFrame to database Progress weights adjusted: - download: 65% -> 60% - database: 10% (new stage) - Other stages adjusted accordingly Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
151 lines
4.5 KiB
Python
151 lines
4.5 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
配置结构定义
|
||
|
||
使用 dataclass 定义所有配置项的结构和类型。
|
||
"""
|
||
from dataclasses import dataclass, field
|
||
from typing import Optional
|
||
from pathlib import Path
|
||
|
||
|
||
@dataclass
|
||
class ERPConfig:
|
||
"""ERP 系统配置"""
|
||
|
||
url: str
|
||
username: str
|
||
password: str
|
||
headless: bool = True
|
||
ignore_https_errors: bool = True
|
||
auto_close_browser: bool = True
|
||
|
||
def validate(self) -> list[str]:
|
||
"""验证配置,返回错误列表"""
|
||
errors = []
|
||
if not self.url:
|
||
errors.append("ERP URL 不能为空")
|
||
if not self.username:
|
||
errors.append("ERP 用户名不能为空")
|
||
if not self.password:
|
||
errors.append("ERP 密码不能为空")
|
||
return errors
|
||
|
||
|
||
@dataclass
|
||
class DatabaseConfig:
|
||
"""数据库配置"""
|
||
|
||
server: str
|
||
database: str
|
||
username: str
|
||
password: str
|
||
driver: str = "ODBC Driver 18 for SQL Server"
|
||
trust_server_certificate: str = "yes"
|
||
|
||
def validate(self) -> list[str]:
|
||
"""验证配置,返回错误列表"""
|
||
errors = []
|
||
if not self.server:
|
||
errors.append("数据库服务器地址不能为空")
|
||
if not self.database:
|
||
errors.append("数据库名称不能为空")
|
||
if not self.username:
|
||
errors.append("数据库用户名不能为空")
|
||
if not self.password:
|
||
errors.append("数据库密码不能为空")
|
||
return errors
|
||
|
||
|
||
@dataclass
|
||
class PathConfig:
|
||
"""文件路径配置"""
|
||
|
||
data_dir: str
|
||
production_id_file: str
|
||
default_output: str = "离散备料计划维护_合并.xlsx"
|
||
validation_output: str = "物料状态校验结果.xlsx"
|
||
|
||
def validate(self) -> list[str]:
|
||
"""验证配置,返回错误列表"""
|
||
errors = []
|
||
if not self.data_dir:
|
||
errors.append("数据目录路径不能为空")
|
||
if not self.production_id_file:
|
||
errors.append("ProductionID 文件路径不能为空")
|
||
return errors
|
||
|
||
|
||
@dataclass
|
||
class ExtractionConfig:
|
||
"""数据提取配置"""
|
||
|
||
batch_size: int = 100
|
||
verbose: bool = True
|
||
auto_convert: bool = True
|
||
merge_batches: bool = True
|
||
enable_db_persistence: bool = False
|
||
|
||
def validate(self) -> list[str]:
|
||
"""验证配置,返回错误列表"""
|
||
errors = []
|
||
if self.batch_size <= 0:
|
||
errors.append("批次大小必须大于 0")
|
||
if self.batch_size > 1000:
|
||
errors.append("批次大小不应超过 1000")
|
||
return errors
|
||
|
||
|
||
@dataclass
|
||
class AppConfig:
|
||
"""应用总配置"""
|
||
|
||
erp: ERPConfig
|
||
database: DatabaseConfig
|
||
paths: PathConfig
|
||
extraction: ExtractionConfig
|
||
|
||
def validate(self) -> list[str]:
|
||
"""验证所有配置,返回错误列表"""
|
||
errors = []
|
||
errors.extend(self.erp.validate())
|
||
errors.extend(self.database.validate())
|
||
errors.extend(self.paths.validate())
|
||
errors.extend(self.extraction.validate())
|
||
return errors
|
||
|
||
def to_dict(self) -> dict:
|
||
"""转换为字典格式(用于保存到 JSON)"""
|
||
return {
|
||
"erp": {
|
||
"url": self.erp.url,
|
||
"username": self.erp.username,
|
||
"password": self.erp.password,
|
||
"headless": self.erp.headless,
|
||
"ignore_https_errors": self.erp.ignore_https_errors,
|
||
"auto_close_browser": self.erp.auto_close_browser,
|
||
},
|
||
"database": {
|
||
"server": self.database.server,
|
||
"database": self.database.database,
|
||
"username": self.database.username,
|
||
"password": self.database.password,
|
||
"driver": self.database.driver,
|
||
"trust_server_certificate": self.database.trust_server_certificate,
|
||
},
|
||
"paths": {
|
||
"data_dir": self.paths.data_dir,
|
||
"production_id_file": self.paths.production_id_file,
|
||
"default_output": self.paths.default_output,
|
||
"validation_output": self.paths.validation_output,
|
||
},
|
||
"extraction": {
|
||
"batch_size": self.extraction.batch_size,
|
||
"verbose": self.extraction.verbose,
|
||
"auto_convert": self.extraction.auto_convert,
|
||
"merge_batches": self.extraction.merge_batches,
|
||
"enable_db_persistence": self.extraction.enable_db_persistence,
|
||
},
|
||
}
|