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,190 @@
|
||||
"""Campuri variabile proprii (custom.*): CRUD, validare si randare pe eticheta."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from cantarire import designer
|
||||
|
||||
|
||||
def _create_field(client, label="Contact firmă", template="Tel. {{company.phone}}",
|
||||
active="on"):
|
||||
return client.post("/campuri/nou", data={
|
||||
"label": label, "template": template, "active": active,
|
||||
}, follow_redirects=True)
|
||||
|
||||
|
||||
def test_fields_page_and_nav(client):
|
||||
page = client.get("/campuri")
|
||||
assert page.status_code == 200
|
||||
html = page.get_data(as_text=True)
|
||||
assert "Câmpuri variabile" in html
|
||||
assert 'href="/campuri/nou"' in html
|
||||
# link in subnavigatia din Setari, nu in bara principala
|
||||
flat = " ".join(html.split())
|
||||
assert 'href="/campuri"' in flat
|
||||
assert ">Câmpuri</a>" in flat
|
||||
|
||||
|
||||
def test_create_field_generates_key_and_shows_preview(client):
|
||||
_create_field(client)
|
||||
html = client.get("/campuri").get_data(as_text=True)
|
||||
assert "Contact firmă" in html
|
||||
assert "custom.contact_firma" in html
|
||||
assert "Tel. 0700 000 000" in html # rezultatul demo
|
||||
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import get_label_field_by_key
|
||||
|
||||
row = get_label_field_by_key("custom.contact_firma")
|
||||
assert row is not None and row["active"] == 1
|
||||
|
||||
|
||||
def test_duplicate_labels_get_unique_keys(client):
|
||||
_create_field(client, label="Alergeni", template="Poate conține gluten")
|
||||
_create_field(client, label="Alergeni", template="Poate conține lactoză")
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import list_label_fields
|
||||
|
||||
keys = [row["key"] for row in list_label_fields()]
|
||||
assert keys == ["custom.alergeni", "custom.alergeni_2"]
|
||||
|
||||
|
||||
def test_unknown_placeholder_is_rejected(client):
|
||||
response = _create_field(client, label="Greșit", template="X {{does.not.exist}}")
|
||||
html = response.get_data(as_text=True)
|
||||
assert "Campuri necunoscute" in html
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import list_label_fields
|
||||
|
||||
assert list_label_fields() == []
|
||||
|
||||
|
||||
def test_empty_template_is_rejected(client):
|
||||
response = _create_field(client, label="Gol", template=" ")
|
||||
assert "Textul campului este obligatoriu." in response.get_data(as_text=True)
|
||||
|
||||
|
||||
def test_update_keeps_key_and_can_deactivate(client):
|
||||
_create_field(client)
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import get_label_field_by_key
|
||||
|
||||
field_id = get_label_field_by_key("custom.contact_firma")["id"]
|
||||
|
||||
client.post(f"/campuri/{field_id}/editare", data={
|
||||
"label": "Contact (nou)", "template": "{{company.phone}}",
|
||||
}, follow_redirects=True)
|
||||
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import get_label_field, list_label_fields
|
||||
|
||||
row = get_label_field(field_id)
|
||||
assert row["label"] == "Contact (nou)"
|
||||
assert row["key"] == "custom.contact_firma" # cheia ramane stabila
|
||||
assert row["active"] == 0 # fara bifa = inactiv
|
||||
assert [f["key"] for f in list_label_fields()] == [] # ascuns din designer
|
||||
|
||||
|
||||
def test_delete_field(client):
|
||||
_create_field(client, label="Șters", template="text")
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import get_label_field_by_key
|
||||
|
||||
field_id = get_label_field_by_key("custom.sters")["id"]
|
||||
response = client.post(f"/campuri/{field_id}/stergere", follow_redirects=True)
|
||||
assert "a fost sters" in response.get_data(as_text=True)
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import get_label_field_by_key
|
||||
|
||||
assert get_label_field_by_key("custom.sters") is None
|
||||
|
||||
|
||||
def test_preview_endpoint(client):
|
||||
ok = client.get("/campuri/previzualizare",
|
||||
query_string={"template": "{{recipe.name}} · {{package.lot}}"})
|
||||
assert ok.get_json() == {"ok": True, "value": "Fasole cu carne · LOT-2610-A"}
|
||||
|
||||
bad = client.get("/campuri/previzualizare", query_string={"template": "{{nope.x}}"})
|
||||
data = bad.get_json()
|
||||
assert data["ok"] is False and "Campuri necunoscute" in data["error"]
|
||||
|
||||
|
||||
def test_custom_field_is_offered_in_designer_picker(client):
|
||||
_create_field(client, label="Contact firmă", template="Tel. {{company.phone}}")
|
||||
response = client.post("/sabloane/designer/nou", data={"name": "Cu camp propriu"},
|
||||
follow_redirects=True)
|
||||
assert response.status_code == 200
|
||||
html = response.get_data(as_text=True)
|
||||
assert "custom.contact_firma" in html
|
||||
assert "Tel. 0700 000 000" in html # sample pentru previzualizare
|
||||
# denumirea trece prin tojson, deci diacriticele sunt escapate
|
||||
assert "Contact firm\\u0103" in html
|
||||
|
||||
|
||||
def test_custom_field_renders_on_label(client):
|
||||
_create_field(client, label="Contact", template="Tel. {{company.phone}}")
|
||||
design = designer.validate_design({
|
||||
"width_mm": 100, "height_mm": 60,
|
||||
"blocks": [
|
||||
{"type": "field", "x": 3, "y": 3, "w": 70, "h": 6, "field": "custom.contact"},
|
||||
],
|
||||
}, 100, 60)
|
||||
assert design["blocks"][0]["field"] == "custom.contact"
|
||||
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import list_label_fields
|
||||
|
||||
fields = designer.apply_custom_fields(designer.demo_fields(), list_label_fields())
|
||||
html = designer.render_design(design, fields, [])
|
||||
assert "Tel. 0700 000 000" in html
|
||||
|
||||
|
||||
def test_deleted_custom_field_renders_empty_not_wrong(client):
|
||||
"""Un design care refera un camp sters nu trebuie sa cada sau sa afiseze altceva."""
|
||||
design = designer.validate_design({
|
||||
"width_mm": 100, "height_mm": 60,
|
||||
"blocks": [{"type": "field", "x": 3, "y": 3, "w": 70, "h": 6, "field": "custom.sters"}],
|
||||
}, 100, 60)
|
||||
assert design["blocks"][0]["field"] == "custom.sters"
|
||||
html = designer.render_design(design, designer.demo_fields(), [])
|
||||
assert "Fasole cu carne" not in html # nu cade pe alt camp
|
||||
assert "> <" in html or html.count("d-field") == 1
|
||||
|
||||
|
||||
def test_custom_field_template_rejects_custom_reference():
|
||||
"""Campurile proprii se pot referi doar la campuri fixe (fara bucle)."""
|
||||
errors = designer.custom_template_errors("X {{custom.altceva}}")
|
||||
assert errors and "Campuri necunoscute" in errors[0]
|
||||
assert designer.custom_template_errors("{{company.phone}}") == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("text,expected", [
|
||||
("", ""),
|
||||
("text simplu", "text simplu"),
|
||||
("{{company.name}}", "Moto Adv SRL"),
|
||||
("{{ package.lot }}/{{package.operator}}", "LOT-2610-A/Maria"),
|
||||
])
|
||||
def test_expand_custom(text, expected):
|
||||
assert designer.expand_custom(text, designer.demo_fields()) == expected
|
||||
|
||||
|
||||
def test_usage_counter_reports_templates(client):
|
||||
_create_field(client, label="Folosit", template="X")
|
||||
client.post("/sabloane/designer/nou", data={"name": "Cu folosit"})
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import (count_label_field_usage, get_default_template,
|
||||
get_label_field_by_key, list_templates,
|
||||
save_template_design)
|
||||
|
||||
field = get_label_field_by_key("custom.folosit")
|
||||
target = [t for t in list_templates() if t["name"] == "Cu folosit"][0]
|
||||
payload = {"version": 1, "width_mm": 100, "height_mm": 60, "blocks": [
|
||||
{"id": "b1", "type": "field", "x": 1, "y": 1, "w": 50, "h": 6,
|
||||
"field": "custom.folosit"},
|
||||
]}
|
||||
save_template_design(target["id"], json.dumps(payload))
|
||||
assert count_label_field_usage(field["key"]) == 1
|
||||
assert count_label_field_usage("custom.inexistent") == 0
|
||||
assert get_default_template() is not None
|
||||
Reference in New Issue
Block a user