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

158 lines
5.5 KiB
Python

"""Legatura produs finit -> sablon de eticheta."""
from __future__ import annotations
import pytest
from cantarire.repositories import ValidationError
def _template_id(client, name: str) -> int:
response = client.post("/sabloane/designer/nou", data={"name": name},
follow_redirects=True)
assert response.status_code == 200
with client.application.app_context():
from cantarire.repositories import list_templates
return [t for t in list_templates() if t["name"] == name][0]["id"]
def _full_recipe(client, recipe_id: int) -> dict:
with client.application.app_context():
from cantarire.repositories import get_recipe
return get_recipe(recipe_id)
def _recipe_form(recipe: dict, template_id: str = "", code: str = "PF-001") -> dict:
components = recipe.get("components") or []
data = {
"code": code,
"name": recipe["name"],
"unit": "g",
"template_id": template_id,
"active": "on",
}
for slot, item in enumerate(components, start=1):
data[f"component_{slot}_id"] = str(item["component_id"])
data[f"component_{slot}_target"] = "100"
return data
def _bind(client, recipe_id: int, template_id) -> None:
"""Salveaza produsul finit cu sablonul dat (componentele raman neschimbate)."""
full = _full_recipe(client, recipe_id)
response = client.post(f"/retete/{recipe_id}/editare",
data=_recipe_form(full, str(template_id or "")))
assert response.status_code == 302, response.get_data(as_text=True)
def test_recipe_form_lists_templates(client, fasole):
page = client.get("/retete/nou")
html = page.get_data(as_text=True)
assert 'name="template_id"' in html
assert "Șablon de etichetă" in html
assert "Eticheta produsului" in html
def test_recipe_can_be_bound_to_template(client, recipe):
template_id = _template_id(client, "Eticheta PF")
_bind(client, recipe["id"], template_id)
with client.application.app_context():
from cantarire.repositories import get_recipe
stored = get_recipe(recipe["id"])
assert stored["template_id"] == template_id
assert stored["template_name"] == "Eticheta PF"
def test_recipe_without_template_stays_empty(client, recipe):
_bind(client, recipe["id"], "")
with client.application.app_context():
from cantarire.repositories import get_recipe
stored = get_recipe(recipe["id"])
assert stored["template_id"] is None
assert stored["template_name"] is None
def test_unknown_template_is_rejected(client, recipe):
full = _full_recipe(client, recipe["id"])
response = client.post(f"/retete/{recipe['id']}/editare",
data=_recipe_form(full, "9999"), follow_redirects=True)
assert "nu exista" in response.get_data(as_text=True)
with client.application.app_context():
from cantarire.repositories import get_recipe
assert get_recipe(recipe["id"])["template_id"] is None
def test_recipe_list_shows_template_name(client, recipe):
template_id = _template_id(client, "Eticheta listă")
_bind(client, recipe["id"], template_id)
html = client.get("/retete").get_data(as_text=True)
assert "Eticheta listă" in html
assert "Etichetă" in html # coloana noua
def test_package_uses_recipe_template(client, recipe):
"""La pornirea unui pachet, sablonul produsului finit are prioritate."""
bound = _template_id(client, "Eticheta legata")
other = _template_id(client, "Eticheta implicita")
_bind(client, recipe["id"], bound)
client.post("/setari", data={
"company_name": "Test SRL", "default_template_id": str(other),
"active_scale_profile": "", "auto_print": "",
})
response = client.post("/api/packages", json={
"recipe_id": recipe["id"], "operator": "Ana", "lot": "L1",
})
assert response.status_code == 201, response.get_json()
package_id = response.get_json()["package"]["id"]
with client.application.app_context():
from cantarire.repositories import get_package
assert get_package(package_id)["template_id"] == bound
def test_explicit_template_still_wins(client, recipe):
bound = _template_id(client, "Eticheta legata 2")
explicit = _template_id(client, "Eticheta explicita")
_bind(client, recipe["id"], bound)
response = client.post("/api/packages", json={
"recipe_id": recipe["id"], "template_id": explicit,
})
assert response.status_code == 201
package_id = response.get_json()["package"]["id"]
with client.application.app_context():
from cantarire.repositories import get_package
assert get_package(package_id)["template_id"] == explicit
def test_template_bound_to_active_recipe_cannot_be_deleted(client, recipe):
template_id = _template_id(client, "Eticheta protejata")
_bind(client, recipe["id"], template_id)
with client.application.app_context():
from cantarire.repositories import delete_template
with pytest.raises(ValidationError) as excinfo:
delete_template(template_id)
assert "produse finite active" in str(excinfo.value)
def test_template_can_be_deleted_after_recipe_detached(client, recipe):
template_id = _template_id(client, "Eticheta detasata")
_bind(client, recipe["id"], template_id)
_bind(client, recipe["id"], "")
with client.application.app_context():
from cantarire.repositories import delete_template, get_template
delete_template(template_id)
assert get_template(template_id) is None