feat: dupliquer un job + enregistrer et créer un autre
Deploy via Portainer / deploy (push) Successful in 1s

This commit is contained in:
Jo
2026-07-06 23:36:21 +02:00
parent ddd0f6bee5
commit d6c53479cf
3 changed files with 84 additions and 30 deletions
+15 -2
View File
@@ -956,8 +956,19 @@ def new_job():
handling_profiles = conn.execute('SELECT * FROM handling_profiles ORDER BY name').fetchall()
material_profiles = conn.execute('SELECT * FROM material_profiles ORDER BY name').fetchall()
pricing_profiles = conn.execute('SELECT * FROM pricing_profiles ORDER BY name').fetchall()
projects = conn.execute('SELECT id,name FROM projects ORDER BY name').fetchall()
settings = get_settings()
# Pré-remplissage depuis un job existant
prefill = None
clone_from = request.args.get('clone_from')
if clone_from:
prefill = conn.execute('SELECT * FROM jobs WHERE id=?', (clone_from,)).fetchone()
if prefill:
prefill = dict(prefill)
prefill['hours'] = prefill['print_time_s'] // 3600
prefill['minutes'] = (prefill['print_time_s'] % 3600) // 60
if request.method == 'POST':
weight_g = float(request.form['weight_g'])
hours = int(request.form.get('hours',0))
@@ -1026,17 +1037,19 @@ def new_job():
r['cotisations_amount'],r['vfl_amount'],r['tva_amount'],r['other_taxes_amount'],
r['tax_amount'],r['final_price'],round(r['final_price'],2),
request.form.get('notes','')))
new_id = conn.execute('SELECT last_insert_rowid()').fetchone()[0]
if material_id:
conn.execute('UPDATE materials SET stock_g=MAX(0,stock_g-?) WHERE id=?', (weight_g, material_id))
conn.commit(); conn.close()
if request.form.get('action') == 'save_and_new':
return redirect(url_for('new_job', clone_from=new_id))
return redirect(url_for('jobs'))
projects = conn.execute('SELECT id,name FROM projects ORDER BY name').fetchall()
conn.close()
return render_template('new_job.html', clients=clients, materials=mats, settings=settings,
machine_profiles=machine_profiles, handling_profiles=handling_profiles,
material_profiles=material_profiles, pricing_profiles=pricing_profiles,
projects=projects)
projects=projects, prefill=prefill)
@app.route('/jobs/<int:id>')
def job_detail(id):
+9 -4
View File
@@ -9,10 +9,15 @@
</a>
<h1 class="mt-1">{{ job.name }}</h1>
</div>
<form method="POST" action="{{ url_for('delete_job', id=job.id) }}"
onsubmit="return confirm('Supprimer cette commande ?')">
<button class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i> Supprimer</button>
</form>
<div class="d-flex gap-2">
<a href="{{ url_for('new_job', clone_from=job.id) }}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-copy me-1"></i>Dupliquer
</a>
<form method="POST" action="{{ url_for('delete_job', id=job.id) }}"
onsubmit="return confirm('Supprimer cette commande ?')">
<button class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i> Supprimer</button>
</form>
</div>
</div>
{% set qty = job.order_qty or 1 %}
+60 -24
View File
@@ -34,15 +34,25 @@
<div class="card-header py-3">Donnees impression</div>
<div class="card-body">
<div class="row g-3 mb-3">
{% if prefill %}
<div class="col-12">
<div class="alert alert-info py-2 mb-0 small">
<i class="bi bi-copy me-1"></i>
Dupliqué depuis <strong>{{ prefill.name }}</strong> — modifiez les champs souhaités.
</div>
</div>
{% endif %}
<div class="col-md-8">
<label class="form-label fw-semibold">Nom de la commande</label>
<input type="text" name="name" id="jobName" class="form-control" required
placeholder="ex: Marqueurs competition salle X">
placeholder="ex: Marqueurs competition salle X"
value="{% if prefill %}Copie — {{ prefill.name }}{% endif %}">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Fichier source</label>
<input type="text" name="source_file" id="sourceFile" class="form-control"
placeholder="nom_fichier.3mf">
placeholder="nom_fichier.3mf"
value="{{ prefill.source_file if prefill else '' }}">
</div>
</div>
<div class="row g-3 mb-3">
@@ -51,7 +61,10 @@
<select name="client_id" id="clientSelect" class="form-select">
<option value="">Sans client</option>
{% for c in clients %}
<option value="{{ c.id }}" data-dm="{{ c.design_multiplier }}">{{ c.name }}</option>
<option value="{{ c.id }}" data-dm="{{ c.design_multiplier }}"
{% if prefill and prefill.client_id == c.id %}selected{% endif %}>
{{ c.name }}
</option>
{% endfor %}
</select>
</div>
@@ -61,7 +74,7 @@
<option value="">— Sans projet —</option>
{% for p in projects %}
<option value="{{ p.id }}"
{% if request.args.get('project_id')|int == p.id %}selected{% endif %}>
{% if (prefill and prefill.project_id == p.id) or request.args.get('project_id')|int == p.id %}selected{% endif %}>
{{ p.name }}
</option>
{% endfor %}
@@ -74,7 +87,8 @@
{% for m in materials %}
<option value="{{ m.id }}" data-price="{{ m.price_per_kg }}"
data-stock="{{ m.stock_g }}" data-threshold="{{ m.low_stock_threshold_g }}"
data-name="{{ m.name }} ({{ m.type }})">
data-name="{{ m.name }} ({{ m.type }})"
{% if prefill and prefill.material_id == m.id %}selected{% endif %}>
{{ m.name }} ({{ m.type }}) — {{ m.price_per_kg }} €/kg
{% if m.stock_g <= 0 %}[RUPTURE]{% elif m.stock_g <= m.low_stock_threshold_g %}[stock bas]{% endif %}
</option>
@@ -87,16 +101,19 @@
<div class="col-md-4">
<label class="form-label fw-semibold">Poids plateau (g)</label>
<input type="number" name="weight_g" id="weight_g" class="form-control"
step="0.01" min="0.1" required placeholder="423.97">
step="0.01" min="0.1" required placeholder="423.97"
value="{{ prefill.weight_g if prefill else '' }}">
<div class="form-text">Poids total du plateau Bambu (toutes pièces)</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Heures</label>
<input type="number" name="hours" id="hours" class="form-control" min="0" value="0">
<input type="number" name="hours" id="hours" class="form-control" min="0"
value="{{ prefill.hours if prefill else 0 }}">
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Minutes</label>
<input type="number" name="minutes" id="minutes" class="form-control" min="0" max="59" value="0">
<input type="number" name="minutes" id="minutes" class="form-control" min="0" max="59"
value="{{ prefill.minutes if prefill else 0 }}">
<div class="form-text">Temps total Bambu</div>
</div>
</div>
@@ -104,13 +121,13 @@
<div class="col-md-6">
<label class="form-label fw-semibold">Pièces par plateau</label>
<input type="number" name="pieces_per_plate" id="pieces_per_plate" class="form-control"
min="1" step="1" value="1">
min="1" step="1" value="{{ prefill.pieces_per_plate if prefill else 1 }}">
<div class="form-text">Combien de pièces tiennent sur un plateau.</div>
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">Quantité commandée</label>
<input type="number" name="order_qty" id="order_qty" class="form-control"
min="1" step="1" value="1">
min="1" step="1" value="{{ prefill.order_qty if prefill else 1 }}">
<div class="form-text">Nombre total de pièces à produire.</div>
</div>
</div>
@@ -134,7 +151,10 @@
<select name="machine_profile_id" id="machineProfileSelect" class="form-select form-select-sm">
<option value="">— Parametres globaux —</option>
{% for p in machine_profiles %}
<option value="{{ p.id }}">{{ p.name }}</option>
<option value="{{ p.id }}"
{% if prefill and prefill.machine_profile_id == p.id %}selected{% endif %}>
{{ p.name }}
</option>
{% endfor %}
</select>
</div>
@@ -145,7 +165,10 @@
<select name="handling_profile_id" id="handlingProfileSelect" class="form-select form-select-sm">
<option value="">— Parametres globaux —</option>
{% for p in handling_profiles %}
<option value="{{ p.id }}">{{ p.name }}</option>
<option value="{{ p.id }}"
{% if prefill and prefill.handling_profile_id == p.id %}selected{% endif %}>
{{ p.name }}
</option>
{% endfor %}
</select>
</div>
@@ -156,7 +179,8 @@
<select name="material_profile_id" id="materialProfileSelect" class="form-select form-select-sm">
<option value="">— Parametres globaux —</option>
{% for p in material_profiles %}
<option value="{{ p.id }}" data-margin="{{ p.material_margin_pct }}">
<option value="{{ p.id }}" data-margin="{{ p.material_margin_pct }}"
{% if prefill and prefill.material_profile_id == p.id %}selected{% endif %}>
{{ p.name }} ({{ p.material_margin_pct }}%)
</option>
{% endfor %}
@@ -171,7 +195,8 @@
{% for p in pricing_profiles %}
<option value="{{ p.id }}"
data-dm="{{ p.design_multiplier }}"
data-margin="{{ p.gross_margin_pct }}">
data-margin="{{ p.gross_margin_pct }}"
{% if prefill and prefill.pricing_profile_id == p.id %}selected{% endif %}>
{{ p.name }}
</option>
{% endfor %}
@@ -198,7 +223,8 @@
</button>
</div>
<input type="number" name="design_multiplier" id="design_multiplier"
class="form-control form-control-sm" step="0.05" min="0" value="1.80">
class="form-control form-control-sm" step="0.05" min="0"
value="{{ prefill.design_multiplier if prefill else 1.80 }}">
</div>
<div class="mb-4">
<label class="form-label fw-semibold d-flex justify-content-between">
@@ -211,7 +237,8 @@
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setMargin(35)">35%</button>
</div>
<input type="range" name="gross_margin_pct" id="gross_margin_pct" class="form-range"
min="0" max="100" step="0.5" value="27.5">
min="0" max="100" step="0.5"
value="{{ prefill.gross_margin_pct if prefill else 27.5 }}">
<div class="d-flex justify-content-between" style="font-size:.75rem;color:#6b7280">
<span>0%</span><span>100%</span>
</div>
@@ -219,18 +246,24 @@
<div class="mb-3">
<label class="form-label fw-semibold">Remise client (%)</label>
<input type="number" name="discount_pct" id="discount_pct" class="form-control"
min="0" max="100" step="0.5" value="0">
min="0" max="100" step="0.5"
value="{{ prefill.discount_pct if prefill else 0 }}">
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Notes internes</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
<textarea name="notes" class="form-control" rows="2">{{ prefill.notes if prefill else '' }}</textarea>
</div>
</div>
</div>
<button type="submit" class="btn w-100 py-2 fw-bold" style="background:#ff6b35;color:#fff">
<i class="bi bi-save me-2"></i>Enregistrer la commande
</button>
<div class="d-flex gap-2">
<button type="submit" name="action" value="save" class="btn flex-fill py-2 fw-bold" style="background:#ff6b35;color:#fff">
<i class="bi bi-save me-2"></i>Enregistrer la commande
</button>
<button type="submit" name="action" value="save_and_new" class="btn py-2 fw-semibold btn-outline-secondary" style="white-space:nowrap">
<i class="bi bi-plus-square me-1"></i>Enregistrer et créer un autre
</button>
</div>
</form>
</div>
@@ -556,8 +589,11 @@ function renderBreakdown(d, body) {
` + capBlock;
}
// Init
updateDMLabel(1.80);
updateMarginDisplay(27.5);
// Init — utiliser les valeurs du champ (prefill ou défaut)
const _initDM = parseFloat(document.getElementById('design_multiplier').value) || 1.80;
const _initMargin = parseFloat(document.getElementById('gross_margin_pct').value) ?? 27.5;
updateDMLabel(_initDM);
updateMarginDisplay(_initMargin);
if (document.getElementById('weight_g').value) recalc();
</script>
{% endblock %}