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
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user