certflow.handlers.shipped_export_handler 源代码

"""已发货导出处理器模块

负责三层数据生命周期中 Layer 2/3 的归档和 Excel 导出操作。
将 Excel 写入、记录拷贝等底层操作从 Service 层分离到 Handler 层。

架构:
    Service → ShippedExportHandler → Model / pandas
"""

from __future__ import annotations

from datetime import datetime
from typing import TYPE_CHECKING, Any

import pandas as pd
from loguru import logger
from sqlalchemy.orm import Session

if TYPE_CHECKING:
    from certflow.models import SalePlan, SalePlanShipped

# 延迟导入以避免循环依赖 (models → sale_plan → handlers → shipped_export_handler → models)
# 实际导入在方法内部执行: from certflow.models import SalePlan, SalePlanShipped

# SalePlan 所有业务列的字段名(排除 id 等自动生成字段,用于 insert)
_SALEPLAN_COPY_FIELDS = [
    "unique_key",
    "original_order",
    "sort_group",
    "sort_order",
    "created_at",
    "updated_at",
    "source_file",
    "source_sheet",
    "import_batch_id",
    "import_date",
    "contract_no",
    "sales_order_no",
    "production_order_no",
    "plan_no",
    "plan_date",
    "planned_delivery_date",
    "contract_delivery_date",
    "customer",
    "project_name",
    "project_unit",
    "product_name",
    "product_model",
    "product_spec",
    "quantity",
    "weight",
    "category",
    "unit_price",
    "total_price",
    "business_dept",
    "affiliated_dept",
    "payment_method",
    "supply_type",
    "tech_requirements",
    "equipment_code",
    "product_code",
    "sn_code",
    "kks_code",
    "production_status",
    "execution_status",
    "execution_date",
    "execution_inferred",
    "certificate_done",
    "nameplate_done",
    "test_report_done",
    "warranty_done",
    "cert_scan_done",
    "certificate_time",
    "nameplate_time",
    "test_report_time",
    "warranty_time",
    "cert_scan_time",
    "progress_percent",
    "certificate_remarks",
    "sales_plan_remarks",
    "order_no",
    "specification",
    "material",
    "certificate_number",
    "cert_product_name",
    "cert_product_model",
    "cert_product_spec",
    "test_standard",
    "format_status",
    "font_colors",
    "background_colors",
    "comments",
    "is_hidden",
]


[文档] class ShippedExportHandler: """已发货导出处理器 负责: - 将 SalePlan 记录拷贝到 SalePlanShipped 归档表 - 导出完整字段 Excel 文件 - 按列定义导出选中行到 Excel Examples: >>> handler = ShippedExportHandler(session) >>> count = handler.archive_to_shipped(plans) >>> result = handler.export_full_excel(plans, "/output") """ def __init__(self, session: Session): """初始化处理器 Args: session: SQLAlchemy 数据库会话对象 """ self.session = session # ============================================================ # 归档操作 # ============================================================
[文档] def copy_to_shipped( self, plan: SalePlan, shipped_by: str, shipped_at: datetime | None = None ) -> SalePlanShipped: """将单条 SalePlan 记录拷贝为 SalePlanShipped 归档记录 不执行 session.add,由调用方控制事务。 Args: plan: 源 SalePlan 记录 shipped_by: 归档来源标识 ("archive" 或 "export_delete") shipped_at: 归档时间,默认当前时间 Returns: SalePlanShipped: 归档记录对象 """ from certflow.models import SalePlanShipped now = shipped_at or datetime.now() shipped = SalePlanShipped(shipped_at=now, shipped_by=shipped_by) for field_name in _SALEPLAN_COPY_FIELDS: if hasattr(plan, field_name): setattr(shipped, field_name, getattr(plan, field_name)) shipped.production_status = "已发货" return shipped
[文档] def batch_archive(self, plans: list[SalePlan], shipped_by: str) -> int: """批量归档 SalePlan → SalePlanShipped 并从主表删除 Args: plans: 要归档的 SalePlan 记录列表 shipped_by: 归档来源标识 Returns: int: 归档记录数 """ now = datetime.now() count = 0 for plan in plans: shipped = self.copy_to_shipped(plan, shipped_by, shipped_at=now) self.session.add(shipped) self.session.delete(plan) count += 1 self.session.commit() logger.info(f"归档完成: {count} 条 → sale_plans_shipped (shipped_by={shipped_by})") return count
# ============================================================ # Excel 导出 # ============================================================
[文档] def export_full_excel(self, plans: list[SalePlan], output_dir: str) -> dict[str, Any]: """导出 SalePlan 完整字段到 Excel 文件 Args: plans: 要导出的 SalePlan 记录列表 output_dir: 输出目录 Returns: {"count": N, "filepath": "..."} """ data = [] for p in plans: data.append({fn: getattr(p, fn, "") for fn in _SALEPLAN_COPY_FIELDS}) now = datetime.now() ts = now.strftime("%Y%m%d_%H%M%S") filepath = f"{output_dir}/已发货导出_{ts}.xlsx" pd.DataFrame(data).to_excel(filepath, index=False) logger.info(f"导出完整 Excel: {len(plans)} 条 → {filepath}") return {"count": len(plans), "filepath": filepath}
[文档] def export_selected_to_excel( self, records: list[Any], columns: list[dict[str, str]], file_path: str, ) -> int: """按列定义导出选中记录到 Excel Args: records: ORM 记录列表(SalePlan 对象) columns: 列定义列表 [{"field": "...", "label": "..."}, ...] file_path: 目标文件路径 Returns: int: 导出记录数 """ data = [] for record in records: row_data = {} for col_def in columns: value = getattr(record, col_def["field"], "") row_data[col_def["label"]] = value data.append(row_data) pd.DataFrame(data).to_excel(file_path, index=False) logger.info(f"导出选中行: {len(data)} 条 → {file_path}") return len(data)
[文档] def export_query_result_to_excel( self, records: list[Any], columns: list[dict[str, str]], file_path: str, ) -> int: """按列定义导出查询结果到 Excel(带 plan_date 格式化) Args: records: ORM 记录列表 columns: 列定义列表 file_path: 目标文件路径 Returns: int: 导出记录数 """ from certflow.utils.date_utils import normalize_plan_date data = [] for record in records: row_data = {} for col_def in columns: value = getattr(record, col_def["field"], "") if col_def["field"] == "plan_date": value = normalize_plan_date(value) or "" row_data[col_def["label"]] = value data.append(row_data) pd.DataFrame(data).to_excel(file_path, index=False) logger.info(f"导出查询结果: {len(data)} 条 → {file_path}") return len(data)