Files
ske087 bff6d70b17 Initial commit: aplicatie de cantarire si etichetare
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
2026-10-18 23:38:45 +03:00

114 lines
3.7 KiB
Python

"""Administrarea campurilor variabile proprii (custom.*).
Un camp propriu este un text care poate include alte campuri fixe, de exemplu
`Tel. {{company.phone}}`. La randarea etichetei, textul este expandat cu
valorile reale ale pachetului.
"""
from __future__ import annotations
from flask import (Blueprint, jsonify, flash, redirect, render_template, request, url_for)
from .. import designer
from ..repositories import (
ValidationError,
count_label_field_usage,
create_label_field,
delete_label_field,
get_label_field,
list_label_fields,
update_label_field,
)
bp = Blueprint("fields", __name__, url_prefix="/campuri")
def _form_context(field: dict, *, is_new: bool) -> dict:
return {
"field": field,
"is_new": is_new,
"catalog": [
{"key": key, "label": label, "sample": sample}
for key, label, sample in designer.FIELD_CATALOG
],
"max_label": designer.MAX_CUSTOM_LABEL,
"max_template": designer.MAX_CUSTOM_TEMPLATE,
}
@bp.get("")
def index():
rows = list_label_fields(include_inactive=True)
for row in rows:
row["usage"] = count_label_field_usage(row["key"])
row["preview"] = designer.expand_custom(
row.get("template") or "",
designer.apply_custom_fields(designer.demo_fields(), [row]),
)
return render_template("fields/list.html", fields=rows)
@bp.route("/nou", methods=["GET", "POST"])
def create():
if request.method == "POST":
data = dict(request.form)
try:
designer.validate_custom_field(data)
create_label_field(data)
flash("Campul a fost adaugat.", "success")
return redirect(url_for("fields.index"))
except ValidationError as exc:
flash(str(exc), "error")
return render_template("fields/form.html", **_form_context(data, is_new=True))
return render_template("fields/form.html",
**_form_context({"active": 1}, is_new=True))
@bp.route("/<int:field_id>/editare", methods=["GET", "POST"])
def edit(field_id: int):
field = get_label_field(field_id)
if field is None:
flash("Campul nu exista.", "error")
return redirect(url_for("fields.index"))
if request.method == "POST":
data = dict(request.form)
try:
designer.validate_custom_field(data)
update_label_field(field_id, data)
flash("Campul a fost actualizat.", "success")
return redirect(url_for("fields.index"))
except ValidationError as exc:
flash(str(exc), "error")
field = {**field, **data}
return render_template("fields/form.html", **_form_context(field, is_new=False))
@bp.post("/<int:field_id>/stergere")
def remove(field_id: int):
field = get_label_field(field_id)
if field is None:
flash("Campul nu exista.", "error")
return redirect(url_for("fields.index"))
delete_label_field(field_id)
usage = count_label_field_usage(field["key"])
if usage:
flash(
f"Campul {field['label']} a fost sters. Atentie: {usage} sabloane il folosesc "
"si il vor afisa gol pana il inlocuiesti.",
"error",
)
else:
flash(f"Campul {field['label']} a fost sters.", "success")
return redirect(url_for("fields.index"))
@bp.get("/previzualizare")
def preview():
"""Valoarea rezultata pentru textul introdus, cu datele demo."""
text = (request.args.get("template") or "")[: designer.MAX_CUSTOM_TEMPLATE * 2]
errors = designer.custom_template_errors(text)
if errors:
return jsonify({"ok": False, "error": errors[0]})
demo = designer.demo_fields()
return jsonify({"ok": True, "value": designer.expand_custom(text, demo)})