823 lines
28 KiB
Python
823 lines
28 KiB
Python
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_feed_dispenser_crud(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 = {"enterprise_id": enterprise_id}
|
|
|
|
create = client.post(
|
|
"/api/feed_dispensers",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Disp A", "farm": "Farm 1", "operator": "Op 1", "type": "dispenser"},
|
|
)
|
|
assert create.status_code == 200
|
|
dispenser_id = create.json()["id"]
|
|
|
|
listed = client.get("/api/feed_dispensers", headers=headers, params=params)
|
|
assert listed.status_code == 200
|
|
assert any(row["id"] == dispenser_id for row in listed.json())
|
|
|
|
update = client.put(
|
|
f"/api/feed_dispensers/{dispenser_id}",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Disp B", "farm": "Farm 2", "operator": "Op 2"},
|
|
)
|
|
assert update.status_code == 200
|
|
assert update.json().get("message")
|
|
|
|
period = client.post(
|
|
f"/api/feed_dispensers/{dispenser_id}/periods",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Morning"},
|
|
)
|
|
assert period.status_code == 200
|
|
period_id = period.json()["id"]
|
|
|
|
period_update = client.put(
|
|
f"/api/feed_dispensers/{dispenser_id}/periods/{period_id}",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Evening"},
|
|
)
|
|
assert period_update.status_code == 200
|
|
|
|
period_delete = client.delete(
|
|
f"/api/feed_dispensers/{dispenser_id}/periods/{period_id}",
|
|
headers=headers,
|
|
params=params,
|
|
)
|
|
assert period_delete.status_code == 200
|
|
|
|
delete = client.delete(
|
|
f"/api/feed_dispensers/{dispenser_id}",
|
|
headers=headers,
|
|
params=params,
|
|
)
|
|
assert delete.status_code == 200
|
|
|
|
|
|
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_component_feed_types(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/feed-types?enterprise_id={enterprise_id}", headers=headers)
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert isinstance(body.get("types"), list)
|
|
assert len(body["types"]) >= 4
|
|
assert body["types"][0]["value"]
|
|
|
|
|
|
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()
|
|
|
|
fq_settings = client.get(f"/api/feed-quality/settings?enterprise_id={enterprise_id}", headers=headers)
|
|
assert fq_settings.status_code == 200
|
|
fq_body = fq_settings.json()
|
|
assert "settings" in fq_body
|
|
assert "defaults" in fq_body
|
|
assert fq_body["settings"]["loading"]["enabled"] is True
|
|
|
|
fq_put = client.put(
|
|
f"/api/feed-quality/settings?enterprise_id={enterprise_id}",
|
|
headers=headers,
|
|
json={"settings": {"loading": {"warning_pct": 12}}},
|
|
)
|
|
assert fq_put.status_code == 200
|
|
assert fq_put.json()["saved"] is True
|
|
assert fq_put.json()["settings"]["loading"]["warning_pct"] == 12.0
|
|
|
|
|
|
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
|
|
|
|
|
|
def test_wesp_compat_transfer_recipe_between_periods(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 = {"enterprise_id": enterprise_id}
|
|
|
|
dispenser = client.post(
|
|
"/api/feed_dispensers",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Disp transfer", "farm": "Farm", "operator": "Op", "type": "dispenser"},
|
|
)
|
|
assert dispenser.status_code == 200
|
|
dispenser_id = dispenser.json()["id"]
|
|
|
|
period_a = client.post(
|
|
f"/api/feed_dispensers/{dispenser_id}/periods",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Morning"},
|
|
)
|
|
period_b = client.post(
|
|
f"/api/feed_dispensers/{dispenser_id}/periods",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Evening"},
|
|
)
|
|
assert period_a.status_code == 200
|
|
assert period_b.status_code == 200
|
|
period_a_id = period_a.json()["id"]
|
|
period_b_id = period_b.json()["id"]
|
|
|
|
recipe = client.post(
|
|
f"/api/periods/{period_a_id}/recipes",
|
|
headers=headers,
|
|
params=params,
|
|
json={
|
|
"name": "Trip A",
|
|
"heads_count": 100,
|
|
"mixing_time": 10,
|
|
"trip_percent": 100,
|
|
"ingredients": [],
|
|
"unloading_groups": [{"name": "G1", "distribution_type": "percent", "value": 100}],
|
|
},
|
|
)
|
|
assert recipe.status_code == 200
|
|
recipe_id = recipe.json()["id"]
|
|
|
|
transfer = client.post(
|
|
f"/api/feed_dispensers/{dispenser_id}/periods/{period_b_id}/recipes/{recipe_id}/transfer",
|
|
headers=headers,
|
|
params=params,
|
|
json={
|
|
"from_dispenser_id": dispenser_id,
|
|
"from_period_id": period_a_id,
|
|
"to_index": 0,
|
|
},
|
|
)
|
|
assert transfer.status_code == 200
|
|
assert transfer.json().get("success") is True
|
|
|
|
src = client.get(f"/api/periods/{period_a_id}/recipes", headers=headers, params=params)
|
|
dst = client.get(f"/api/periods/{period_b_id}/recipes", headers=headers, params=params)
|
|
assert src.status_code == 200
|
|
assert dst.status_code == 200
|
|
assert src.json() == []
|
|
assert len(dst.json()) == 1
|
|
assert dst.json()[0]["id"] == recipe_id
|
|
|
|
|
|
def test_wesp_compat_move_recipe_within_period(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 = {"enterprise_id": enterprise_id}
|
|
|
|
dispenser = client.post(
|
|
"/api/feed_dispensers",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Disp move", "farm": "Farm", "operator": "Op", "type": "dispenser"},
|
|
)
|
|
assert dispenser.status_code == 200
|
|
dispenser_id = dispenser.json()["id"]
|
|
|
|
period = client.post(
|
|
f"/api/feed_dispensers/{dispenser_id}/periods",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Morning"},
|
|
)
|
|
assert period.status_code == 200
|
|
period_id = period.json()["id"]
|
|
|
|
def _create_recipe(name: str) -> str:
|
|
resp = client.post(
|
|
f"/api/periods/{period_id}/recipes",
|
|
headers=headers,
|
|
params=params,
|
|
json={
|
|
"name": name,
|
|
"heads_count": 100,
|
|
"mixing_time": 10,
|
|
"trip_percent": 100,
|
|
"ingredients": [],
|
|
"unloading_groups": [{"name": "G1", "distribution_type": "percent", "value": 100}],
|
|
},
|
|
)
|
|
assert resp.status_code == 200
|
|
return resp.json()["id"]
|
|
|
|
first_id = _create_recipe("First")
|
|
second_id = _create_recipe("Second")
|
|
|
|
move = client.put(
|
|
f"/api/recipes/{first_id}/move",
|
|
headers=headers,
|
|
params=params,
|
|
json={"from_index": 0, "to_index": 1, "period_id": period_id},
|
|
)
|
|
assert move.status_code == 200
|
|
assert move.json().get("success") is True
|
|
|
|
listed = client.get(f"/api/periods/{period_id}/recipes", headers=headers, params=params)
|
|
assert listed.status_code == 200
|
|
ids = [row["id"] for row in listed.json()]
|
|
assert ids == [second_id, first_id]
|
|
|
|
|
|
def test_wesp_compat_sklad(client: TestClient):
|
|
from datetime import date, timedelta
|
|
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}"}
|
|
params = {"enterprise_id": enterprise_id}
|
|
|
|
component = client.post(
|
|
"/api/components",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Corn", "type": "grain", "dryMatter": 88, "protein": 9, "energy": 13, "price": 15},
|
|
)
|
|
assert component.status_code == 200
|
|
component_id = component.json()["id"]
|
|
|
|
today = date.today()
|
|
date_from = (today - timedelta(days=30)).isoformat()
|
|
date_to = today.isoformat()
|
|
|
|
empty = client.get("/api/sklad", headers=headers, params={**params, "date_from": date_from, "date_to": date_to})
|
|
assert empty.status_code == 200
|
|
assert empty.json()["items"] == []
|
|
|
|
apply_report_change(
|
|
enterprise_id,
|
|
"loading_report",
|
|
"lr-sklad-1",
|
|
"upsert",
|
|
{
|
|
"recipe_id": "recipe-1",
|
|
"recipe_name": "Mix",
|
|
"start_time": datetime.now(UTC).isoformat(),
|
|
"total_weight": 500.0,
|
|
"components": [
|
|
{"component_id": component_id, "component_name": "Corn", "actual_weight": 42.5},
|
|
],
|
|
},
|
|
1,
|
|
"h1",
|
|
)
|
|
|
|
report_only = client.get("/api/sklad", headers=headers, params={**params, "date_from": date_from, "date_to": date_to})
|
|
assert report_only.status_code == 200
|
|
items = report_only.json()["items"]
|
|
assert len(items) == 1
|
|
assert items[0]["component_id"] == component_id
|
|
assert items[0]["has_stock"] is False
|
|
assert items[0]["consumed_total_kg"] == 42.5
|
|
|
|
added = client.post("/api/sklad/add", headers=headers, params=params, json={"component_id": component_id})
|
|
assert added.status_code == 200
|
|
assert added.json()["success"] is True
|
|
|
|
updated = client.post(
|
|
f"/api/sklad/{component_id}",
|
|
headers=headers,
|
|
params=params,
|
|
json={"total_kg": 1000.0, "inflow_kg": 50.0},
|
|
)
|
|
assert updated.status_code == 200
|
|
assert updated.json()["success"] is True
|
|
|
|
stocked = client.get("/api/sklad", headers=headers, params={**params, "date_from": date_from, "date_to": date_to})
|
|
assert stocked.status_code == 200
|
|
stocked_items = stocked.json()["items"]
|
|
assert len(stocked_items) == 1
|
|
assert stocked_items[0]["has_stock"] is True
|
|
assert stocked_items[0]["remaining_kg"] == 1007.5
|
|
|
|
chart = client.get(
|
|
f"/api/sklad/{component_id}/chart",
|
|
headers=headers,
|
|
params={**params, "date_from": date_from, "date_to": date_to},
|
|
)
|
|
assert chart.status_code == 200
|
|
assert isinstance(chart.json().get("points"), list)
|
|
assert len(chart.json()["points"]) > 0
|
|
|
|
removed = client.delete(f"/api/sklad/{component_id}", headers=headers, params=params)
|
|
assert removed.status_code == 200
|
|
assert removed.json()["success"] is True
|
|
|
|
|
|
def test_wesp_compat_feed_accounting(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}"}
|
|
params = {"enterprise_id": enterprise_id}
|
|
|
|
org = client.get("/api/feed-accounting/org-settings", headers=headers, params=params)
|
|
assert org.status_code == 200
|
|
body = org.json()
|
|
assert "organization_name" in body
|
|
assert "available_farms" in body
|
|
assert body["signatures"]["zootechnician"] is False
|
|
|
|
saved = client.put(
|
|
"/api/feed-accounting/org-settings",
|
|
headers=headers,
|
|
params=params,
|
|
json={
|
|
"organization_name": "Test Farm LLC",
|
|
"zootechnician": "Ivanov I.I.",
|
|
"warehouse_keeper": "Petrov P.P.",
|
|
},
|
|
)
|
|
assert saved.status_code == 200
|
|
assert saved.json().get("success") is True
|
|
|
|
apply_report_change(
|
|
enterprise_id,
|
|
"loading_report",
|
|
"lr-fa-1",
|
|
"upsert",
|
|
{
|
|
"recipe_id": "recipe-1",
|
|
"recipe_name": "Mix",
|
|
"start_time": "2026-06-15T10:00:00+00:00",
|
|
"total_weight": 500.0,
|
|
"components": [{"component_name": "Corn", "actual_weight": 25.0}],
|
|
},
|
|
1,
|
|
"h1",
|
|
)
|
|
|
|
preview = client.get(
|
|
"/api/feed-accounting/sp20-preview",
|
|
headers=headers,
|
|
params={**params, "month": "2026-06"},
|
|
)
|
|
assert preview.status_code == 200
|
|
preview_body = preview.json()
|
|
assert preview_body["lines_count"] == 1
|
|
assert preview_body["total_kg"] == 25.0
|
|
assert preview_body["has_requisites"] is True
|
|
|
|
xlsx = client.get(
|
|
"/api/feed-accounting/consumption.xlsx",
|
|
headers=headers,
|
|
params={**params, "date_from": "2026-06-01", "date_to": "2026-06-30"},
|
|
)
|
|
assert xlsx.status_code == 200
|
|
assert "spreadsheetml" in (xlsx.headers.get("content-type") or "")
|
|
assert len(xlsx.content) > 100
|
|
|
|
pdf = client.get(
|
|
"/api/feed-accounting/sp20.pdf",
|
|
headers=headers,
|
|
params={**params, "month": "2026-06"},
|
|
)
|
|
assert pdf.status_code == 200
|
|
assert (pdf.headers.get("content-type") or "").startswith("application/pdf")
|
|
assert pdf.content[:4] == b"%PDF"
|
|
|
|
|
|
def test_wesp_compat_mill_recipes_list_after_create(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 = {"enterprise_id": enterprise_id}
|
|
|
|
mill = client.post(
|
|
"/api/feed_dispensers",
|
|
headers=headers,
|
|
params=params,
|
|
json={"name": "Mill 1", "farm": "Farm 1", "operator": "Op 1", "type": "mill"},
|
|
)
|
|
assert mill.status_code == 200
|
|
mill_id = mill.json()["id"]
|
|
|
|
empty = client.get(f"/api/feed_dispensers/{mill_id}/recipes", headers=headers, params=params)
|
|
assert empty.status_code == 200
|
|
assert empty.json() == []
|
|
|
|
created = client.post(
|
|
"/api/recipes",
|
|
headers=headers,
|
|
params=params,
|
|
json={
|
|
"name": "Mill Mix",
|
|
"heads_count": 50,
|
|
"mixing_time": 5,
|
|
"trip_percent": 100,
|
|
"dry_matter_locked": False,
|
|
"unloading_link_broken": False,
|
|
"dispenser_id": mill_id,
|
|
"ingredients": [],
|
|
"unloading_groups": [],
|
|
},
|
|
)
|
|
assert created.status_code == 200
|
|
recipe_id = created.json()["id"]
|
|
|
|
listed = client.get(f"/api/feed_dispensers/{mill_id}/recipes", headers=headers, params=params)
|
|
assert listed.status_code == 200
|
|
recipes = listed.json()
|
|
assert len(recipes) == 1
|
|
assert recipes[0]["id"] == recipe_id
|
|
assert recipes[0]["name"] == "Mill Mix"
|
|
|