"""API /api/analytics.""" from __future__ import annotations import unittest from datetime import datetime from app import create_app, db from app.models import Component, LoadingReport, LoadingReportComponent, Recipe from app.services.setup_state import mark_setup_complete from tests.helpers.zootech_test_helpers import ZootechTestConfig class AnalyticsApiTests(unittest.TestCase): def setUp(self) -> None: self.app = create_app(ZootechTestConfig) self.client = self.app.test_client() self.ctx = self.app.app_context() self.ctx.push() db.create_all() mark_setup_complete(self.app) self.client.post( "/api/auth/login", json={"login": "zootech-test-admin", "password": "zootech-test-secret"}, ) db.session.add( Component( id="api-an-c1", name="Силос", type="forage", dry_matter=30.0, protein=0.0, energy=0.0, price=3.0, ) ) db.session.add( Recipe(id="api-an-r1", name="Рейс", heads_per_trip=1, content_hash="") ) report = LoadingReport( id="api-an-lr", recipe_id="api-an-r1", recipe_name="Рейс", start_time=datetime.now(), target_mixing_time=60, actual_mixing_time=60, total_weight=100.0, created_by="system", updated_by="system", ) db.session.add(report) db.session.flush() db.session.add( LoadingReportComponent( report_id=report.id, component_id="api-an-c1", component_name="Силос", target_weight=100.0, actual_weight=150.0, overload=50.0, loading_order=1, created_by="system", updated_by="system", ) ) db.session.commit() self.today = datetime.now().strftime("%Y-%m-%d") def tearDown(self) -> None: db.session.remove() db.drop_all() self.ctx.pop() def test_finance_endpoint(self) -> None: resp = self.client.get( f"/api/analytics/finance?date_from={self.today}&date_to={self.today}" ) self.assertEqual(resp.status_code, 200) body = resp.get_json() self.assertEqual(body["overloadRub"], 150.0) self.assertIn("dominantIssue", body) self.assertEqual(body["dominantIssue"], "overload") def test_plan_fact_endpoint(self) -> None: resp = self.client.get( f"/api/analytics/plan-fact?date_from={self.today}&date_to={self.today}" ) self.assertEqual(resp.status_code, 200) body = resp.get_json() self.assertEqual(body["reportCount"], 1) self.assertEqual(body["items"][0]["loadingReportId"], "api-an-lr") def test_stock_forecast_endpoint(self) -> None: resp = self.client.get("/api/analytics/stock-forecast") self.assertEqual(resp.status_code, 200) body = resp.get_json() self.assertIn("items", body) self.assertIn("alertBanner", body) def test_finance_export_three_sheets(self) -> None: resp = self.client.get( f"/api/analytics/finance/export?date_from={self.today}&date_to={self.today}" ) self.assertEqual(resp.status_code, 200) from openpyxl import load_workbook import io wb = load_workbook(io.BytesIO(resp.data)) self.assertEqual(set(wb.sheetnames), {"Итоги", "Сравнение", "Подробно"}) def test_analytics_export_xlsx(self) -> None: resp = self.client.get( f"/api/analytics/export?format=xlsx&date_from={self.today}&date_to={self.today}" ) self.assertEqual(resp.status_code, 200) self.assertIn("spreadsheetml", resp.content_type or resp.mimetype or "") def test_analytics_export_comparison_sheet_layout(self) -> None: resp = self.client.get( f"/api/analytics/export?format=xlsx§ion=comparison&date_from={self.today}&date_to={self.today}" ) self.assertEqual(resp.status_code, 200) from openpyxl import load_workbook from openpyxl.utils import get_column_letter import io wb = load_workbook(io.BytesIO(resp.data)) self.assertEqual(wb.sheetnames, ["Сравнение"]) ws = wb["Сравнение"] headers = [cell.value for cell in ws[1]] self.assertIn("Разница План/Факт, кг", headers) self.assertNotIn("Факс", "".join(str(h) for h in headers if h)) for header in ( "По рецепту (База), кг", "На сегодня (План), кг", "Разница База/Факт, кг", "Разница План/Факт, кг", ): col_idx = headers.index(header) + 1 width = ws.column_dimensions[get_column_letter(col_idx)].width self.assertGreaterEqual(width, 18, msg=f"Column {header} too narrow: {width}") self.assertGreater(len(ws.conditional_formatting), 0) def test_analytics_export_pdf(self) -> None: resp = self.client.get( f"/api/analytics/export?format=pdf&date_from={self.today}&date_to={self.today}" ) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.content_type, "application/pdf") self.assertTrue(resp.data.startswith(b"%PDF")) def test_analytics_export_summary_only_xlsx(self) -> None: resp = self.client.get( f"/api/analytics/export?format=xlsx§ion=summary&date_from={self.today}&date_to={self.today}" ) self.assertEqual(resp.status_code, 200) from openpyxl import load_workbook import io wb = load_workbook(io.BytesIO(resp.data)) self.assertEqual(wb.sheetnames, ["Итоги"]) ws = wb["Итоги"] header_row = next( row[0].row for row in ws.iter_rows(min_row=1, max_col=1) if row[0].value == "Ферма" ) header_values = [cell.value for cell in ws[header_row]] self.assertIn("Ферма", header_values) self.assertIn("Перерасход, ₽", header_values) net_row = next( row[0].row for row in ws.iter_rows(min_row=1, max_col=1) if row[0].value == "Итого по деньгам, ₽" ) net_value = ws.cell(row=net_row, column=2).value self.assertGreaterEqual(float(net_value or 0), 0) hint = ws.cell(row=net_row + 1, column=1).value self.assertIn("перерасход", str(hint).lower()) if __name__ == "__main__": unittest.main()