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
142 lines
5.8 KiB
HTML
142 lines
5.8 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}{{ "Produs finit nou" if is_new else "Editare produs finit" }}{% endblock %}
|
||
|
||
{% block content %}
|
||
<section class="page-head">
|
||
<div>
|
||
<h1>{{ "Produs finit nou" if is_new else "Editare produs finit" }}</h1>
|
||
<p class="subtitle">Ordinea componentelor este ordinea în care se cântăresc.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="grid template-grid">
|
||
<section class="card">
|
||
<form method="post" class="form">
|
||
<div class="field-row">
|
||
<div class="field">
|
||
<label for="code">Cod <span class="req">*</span></label>
|
||
<input id="code" name="code" type="text" maxlength="40" required value="{{ recipe.code or '' }}">
|
||
</div>
|
||
<div class="field">
|
||
<label for="unit">Unitate</label>
|
||
<select id="unit" name="unit">
|
||
{% for u in ["g", "kg", "mg", "t", "lb", "oz"] %}
|
||
<option value="{{ u }}" {{ 'selected' if (recipe.unit or 'g') == u }}>{{ u }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label for="name">Denumire pe etichetă <span class="req">*</span></label>
|
||
<input id="name" name="name" type="text" maxlength="120" required
|
||
value="{{ recipe.name or '' }}" placeholder="ex. Fasole cu carne">
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label for="target_weight">Greutate totală țintă (opțional)</label>
|
||
<input id="target_weight" name="target_weight" type="number" step="0.001" min="0"
|
||
value="{{ recipe.target_weight|weight if recipe.target_weight else '' }}" placeholder="ex. 450">
|
||
<p class="muted small">Doar orientativă; eticheta afișează greutatea reală cântărită.</p>
|
||
</div>
|
||
|
||
<h2>Etichetă</h2>
|
||
<div class="field">
|
||
<label for="template_id">Șablon de etichetă</label>
|
||
<select id="template_id" name="template_id">
|
||
<option value="">— șablonul implicit din Setări —</option>
|
||
{% for t in templates %}
|
||
<option value="{{ t.id }}" {{ 'selected' if recipe.template_id|string == t.id|string }}>
|
||
{{ t.name }} · {{ t.width_mm|weight }} × {{ t.height_mm|weight }} mm
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
<p class="muted small">
|
||
{% if templates %}
|
||
Eticheta acestui produs finit se randează cu șablonul ales aici.
|
||
<a href="{{ url_for('label_templates.index') }}">Vezi șabloanele</a> ·
|
||
<a href="{{ url_for('label_templates.index') }}">creează unul nou</a>
|
||
{% else %}
|
||
Nu există șabloane. <a href="{{ url_for('label_templates.index') }}">Creează întâi un șablon</a>,
|
||
altfel eticheta nu poate fi tipărită.
|
||
{% endif %}
|
||
</p>
|
||
</div>
|
||
|
||
<h2>Componente</h2>
|
||
<p class="muted small">Completează cel puțin primul rând. Ordinea rândurilor = ordinea cântăririi.</p>
|
||
|
||
{% for row in slot_rows %}
|
||
<div class="component-row">
|
||
<span class="component-index">{{ row.slot }}</span>
|
||
<div class="field grow">
|
||
<label for="component_{{ row.slot }}_id">
|
||
Component {{ row.slot }}{% if row.slot == 1 %} <span class="req">*</span>{% endif %}
|
||
</label>
|
||
<select id="component_{{ row.slot }}_id" name="component_{{ row.slot }}_id">
|
||
<option value="">— {{ "obligatoriu" if row.slot == 1 else "opțional" }} —</option>
|
||
{% for c in components %}
|
||
<option value="{{ c.id }}"
|
||
{{ 'selected' if row.component_id|string == c.id|string }}>
|
||
{{ c.code }} · {{ c.name }} ({{ c.unit }})
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="field component-target">
|
||
<label for="component_{{ row.slot }}_target">Țintă</label>
|
||
<input id="component_{{ row.slot }}_target" name="component_{{ row.slot }}_target"
|
||
type="number" step="0.001" min="0" value="{{ row.target|weight if row.target else '' }}">
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
<div class="field">
|
||
<label for="note">Observații interne</label>
|
||
<textarea id="note" name="note" rows="2" maxlength="500">{{ recipe.note or '' }}</textarea>
|
||
</div>
|
||
|
||
<label class="check">
|
||
<input type="checkbox" name="active" {{ 'checked' if recipe.active in [True, 1, '1', 'on', 'true'] or is_new }}>
|
||
Produs finit activ
|
||
</label>
|
||
|
||
<div class="actions">
|
||
<button class="btn primary" type="submit">Salvează</button>
|
||
<a class="btn ghost" href="{{ url_for('recipes.index') }}">Renunță</a>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<aside class="card help-card">
|
||
<h2>Cum funcționează</h2>
|
||
<p class="small">
|
||
La cântărire alegi produsul finit. Aplicația cere pe rând fiecare componentă, în ordinea de aici.
|
||
După ultima componentă eticheta se tipărește automat.
|
||
</p>
|
||
<h2>Eticheta produsului</h2>
|
||
<p class="small">
|
||
Șablonul ales aici este folosit automat la tipărirea etichetelor pentru acest produs finit;
|
||
dacă lași gol, se folosește șablonul implicit din Setări.
|
||
</p>
|
||
<h2>Exemplu</h2>
|
||
<p class="small">
|
||
<strong>Fasole cu carne</strong>, unitate <code>g</code>:
|
||
</p>
|
||
<ul class="var-list">
|
||
<li>1 · Fasole — țintă 250</li>
|
||
<li>2 · Carne — țintă 200</li>
|
||
</ul>
|
||
<p class="small muted">
|
||
Eticheta va arăta totalul real cântărit și defalcarea:
|
||
<code>250 g Fasole</code>, <code>200 g Carne</code>.
|
||
</p>
|
||
<h2>Fără componentă nouă?</h2>
|
||
<p class="small muted">
|
||
Dacă lipsește o componentă din listă, o adaugi întâi în
|
||
<a href="{{ url_for('components.create') }}">Componente</a>, apoi revii aici.
|
||
</p>
|
||
</aside>
|
||
</div>
|
||
{% endblock %}
|