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
158 lines
5.5 KiB
Python
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
|