ForgeFlow-ERP/backend/app/api/routes/equipment.py
2026-06-12 16:00:56 +08:00

137 lines
5.8 KiB
Python

from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.db.session import get_db
from app.models.operations import Equipment, MaintenanceOrder, WorkCenter
from app.schemas.operations import (
EquipmentCreate,
EquipmentRead,
MaintenanceOrderCreate,
MaintenanceOrderRead,
)
from app.services.auth import require_authenticated_user
from app.services.operations import get_equipment_query, get_maintenance_orders_query, to_decimal
from app.services.system_permissions import ensure_employee_has_permission
router = APIRouter(dependencies=[Depends(require_authenticated_user)])
def _code_token(value: str | None, fallback: str, max_length: int = 12) -> str:
token = "".join(ch for ch in str(value or "").upper() if ch.isalnum())
return (token or fallback)[-max_length:]
def _next_doc_no(db: Session, model, column_name: str, prefix: str) -> str:
column = getattr(model, column_name)
count = db.scalar(select(func.count(model.id)).where(column.like(f"{prefix}-%"))) or 0
return f"{prefix}-{count + 1:03d}"
def build_equipment_code(db: Session, work_center: WorkCenter, equipment_name: str | None) -> str:
date_part = datetime.now().strftime("%Y%m%d")
center_part = _code_token(work_center.center_code or work_center.center_name, f"WC{work_center.id:04d}", 10)
equipment_part = _code_token(equipment_name, "EQ", 10)
return _next_doc_no(db, Equipment, "equipment_code", f"EQ-NB-{date_part}-{center_part}-{equipment_part}")
def build_maintenance_order_no(db: Session, equipment: Equipment, maintenance_type: str | None) -> str:
date_part = datetime.now().strftime("%Y%m%d")
equipment_part = _code_token(equipment.equipment_code or equipment.equipment_name, f"EQ{equipment.id:04d}", 14)
type_part = _code_token(maintenance_type, "MTN", 8)
return _next_doc_no(db, MaintenanceOrder, "order_no", f"MTN-NB-{date_part}-{equipment_part}-{type_part}")
@router.get("/assets", response_model=list[EquipmentRead])
def list_equipment_assets(
limit: int = Query(default=100, ge=1, le=500),
db: Session = Depends(get_db),
) -> list[EquipmentRead]:
rows = db.execute(get_equipment_query(limit=limit)).mappings().all()
return [EquipmentRead.model_validate(dict(row)) for row in rows]
@router.post("/assets", response_model=EquipmentRead)
def create_equipment_asset(payload: EquipmentCreate, db: Session = Depends(get_db)) -> EquipmentRead:
work_center = db.get(WorkCenter, payload.work_center_id)
if not work_center:
raise HTTPException(status_code=404, detail="工作中心不存在")
equipment = Equipment(
equipment_code=build_equipment_code(db, work_center, payload.equipment_name),
equipment_name=payload.equipment_name,
category_name=payload.category_name,
work_center_id=payload.work_center_id,
manufacturer=payload.manufacturer,
model_no=payload.model_no,
install_date=payload.install_date,
rated_power_kw=to_decimal(payload.rated_power_kw, "0.01"),
occupied_area_sqm=to_decimal(payload.occupied_area_sqm, "0.01"),
maintenance_cycle_hours=to_decimal(payload.maintenance_cycle_hours, "0.01"),
last_maintenance_at=None,
next_maintenance_at=payload.next_maintenance_at,
status=payload.status,
remark=payload.remark,
)
db.add(equipment)
db.commit()
row = db.execute(get_equipment_query(limit=1).where(Equipment.id == equipment.id)).mappings().first()
if not row:
raise HTTPException(status_code=500, detail="设备创建后读取失败")
return EquipmentRead.model_validate(dict(row))
@router.get("/maintenance-orders", response_model=list[MaintenanceOrderRead])
def list_maintenance_orders(
limit: int = Query(default=100, ge=1, le=500),
db: Session = Depends(get_db),
) -> list[MaintenanceOrderRead]:
rows = db.execute(get_maintenance_orders_query(limit=limit)).mappings().all()
return [MaintenanceOrderRead.model_validate(dict(row)) for row in rows]
@router.post("/maintenance-orders", response_model=MaintenanceOrderRead)
def create_maintenance_order(payload: MaintenanceOrderCreate, db: Session = Depends(get_db)) -> MaintenanceOrderRead:
equipment = db.get(Equipment, payload.equipment_id)
if not equipment:
raise HTTPException(status_code=404, detail="设备不存在")
ensure_employee_has_permission(
db,
payload.owner_employee_id,
"MENU_EQUIPMENT_MANAGEMENT",
"负责人",
required=False,
)
order = MaintenanceOrder(
order_no=build_maintenance_order_no(db, equipment, payload.maintenance_type),
equipment_id=payload.equipment_id,
plan_id=None,
maintenance_type=payload.maintenance_type,
start_time=payload.start_time or datetime.now(),
end_time=payload.end_time,
downtime_minutes=to_decimal(payload.downtime_minutes, "0.01"),
owner_employee_id=payload.owner_employee_id,
result_summary=payload.result_summary,
cost_amount=to_decimal(payload.cost_amount, "0.01"),
status=payload.status,
)
db.add(order)
if payload.status == "COMPLETED" or payload.end_time:
finished_time = payload.end_time or datetime.now()
equipment.last_maintenance_at = finished_time
if to_decimal(equipment.maintenance_cycle_hours, "0.01") > 0:
equipment.next_maintenance_at = finished_time + timedelta(hours=float(equipment.maintenance_cycle_hours))
db.add(equipment)
db.commit()
row = db.execute(get_maintenance_orders_query(limit=1).where(MaintenanceOrder.id == order.id)).mappings().first()
if not row:
raise HTTPException(status_code=500, detail="保养工单创建后读取失败")
return MaintenanceOrderRead.model_validate(dict(row))