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//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//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 ], } )