ForgeFlow-ERP/backend/app/services/system_config.py
2026-06-12 16:00:56 +08:00

211 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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