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:
2026-10-18 23:38:45 +03:00
commit bff6d70b17
107 changed files with 21428 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block title %}{{ "Component nou" if is_new else "Editare component" }}{% endblock %}
{% block content %}
<section class="page-head">
<div>
<h1>{{ "Component nou" if is_new else "Editare component" }}</h1>
<p class="subtitle">Numele apare pe etichetă, lângă greutate.</p>
</div>
</section>
<section class="card narrow">
<form method="post" class="form">
<div class="field">
<label for="code">Cod <span class="req">*</span></label>
<input id="code" name="code" type="text" maxlength="40" required
value="{{ component.code or '' }}" autofocus>
</div>
<div class="field">
<label for="name">Denumire <span class="req">*</span></label>
<input id="name" name="name" type="text" maxlength="120" required
value="{{ component.name or '' }}" placeholder="ex. Fasole">
</div>
<div class="field">
<label for="unit">Unitate de măsură</label>
<select id="unit" name="unit">
{% for u in ["g", "kg", "mg", "t", "lb", "oz"] %}
<option value="{{ u }}" {{ 'selected' if (component.unit or 'g') == u }}>{{ u }}</option>
{% endfor %}
</select>
</div>
<div class="field">
<label for="description">Descriere</label>
<textarea id="description" name="description" rows="3" maxlength="500">{{ component.description or '' }}</textarea>
</div>
<label class="check">
<input type="checkbox" name="active" {{ 'checked' if component.active in [True, 1, '1', 'on', 'true'] or is_new }}>
Component activ
</label>
<div class="actions">
<button class="btn primary" type="submit">Salvează</button>
<a class="btn ghost" href="{{ url_for('components.index') }}">Renunță</a>
</div>
</form>
</section>
{% endblock %}
+47
View File
@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block title %}Componente{% endblock %}
{% block content %}
<section class="page-head">
<div>
<h1>Componente</h1>
<p class="subtitle">Ce se cântărește: fasole, carne, garnitură etc.</p>
</div>
<a class="btn primary" href="{{ url_for('components.create') }}">+ Component nou</a>
</section>
<section class="card">
{% if components %}
<div class="table-wrap">
<table class="table">
<thead>
<tr><th>Cod</th><th>Denumire</th><th>UM</th><th>Descriere</th><th>Stare</th><th></th></tr>
</thead>
<tbody>
{% for c in components %}
<tr class="{{ 'row-muted' if not c.active }}">
<td><code>{{ c.code }}</code></td>
<td>{{ c.name }}</td>
<td>{{ c.unit }}</td>
<td class="muted">{{ c.description or "-" }}</td>
<td>
{% if c.active %}<span class="badge ok">activ</span>
{% else %}<span class="badge off">inactiv</span>{% endif %}
</td>
<td class="right nowrap">
<a class="link" href="{{ url_for('components.edit', component_id=c.id) }}">Editează</a>
<form class="inline" method="post" action="{{ url_for('components.remove', component_id=c.id) }}"
onsubmit="return confirm('Ștergi componentul {{ c.code }}? Dacă e folosit într-o rețetă, va fi doar dezactivat.');">
<button class="link danger" type="submit">Șterge</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="muted">Nu există componente. <a href="{{ url_for('components.create') }}">Adaugă primul component</a>.</p>
{% endif %}
</section>
{% endblock %}