from flask import Blueprint, Response, jsonify, request from app.routes.auth_decorators import require_auth from app.services.analytics.export_sections import export_filename_base, parse_export_sections from app.services.analytics.finance_summary import build_finance_summary from app.services.analytics.pdf_export import build_analytics_pdf from app.services.analytics.plan_fact_layers import build_plan_fact_rows from app.services.analytics.stock_forecast import build_stock_forecast from app.services.analytics.xlsx_summary import build_finance_summary_xlsx bp = Blueprint("analytics", __name__, url_prefix="/api/analytics") def _error(message: str, status_code: int = 400): return jsonify({"error": True, "message": message}), status_code def _export_params(): 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: return None, None, None, None, _error("Укажите date_from и date_to (YYYY-MM-DD)", 400) recipe_id = (request.args.get("recipe_id") or "").strip() or None recipe_ids = (request.args.get("recipe_ids") or "").strip() or None return date_from, date_to, recipe_id, recipe_ids, None @bp.get("/finance") @require_auth def finance_summary(): return jsonify( build_finance_summary( date_from=request.args.get("date_from"), date_to=request.args.get("date_to"), recipe_id=(request.args.get("recipe_id") or "").strip() or None, recipe_ids=(request.args.get("recipe_ids") or "").strip() or None, ) ) @bp.get("/plan-fact") @require_auth def plan_fact(): return jsonify( build_plan_fact_rows( date_from=request.args.get("date_from"), date_to=request.args.get("date_to"), recipe_id=(request.args.get("recipe_id") or "").strip() or None, recipe_ids=(request.args.get("recipe_ids") or "").strip() or None, client_id=(request.args.get("client_id") or "").strip() or None, ) ) @bp.get("/stock-forecast") @require_auth def stock_forecast(): from app.models import Component return jsonify( build_stock_forecast( plan_date=request.args.get("plan_date"), Component=Component, ) ) def _filter_context_kwargs(): return { "filter_farms": (request.args.get("filter_farms") or "").strip() or None, "filter_dispensers": (request.args.get("filter_dispensers") or "").strip() or None, "filter_recipes": (request.args.get("filter_recipes") or "").strip() or None, } @bp.get("/export") @require_auth def analytics_export(): date_from, date_to, recipe_id, recipe_ids, err = _export_params() if err: return err fmt = (request.args.get("format") or "xlsx").strip().lower() section = (request.args.get("section") or "all").strip().lower() sections = parse_export_sections(section) base_name = export_filename_base( date_from=date_from, date_to=date_to, sections=sections ) ctx = _filter_context_kwargs() if fmt == "pdf": data = build_analytics_pdf( date_from=date_from, date_to=date_to, recipe_id=recipe_id, recipe_ids=recipe_ids, section=section, **ctx, ) return Response( data, mimetype="application/pdf", headers={ "Content-Disposition": f'attachment; filename="{base_name}.pdf"' }, ) if fmt in ("xlsx", "excel"): data = build_finance_summary_xlsx( date_from=date_from, date_to=date_to, recipe_id=recipe_id, recipe_ids=recipe_ids, section=section, **ctx, ) return Response( data, mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={ "Content-Disposition": f'attachment; filename="{base_name}.xlsx"' }, ) return _error("format должен быть xlsx или pdf", 400) @bp.get("/finance/export") @require_auth def finance_export(): date_from, date_to, recipe_id, recipe_ids, err = _export_params() if err: return err section = (request.args.get("section") or "all").strip().lower() sections = parse_export_sections(section) data = build_finance_summary_xlsx( date_from=date_from, date_to=date_to, recipe_id=recipe_id, recipe_ids=recipe_ids, section=section, **_filter_context_kwargs(), ) filename = f"{export_filename_base(date_from=date_from, date_to=date_to, sections=sections)}.xlsx" return Response( data, mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": f'attachment; filename="{filename}"'}, )