Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
from datetime import timedelta
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import config, report_exec as RE, report_people as RP, reports as R, schemas as S
|
||||
from ..db import get_db
|
||||
from ..models import Department, Equipment, Location, Position, User
|
||||
from ..security import require_engineer
|
||||
|
||||
router = APIRouter(prefix="/reports", tags=["reports"])
|
||||
|
||||
CATALOG = [{"id": "completed-orders", "title": "Выполненные наряды",
|
||||
"description": "Список: машина, чек-лист, кто выполнил, должность, когда. Период обязателен."},
|
||||
{"id": "equipment-state", "title": "Текущее состояние",
|
||||
"description": "Обслуживаемые узлы машин: что требует обслуживания сейчас, что скоро, остаток и прогноз."},
|
||||
{"id": "execution", "title": "Выполнение работ",
|
||||
"description": "По оборудованию: процент выполнения чек-листов, суточных осмотров, ТО и заданий, отклонения и комментарии."},
|
||||
{"id": "people", "title": "По сотрудникам",
|
||||
"description": "Количество и качество выполненных нарядов и задач, трудоёмкость; списком и календарём."}]
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_reports(_: User = Depends(require_engineer)):
|
||||
return CATALOG
|
||||
|
||||
|
||||
@router.get("/completed-orders")
|
||||
def completed_orders(
|
||||
date_from: S.datetime = Query(..., alias="from", description="Начало периода (дата и время)"),
|
||||
date_to: S.datetime = Query(..., alias="to", description="Конец периода (дата и время)"),
|
||||
equipment_id: Optional[int] = None, position_id: Optional[int] = None, user_id: Optional[int] = None,
|
||||
include_review: bool = False, format: Literal["json", "pdf", "csv"] = "json",
|
||||
db: Session = Depends(get_db), u: User = Depends(require_engineer)):
|
||||
if date_to <= date_from:
|
||||
raise HTTPException(422, "Конец периода должен быть позже начала")
|
||||
if date_to - date_from > timedelta(days=366):
|
||||
raise HTTPException(422, "Период не должен превышать 366 дней")
|
||||
f = R.Filters(date_from, date_to, equipment_id, position_id, user_id, include_review)
|
||||
rows = R.completed_orders(db, f)
|
||||
stamp = config.now().strftime("%Y%m%d_%H%M")
|
||||
if format == "pdf":
|
||||
data = R.to_pdf("Отчёт: выполненные наряды", f, R.describe_filters(db, f), rows, u.full_name or u.username)
|
||||
return Response(data, media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'attachment; filename="completed_orders_{stamp}.pdf"'})
|
||||
if format == "csv":
|
||||
return Response(R.to_csv(rows), media_type="text/csv; charset=utf-8",
|
||||
headers={"Content-Disposition": f'attachment; filename="completed_orders_{stamp}.csv"'})
|
||||
return {"from": config.iso(date_from), "to": config.iso(date_to), "total": len(rows), "truncated": len(rows) >= R.MAX_ROWS,
|
||||
"rows": [{**r, "finished_at": config.iso(r["finished_at"])} for r in rows]}
|
||||
|
||||
|
||||
@router.get("/equipment-state")
|
||||
def equipment_state(equipment_id: Optional[int] = None, location_id: Optional[int] = None,
|
||||
state: Literal["", "due", "soon", "attention", "ok"] = "", include_inactive: bool = False,
|
||||
format: Literal["json", "pdf", "csv"] = "json",
|
||||
db: Session = Depends(get_db), u: User = Depends(require_engineer)):
|
||||
"""Текущее состояние обслуживаемых узлов: что требует обслуживания, что скоро, остаток и прогноз."""
|
||||
f = R.StateFilters(equipment_id, location_id, state, include_inactive)
|
||||
all_rows = R.equipment_state(db, f) # сводка считается по всем узлам выбранных машин/мест, независимо от фильтра состояния
|
||||
from .. import scope
|
||||
visible = scope.equipment_ids(db, u) # оборудование чужих отделов не показываем
|
||||
if visible is not None:
|
||||
all_rows = [r for r in all_rows if r["equipment_id"] in visible]
|
||||
rows = R.filter_state(all_rows, state)
|
||||
stamp = config.now().strftime("%Y%m%d_%H%M")
|
||||
if format == "csv":
|
||||
return Response(R.state_csv(rows), media_type="text/csv; charset=utf-8",
|
||||
headers={"Content-Disposition": f'attachment; filename="equipment_state_{stamp}.csv"'})
|
||||
if format == "pdf":
|
||||
txt = []
|
||||
if equipment_id and (e := db.get(Equipment, equipment_id)):
|
||||
txt.append(f"машина: {e.name}")
|
||||
if location_id and (loc := db.get(Location, location_id)):
|
||||
txt.append(f"место: {loc.name}")
|
||||
txt.append({"": "все узлы", "due": "только «Требуется»", "soon": "только «Скоро»",
|
||||
"attention": "«Требуется» и «Скоро»", "ok": "только «В норме»"}[state])
|
||||
data = R.state_pdf(f, txt, rows, u.full_name or u.username)
|
||||
return Response(data, media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'attachment; filename="equipment_state_{stamp}.pdf"'})
|
||||
summary = {k: sum(1 for r in all_rows if r["state"] == k) for k in ("due", "soon", "ok", "nodata")}
|
||||
return {"generated_at": config.iso(config.now()), "total": len(rows), "summary": summary, "rows": rows}
|
||||
|
||||
|
||||
@router.get("/execution")
|
||||
def execution(date_from: S.datetime = Query(..., alias="from"), date_to: S.datetime = Query(..., alias="to"),
|
||||
equipment_id: Optional[int] = None, location_id: Optional[int] = None,
|
||||
format: Literal["json", "pdf", "csv"] = "json",
|
||||
db: Session = Depends(get_db), u: User = Depends(require_engineer)):
|
||||
"""Выполнение чек-листов, суточных осмотров, ТО и суточных заданий по оборудованию: % и отклонения."""
|
||||
if date_to <= date_from:
|
||||
raise HTTPException(422, "Конец периода должен быть позже начала")
|
||||
if date_to - date_from > timedelta(days=366):
|
||||
raise HTTPException(422, "Период не должен превышать 366 дней")
|
||||
data = RE.execution(db, date_from, date_to, equipment_id, location_id)
|
||||
stamp = config.now().strftime("%Y%m%d_%H%M")
|
||||
if format == "csv":
|
||||
return Response(RE.to_csv(data), media_type="text/csv; charset=utf-8",
|
||||
headers={"Content-Disposition": f'attachment; filename="execution_{stamp}.csv"'})
|
||||
if format == "pdf":
|
||||
txt = []
|
||||
if equipment_id and (e := db.get(Equipment, equipment_id)):
|
||||
txt.append(f"машина: {e.name}")
|
||||
if location_id and (loc := db.get(Location, location_id)):
|
||||
txt.append(f"место: {loc.name}")
|
||||
return Response(RE.to_pdf(data, date_from, date_to, txt, u.full_name or u.username), media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'attachment; filename="execution_{stamp}.pdf"'})
|
||||
return {"from": config.iso(date_from), "to": config.iso(date_to), **data}
|
||||
|
||||
|
||||
@router.get("/people")
|
||||
def people(date_from: S.datetime = Query(..., alias="from"), date_to: S.datetime = Query(..., alias="to"),
|
||||
department_id: Optional[int] = None, position_id: Optional[int] = None, user_id: Optional[int] = None,
|
||||
format: Literal["json", "pdf", "csv", "csv_calendar"] = "json",
|
||||
db: Session = Depends(get_db), u: User = Depends(require_engineer)):
|
||||
"""По сотрудникам: количество и качество выполненных нарядов и задач за период, трудоёмкость, календарь по дням."""
|
||||
if date_to <= date_from:
|
||||
raise HTTPException(422, "Конец периода должен быть позже начала")
|
||||
if date_to - date_from > timedelta(days=366):
|
||||
raise HTTPException(422, "Период не должен превышать 366 дней")
|
||||
data = RP.people(db, date_from, date_to, department_id, position_id, user_id)
|
||||
stamp = config.now().strftime("%Y%m%d_%H%M")
|
||||
if format in ("csv", "csv_calendar"):
|
||||
return Response(RP.to_csv(data, calendar=format == "csv_calendar"), media_type="text/csv; charset=utf-8",
|
||||
headers={"Content-Disposition": f'attachment; filename="people_{stamp}.csv"'})
|
||||
if format == "pdf":
|
||||
txt = []
|
||||
for model, val, label in ((Department, department_id, "отдел"), (Position, position_id, "должность"), (User, user_id, "сотрудник")):
|
||||
if val and (x := db.get(model, val)):
|
||||
txt.append(f"{label}: {getattr(x, 'full_name', None) or getattr(x, 'name', None) or getattr(x, 'username', '')}")
|
||||
return Response(RP.to_pdf(data, date_from, date_to, txt, u.full_name or u.username), media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'attachment; filename="people_{stamp}.pdf"'})
|
||||
return {"from": config.iso(date_from), "to": config.iso(date_to), **data}
|
||||
Reference in New Issue
Block a user