189 lines
7.2 KiB
Python
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()
|