Files
2026-07-17 12:57:18 +03:00

257 lines
8.7 KiB
Python

from datetime import datetime, timedelta
from flask import Blueprint, jsonify, request
from sqlalchemy import select
from app import db
from app.models import (
ComponentLoadingTime,
LoadingReport,
LoadingReportComponent,
UnloadingReport,
UnloadingReportGroup,
)
from app.routes.auth_decorators import require_auth
bp = Blueprint("reports", __name__, url_prefix="/api/reports")
def _error(message: str, status_code: int = 400):
return jsonify({"error": True, "message": message}), status_code
def _parse_date_range():
date_from = (request.args.get("date_from") or "").strip()
date_to = (request.args.get("date_to") or "").strip()
if not date_from or not date_to:
# Legacy fallback: last 24h when explicit range is missing.
end = datetime.now()
return end - timedelta(hours=24), end + timedelta(seconds=1), None
try:
start = datetime.strptime(date_from, "%Y-%m-%d")
end = datetime.strptime(date_to, "%Y-%m-%d") + timedelta(days=1)
return start, end, None
except ValueError:
return None, None, _error("Некорректный формат date_from/date_to (YYYY-MM-DD)", 400)
@bp.get("/ping")
def reports_ping():
"""Временный health-check эндпоинт для модуля отчетов."""
return jsonify({"status": "ok"}), 200
@bp.get("/loading")
@require_auth
def list_loading_reports():
"""Список отчётов загрузки с пагинацией."""
try:
limit = int(request.args.get("limit", 100))
offset = int(request.args.get("offset", 0))
except ValueError:
return _error("Некорректные параметры пагинации", 400)
reports = db.session.execute(
select(LoadingReport)
.where(LoadingReport.is_deleted.is_(False))
.order_by(LoadingReport.start_time.desc())
.offset(offset)
.limit(limit)
).scalars().all()
return jsonify(
[
{
"id": r.id,
"recipe_id": r.recipe_id,
"recipe_name": r.recipe_name,
"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,
"version": r.version,
}
for r in reports
]
)
@bp.get("")
@require_auth
def list_reports_legacy():
"""Legacy-compatible aggregated reports endpoint used by reports UI."""
start, end, range_error = _parse_date_range()
if range_error:
return range_error
try:
limit = int(request.args.get("limit", 500))
offset = int(request.args.get("offset", 0))
except ValueError:
return _error("Некорректные параметры пагинации", 400)
reports = db.session.execute(
select(LoadingReport)
.where(
LoadingReport.is_deleted.is_(False),
LoadingReport.start_time >= start,
LoadingReport.start_time < end,
)
.order_by(LoadingReport.start_time.desc())
.offset(offset)
.limit(limit)
).scalars().all()
if not reports:
return jsonify([])
report_ids = [r.id for r in reports]
components = db.session.execute(
select(LoadingReportComponent)
.where(
LoadingReportComponent.report_id.in_(report_ids),
LoadingReportComponent.is_deleted.is_(False),
)
.order_by(LoadingReportComponent.loading_order.asc())
).scalars().all()
loading_times = db.session.execute(
select(ComponentLoadingTime)
.where(
ComponentLoadingTime.report_id.in_(report_ids),
ComponentLoadingTime.is_deleted.is_(False),
)
.order_by(ComponentLoadingTime.loading_order.asc())
).scalars().all()
unloading_rows = db.session.execute(
select(UnloadingReport)
.where(
UnloadingReport.loading_report_id.in_(report_ids),
UnloadingReport.is_deleted.is_(False),
)
.order_by(UnloadingReport.start_time.desc())
).scalars().all()
unloading_by_loading_id = {u.loading_report_id: u for u in unloading_rows}
unloading_ids = [u.id for u in unloading_rows]
unloading_groups = (
db.session.execute(
select(UnloadingReportGroup)
.where(
UnloadingReportGroup.report_id.in_(unloading_ids),
UnloadingReportGroup.is_deleted.is_(False),
)
.order_by(UnloadingReportGroup.order.asc())
).scalars().all()
if unloading_ids
else []
)
components_map = {}
for c in components:
components_map.setdefault(c.report_id, []).append(
{
"id": c.id,
"component_id": c.component_id,
"component_name": c.component_name,
"target_weight": c.target_weight,
"actual_weight": c.actual_weight,
"overload": c.overload,
"loading_order": c.loading_order,
}
)
loading_times_map = {}
for t in loading_times:
loading_times_map.setdefault(t.report_id, []).append(
{
"id": t.id,
"component_name": t.component_name,
"start_time": t.start_time.isoformat() if t.start_time else None,
"end_time": t.end_time.isoformat() if t.end_time else None,
"loading_duration": t.loading_duration,
"loading_order": t.loading_order,
}
)
unloading_groups_map = {}
for g in unloading_groups:
unloading_groups_map.setdefault(g.report_id, []).append(
{
"id": g.id,
"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,
}
)
payload = []
for r in reports:
unloading = unloading_by_loading_id.get(r.id)
unloading_payload = None
if unloading:
unloading_payload = {
"id": unloading.id,
"start_time": unloading.start_time.isoformat() if unloading.start_time else None,
"end_time": unloading.end_time.isoformat() if unloading.end_time else None,
"total_weight": unloading.total_weight,
"total_unloaded_weight": unloading.total_unloaded_weight,
"remaining_weight": unloading.remaining_weight,
"unloading_groups": unloading_groups_map.get(unloading.id, []),
}
payload.append(
{
"id": r.id,
"recipe_id": r.recipe_id,
"recipe_name": r.recipe_name,
"start_time": r.start_time.isoformat() if r.start_time else None,
"end_time": r.end_time.isoformat() if r.end_time else None,
"target_mixing_time": r.target_mixing_time,
"actual_mixing_time": r.actual_mixing_time,
"total_weight": r.total_weight,
"dispenser_type": r.dispenser_type,
"components": components_map.get(r.id, []),
"component_loading_times": loading_times_map.get(r.id, []),
"unloading_data": unloading_payload,
}
)
return jsonify(payload)
@bp.get("/unloading")
@require_auth
def list_unloading_reports():
"""Список отчётов выгрузки с пагинацией."""
try:
limit = int(request.args.get("limit", 100))
offset = int(request.args.get("offset", 0))
except ValueError:
return _error("Некорректные параметры пагинации", 400)
reports = db.session.execute(
select(UnloadingReport)
.where(UnloadingReport.is_deleted.is_(False))
.order_by(UnloadingReport.start_time.desc())
.offset(offset)
.limit(limit)
).scalars().all()
return jsonify(
[
{
"id": r.id,
"recipe_id": r.recipe_id,
"recipe_name": r.recipe_name,
"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,
"version": r.version,
}
for r in reports
]
)