from __future__ import annotations from datetime import UTC, datetime from uuid import uuid4 import pytest from fastapi.testclient import TestClient from app.modules.zootech.catalog_apply import apply_catalog_change from tests.orchestrator_dual_harness import make_enterprise def test_wesp_compat_feed_dispensers(client: TestClient): enterprise_id = make_enterprise(client) login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"}) token = login.json()["access_token"] session = client.post( f"/api/v1/enterprise/enterprises/{enterprise_id}/session", headers={"Authorization": f"Bearer {token}"}, ) token = session.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} resp = client.get(f"/api/feed_dispensers?enterprise_id={enterprise_id}", headers=headers) assert resp.status_code == 200 assert isinstance(resp.json(), list) def test_wesp_compat_components(client: TestClient): enterprise_id = make_enterprise(client) login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"}) token = login.json()["access_token"] session = client.post( f"/api/v1/enterprise/enterprises/{enterprise_id}/session", headers={"Authorization": f"Bearer {token}"}, ) token = session.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} resp = client.get(f"/api/components?enterprise_id={enterprise_id}", headers=headers) assert resp.status_code == 200 assert isinstance(resp.json(), list) def test_wesp_compat_recipe_includes_ingredients_and_unloading_groups(client: TestClient): enterprise_id = make_enterprise(client) login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"}) token = login.json()["access_token"] session = client.post( f"/api/v1/enterprise/enterprises/{enterprise_id}/session", headers={"Authorization": f"Bearer {token}"}, ) token = session.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} recipe_id = "recipe-compat-1" ing_id = "ing-compat-1" grp_id = "grp-compat-1" apply_catalog_change( enterprise_id, "recipe", recipe_id, "upsert", { "id": recipe_id, "name": "Compat Mix", "heads_per_trip": 100, "mixing_time": 10, "trip_percent": 100.0, }, 1, "r1", ) apply_catalog_change( enterprise_id, "ingredient", ing_id, "upsert", { "id": ing_id, "recipe_id": recipe_id, "name": "Corn", "amount": 12.5, "order": 1, "dry_matter": 88.0, }, 1, "i1", ) apply_catalog_change( enterprise_id, "unloading_group", grp_id, "upsert", { "id": grp_id, "recipe_id": recipe_id, "name": "Barn A", "distribution_type": "percent", "value": 50.0, "weight": 500.0, "order": 1, }, 1, "g1", ) resp = client.get(f"/api/recipes/{recipe_id}?enterprise_id={enterprise_id}", headers=headers) assert resp.status_code == 200 body = resp.json() assert body["name"] == "Compat Mix" assert len(body.get("ingredients") or []) == 1 assert body["ingredients"][0]["name"] == "Corn" assert len(body.get("unloading_groups") or body.get("unloadingGroups") or []) == 1 assert body["unloading_groups"][0]["name"] == "Barn A" def test_wesp_compat_recipe_calculate(client: TestClient): resp = client.post( "/api/recipes/calculate", json={ "ingredients": [{"weightPerHead": 10, "dryMatter": 88, "component_id": "c1"}], "headsCount": 100, "tripPercent": 100, "unloadingGroups": [{"distributionType": "percent", "value": 50}], }, ) assert resp.status_code == 200 body = resp.json() assert len(body.get("ingredients") or []) == 1 assert len(body.get("unloadingGroups") or []) == 1 def test_wesp_compat_recipe_put_persists_unloading_group(client: TestClient): enterprise_id = make_enterprise(client) login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"}) token = login.json()["access_token"] session = client.post( f"/api/v1/enterprise/enterprises/{enterprise_id}/session", headers={"Authorization": f"Bearer {token}"}, ) token = session.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} recipe_id = "recipe-put-1" apply_catalog_change( enterprise_id, "recipe", recipe_id, "upsert", {"id": recipe_id, "name": "Before", "heads_per_trip": 100, "mixing_time": 10, "trip_percent": 100.0}, 1, "r1", ) put = client.put( f"/api/recipes/{recipe_id}?enterprise_id={enterprise_id}", headers=headers, json={ "name": "After", "heads_count": 100, "mixing_time": 10, "trip_percent": 100, "dry_matter_locked": False, "unloading_link_broken": False, "ingredients": [], "unloading_groups": [ { "name": "Group A", "distribution_type": "percent", "value": 100, "weight": 500, "order": 1, } ], }, ) assert put.status_code == 200 got = client.get(f"/api/recipes/{recipe_id}?enterprise_id={enterprise_id}", headers=headers) assert got.status_code == 200 groups = got.json().get("unloading_groups") or [] assert len(groups) == 1 assert groups[0]["name"] == "Group A" def test_wesp_compat_analytics_endpoints(client: TestClient): enterprise_id = make_enterprise(client) login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"}) token = login.json()["access_token"] session = client.post( f"/api/v1/enterprise/enterprises/{enterprise_id}/session", headers={"Authorization": f"Bearer {token}"}, ) token = session.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} params = f"enterprise_id={enterprise_id}&date_from=2026-07-01&date_to=2026-07-15" finance = client.get(f"/api/analytics/finance?{params}", headers=headers) assert finance.status_code == 200 assert "overloadRub" in finance.json() plan_fact = client.get(f"/api/analytics/plan-fact?{params}", headers=headers) assert plan_fact.status_code == 200 assert plan_fact.json()["items"] == [] summary = client.get(f"/api/feed-quality/alerts/summary?{params}", headers=headers) assert summary.status_code == 200 assert "total" in summary.json() alerts = client.get(f"/api/feed-quality/alerts?{params}", headers=headers) assert alerts.status_code == 200 assert "items" in alerts.json() def test_wesp_compat_reports_list(client: TestClient): from app.modules.zootech.report_apply import apply_report_change enterprise_id = make_enterprise(client) login = client.post("/api/v1/auth/login", json={"email": "admin@compton.example", "password": "Admin1234"}) token = login.json()["access_token"] session = client.post( f"/api/v1/enterprise/enterprises/{enterprise_id}/session", headers={"Authorization": f"Bearer {token}"}, ) token = session.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} loading_id = "lr-test-1" apply_report_change( enterprise_id, "loading_report", loading_id, "upsert", { "recipe_id": "recipe-1", "recipe_name": "Test Mix", "start_time": datetime.now(UTC).isoformat(), "end_time": datetime.now(UTC).isoformat(), "total_weight": 1200.0, "dispenser_type": "dispenser", "components": [{"component_name": "Corn", "actual_weight": 100.0}], }, 1, "h1", ) apply_report_change( enterprise_id, "unloading_report", "ur-test-1", "upsert", { "loading_report_id": loading_id, "start_time": datetime.now(UTC).isoformat(), "total_weight": 1200.0, "total_unloaded_weight": 1100.0, "remaining_weight": 100.0, }, 1, "u1", ) resp = client.get(f"/api/reports?enterprise_id={enterprise_id}", headers=headers) assert resp.status_code == 200 body = resp.json() assert isinstance(body, list) assert len(body) == 1 assert body[0]["recipe_id"] == "recipe-1" assert body[0]["unloading_data"]["total_unloaded_weight"] == 1100.0 def test_apply_report_change_update_adds_components(client: TestClient): import json from app.core.database import session_scope from app.modules.zootech.report_apply import apply_report_change from app.modules.zootech.report_models import ZootechLoadingReport from sqlalchemy import select enterprise_id = make_enterprise(client) loading_id = "lr-test-components" apply_report_change( enterprise_id, "loading_report", loading_id, "upsert", { "recipe_id": "recipe-1", "recipe_name": "Test Mix", "start_time": datetime.now(UTC).isoformat(), "total_weight": 1200.0, }, 1, "h1", ) apply_report_change( enterprise_id, "loading_report", loading_id, "upsert", { "recipe_id": "recipe-1", "recipe_name": "Test Mix", "start_time": datetime.now(UTC).isoformat(), "total_weight": 1200.0, "components": [{"component_name": "Corn", "actual_weight": 100.0, "overload": 5.0}], }, 2, "h2", ) with session_scope() as db: row = db.scalar( select(ZootechLoadingReport).where( ZootechLoadingReport.enterprise_id == enterprise_id, ZootechLoadingReport.id == loading_id, ) ) assert row is not None payload = json.loads(row.payload_json or "{}") assert len(payload.get("components") or []) == 1 assert payload["components"][0]["component_name"] == "Corn" def test_apply_report_change_feed_alert(client: TestClient): from app.core.database import session_scope from app.modules.zootech.report_apply import apply_report_change from app.modules.zootech.report_models import ZootechFeedAlert from sqlalchemy import select enterprise_id = make_enterprise(client) alert_id = "fa-test-1" apply_report_change( enterprise_id, "feed_alert", alert_id, "upsert", { "event_type": "UNDERLOAD", "severity": "error", "loading_report_id": "lr-1", "recipe_id": "recipe-1", "recipe_name": "Mix", "detail": "Underload detected", "deviation_kg": -5.0, }, 1, "h1", ) with session_scope() as db: row = db.scalar( select(ZootechFeedAlert).where( ZootechFeedAlert.enterprise_id == enterprise_id, ZootechFeedAlert.id == alert_id, ) ) assert row is not None assert row.alert_type == "UNDERLOAD" assert row.message == "Underload detected" assert row.payload_json