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

This commit is contained in:
darkorion
2026-10-13 09:09:53 +03:00
commit c298f5a830
247 changed files with 23200 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
"""Отчёт по сотрудникам, трудоёмкость задач, возвраты нарядов, фильтры вкладки «Задачи»."""
import os
import tempfile
from datetime import timedelta
os.environ.setdefault("DATABASE_URL", os.environ.get("TEST_DATABASE_URL") or "sqlite:///" + os.path.join(tempfile.mkdtemp(), "pp.db").replace("\\", "/"))
os.environ.setdefault("SCHEDULER_INTERVAL", "3600")
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-pytest-0123456789abcdef")
os.environ.setdefault("DATA_DIR", tempfile.mkdtemp())
from fastapi.testclient import TestClient # noqa: E402
from app import config # noqa: E402
from app.main import app # noqa: E402
V1 = "/api/v1"
PW = "Pass1234567"
def test_people_report_and_work_filters():
with TestClient(app) as c:
login = lambda u, p=PW: {"Authorization": "Bearer " + c.post(f"{V1}/auth/login", json={"username": u, "password": p}).json()["access_token"]}
adm = login("admin", "admin")
eng_pos = c.post(f"{V1}/positions", headers=adm, json={"name": "PP-Инженер", "kind": "engineer"}).json()
dep = c.post(f"{V1}/departments", headers=adm, json={"name": "PP-Служба"}).json()
a = c.post(f"{V1}/users", headers=adm, json={"username": "pp_a", "full_name": "Альфа Работник", "password": PW, "department_id": dep["id"]}).json()
c.post(f"{V1}/users", headers=adm, json={"username": "pp_e", "full_name": "Инж Проверяющий", "password": PW, "position_id": eng_pos["id"]})
ta, te = login("pp_a"), login("pp_e")
# задача с нормой трудоёмкости и задача без нормы (фактическое время)
t1 = c.post(f"{V1}/tasks", headers=te, json={"title": "Норма 2.5 ч", "assignee_id": a["id"], "labor_hours": 2.5}).json()
assert t1["labor_hours"] == 2.5
for s, cm in (("in_progress", ""), ("done", "ok")):
c.post(f"{V1}/tasks/{t1['id']}/status", headers=ta, json={"status": s, "comment": cm})
t2 = c.post(f"{V1}/tasks", headers=te, json={"title": "Отменённая", "assignee_id": a["id"]}).json()
c.post(f"{V1}/tasks/{t2['id']}/status", headers=ta, json={"status": "paused", "comment": "жду"})
c.post(f"{V1}/tasks/{t2['id']}/status", headers=ta, json={"status": "cancelled", "comment": "не нужно"})
# фильтры вкладки «Задачи»: поставленные мной
mine_set = c.get(f"{V1}/tasks", headers=te, params={"created_by_me": True}).json()
assert {x["title"] for x in mine_set} >= {"Норма 2.5 ч", "Отменённая"}
# наряд: возврат с проверки и отклонение от нормы
cl = c.post(f"{V1}/checklists", headers=adm, json={"name": "PP-чек", "items": [{"text": "Давление", "answer_type": "number", "min_value": 1, "max_value": 2}]}).json()
o = c.post(f"{V1}/orders", headers=te, json={"title": "PP-наряд", "checklist_id": cl["id"], "assignee_user_id": a["id"],
"reviewer_user_id": c.get(f"{V1}/me", headers=te).json()["id"]}).json()
item = c.get(f"{V1}/orders/{o['id']}", headers=ta).json()["items"][0]
c.put(f"{V1}/orders/{o['id']}/items/{item['id']}", headers=ta, json={"value": "5"})
st = c.post(f"{V1}/orders/{o['id']}/submit", headers=ta).json()["status"]
if st == "review":
assert c.post(f"{V1}/orders/{o['id']}/review", headers=te, json={"approve": False, "comment": "Переделать"}).status_code == 200
c.post(f"{V1}/orders/{o['id']}/submit", headers=ta)
c.post(f"{V1}/orders/{o['id']}/review", headers=te, json={"approve": True})
now = config.now()
q = {"from": (now - timedelta(days=1)).isoformat(), "to": (now + timedelta(days=1)).isoformat()}
assert c.get(f"{V1}/reports/people", headers=ta, params=q).status_code == 403
rep = c.get(f"{V1}/reports/people", headers=te, params={**q, "department_id": dep["id"]}).json()
assert [r["name"] for r in rep["rows"]] == ["Альфа Работник"]
r = rep["rows"][0]
assert r["tasks_done"] == 1 and r["tasks_cancelled"] == 1 and r["tasks_paused"] == 1 and r["orders_done"] == 1
assert r["deviations"] == 1 and r["hours"] >= 2.5 and r["on_time_pct"] is not None
day = now.date().isoformat()
assert r["calendar"][day]["n"] == 2 and r["calendar"][day]["h"] >= 2.5
assert len(rep["days"]) >= 2
pdf = c.get(f"{V1}/reports/people", headers=te, params={**q, "format": "pdf"})
assert pdf.status_code == 200 and pdf.content[:5] == b"%PDF-"
csv = c.get(f"{V1}/reports/people", headers=te, params={**q, "format": "csv_calendar"}).content.decode("utf-8-sig")
assert "Альфа Работник" in csv and "Итого ч" in csv
# фильтры инцидентов для вкладки «Задачи»
eq = c.post(f"{V1}/equipment", headers=adm, json={"name": "PP-Станок"}).json()
c.post(f"{V1}/incidents", headers=ta, json={"equipment_id": eq["id"], "title": "PP-авария"})
assert [i["title"] for i in c.get(f"{V1}/incidents", headers=ta, params={"mine": True}).json()] == ["PP-авария"]
assert c.get(f"{V1}/incidents", headers=ta, params={"q": "pp-стан"}).json()[0]["title"] == "PP-авария"
assert all(i["source"] == "staff" for i in c.get(f"{V1}/incidents", headers=ta, params={"source": "staff"}).json())