certflow.config.settings 源代码

"""CertFlow 配置管理 - 所有值从 config.yaml / .env 读取,零硬编码

提供模块级配置常量(直接通过 _cfg() 读取 YAML)和工具函数.
"""

import os
import sys
from pathlib import Path
from typing import Any

import yaml
from loguru import logger

# ============================================================
# 路径辅助函数
# ============================================================


def _app_base_dir() -> Path:
    """获取应用程序基础目录

    自动检测运行环境,兼容 PyInstaller 打包后的可执行文件和开发环境.

    Returns:
        Path: 应用程序根目录路径
            - 打包环境: 可执行文件所在目录
            - 开发环境: 项目根目录(向上查找4级父目录)
    """
    if getattr(sys, "frozen", False):
        return Path(sys.executable).parent
    return Path(__file__).resolve().parent.parent.parent.parent


BASE_DIR = _app_base_dir()


def _resolve_path(path_value: Any, base: Path = BASE_DIR) -> Path:
    """解析路径,将相对路径转换为绝对路径

    支持相对路径(相对于BASE_DIR)和绝对路径两种形式.

    Args:
        path_value: 待解析的路径值,可以是字符串或Path对象
        base: 基准目录,默认为应用程序根目录

    Returns:
        Path: 解析后的绝对路径
            - 空值或None: 返回 base/output
            - 绝对路径: 直接返回
            - 相对路径: 相对于base拼接后返回
    """
    if not path_value:
        return base / "output"
    if not isinstance(path_value, str):
        path_value = str(path_value)
    p = Path(path_value)
    if p.is_absolute():
        return p
    return base / p


# ============================================================
# YAML 配置读取(惰性加载 + 缓存)
# ============================================================

_config_cache: dict | None = None


def _load_config() -> dict:
    """加载 config.yaml 为原始字典

    使用惰性加载策略,仅在首次调用时加载配置并缓存.

    Returns:
        dict: 配置字典,包含所有YAML配置数据
    """
    global _config_cache
    if _config_cache is None:
        from certflow.config.loader import ConfigLoader

        loader = ConfigLoader()
        root = loader.load()
        _config_cache = root.model_dump(exclude_none=False)
    return _config_cache


def _cfg(key: str, default: Any = None) -> Any:
    """从 YAML 配置读取嵌套键值

    支持使用点号分隔的路径访问嵌套配置,如 "paths.sales_plan.primary".

    Args:
        key: 点号分隔的配置键路径
        default: 键不存在时返回的默认值

    Returns:
        Any: 配置值,如果路径不存在则返回默认值

    Examples:
        >>> _cfg("app.name")
        "CertFlow"
        >>> _cfg("paths.database.dir", "database")
        "database"
    """
    config = _load_config()
    parts = key.split(".")
    value: Any = config
    for part in parts:
        if isinstance(value, dict):
            value = value.get(part)
            if value is None:
                return default
        else:
            return default
    return value


def _env(key: str) -> str:
    """读取环境变量值

    Args:
        key: 环境变量名称

    Returns:
        str: 环境变量的值,不存在时返回空字符串
    """
    return os.getenv(key, "")


