318 lines
11 KiB
Python
318 lines
11 KiB
Python
import logging
|
|
from datetime import datetime
|
|
|
|
from flask import Blueprint, jsonify, request
|
|
|
|
logger = logging.getLogger(__name__)
|
|
from sqlalchemy import func, select
|
|
|
|
from app import db
|
|
from app.models import (
|
|
ComponentLoadingTime,
|
|
LoadingReport,
|
|
LoadingReportComponent,
|
|
Recipe,
|
|
UnloadingReport,
|
|
UnloadingReportGroup,
|
|
)
|
|
from app.routes.auth_decorators import require_auth, require_paired_terminal
|
|
|
|
bp = Blueprint("reports_legacy", __name__, url_prefix="/api")
|
|
|
|
|
|
def _error(message: str, status_code: int = 400):
|
|
return jsonify({"error": True, "message": message}), status_code
|
|
|
|
|
|
@bp.post("/save_report")
|
|
@require_paired_terminal
|
|
def save_report_legacy():
|
|
data = request.get_json() or {}
|
|
recipe_id = data.get("recipe_id")
|
|
if not recipe_id:
|
|
return _error("Не указан ID рецепта", 400)
|
|
|
|
recipe = db.session.execute(
|
|
select(Recipe).where(Recipe.id == recipe_id, Recipe.is_deleted.is_(False))
|
|
).scalar_one_or_none()
|
|
if not recipe:
|
|
return _error("Рецепт не найден", 404)
|
|
|
|
try:
|
|
total_weight = float(data.get("total_weight", 0))
|
|
target_mixing_time = int(data.get("target_mixing_time", 0))
|
|
actual_mixing_time = int(data.get("actual_mixing_time", 0))
|
|
except (TypeError, ValueError):
|
|
return _error("Некорректные числовые поля отчёта", 400)
|
|
|
|
components = data.get("components", []) or []
|
|
if not components:
|
|
return _error("Нет данных о компонентах", 400)
|
|
|
|
report = LoadingReport(
|
|
recipe_id=recipe_id,
|
|
recipe_name=recipe.name,
|
|
start_time=datetime.now(),
|
|
end_time=datetime.now(),
|
|
target_mixing_time=target_mixing_time,
|
|
actual_mixing_time=actual_mixing_time,
|
|
total_weight=total_weight,
|
|
dispenser_type=(data.get("dispenser_type") or "dispenser"),
|
|
created_by="system",
|
|
updated_by="system",
|
|
)
|
|
db.session.add(report)
|
|
db.session.flush()
|
|
|
|
for idx, comp in enumerate(components, start=1):
|
|
db.session.add(
|
|
LoadingReportComponent(
|
|
report_id=report.id,
|
|
component_id=comp.get("component_id") or comp.get("componentId"),
|
|
component_name=str(comp.get("name") or comp.get("component_name") or "—"),
|
|
target_weight=float(comp.get("target_weight", 0) or 0),
|
|
actual_weight=float(comp.get("actual_weight", 0) or 0),
|
|
overload=float(comp.get("overload", 0) or 0),
|
|
loading_order=idx,
|
|
created_by="system",
|
|
updated_by="system",
|
|
)
|
|
)
|
|
|
|
for item in data.get("component_loading_times", []) or []:
|
|
try:
|
|
start_time = datetime.fromisoformat(item["start_time"])
|
|
end_time = datetime.fromisoformat(item["end_time"])
|
|
except Exception:
|
|
continue
|
|
db.session.add(
|
|
ComponentLoadingTime(
|
|
report_id=report.id,
|
|
component_name=str(item.get("component_name") or "—"),
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
loading_duration=float(item.get("loading_duration", 0) or 0),
|
|
loading_order=int(item.get("loading_order", 0) or 0),
|
|
created_by="system",
|
|
updated_by="system",
|
|
)
|
|
)
|
|
|
|
db.session.commit()
|
|
try:
|
|
from app.services.feed_quality.evaluator import evaluate_loading_report
|
|
|
|
evaluate_loading_report(report.id, send_notifications=True)
|
|
except Exception:
|
|
logger.exception("[FEED-QUALITY] save_report evaluate failed report_id=%s", report.id)
|
|
return jsonify({"status": "success", "message": "Отчёт успешно сохранён", "report_id": report.id})
|
|
|
|
|
|
@bp.get("/consumption_by_component")
|
|
@require_auth
|
|
def consumption_by_component():
|
|
rows = db.session.execute(
|
|
select(
|
|
LoadingReportComponent.component_id,
|
|
LoadingReportComponent.component_name,
|
|
func.coalesce(
|
|
func.sum(
|
|
LoadingReportComponent.actual_weight
|
|
+ func.coalesce(LoadingReportComponent.overload, 0)
|
|
),
|
|
0,
|
|
).label("total_actual_weight"),
|
|
func.count(LoadingReportComponent.id).label("report_count"),
|
|
)
|
|
.join(LoadingReport, LoadingReport.id == LoadingReportComponent.report_id)
|
|
.where(LoadingReportComponent.is_deleted.is_(False))
|
|
.where(LoadingReport.is_deleted.is_(False))
|
|
.group_by(
|
|
LoadingReportComponent.component_id,
|
|
LoadingReportComponent.component_name,
|
|
)
|
|
).all()
|
|
return jsonify(
|
|
[
|
|
{
|
|
"component_id": r.component_id,
|
|
"component_name": r.component_name or "—",
|
|
"total_actual_weight": round(float(r.total_actual_weight or 0), 2),
|
|
"report_count": int(r.report_count or 0),
|
|
}
|
|
for r in rows
|
|
]
|
|
)
|
|
|
|
|
|
@bp.get("/reports/<string:report_id>/loading_times")
|
|
@require_auth
|
|
def report_loading_times(report_id: str):
|
|
report = db.session.execute(
|
|
select(LoadingReport).where(LoadingReport.id == report_id, LoadingReport.is_deleted.is_(False))
|
|
).scalar_one_or_none()
|
|
if not report:
|
|
return _error("Отчёт не найден", 404)
|
|
rows = db.session.execute(
|
|
select(ComponentLoadingTime)
|
|
.where(
|
|
ComponentLoadingTime.report_id == report_id,
|
|
ComponentLoadingTime.is_deleted.is_(False),
|
|
)
|
|
.order_by(ComponentLoadingTime.loading_order.asc())
|
|
).scalars().all()
|
|
return jsonify(
|
|
{
|
|
"status": "success",
|
|
"loading_times": [
|
|
{
|
|
"component_name": lt.component_name,
|
|
"start_time": lt.start_time.isoformat() if lt.start_time else None,
|
|
"end_time": lt.end_time.isoformat() if lt.end_time else None,
|
|
"loading_duration": float(lt.loading_duration or 0),
|
|
"loading_order": lt.loading_order,
|
|
}
|
|
for lt in rows
|
|
],
|
|
}
|
|
)
|
|
|
|
|
|
@bp.post("/save_unloading_report")
|
|
@require_paired_terminal
|
|
def save_unloading_report_legacy():
|
|
data = request.get_json() or {}
|
|
loading_report_id = data.get("loading_report_id")
|
|
recipe_id = data.get("recipe_id")
|
|
if not loading_report_id:
|
|
return _error("Не указан ID отчёта о загрузке", 400)
|
|
if not recipe_id:
|
|
return _error("Не указан ID рецепта", 400)
|
|
|
|
recipe = db.session.execute(
|
|
select(Recipe).where(Recipe.id == recipe_id, Recipe.is_deleted.is_(False))
|
|
).scalar_one_or_none()
|
|
if not recipe:
|
|
return _error("Рецепт не найден", 404)
|
|
|
|
report = UnloadingReport(
|
|
recipe_id=recipe_id,
|
|
recipe_name=recipe.name,
|
|
loading_report_id=loading_report_id,
|
|
start_time=datetime.now(),
|
|
end_time=datetime.now(),
|
|
total_weight=float(data.get("total_weight", 0) or 0),
|
|
total_unloaded_weight=float(data.get("total_unloaded_weight", 0) or 0),
|
|
remaining_weight=float(data.get("remaining_weight", 0) or 0),
|
|
created_by="system",
|
|
updated_by="system",
|
|
)
|
|
db.session.add(report)
|
|
db.session.flush()
|
|
|
|
for group in data.get("unloading_groups", []) or []:
|
|
db.session.add(
|
|
UnloadingReportGroup(
|
|
report_id=report.id,
|
|
name=str(group.get("name") or "—"),
|
|
target_weight=float(group.get("target_weight", 0) or 0),
|
|
unloaded_weight=float(group.get("unloaded_weight", 0) or 0),
|
|
remaining_weight=float(group.get("remaining_weight", 0) or 0),
|
|
distribution_type=str(group.get("distribution_type") or "percent"),
|
|
distribution_value=float(group.get("distribution_value", 0) or 0),
|
|
order=int(group.get("order", 0) or 0),
|
|
created_by="system",
|
|
updated_by="system",
|
|
)
|
|
)
|
|
|
|
db.session.commit()
|
|
try:
|
|
from app.services.feed_quality.evaluator import evaluate_unloading_report
|
|
|
|
evaluate_unloading_report(report.id, send_notifications=True)
|
|
except Exception:
|
|
logger.exception(
|
|
"[FEED-QUALITY] save_unloading_report evaluate failed unloading_id=%s", report.id
|
|
)
|
|
return jsonify(
|
|
{
|
|
"status": "success",
|
|
"message": "Отчёт о выгрузке сохранён",
|
|
"unloading_report_id": report.id,
|
|
}
|
|
)
|
|
|
|
|
|
@bp.get("/unloading_reports")
|
|
@require_auth
|
|
def unloading_reports_legacy():
|
|
rows = db.session.execute(
|
|
select(UnloadingReport)
|
|
.where(UnloadingReport.is_deleted.is_(False))
|
|
.order_by(UnloadingReport.start_time.desc())
|
|
).scalars().all()
|
|
return jsonify(
|
|
[
|
|
{
|
|
"id": r.id,
|
|
"recipe_id": r.recipe_id,
|
|
"recipe_name": r.recipe_name,
|
|
"loading_report_id": r.loading_report_id,
|
|
"start_time": r.start_time.isoformat() if r.start_time else None,
|
|
"end_time": r.end_time.isoformat() if r.end_time else None,
|
|
"total_weight": r.total_weight,
|
|
"total_unloaded_weight": r.total_unloaded_weight,
|
|
"remaining_weight": r.remaining_weight,
|
|
"unloading_groups": [
|
|
{
|
|
"name": g.name,
|
|
"target_weight": g.target_weight,
|
|
"unloaded_weight": g.unloaded_weight,
|
|
"remaining_weight": g.remaining_weight,
|
|
"distribution_type": g.distribution_type,
|
|
"distribution_value": g.distribution_value,
|
|
"order": g.order,
|
|
}
|
|
for g in sorted(r.groups, key=lambda x: x.order)
|
|
if not g.is_deleted
|
|
],
|
|
}
|
|
for r in rows
|
|
]
|
|
)
|
|
|
|
|
|
@bp.get("/reports/<string:report_id>/unloading_groups")
|
|
@require_auth
|
|
def report_unloading_groups(report_id: str):
|
|
report = db.session.execute(
|
|
select(UnloadingReport).where(
|
|
UnloadingReport.id == report_id, UnloadingReport.is_deleted.is_(False)
|
|
)
|
|
).scalar_one_or_none()
|
|
if not report:
|
|
return _error("Отчёт выгрузки не найден", 404)
|
|
return jsonify(
|
|
{
|
|
"status": "success",
|
|
"recipe_name": report.recipe_name,
|
|
"total_weight": report.total_weight,
|
|
"total_unloaded_weight": report.total_unloaded_weight,
|
|
"remaining_weight": report.remaining_weight,
|
|
"unloading_groups": [
|
|
{
|
|
"name": g.name,
|
|
"target_weight": g.target_weight,
|
|
"unloaded_weight": g.unloaded_weight,
|
|
"remaining_weight": g.remaining_weight,
|
|
"distribution_type": g.distribution_type,
|
|
"distribution_value": g.distribution_value,
|
|
"order": g.order,
|
|
}
|
|
for g in sorted(report.groups, key=lambda x: x.order)
|
|
if not g.is_deleted
|
|
],
|
|
}
|
|
)
|