feat: dupliquer un job + enregistrer et créer un autre
Deploy via Portainer / deploy (push) Successful in 1s
Deploy via Portainer / deploy (push) Successful in 1s
This commit is contained in:
@@ -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,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
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user