from __future__ import annotations import re from datetime import datetime from sqlalchemy import select, update from sqlalchemy.orm import Session from app.models.org import SystemConfig RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE = "RAW_MATERIAL_LOT_PREFIX" RAW_MATERIAL_LOT_PREFIX_DEFAULT = "YL" SMART_OPERATION_REPORT_CONFIG_CODE = "SMART_OPERATION_REPORT_ENABLED" MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE = "MINIAPP_OPERATION_REPORT_LAST_SYNCED_AT" MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_NAME = "小程序报工上次同步成功时间" MINIAPP_OPERATION_REPORT_LAST_SYNC_FORMAT = "%Y-%m-%d %H:%M:%S" def normalize_raw_material_lot_prefix(value: str | None) -> str: normalized = re.sub(r"[^A-Z0-9]", "", str(value or "").upper()) return normalized or RAW_MATERIAL_LOT_PREFIX_DEFAULT def normalize_enabled_config_value(value: object) -> str: if isinstance(value, bool): return "开启" if value else "关闭" normalized = str(value or "").strip().lower() if normalized in {"开启", "启用", "true", "1", "yes", "on", "enabled"}: return "开启" if normalized in {"关闭", "停用", "false", "0", "no", "off", "disabled"}: return "关闭" return "开启" def get_system_config_value(db: Session, config_code: str, default: str = "") -> str: value = db.scalar( select(SystemConfig.config_value).where( SystemConfig.config_code == config_code, SystemConfig.status == "ACTIVE", ) ) return str(value) if value is not None else default def get_raw_material_lot_prefix(db: Session) -> str: value = get_system_config_value( db, RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE, RAW_MATERIAL_LOT_PREFIX_DEFAULT, ) return normalize_raw_material_lot_prefix(value) def get_smart_operation_report_enabled(db: Session) -> bool: value = get_system_config_value(db, SMART_OPERATION_REPORT_CONFIG_CODE, "开启") return normalize_enabled_config_value(value) == "开启" def format_miniapp_operation_report_last_sync(value: datetime) -> str: return value.strftime(MINIAPP_OPERATION_REPORT_LAST_SYNC_FORMAT) def parse_miniapp_operation_report_last_sync(value: str | None) -> datetime | None: normalized = str(value or "").strip() if not normalized: return None return datetime.strptime(normalized, MINIAPP_OPERATION_REPORT_LAST_SYNC_FORMAT) def get_miniapp_operation_report_last_sync(db: Session) -> datetime | None: value = get_system_config_value(db, MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE, "") return parse_miniapp_operation_report_last_sync(value) def upsert_miniapp_operation_report_last_sync( db: Session, timestamp: datetime, *, updated_by: int | None = None, ) -> SystemConfig: return upsert_system_config( db, config_code=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE, config_name=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_NAME, config_value=format_miniapp_operation_report_last_sync(timestamp), remark="工序报工小程序同步 ERP 的上次成功截止时间", updated_by=updated_by, now=timestamp, ) def ensure_miniapp_operation_report_last_sync_config( db: Session, *, updated_by: int | None = None, ) -> SystemConfig: row = db.scalar(select(SystemConfig).where(SystemConfig.config_code == MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE)) if row: return row timestamp = datetime.now() row = SystemConfig( config_code=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE, config_name=MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_NAME, config_value="", remark="工序报工小程序同步 ERP 的上次成功截止时间", status="ACTIVE", created_by=updated_by, updated_by=updated_by, created_at=timestamp, updated_at=timestamp, ) db.add(row) db.flush() return row def lock_miniapp_operation_report_last_sync(db: Session) -> SystemConfig | None: ensure_miniapp_operation_report_last_sync_config(db) return db.scalar( select(SystemConfig) .where( SystemConfig.config_code == MINIAPP_OPERATION_REPORT_LAST_SYNC_CONFIG_CODE, SystemConfig.status == "ACTIVE", ) .with_for_update() ) def upsert_smart_operation_report_config( db: Session, *, enabled: bool, updated_by: int | None = None, remark: str | None = None, now: datetime | None = None, ) -> SystemConfig: return upsert_system_config( db, config_code=SMART_OPERATION_REPORT_CONFIG_CODE, config_name="对接智能报工小程序", config_value=normalize_enabled_config_value(enabled), remark=remark or "开启后显示工序报工并按小程序报工数据计算生产进度;关闭后按ERP入库数据反推生产进度", updated_by=updated_by, now=now, ) def lock_raw_material_lot_prefix_config(db: Session) -> None: """Serialize lot-number generation on databases that support row locks.""" db.scalar( select(SystemConfig.id) .where( SystemConfig.config_code == RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE, SystemConfig.status == "ACTIVE", ) .with_for_update() ) def upsert_system_config( db: Session, *, config_code: str, config_name: str, config_value: str, remark: str | None = None, updated_by: int | None = None, now: datetime | None = None, ) -> SystemConfig: timestamp = now or datetime.now() saved_value = ( normalize_raw_material_lot_prefix(config_value) if config_code == RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE else str(config_value or "").strip() ) row = db.scalar(select(SystemConfig).where(SystemConfig.config_code == config_code)) if not row: row = SystemConfig( config_code=config_code, created_by=updated_by, created_at=timestamp, ) row.config_name = config_name row.config_value = saved_value row.remark = remark row.status = "ACTIVE" row.updated_by = updated_by row.updated_at = timestamp db.add(row) db.flush() return row # Use a Core update so caller-supplied business timestamps are not overwritten by # the global ORM before_update audit hook. db.execute( update(SystemConfig) .where(SystemConfig.id == row.id) .values( config_name=config_name, config_value=saved_value, remark=remark, status="ACTIVE", updated_by=updated_by, updated_at=timestamp, ) ) db.flush() db.refresh(row) return row