77 lines
3.0 KiB
Python
77 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from app import db
|
|
from app.lab.commands.write_audit import write_audit
|
|
from app.lab.db_guard import retry_locked
|
|
from app.lab.loaders.execution_loader import load_execution
|
|
from app.lab.loaders.ration_loader import load_ration
|
|
from app.models import Component, Ingredient, Recipe
|
|
from app.models.base import default_uuid
|
|
from app.services.recipe_update_service import _enqueue_recipe_children_sync
|
|
|
|
|
|
@retry_locked
|
|
def apply_from_master(recipe_id: str, user_id: str = "system") -> dict:
|
|
snapshot = load_ration(recipe_id)
|
|
if not snapshot.exists:
|
|
raise LookupError("Мастер рациона не найден")
|
|
recipe = Recipe.query.filter_by(id=recipe_id, is_deleted=False).first()
|
|
if recipe is None:
|
|
raise LookupError("Рецепт не найден")
|
|
heads = int(recipe.heads_per_trip or 0)
|
|
if heads <= 0:
|
|
raise ValueError("Количество голов должно быть > 0")
|
|
|
|
execution = load_execution(recipe_id)
|
|
by_component = {str(l.component_id): l for l in execution.lines if l.component_id}
|
|
master_by_component = {
|
|
str(l.component_id): l
|
|
for l in snapshot.lines
|
|
if l.component_id and l.in_ration
|
|
}
|
|
|
|
touched_ingredient_ids: list[str] = []
|
|
for comp_id, master_line in master_by_component.items():
|
|
daily_kg = float(master_line.daily_kg or 0)
|
|
weight_per_head = daily_kg / heads
|
|
ing = by_component.get(comp_id)
|
|
comp = Component.query.get(comp_id)
|
|
dm_pct = float(comp.dry_matter or 0) if comp else float(master_line.dry_matter or 0)
|
|
if ing:
|
|
row = Ingredient.query.get(ing.ingredient_id)
|
|
if row is None:
|
|
continue
|
|
row.weight_per_head = weight_per_head
|
|
row.amount = weight_per_head
|
|
row.dry_matter = dm_pct
|
|
row.updated_by = user_id
|
|
touched_ingredient_ids.append(row.id)
|
|
else:
|
|
comp_name = master_line.ingredient_name or comp_id
|
|
new_ing = Ingredient(
|
|
id=default_uuid(),
|
|
recipe_id=recipe_id,
|
|
component_id=comp_id,
|
|
name=comp_name[:100],
|
|
amount=weight_per_head,
|
|
weight_per_head=weight_per_head,
|
|
dry_matter=dm_pct,
|
|
order=len(touched_ingredient_ids),
|
|
created_by=user_id,
|
|
updated_by=user_id,
|
|
)
|
|
db.session.add(new_ing)
|
|
touched_ingredient_ids.append(new_ing.id)
|
|
|
|
master_ids = set(master_by_component)
|
|
for line in execution.lines:
|
|
if line.component_id and str(line.component_id) not in master_ids:
|
|
row = Ingredient.query.get(line.ingredient_id)
|
|
if row and not row.is_deleted:
|
|
row.soft_delete(user_id)
|
|
|
|
_enqueue_recipe_children_sync(recipe_id)
|
|
write_audit("APPLY_FROM_MASTER", "lab_recipe_ration", recipe_id, user_id)
|
|
db.session.commit()
|
|
return {"recipeId": recipe_id, "ingredientsUpdated": len(touched_ingredient_ids)}
|