"""Замена компонентов в плане на день.""" from __future__ import annotations import unittest from app import create_app, db from app.models import Component 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, seed_dispenser_period_recipes, ) from tests.helpers.zootech_test_helpers import ZootechTestConfig class DailyPlanReplacementTests(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) seed_dispenser_period_recipes() self.client.post( "/api/auth/login", json={"login": "zootech-test-admin", "password": "zootech-test-secret"}, ) 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() def tearDown(self) -> None: db.session.remove() db.drop_all() self.ctx.pop() def test_replace_ingredient_shows_in_plan_and_totals(self) -> None: replace_ingredient( E2E_DISP_RECIPE_1, "e2e-disp-ing-1", "e2e-disp-comp-alt", "2026-06-07", ) plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date="2026-06-07") trip = plan["periods"][0]["trips"][0] ing = trip["ingredients"][0] self.assertTrue(ing["replacedToday"]) self.assertIn("Alt Silo", ing["name"]) totals = {row["name"]: row["totalKg"] for row in plan["ingredientTotals"]} self.assertEqual(totals["Alt Silo"], 20.0) self.assertEqual(plan["ingredientGrandTotalKg"], 50.0) def test_replace_recalculates_by_dry_matter_when_locked(self) -> None: from app.models import Recipe recipe = db.session.get(Recipe, E2E_DISP_RECIPE_1) recipe.dry_matter_locked = True db.session.commit() replace_ingredient( E2E_DISP_RECIPE_1, "e2e-disp-ing-1", "e2e-disp-comp-alt", "2026-06-07", ) plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date="2026-06-07") ing = plan["periods"][0]["trips"][0]["ingredients"][0] self.assertEqual(ing["recalculationMode"], "dry_matter") self.assertAlmostEqual(ing["weightPerHead"], 1.57, places=2) self.assertAlmostEqual(ing["totalKg"], 31.4, places=1) totals = {row["name"]: row["totalKg"] for row in plan["ingredientTotals"]} self.assertAlmostEqual(totals["Alt Silo"], 31.4, places=1) self.assertAlmostEqual(plan["ingredientGrandTotalKg"], 61.4, places=1) def test_component_alternatives_api(self) -> None: resp = self.client.get( f"/api/daily-plan/component-alternatives?component_id={E2E_DISP_COMP}" ) self.assertEqual(resp.status_code, 200, resp.get_data(as_text=True)) body = resp.get_json() self.assertIn("similar", body) self.assertIn("results", body) self.assertTrue(body["results"]) def test_replace_component_in_all_plan_recipes(self) -> None: created = self.client.post( "/api/daily-plan/replacements/components", json={ "componentId": E2E_DISP_COMP, "replacementComponentId": "e2e-disp-comp-alt", "dispenserId": E2E_DISP_ID, "date": "2026-06-09", }, ) self.assertEqual(created.status_code, 200, created.get_data(as_text=True)) body = created.get_json() self.assertEqual(body.get("count"), 2) plan = build_daily_plan(dispenser_id=E2E_DISP_ID, plan_date="2026-06-09") replaced = 0 for period in plan["periods"]: for trip in period["trips"]: for ing in trip["ingredients"]: if ing.get("originalComponentId") == E2E_DISP_COMP: self.assertTrue(ing["replacedToday"]) self.assertIn("Alt Silo", ing["name"]) replaced += 1 self.assertEqual(replaced, 2) def test_post_and_delete_replacement(self) -> None: created = self.client.post( "/api/daily-plan/replacements/ingredients", json={ "recipeId": E2E_DISP_RECIPE_1, "ingredientId": "e2e-disp-ing-1", "replacementComponentId": "e2e-disp-comp-alt", "date": "2026-06-08", }, ) self.assertEqual(created.status_code, 200, created.get_data(as_text=True)) listed = self.client.get("/api/notifications?category=daily_plan").get_json() self.assertTrue(any("→" in x.get("title", "") for x in listed["items"])) deleted = self.client.delete( "/api/daily-plan/replacements/ingredients" f"?recipe_id={E2E_DISP_RECIPE_1}&ingredient_id=e2e-disp-ing-1&date=2026-06-08" ) self.assertEqual(deleted.status_code, 200) listed2 = self.client.get("/api/notifications?category=daily_plan").get_json() self.assertTrue(any("снова в плане" in x.get("title", "") for x in listed2["items"])) if __name__ == "__main__": unittest.main()