Files
site/WESP_REL/tests/test_daily_plan_adjustments.py
T
2026-07-17 12:57:18 +03:00

189 lines
7.2 KiB
Python

"""Временная правка СВ% компонентов в плане на день."""
from __future__ import annotations
import unittest
from app import create_app, db
from app.models import Component, Ingredient, Recipe
from app.services.daily_plan.adjustments import (
adjust_component_norm,
get_component_adjustment_map,
list_component_norms_for_plan,
undo_component_norm_adjustment,
)
from app.services.daily_plan.builder import build_daily_plan
from app.services.daily_plan.replacements import replace_ingredient
from app.services.setup_state import mark_setup_complete
from tests.helpers.dispenser_recipe_fixtures import (
E2E_DISP_COMP,
E2E_DISP_ID,
E2E_DISP_RECIPE_1,
E2E_DISP_RECIPE_2,
seed_dispenser_period_recipes,
)
from tests.helpers.zootech_test_helpers import ZootechTestConfig
class DailyPlanAdjustmentsTests(unittest.TestCase):
PLAN_DATE = "2026-06-07"
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)
seed_dispenser_period_recipes()
alt = Component(
id="e2e-disp-comp-alt",
name="Alt Silo",
type="silage",
dry_matter=35.0,
protein=3.0,
energy=6.0,
price=0.0,
)
db.session.add(alt)
db.session.commit()
self.client.post(
"/api/auth/login",
json={"login": "zootech-test-admin", "password": "zootech-test-secret"},
)
def tearDown(self) -> None:
db.session.remove()
db.drop_all()
self.ctx.pop()
def test_adjustment_applies_to_all_trips_with_component(self) -> None:
adjust_component_norm(
E2E_DISP_COMP,
self.PLAN_DATE,
dry_matter=50.0,
)
plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date=self.PLAN_DATE)
trips = plan["periods"][0]["trips"]
self.assertEqual(len(trips), 2)
for trip in trips:
ing = trip["ingredients"][0]
self.assertTrue(ing["adjustedToday"])
self.assertEqual(ing["dryMatterPct"], 50.0)
master_wph = 1.0 if trip["recipeId"] == E2E_DISP_RECIPE_1 else 2.0
self.assertEqual(ing["weightPerHead"], master_wph)
self.assertAlmostEqual(ing["dryMatterPerHead"], master_wph * 0.5, places=4)
self.assertEqual(ing["originalWeightPerHead"], master_wph)
def test_master_ingredient_unchanged_after_adjustment(self) -> None:
adjust_component_norm(
E2E_DISP_COMP,
self.PLAN_DATE,
dry_matter=45.0,
)
ing = db.session.get(Ingredient, "e2e-disp-ing-1")
self.assertEqual(float(ing.weight_per_head), 1.0)
master = self.client.get(f"/api/recipes/{E2E_DISP_RECIPE_1}").get_json()
self.assertEqual(master["ingredients"][0]["weightPerHead"], 1.0)
self.assertNotIn("adjustedToday", master["ingredients"][0])
overlay = self.client.get(
f"/api/recipes/{E2E_DISP_RECIPE_1}?date={self.PLAN_DATE}"
).get_json()
self.assertTrue(overlay["ingredients"][0]["adjustedToday"])
self.assertEqual(overlay["ingredients"][0]["weightPerHead"], 1.0)
self.assertEqual(overlay["ingredients"][0]["dry_matter"], 45.0)
self.assertAlmostEqual(overlay["ingredients"][0]["dry_matter_per_head"], 0.45, places=4)
self.assertEqual(overlay["ingredients"][0]["originalWeightPerHead"], 1.0)
def test_component_norms_api_and_undo(self) -> None:
created = self.client.post(
"/api/daily-plan/adjustments/components",
json={
"componentId": E2E_DISP_COMP,
"dryMatter": 40.0,
"dryMatterLocked": True,
"date": self.PLAN_DATE,
"duration": "today",
},
)
self.assertEqual(created.status_code, 200, created.get_data(as_text=True))
listed = self.client.get(
f"/api/daily-plan/component-norms?dispenser_id={E2E_DISP_ID}&date={self.PLAN_DATE}"
).get_json()
self.assertEqual(len(listed["items"]), 1)
item = listed["items"][0]
self.assertEqual(item["componentId"], E2E_DISP_COMP)
self.assertTrue(item["adjustedToday"])
self.assertEqual(item["planDryMatterPct"], 40.0)
self.assertTrue(item["planDryMatterLocked"])
self.assertEqual(item["usageCount"], 2)
deleted = self.client.delete(
f"/api/daily-plan/adjustments/components?component_id={E2E_DISP_COMP}&date={self.PLAN_DATE}"
)
self.assertEqual(deleted.status_code, 200)
self.assertEqual(get_component_adjustment_map(self.PLAN_DATE), {})
plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date=self.PLAN_DATE)
ing = plan["periods"][0]["trips"][0]["ingredients"][0]
self.assertFalse(ing["adjustedToday"])
def test_adjustment_before_replacement_on_same_ingredient(self) -> None:
adjust_component_norm(
E2E_DISP_COMP,
self.PLAN_DATE,
dry_matter=50.0,
)
replace_ingredient(
E2E_DISP_RECIPE_1,
"e2e-disp-ing-1",
"e2e-disp-comp-alt",
self.PLAN_DATE,
)
plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date=self.PLAN_DATE)
ing = plan["periods"][0]["trips"][0]["ingredients"][0]
self.assertTrue(ing["replacedToday"])
self.assertTrue(ing["adjustedToday"])
self.assertEqual(ing["weightPerHead"], 1.0)
self.assertEqual(ing["dryMatterPct"], 35.0)
def test_locked_mode_adjustment_keeps_dry_matter_per_head(self) -> None:
recipe = db.session.get(Recipe, E2E_DISP_RECIPE_1)
self.assertIsNotNone(recipe)
recipe.dry_matter_locked = True
db.session.commit()
adjust_component_norm(
E2E_DISP_COMP,
self.PLAN_DATE,
dry_matter=40.0,
)
plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date=self.PLAN_DATE)
ing = plan["periods"][0]["trips"][0]["ingredients"][0]
self.assertAlmostEqual(ing["dryMatterPerHead"], 0.55, places=4)
self.assertAlmostEqual(ing["weightPerHead"], 1.38, places=2)
self.assertEqual(ing["dryMatterPct"], 40.0)
def test_undo_service(self) -> None:
adjust_component_norm(E2E_DISP_COMP, self.PLAN_DATE, dry_matter=42.0)
self.assertTrue(
undo_component_norm_adjustment(E2E_DISP_COMP, self.PLAN_DATE)
)
self.assertEqual(get_component_adjustment_map(self.PLAN_DATE), {})
def test_list_component_norms_for_plan(self) -> None:
rows = list_component_norms_for_plan(self.PLAN_DATE, dispenser_id=E2E_DISP_ID)
self.assertEqual(len(rows), 1)
self.assertEqual(rows[0]["usageCount"], 2)
adjust_component_norm(E2E_DISP_COMP, self.PLAN_DATE, dry_matter=48.0)
rows2 = list_component_norms_for_plan(self.PLAN_DATE, dispenser_id=E2E_DISP_ID)
self.assertTrue(rows2[0]["adjustedToday"])
self.assertEqual(rows2[0]["planDryMatterPct"], 48.0)
if __name__ == "__main__":
unittest.main()