137 lines
5.8 KiB
Python
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))
|