bff6d70b17
Aplicatie Flask + SQLite pentru productie alimentara: cantarirea componentelor unui produs finit si tiparirea etichetei prin QZ Tray. - produse finite (retete) formate din componente cantarite - designer vizual de etichete (mm/px, zona sigura, campuri variabile, cod QR cu continut din mai multe bucati) - legare produs finit -> sablon de eticheta - navigare pe doua taburi: Cantarire / Setari - administrare: componente, produse finite, sabloane, campuri, istoric - backup, resetare baza de date, checklist de pregatire, /healthz - CLI de administrare si serviciu systemd pentru productie - vendor/syncpulse-scale-bridge (GPL-3.0-or-later) pentru citirea cantarului
86 lines
2.7 KiB
Python
86 lines
2.7 KiB
Python
"""Administrarea produselor finite (retetelor)."""
|
|
from __future__ import annotations
|
|
|
|
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
|
|
|
from ..repositories import (
|
|
MAX_RECIPE_COMPONENTS,
|
|
ValidationError,
|
|
create_recipe,
|
|
delete_recipe,
|
|
get_recipe,
|
|
list_components,
|
|
list_recipes,
|
|
list_templates,
|
|
update_recipe,
|
|
)
|
|
|
|
bp = Blueprint("recipes", __name__, url_prefix="/retete")
|
|
|
|
|
|
def _slot_rows(source: dict | None) -> list[dict]:
|
|
source = source or {}
|
|
components = source.get("components") or []
|
|
rows = []
|
|
for slot in range(1, MAX_RECIPE_COMPONENTS + 1):
|
|
existing = next((c for c in components if c.get("position") == slot), None)
|
|
rows.append({
|
|
"slot": slot,
|
|
"component_id": existing.get("component_id") if existing else source.get(f"component_{slot}_id", ""),
|
|
"target": existing.get("target_weight") if existing else source.get(f"component_{slot}_target", ""),
|
|
})
|
|
return rows
|
|
|
|
|
|
def _render(template, is_new: bool, recipe: dict | None = None):
|
|
return render_template(
|
|
template,
|
|
recipe=recipe or {},
|
|
is_new=is_new,
|
|
components=list_components(),
|
|
templates=list_templates(),
|
|
slot_rows=_slot_rows(recipe),
|
|
)
|
|
|
|
|
|
@bp.get("")
|
|
def index():
|
|
return render_template("recipes/list.html", recipes=list_recipes(include_inactive=True))
|
|
|
|
|
|
@bp.route("/nou", methods=["GET", "POST"])
|
|
def create():
|
|
if request.method == "POST":
|
|
try:
|
|
create_recipe(request.form)
|
|
flash("Produsul finit a fost adaugat.", "success")
|
|
return redirect(url_for("recipes.index"))
|
|
except ValidationError as exc:
|
|
flash(str(exc), "error")
|
|
return _render("recipes/form.html", True, dict(request.form) if request.method == "POST" else {})
|
|
|
|
|
|
@bp.route("/<int:recipe_id>/editare", methods=["GET", "POST"])
|
|
def edit(recipe_id: int):
|
|
recipe = get_recipe(recipe_id)
|
|
if recipe is None:
|
|
flash("Produsul finit nu exista.", "error")
|
|
return redirect(url_for("recipes.index"))
|
|
|
|
if request.method == "POST":
|
|
try:
|
|
update_recipe(recipe_id, request.form)
|
|
flash("Produsul finit a fost actualizat.", "success")
|
|
return redirect(url_for("recipes.index"))
|
|
except ValidationError as exc:
|
|
flash(str(exc), "error")
|
|
recipe = {**recipe, **dict(request.form)}
|
|
return _render("recipes/form.html", False, recipe)
|
|
|
|
|
|
@bp.post("/<int:recipe_id>/stergere")
|
|
def remove(recipe_id: int):
|
|
delete_recipe(recipe_id)
|
|
flash("Produsul finit a fost sters sau dezactivat (daca are pachete in istoric).", "success")
|
|
return redirect(url_for("recipes.index"))
|