[文档] def get_config_value(key: str, env_var: str | None = None, default: Any = None) -> Any: """获取配置值,优先级: 环境变量 > YAML > 传入默认值 提供统一的配置读取接口,优先使用环境变量,其次使用YAML配置, 最后使用代码中的默认值. Args: key: YAML配置中的键路径 env_var: 环境变量名称,可选 default: 默认值 Returns: Any: 获取到的配置值 Examples: >>> get_config_value("paths.sales_plan.primary", "VALVE_SALES_PLAN_PATH") "/path/to/sales/plan.xlsx" """ if env_var: value = _env(env_var) if value: return value value = _cfg(key) if value is not None: return value return default
# ============================================================ # 应用信息 # ============================================================ APP_NAME: str = _cfg("app.name", "CertFlow") """应用程序名称""" APP_VERSION: str = _cfg("app.version", "0.0.1") """应用程序版本号""" APP_YEAR: int = _cfg("app.year", 2026) """版权年份""" APP_ORGANIZATION: str = _cfg("app.organization", "CertFlow Team") """组织名称""" APP_DEBUG: bool = _cfg("app.debug", False) """是否启用调试模式""" # ============================================================ # 目录路径 # ============================================================ CONFIG_DIR: Path = BASE_DIR / "config" """配置文件目录""" DATABASE_DIR: Path = _resolve_path(_cfg("paths.database.dir", "database")) """数据库文件目录""" TEMP_DIR: Path = _resolve_path(_cfg("paths.temp.base", "temp")) """临时文件根目录""" LOGS_DIR: Path = _resolve_path(_cfg("paths.logs", "logs")) """日志文件目录""" RESOURCES_DIR: Path = _resolve_path(_cfg("paths.resources", "resources")) """资源文件目录""" UI_CONFIG_FILE: Path = CONFIG_DIR / "ui.yaml" """UI配置文件路径""" # 数据库 DATABASE_PATH: Path = DATABASE_DIR / str(_cfg("paths.database.name", "certflow.db")) """SQLite数据库文件完整路径""" DATABASE_URL: str = f"sqlite:///{DATABASE_PATH}" """数据库连接URL""" # ============================================================ # 文件路径(环境变量优先) # ============================================================ SALES_PLAN_PATH: str | None = get_config_value("paths.sales_plan.primary", "VALVE_SALES_PLAN_PATH") """销售计划主文件路径(优先使用环境变量)""" SALES_PLAN_NETWORK: str | None = get_config_value( "paths.sales_plan.network", "VALVE_SALES_PLAN_NETWORK" ) """销售计划网络路径""" SALES_PLAN_LOCAL_COPY: str | None = get_config_value( "paths.sales_plan.local_copy", "VALVE_SALES_PLAN_LOCAL_COPY" ) """销售计划本地副本路径""" SALES_PLAN_MASTER_FILE: str | None = get_config_value( "paths.sales_plan.master_file", "VALVE_MASTER_ORDER_FILE" ) """主订单文件路径""" SALES_PLAN_MASTER_SHEET: str | None = _cfg("paths.sales_plan.master_sheet") """主订单工作表名称""" QUALIFIED_LIST_PATH: str | None = get_config_value( "paths.qualified_list.primary", "VALVE_QUALIFIED_LIST_PATH" ) """合格清单文件路径""" QUALIFIED_LIST_XLSM_ORIGINAL: str | None = get_config_value( "paths.qualified_list.xlsm_original", "" ) """原始合格清单XLSM文件路径""" QUALIFIED_LIST_SHEET: str | None = _cfg("paths.qualified_list.sheet") """合格清单工作表名称""" NAMEPLATE_DB_PATH: str | None = get_config_value( "paths.nameplate_db.primary", "VALVE_NAMEPLATE_DB_PATH" ) """铭牌数据库路径""" NAMEPLATE_DB_BACKUP: str | None = _cfg("paths.nameplate_db.backup") """铭牌数据库备份路径""" QUALITY_CERT_FOREIGN_DIR: str | None = get_config_value( "paths.quality_cert.foreign_dir", "VALVE_QUALITY_CERT_FOREIGN_DIR" ) """外协质量证书目录""" QUALITY_CERT_SELF_MADE_DIR: str | None = _cfg("paths.quality_cert.self_made_dir") """自制质量证书目录""" QUALITY_CERT_REPORT_DIR: str | None = _cfg("paths.quality_cert.report_dir") """质量证书报告目录""" TRANSFER_DOCS_NETWORK: str | None = get_config_value( "paths.transfer_docs.network", "VALVE_TRANSFER_DOCS_NETWORK" ) """交接文档网络路径""" TRANSFER_DOCS_LOCAL: str | None = get_config_value( "paths.transfer_docs.local", "VALVE_TRANSFER_DOCS_LOCAL" ) """交接文档本地路径""" CERT_HISTORY_PATH: str | None = get_config_value( "paths.backup.cert_history", "VALVE_CERT_BACKUP_PATH" ) """证书历史备份路径""" # ============================================================ # 输出目录 # ============================================================ OUTPUT_BASE: Path = _resolve_path(_cfg("paths.output.base", "output")) """输出文件根目录""" OUTPUT_TEST_REPORT: Path = OUTPUT_BASE / str(_cfg("paths.output.test_report", "reports")) """试压报告输出目录""" OUTPUT_QUALITY_CERT: Path = OUTPUT_BASE / str(_cfg("paths.output.quality_cert", "certificates")) """质量证书输出目录""" OUTPUT_CERTIFICATE_IMAGES: Path = OUTPUT_BASE / str( _cfg("paths.output.certificate_images", "images") ) """证书图片输出目录""" OUTPUT_NAMEPLATE: Path = OUTPUT_BASE / str(_cfg("paths.output.nameplate", "nameplates")) """铭牌输出目录""" OUTPUT_BACKUPS: Path = OUTPUT_BASE / str(_cfg("paths.output.backups", "backups")) """备份文件输出目录""" # 图片资源路径 NAMEPLATE_IMAGE_PATH: str | None = get_config_value( "paths.images.nameplate", "VALVE_NAMEPLATE_IMAGE_PATH" ) """铭牌图片路径""" NAMEPLATE_IMAGE_BACKUP: str | None = _cfg("paths.images.nameplate_backup") """铭牌图片备份路径""" # Excel 排版配置 ROW_HEIGHT_TITLE: Any = _cfg("paths.excel_layout.row_height.title", 20) """Excel标题行高度""" ROW_HEIGHT_HEADER: Any = _cfg("paths.excel_layout.row_height.header", 25) """Excel表头行高度""" ROW_HEIGHT_DATA: Any = _cfg("paths.excel_layout.row_height.data", 18) """Excel数据行高度""" ROW_HEIGHT_SUMMARY: Any = _cfg("paths.excel_layout.row_height.summary", 22) """Excel汇总行高度""" # ============================================================ # 编号规则配置 # ============================================================ NUMBERING_PREFIX: str = _cfg("numbering.prefix", "V") """编号前缀(V2)""" NUMBERING_FORMAT: str = _cfg("numbering.format", "{prefix}{year}{month}{seq:03d}{suffix}") """编号格式模板(V2)""" NUMBERING_SUFFIXES: dict = _cfg("numbering.suffixes", {}) """编号后缀映射(V2)""" NUMBERING_AUTO_NUMBER_MODE: dict | None = _cfg("numbering.auto_number_mode") """自动编号模式配置(V2)""" NUMBERING_SUFFIX_RULES: list = _cfg("numbering.suffix_rules", []) """编号后缀规则列表(V2)""" # 旧接口兼容: certificate 段(V1 编号配置) CERTIFICATE_PREFIX: str = _cfg("certificate.prefix", NUMBERING_PREFIX) """证书编号前缀(V1兼容)""" CERTIFICATE_DATE_FORMAT: str = _cfg("certificate.date_format", "%Y%m%d") """证书日期格式(V1兼容)""" CERTIFICATE_FORMAT_TEMPLATE: str = _cfg("certificate.format_template", "{prefix}{date}{seq:04d}") """证书编号格式模板(V1兼容)""" # ============================================================ # 颜色映射 # ============================================================ VBA_COLORS: dict = _cfg("color_mapping.vba_colors", {}) """VBA颜色索引到颜色的映射""" FONT_COLOR_TO_SHIPPING: dict = _cfg("color_mapping.font_color_to_shipping", {}) """字体颜色到发货状态的映射""" BG_COLOR_TO_STATUS: dict = _cfg("color_mapping.bg_color_to_status", {}) """背景颜色到状态的映射""" THEME_FONT_COLORS: dict = _cfg("color_mapping.theme_font_colors", {}) """THEME 字体颜色到状态的映射""" # ============================================================ # 重复检查 / 唯一标识规则 # ============================================================ DUPLICATE_CHECK_FIELDS: list = _cfg("duplicate_check.fields", []) """重复检查字段列表""" UNIQUE_KEY_SEPARATOR: str = _cfg("unique_key.separator", "|") """唯一键字段分隔符""" UNIQUE_KEY_DATE_TO_SERIAL: bool = _cfg("unique_key.date_to_serial", True) """是否将日期转换为序列号""" UNIQUE_KEY_FIELD_FALLBACKS: dict = _cfg("unique_key.key_field_fallbacks", {}) """唯一键字段回退映射""" # ============================================================ # 排序规则 # ============================================================ SORT_RULES_KEYS: list = _cfg("sort_rules.keys", []) """排序规则键列表""" SORT_KEYS: list[str] = [k.get("field", "") for k in SORT_RULES_KEYS] if SORT_RULES_KEYS else [] """排序字段名列表(向后兼容)""" # ============================================================ # 分组规则 # ============================================================ GROUP_BY_FIELDS: list = _cfg("group_by.fields", []) """分组字段列表""" GROUP_BY_FIELDS_NO_PROJECT: list = _cfg("group_by.fields_no_project", []) """非项目数据的分组字段列表""" GROUP_BY_SUMMARY_FIELD: str = _cfg("group_by.summary_field", "数量") """分组汇总字段名称""" GROUP_BY_NUMBER_FORMAT: str = _cfg("group_by.number_format", "编号:{:03d}") """组内编号格式""" # ============================================================ # 导出配置 # ============================================================ EXPORT_HYPERLINK_COLUMN: int = _cfg("export.hyperlink_column", 44) """超链接所在列号""" EXPORT_HYPERLINK_REF_PREFIX: str = _cfg("export.hyperlink_ref_prefix", "AR") """超链接引用前缀""" EXPORT_HYPERLINK_WIDTH: int = _cfg("export.hyperlink_width", 20) """超链接列宽度(字符数)""" EXPORT_SUMMARY_LABEL: str = _cfg("export.summary.label", "合计") """汇总行标签文本""" EXPORT_SUMMARY_LABEL_COLUMN: int = _cfg("export.summary.label_column", 1) """汇总标签所在列号""" EXPORT_SUMMARY_BG_COLOR: str = _cfg("export.summary.bg_color", "E0E0E0") """汇总行背景颜色""" EXPORT_SUMMARY_COL_RANGE: int = _cfg("export.summary.col_range", 25) """汇总列范围""" EXPORT_SUMMARY_SUM_COLUMNS: list = _cfg("export.summary.sum_columns", []) """需要求和的列列表""" # ============================================================ # 销售计划配置 # ============================================================ SALE_PLAN_COLUMNS: dict = _cfg("sales_plan.column_mapping.direct", {}) """销售计划列映射字典""" COLUMN_ALIASES: dict = _cfg("sales_plan.column_mapping.aliases", {}) """列别名映射""" SALES_PLAN_HEADER_ROW: int = _cfg("sales_plan.header_row", 2) """销售计划表头行号(从1开始)""" SALES_PLAN_DATA_START_ROW: int = _cfg("sales_plan.data_start_row", 3) """销售计划数据起始行号(从1开始)""" SALES_PLAN_SHEET_PATTERN: str = _cfg("sales_plan.sheet_pattern", "{month}月") """销售计划工作表名称匹配模式""" SALES_PLAN_HEADER_KEYWORDS: list = _cfg("sales_plan.header_keywords", []) """表头识别关键词列表""" REQUIRED_FIELDS: list = _cfg("sales_plan.required_fields", []) """销售计划必填字段列表""" STYLED_IMPORT_COLUMNS: list = _cfg("sales_plan.styled_import_columns", []) """带格式导入时保留的列列表""" # ============================================================ # 合格证清单配置 # ============================================================ QUALIFIED_LIST_HEADER_ROW: int = _cfg("qualified_list.header_row", 2) """合格清单表头行号(从1开始)""" QUALIFIED_LIST_DATA_START_ROW: int = _cfg("qualified_list.data_start_row", 3) """合格清单数据起始行号(从1开始)""" QUALIFIED_LIST_REQUIRED_FIELDS: list = _cfg("qualified_list.required_fields", []) """合格清单必填字段列表""" # ============================================================ # 数据导入配置 # ============================================================ IMPORT_MAPPING_PREPROCESSOR: dict | None = _cfg("import_mapping.preprocessor") """导入映射预处理器配置""" IMPORT_MAPPING_COLUMN_MAPPING: list = _cfg("import_mapping.column_mapping", []) """导入列映射规则列表""" IMPORT_MAPPING_DEFAULT_VALUES: dict = _cfg("import_mapping.default_values", {}) """导入默认值映射""" # ============================================================ # 试压报告配置 # ============================================================ TEST_REPORT_COMPANY_NAME: str = _cfg("test_report.company_name", "") """试压报告公司名称""" TEST_REPORT_FORM_CODE: str = _cfg("test_report.form_code", "") """试压报告表单代码""" # ============================================================ # 日志配置 # ============================================================ _log_level_raw: Any = _cfg("logging.level", "INFO") LOG_LEVEL: str = str(_log_level_raw) if _log_level_raw else "INFO" """日志级别(DEBUG/INFO/WARNING/ERROR/CRITICAL)""" LOG_ROTATION: str = _cfg("logging.rotation", "10 MB") """日志轮转条件(如"10 MB"或"1 day")""" LOG_RETENTION: str = _cfg("logging.retention", "30 days") """日志保留时间(如"30 days")""" LOG_COMPRESSION: str = _cfg("logging.compression", "zip") """日志压缩格式""" LOG_FORMAT: str = _cfg("logging.format", "{time} | {level} | {name}:{function}:{line} - {message}") """日志格式字符串""" # 处理环境变量占位符 if LOG_LEVEL.startswith("${"): LOG_LEVEL = LOG_LEVEL.replace("${LOG_LEVEL:", "").rstrip("}") or "INFO" # ============================================================ # 打印机配置 # ============================================================ DEFAULT_PRINTER: str | None = get_config_value("printer.default", "VALVE_DEFAULT_PRINTER") """默认打印机名称""" SKIP_PRINTING: bool = _env("VALVE_SKIP_PRINTING") == "true" """是否跳过打印(用于测试)""" # ============================================================ # 模板文件路径 # ============================================================ TEMPLATES: dict = _cfg("templates", {}) """模板配置字典""" def _get_template(path_key: str) -> Path: """获取模板文件路径 优先从顶层templates配置读取,回退到V1的paths.templates配置. Args: path_key: 模板键名,如"certificate_chinese" Returns: Path: 模板文件的绝对路径 """ val = TEMPLATES.get(path_key, "") if val: return _resolve_path(val) v1_templates = _cfg("paths.templates", {}) return _resolve_path(v1_templates.get(path_key, "")) TEMPLATE_CERTIFICATE_CHINESE: Path = _get_template("certificate_chinese") """中文合格证模板路径""" TEMPLATE_CERTIFICATE_BILINGUAL: Path = _get_template("certificate_bilingual") """中英文合格证模板路径""" TEMPLATE_CERTIFICATE_RUSSIAN: Path = _get_template("certificate_russian") """俄文合格证模板路径""" TEMPLATE_TEST_REPORT_SMALL: Path = _get_template("test_report_small") """小型试压报告模板路径""" TEMPLATE_TEST_REPORT_MEDIUM: Path = _get_template("test_report_medium") """中型试压报告模板路径""" TEMPLATE_QUALITY_CERT_FILE: Path = _get_template("quality_cert_file") """质量证书模板文件路径""" # ============================================================ # 图片背景 # ============================================================ IMAGE_BG_CHINESE: str = _cfg("image_backgrounds.chinese", "") """中文版证书背景图片路径""" IMAGE_BG_BILINGUAL: str = _cfg("image_backgrounds.bilingual", "") """中英文双语版证书背景图片路径""" IMAGE_BG_RUSSIAN: str = _cfg("image_backgrounds.russian", "") """俄文版证书背景图片路径""" # ============================================================ # 数据库配置 # ============================================================ DATABASE_TYPE: str = _cfg("database.type", "sqlite") """数据库类型(sqlite/mysql/postgresql)""" DATABASE_ECHO: bool = _cfg("database.echo", False) """是否打印SQL语句""" DATABASE_POOL_SIZE: int = _cfg("database.pool_size", 5) """数据库连接池大小""" DATABASE_ACCESS_ENABLED: bool = _cfg("database.access.enabled", False) """是否启用Access数据库""" DATABASE_ACCESS_PRIMARY: str = _cfg("database.access.primary", "") """Access主数据库路径""" DATABASE_ACCESS_BACKUP: str = _cfg("database.access.backup", "") """Access备份数据库路径""" # ============================================================ # 环境信息 # ============================================================ ENV_COMPUTER_NAME: str | None = get_config_value("environment.computer_name", "COMPUTERNAME") """当前计算机名称""" ENV_USERNAME: str | None = get_config_value("environment.username", "USERNAME") """当前用户名""" # ============================================================ # 颜色值映射 # ============================================================ COLORS_YELLOW: int = _cfg("colors.yellow", 65535) """黄色的RGB整数值""" COLORS_BLUE: int = _cfg("colors.blue", 16711680) """蓝色的RGB整数值""" COLORS_RED: int = _cfg("colors.red", 255) """红色的RGB整数值""" COLORS_GREEN: int = _cfg("colors.green", 65280) """绿色的RGB整数值""" COLORS_LIGHT_BLUE: int = _cfg("colors.light_blue", 15773696) """浅蓝色的RGB整数值""" # ============================================================ # Excel 库选择 # ============================================================ USE_XLWINGS: bool = _env("VALVE_USE_XLWINGS") == "true" """是否使用xlwings库操作Excel""" USE_OPENPYXL: bool = _env("VALVE_USE_OPENPYXL") == "true" """是否使用openpyxl库操作Excel""" # ============================================================ # 导入去重配置(V1 兼容 — 现从 IDGenerator 获取唯一键字段) # ============================================================ # UNIQUE_KEY_FIELDS 和 DEDUP_KEYS 已迁移至 IDGenerator.get_all_unique_key_fields() # 如需保持向后兼容,请通过以下方式获取: # from certflow.handlers.id_generator import IDGenerator # UNIQUE_KEY_FIELDS = IDGenerator.get_all_unique_key_fields() # DEDUP_KEYS = UNIQUE_KEY_FIELDS # DEDUPLICATION_STRATEGY: str = _cfg("import_deduplication.strategy", "skip") # """去重策略(skip/overwrite)""" UPDATE_ON_DUPLICATE: bool = _cfg("import_deduplication.update_on_duplicate", True) """重复时是否检测变更并更新 SalePlan 字段""" MONITORED_FIELDS: list = _cfg( "import_deduplication.monitored_fields", [ "product_name", "product_model", "product_spec", "quantity", "customer", "project_name", "plan_date", ], ) """导入时监控变更的字段列表""" # ============================================================ # 确保输出目录存在 # ============================================================ for _dir in [ OUTPUT_TEST_REPORT, OUTPUT_QUALITY_CERT, OUTPUT_CERTIFICATE_IMAGES, OUTPUT_NAMEPLATE, OUTPUT_BACKUPS, ]: _dir.mkdir(parents=True, exist_ok=True) LOGS_DIR.mkdir(parents=True, exist_ok=True) DATABASE_DIR.mkdir(parents=True, exist_ok=True) # ============================================================ # 排序规则(完整版,从 YAML 动态读取) # ============================================================
[文档] def get_sort_rules() -> list[dict[str, str]]: """获取完整排序规则列表 Returns: List[Dict[str, str]]: 排序规则列表,每个元素包含field和order字段 Examples: >>> get_sort_rules() [{"field": "order_number", "order": "asc"}, {"field": "amount", "order": "desc"}] """ keys: list = _cfg("sort_rules.keys", []) return [{"field": str(k.get("field", "")), "order": str(k.get("order", "asc"))} for k in keys]
# ============================================================ # 分组与排序配置(V3 新增) # ============================================================ # 分组配置 GROUPING_KEYS: list = _cfg("grouping.group_keys", []) """分组键列表(英文字段名)""" GROUPING_KEYS_NO_PROJECT: list = _cfg("grouping.group_keys_no_project", []) """无项目名称时的分组键列表(英文字段名)""" # 分组序号配置 GROUPING_USE_GLOBAL_PREFIX: bool = _cfg("grouping.numbering.use_global_prefix", True) GROUPING_PREFIX_FORMAT: str = _cfg("grouping.numbering.prefix_format", "G{:03d}") GROUPING_SEQ_FORMAT: str = _cfg("grouping.numbering.seq_format", "{:03d}") GROUPING_SEPARATOR: str = _cfg("grouping.numbering.separator", "-") GROUP_INDEX_FIELD: str = _cfg("grouping.numbering.group_index_field", "_group_index") # 排序配置(组内排序) SORTING_KEYS: list = _cfg("sorting.keys", []) """组内排序键列表,每个元素包含 field 和 order""" # 提取排序字段名列表(供 Sorter 使用) SORTING_FIELD_NAMES: list = [k.get("field", "") for k in SORTING_KEYS if k.get("field")] """排序字段名列表(按优先级顺序)""" # ============================================================ # 颜色映射配置 # ============================================================ FONT_COLOR_STATUS_MAP: dict = _cfg("color_mapping.font_colors", {}) BG_COLOR_STATUS_MAP: dict = _cfg("color_mapping.bg_colors", {}) # ============================================================ # 已完成工单回填配置 # ============================================================ COMPLETED_ORDERS_BACKFILL: dict = _cfg("completed_orders_backfill", {}) # ============================================================ # 字段默认值 # ============================================================
[文档] def get_field_default(field_name: str, default: str | None = None) -> Any: """获取证书字段的默认值 Args: field_name: 字段名称 default: 默认值(字段不存在时返回) Returns: Any: 字段默认值 """ defaults: dict = _cfg("certificate.defaults", {}) return defaults.get(field_name, default)
# ============================================================ # 列名工具 # ============================================================
[文档] def get_column_by_alias(excel_column: str) -> str | None: """通过别名查找目标列名 在列别名映射中查找给定的Excel列名,返回对应的目标字段名. Args: excel_column: Excel中的列名或别名 Returns: Optional[str]: 目标字段名,未找到时返回None Examples: >>> get_column_by_alias("订单号") "order_number" >>> get_column_by_alias("客户名称") "customer_name" """ aliases: dict = COLUMN_ALIASES or {} for target, alias_list in aliases.items(): if excel_column in alias_list: return str(target) return None
# ============================================================ # UI 配置快捷方法 # ============================================================
[文档] def get_ui_window_title() -> str: """获取UI窗口标题 Returns: str: 窗口标题,默认为"CertFlow" """ return str(_cfg("ui.window.title", "CertFlow"))
[文档] def get_ui_table_alternating_colors() -> bool: """获取UI表格是否使用交替行颜色 Returns: bool: 是否启用交替行颜色,默认为True """ return bool(_cfg("ui.table.alternating_row_colors", True))
# ============================================================ # 目录确保 # ============================================================
[文档] def ensure_directories() -> None: """创建所有必需的目录 遍历所有配置的目录路径,确保它们存在. 包括数据库目录、临时文件目录、日志目录、资源目录及其子目录. """ for dir_path in [DATABASE_DIR, TEMP_DIR, LOGS_DIR, RESOURCES_DIR]: dir_path.mkdir(parents=True, exist_ok=True) temp_subdirs = [ str(_cfg("paths.temp.excel", "excel")), str(_cfg("paths.temp.reports", "reports")), str(_cfg("paths.temp.images", "images")), str(_cfg("paths.temp.scans", "scans")), ] for subdir in temp_subdirs: (TEMP_DIR / subdir).mkdir(exist_ok=True) template_base: str = str(_cfg("paths.templates.base", "data/templates")) template_dir = BASE_DIR / template_base template_dir.mkdir(parents=True, exist_ok=True) logger.info("All directories verified/created successfully")
# ============================================================ # 配置重载 # ============================================================
[文档] def reload_config() -> None: """强制重新加载 YAML 配置 清除配置缓存,下次读取时会重新加载配置文件. 用于运行时动态更新配置. """ global _config_cache _config_cache = None _load_config() logger.info("Configuration reloaded")
# ============================================================ # 用户配置管理(userconfig.yaml 读写) # ============================================================ _user_config_cache: dict | None = None def _get_user_config() -> dict: """加载 userconfig.yaml(惰性缓存) 读取用户配置文件,用于存储用户偏好设置和导入历史. Returns: Dict: 用户配置字典,文件不存在时返回空字典 """ global _user_config_cache if _user_config_cache is None: user_config_path = BASE_DIR / "config" / "userconfig.yaml" if user_config_path.exists(): with open(user_config_path, encoding="utf-8") as f: _user_config_cache = yaml.safe_load(f) or {} else: _user_config_cache = {} return _user_config_cache # type: ignore[return-value] def _save_user_config(data: dict) -> None: """保存 userconfig.yaml 并刷新缓存 Args: data: 要保存的用户配置数据 """ global _user_config_cache user_config_path = BASE_DIR / "config" / "userconfig.yaml" with open(user_config_path, "w", encoding="utf-8") as f: yaml.dump(data, f, allow_unicode=True, default_flow_style=False) _user_config_cache = data
[文档] def get_recent_files() -> list[dict]: """获取最近打开的文件列表 Returns: List[Dict]: 最近文件列表,每个元素包含path和last_used字段 """ return _get_user_config().get("recent_files", [])
[文档] def add_recent_file(file_path: str) -> None: """添加文件到最近打开列表 如果文件已存在,会移到列表开头. Args: file_path: 文件路径 """ from datetime import datetime user_config = _get_user_config() recent_files: list = user_config.get("recent_files", []) recent_files = [f for f in recent_files if f.get("path") != file_path] recent_files.insert( 0, {"path": file_path, "last_used": datetime.now().strftime("%Y-%m-%d %H:%M:%S")} ) user_config["recent_files"] = recent_files[:10] _save_user_config(user_config)
[文档] def get_user_preference(key: str, default: Any = None) -> Any: """获取用户偏好设置 Args: key: 偏好设置的键名 default: 默认值 Returns: Any: 偏好设置值 """ return _get_user_config().get("preferences", {}).get(key, default)
[文档] def get_skipped_columns(file_path: str) -> list[str]: """获取导入时跳过的列列表 Args: file_path: 文件路径 Returns: List[str]: 跳过的列名列表 """ user_config = _get_user_config() import_history = user_config.get("import_history", {}) return import_history.get(file_path, {}).get("skipped_columns", [])
[文档] def save_skipped_columns(file_path: str, skipped_columns: list[str]) -> None: """保存导入时跳过的列配置 Args: file_path: 文件路径 skipped_columns: 跳过的列名列表 """ user_config = _get_user_config() user_config.setdefault("import_history", {}).setdefault(file_path, {}) user_config["import_history"][file_path]["skipped_columns"] = skipped_columns _save_user_config(user_config) logger.debug(f"已保存跳过列: {file_path} -> {skipped_columns}")
[文档] def get_import_config(file_path: str) -> dict[str, Any]: """获取文件的导入配置 Args: file_path: 文件路径 Returns: Dict[str, Any]: 导入配置字典 """ return _get_user_config().get("import_history", {}).get(file_path, {})
[文档] def save_import_config(file_path: str, config: dict[str, Any]) -> None: """保存文件的导入配置 Args: file_path: 文件路径 config: 导入配置字典 """ user_config = _get_user_config() user_config.setdefault("import_history", {}).setdefault(file_path, {}).update(config) _save_user_config(user_config) logger.debug(f"已保存导入配置: {file_path}")
[文档] def get_recent_import_files(max_count: int = 5) -> list[dict]: """获取最近导入的文件列表 Args: max_count: 最大返回数量,默认为5 Returns: List[Dict]: 最近导入文件列表 """ recent: list = _get_user_config().get("recent_import_files", []) return recent[:max_count]
[文档] def add_recent_import_file(file_path: str, config: dict[str, Any] | None = None) -> None: """添加文件到最近导入列表 Args: file_path: 文件路径 config: 导入配置(可选) """ from datetime import datetime user_config = _get_user_config() recent: list = user_config.get("recent_import_files", []) recent = [f for f in recent if f.get("path") != file_path] record: dict[str, Any] = { "path": file_path, "name": Path(file_path).name, "last_used": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } if config: record["config"] = { "sheet_name": config.get("sheet_name"), "header_row": config.get("header_row"), "data_start_row": config.get("data_start_row"), "skip_rows": config.get("skip_rows", 0), "column_mapping": config.get("column_mapping", {}), } recent.insert(0, record) user_config["recent_import_files"] = recent[:10] _save_user_config(user_config) logger.debug(f"已记录最近文件: {file_path}")
[文档] def get_file_import_config(file_path: str) -> dict[str, Any]: """获取文件的导入配置(优先使用最近文件中的配置) Args: file_path: 文件路径 Returns: Dict[str, Any]: 导入配置字典 """ for item in get_recent_import_files(): if item.get("path") == file_path: return item.get("config", {}) return _get_user_config().get("file_sheet_configs", {}).get(file_path, {})
[文档] def clear_recent_files() -> None: """清空最近文件列表""" user_config = _get_user_config() user_config["recent_import_files"] = [] _save_user_config(user_config) logger.info("已清空最近文件列表")
[文档] def save_sheet_config(file_path: str, sheet_configs: dict[str, Any]) -> None: """保存工作簿内的工作表配置 Args: file_path: Excel文件路径 sheet_configs: 工作表配置字典,键为工作表名称,值为配置内容 """ user_config = _get_user_config() user_config.setdefault("file_sheet_configs", {})[file_path] = sheet_configs _save_user_config(user_config)
[文档] def ensure_ui_config() -> None: """确保UI配置文件存在 如果UI配置文件不存在,创建带有默认配置的ui.yaml文件. """ CONFIG_DIR.mkdir(parents=True, exist_ok=True) if not UI_CONFIG_FILE.exists(): _create_default_ui_config() logger.info(f"已创建默认UI配置文件: {UI_CONFIG_FILE}")
def _create_default_ui_config() -> None: """创建默认的UI配置文件 生成包含主窗口配置、导航按钮、页面配置、样式定义的默认UI配置. """ default_config = { "main_window": { "title": "{app_name} - {app_version}", "min_width": 1280, "min_height": 800, "default_width": 1440, "default_height": 900, }, "title_bar": { "text": "{app_name} - 合格证打印管理系统", "font_size": 20, "font_weight": "bold", "padding": 15, "background_color": "#2c7be5", "text_color": "white", "alignment": "center", }, "nav_buttons": [ { "name": "import", "text": "导入销售计划", "icon": "", "tooltip": "导入Excel销售计划文件", "page_index": 1, "style": {"padding": "20px", "font_size": "16px", "min_width": "150px"}, }, { "name": "print", "text": "打印合格证", "icon": "", "tooltip": "打印合格证", "page_index": None, "style": {"padding": "20px", "font_size": "16px", "min_width": "150px"}, }, { "name": "report", "text": "生成报告", "icon": "", "tooltip": "生成统计报告", "page_index": None, "style": {"padding": "20px", "font_size": "16px", "min_width": "150px"}, }, { "name": "log", "text": "查看日志", "icon": "", "tooltip": "查看系统日志", "page_index": 2, "style": {"padding": "20px", "font_size": "16px", "min_width": "150px"}, }, ], "welcome_page": { "title": { "text": "欢迎使用{app_name}", "font_size": 24, "margin": "50px", "alignment": "center", }, "description": { "text": "请选择要执行的操作", "font_size": 14, "margin": "20px", "alignment": "center", }, }, "import_page": { "title": { "text": "销售计划导入", "font_size": 18, "margin": "20px", "font_weight": "bold", "alignment": "center", }, "import_button": { "text": "选择Excel文件导入", "style": {"padding": "15px", "font_size": "14px"}, "file_filter": "Excel文件 (*.xlsx *.xls)", }, "result_area": {"placeholder": "导入结果将显示在这里...", "read_only": True}, "back_button": {"text": "返回首页", "style": {"padding": "10px"}}, }, "log_page": { "title": { "text": "系统日志", "font_size": 18, "margin": "20px", "font_weight": "bold", "alignment": "center", }, "back_button": {"text": "返回首页", "style": {"padding": "10px"}}, "max_lines": 1000, "auto_scroll": True, }, "status_bar": {"default_message": "就绪", "timeout_ms": 3000}, "styles": { "global": "QMainWindow {\n background-color: #f5f5f5;\n}\n", "button": { "default": ( "QPushButton {\n background-color: #2c7be5;\n color: white;\n" " border: none;\n border-radius: 5px;\n}\n" "QPushButton:hover {\n background-color: #1c68c5;\n}\n" "QPushButton:pressed {\n background-color: #1557a3;\n}\n" ) }, "text_edit": ( "QTextEdit {\n border: 1px solid #ddd;\n border-radius: 5px;\n" " padding: 10px;\n font-family: monospace;\n}\n" ), }, } with open(UI_CONFIG_FILE, "w", encoding="utf-8") as f: yaml.dump(default_config, f, allow_unicode=True, default_flow_style=False, indent=2)