Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android

This commit is contained in:
darkorion
2026-10-13 09:09:53 +03:00
commit 67e411eea8
246 changed files with 23152 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
"""Регламентное обслуживание: узлы машин, их правила, наработка (моточасы), сброс и история."""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.orm import Session
from .. import config, schemas as S, service
from ..db import get_db
from ..models import Equipment, MeterReading, ServiceEvent, ServiceUnit, User
from ..security import current_user, require_engineer
from .catalog import commit, get_or_404
router = APIRouter(tags=["service"])
def unit_out(u: ServiceUnit, eq: Equipment, rate: float | None) -> S.ServiceUnitOut:
out = S.ServiceUnitOut.model_validate(u)
for k, v in service.evaluate(u, eq.meter_hours or 0, rate).items():
setattr(out, k, v)
return out
def check_rule(u: ServiceUnit):
"""Проверка правила после создания/правки (в PATCH поля приходят частично)."""
try:
S.ServiceUnitIn(name=u.name, mode=u.mode, interval_hours=u.interval_hours, interval_value=u.interval_value,
interval_unit=u.interval_unit).check_rule()
except ValueError as e:
raise HTTPException(422, str(e))
@router.get("/equipment/{id}/service-units", response_model=list[S.ServiceUnitOut])
def list_units(id: int, db: Session = Depends(get_db), _: User = Depends(current_user)):
eq = get_or_404(db, Equipment, id)
rate = service.daily_rate(db, id)
units = db.scalars(select(ServiceUnit).where(ServiceUnit.equipment_id == id).order_by(ServiceUnit.id)).all()
out = [unit_out(u, eq, rate) for u in units]
return sorted(out, key=lambda x: (not x.is_active, service.state_rank(x.state), -x.percent))
@router.post("/equipment/{id}/service-units", response_model=S.ServiceUnitOut, status_code=201)
def create_unit(id: int, data: S.ServiceUnitIn, db: Session = Depends(get_db), me: User = Depends(require_engineer)):
eq = get_or_404(db, Equipment, id)
try:
data.check_rule()
except ValueError as e:
raise HTTPException(422, str(e))
d = data.model_dump(exclude={"last_service_at", "last_service_hours"})
if data.mode == "hours":
d["interval_value"] = d["interval_unit"] = None
elif data.mode == "calendar":
d["interval_hours"] = None
u = ServiceUnit(equipment_id=id, **d)
at = data.last_service_at or config.now()
hours = eq.meter_hours or 0 if data.last_service_hours is None else data.last_service_hours
if hours > (eq.meter_hours or 0):
raise HTTPException(422, "Наработка при последнем обслуживании не может быть больше текущей наработки машины")
u.last_service_at, u.last_service_hours = at, hours
db.add(u)
db.flush()
db.add(ServiceEvent(unit_id=u.id, at=at, hours=hours, kind="initial", user_id=me.id, note="Начальное состояние"))
commit(db)
return unit_out(u, eq, service.daily_rate(db, id))
@router.patch("/service-units/{id}", response_model=S.ServiceUnitOut)
def update_unit(id: int, data: S.ServiceUnitPatch, db: Session = Depends(get_db), _: User = Depends(require_engineer)):
u = get_or_404(db, ServiceUnit, id)
for k, v in data.model_dump(exclude_unset=True).items():
if v is None and k in ("name", "mode", "warn_percent", "is_active"):
continue
setattr(u, k, v)
if u.mode == "hours":
u.interval_value = u.interval_unit = None
elif u.mode == "calendar":
u.interval_hours = None
check_rule(u)
commit(db)
eq = db.get(Equipment, u.equipment_id)
return unit_out(u, eq, service.daily_rate(db, eq.id))
@router.delete("/service-units/{id}", status_code=204)
def delete_unit(id: int, db: Session = Depends(get_db), _: User = Depends(require_engineer)):
u = get_or_404(db, ServiceUnit, id)
service.unlink_unit(db, id)
db.delete(u)
commit(db)
@router.post("/service-units/{id}/reset", response_model=S.ServiceUnitOut)
def reset(id: int, data: S.ServiceResetIn, db: Session = Depends(get_db), me: User = Depends(require_engineer)):
"""Сброс узла: обслуживание или замена выполнены отдельно от наряда либо при внештатной ситуации."""
u = get_or_404(db, ServiceUnit, id)
eq = db.get(Equipment, u.equipment_id)
if data.hours is not None and data.hours > (eq.meter_hours or 0):
raise HTTPException(422, "Наработка не может быть больше текущей наработки машины — сначала внесите показание счётчика")
if data.at and data.at > config.now():
raise HTTPException(422, "Дата обслуживания не может быть в будущем")
service.reset_unit(db, u, at=data.at, hours=data.hours, kind="reset", user_id=me.id, note=data.note.strip())
commit(db)
return unit_out(u, eq, service.daily_rate(db, eq.id))
@router.get("/service-units/{id}/events", response_model=list[S.ServiceEventOut])
def unit_events(id: int, limit: int = Query(100, ge=1, le=500), db: Session = Depends(get_db), _: User = Depends(current_user)):
get_or_404(db, ServiceUnit, id)
names = {i: (n or un) for i, n, un in db.execute(select(User.id, User.full_name, User.username))}
rows = db.scalars(select(ServiceEvent).where(ServiceEvent.unit_id == id).order_by(ServiceEvent.at.desc(), ServiceEvent.id.desc()).limit(limit)).all()
out = []
for e in rows:
o = S.ServiceEventOut.model_validate(e)
o.user_name = names.get(e.user_id)
out.append(o)
return out
# ---------- наработка (моточасы) ----------
@router.get("/equipment/{id}/meter", response_model=list[S.MeterOut])
def meter_history(id: int, limit: int = Query(50, ge=1, le=500), db: Session = Depends(get_db), _: User = Depends(current_user)):
get_or_404(db, Equipment, id)
return db.scalars(select(MeterReading).where(MeterReading.equipment_id == id)
.order_by(MeterReading.at.desc(), MeterReading.id.desc()).limit(limit)).all()
@router.post("/equipment/{id}/meter", response_model=S.EquipmentOut, status_code=201)
def add_meter(id: int, data: S.MeterIn, db: Session = Depends(get_db), me: User = Depends(require_engineer)):
eq = get_or_404(db, Equipment, id)
if data.at and data.at > config.now():
raise HTTPException(422, "Время показания не может быть в будущем")
try:
service.record_meter(db, eq, data.hours, at=data.at, user_id=me.id, note=data.note.strip(), correction=data.correction)
except ValueError as e:
raise HTTPException(422, str(e))
commit(db)
return eq