@@ -0,0 +1,256 @@
|
||||
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
|
||||
]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user