diff --git a/app.py b/app.py index 9380961..7585547 100644 --- a/app.py +++ b/app.py @@ -173,6 +173,10 @@ def init_db(): 'ALTER TABLE jobs ADD COLUMN pieces_per_plate INTEGER DEFAULT 1', 'ALTER TABLE jobs ADD COLUMN order_qty INTEGER DEFAULT 1', 'ALTER TABLE jobs ADD COLUMN price_per_piece REAL', + 'ALTER TABLE clients ADD COLUMN default_machine_profile_id INTEGER', + 'ALTER TABLE clients ADD COLUMN default_handling_profile_id INTEGER', + 'ALTER TABLE clients ADD COLUMN default_material_profile_id INTEGER', + 'ALTER TABLE clients ADD COLUMN default_pricing_profile_id INTEGER', ] for sql in migrations: try: @@ -453,7 +457,10 @@ def find_next_slot(print_time_s): candidates.append(datetime(d.year, d.month, d.day, h0, m0)) candidates.append(datetime(d.year, d.month, d.day, h1, m1)) + now = datetime.now() for start_dt in candidates: + if start_dt <= now: # ignorer les créneaux déjà passés + continue end_dt = start_dt + timedelta(seconds=total_sec) for printer in printers: pid = printer['id'] @@ -516,30 +523,64 @@ def clients(): conn.close() return render_template('clients.html', clients=clients) +def _get_all_profiles(conn): + return { + 'machine_profiles': conn.execute('SELECT id,name FROM machine_profiles ORDER BY name').fetchall(), + 'handling_profiles': conn.execute('SELECT id,name FROM handling_profiles ORDER BY name').fetchall(), + 'material_profiles': conn.execute('SELECT id,name FROM material_profiles ORDER BY name').fetchall(), + 'pricing_profiles': conn.execute('SELECT id,name FROM pricing_profiles ORDER BY name').fetchall(), + } + @app.route('/clients/new', methods=['GET','POST']) def new_client(): + conn = get_db() if request.method == 'POST': - conn = get_db() - conn.execute('INSERT INTO clients(name,email,design_multiplier,notes) VALUES(?,?,?,?)', - (request.form['name'], request.form.get('email',''), - float(request.form.get('design_multiplier', 1.80)), request.form.get('notes',''))) + def _int(k): return int(request.form[k]) if request.form.get(k) else None + conn.execute('''INSERT INTO clients + (name,email,notes,default_machine_profile_id,default_handling_profile_id, + default_material_profile_id,default_pricing_profile_id) + VALUES(?,?,?,?,?,?,?)''', + (request.form['name'], request.form.get('email',''), request.form.get('notes',''), + _int('default_machine_profile_id'), _int('default_handling_profile_id'), + _int('default_material_profile_id'), _int('default_pricing_profile_id'))) conn.commit(); conn.close() return redirect(url_for('clients')) - return render_template('client_form.html', client=None) + profiles = _get_all_profiles(conn) + conn.close() + return render_template('client_form.html', client=None, **profiles) @app.route('/clients//edit', methods=['GET','POST']) def edit_client(id): conn = get_db() client = conn.execute('SELECT * FROM clients WHERE id=?',(id,)).fetchone() if request.method == 'POST': - conn.execute('UPDATE clients SET name=?,email=?,design_multiplier=?,notes=? WHERE id=?', - (request.form['name'], request.form.get('email',''), - float(request.form.get('design_multiplier', client['design_multiplier'])), - request.form.get('notes',''), id)) + def _int(k): return int(request.form[k]) if request.form.get(k) else None + conn.execute('''UPDATE clients SET name=?,email=?,notes=?, + default_machine_profile_id=?,default_handling_profile_id=?, + default_material_profile_id=?,default_pricing_profile_id=? WHERE id=?''', + (request.form['name'], request.form.get('email',''), request.form.get('notes',''), + _int('default_machine_profile_id'), _int('default_handling_profile_id'), + _int('default_material_profile_id'), _int('default_pricing_profile_id'), id)) conn.commit(); conn.close() return redirect(url_for('clients')) + profiles = _get_all_profiles(conn) conn.close() - return render_template('client_form.html', client=client) + return render_template('client_form.html', client=client, **profiles) + +@app.route('/api/client//defaults') +def api_client_defaults(id): + conn = get_db() + c = conn.execute('SELECT default_machine_profile_id, default_handling_profile_id, \ + default_material_profile_id, default_pricing_profile_id FROM clients WHERE id=?',(id,)).fetchone() + conn.close() + if not c: + return jsonify({}) + return jsonify({ + 'machine_profile_id': c['default_machine_profile_id'], + 'handling_profile_id': c['default_handling_profile_id'], + 'material_profile_id': c['default_material_profile_id'], + 'pricing_profile_id': c['default_pricing_profile_id'], + }) @app.route('/clients//delete', methods=['POST']) def delete_client(id): diff --git a/templates/client_form.html b/templates/client_form.html index 5be5bed..dd28c19 100644 --- a/templates/client_form.html +++ b/templates/client_form.html @@ -10,10 +10,11 @@
-
+
+
- +
-
+ +
+
+ Profils par défaut +
Pré-sélectionnés automatiquement dans "Nouveau calcul" quand ce client est choisi.
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
@@ -40,5 +99,3 @@
{% endblock %} - -{% block scripts %}{% endblock %} diff --git a/templates/new_job.html b/templates/new_job.html index 532164c..52c40a4 100644 --- a/templates/new_job.html +++ b/templates/new_job.html @@ -328,14 +328,30 @@ document.getElementById('pricingProfileSelect').addEventListener('change', funct } }); -// ── Client : auto-remplir DM ────────────────────────────────────────────────── +// ── Client : auto-remplir profils par défaut ───────────────────────────────── document.getElementById('clientSelect').addEventListener('change', function() { - const dm = this.options[this.selectedIndex].dataset.dm; - if (dm) { - document.getElementById('design_multiplier').value = parseFloat(dm); - updateDMLabel(parseFloat(dm)); - recalc(); - } + const clientId = this.value; + if (!clientId) return; + fetch('/api/client/' + clientId + '/defaults') + .then(r => r.json()) + .then(d => { + if (d.machine_profile_id != null) document.getElementById('machineProfileSelect').value = d.machine_profile_id; + if (d.handling_profile_id != null) document.getElementById('handlingProfileSelect').value = d.handling_profile_id; + if (d.material_profile_id != null) document.getElementById('materialProfileSelect').value = d.material_profile_id; + if (d.pricing_profile_id != null) { + const sel = document.getElementById('pricingProfileSelect'); + sel.value = d.pricing_profile_id; + // Appliquer DM + marge du profil tarif sélectionné + const opt = sel.options[sel.selectedIndex]; + if (opt && opt.value) { + const dm = parseFloat(opt.dataset.dm); + const margin = parseFloat(opt.dataset.margin); + if (!isNaN(dm)) { document.getElementById('design_multiplier').value = dm; updateDMLabel(dm); } + if (!isNaN(margin)) { marginSlider.value = margin; updateMarginDisplay(margin); } + } + } + recalc(); + }); }); // ── Matiere : afficher stock ────────────────────────────────────────────────── @@ -374,7 +390,7 @@ function recalc() { hours: h, minutes: m, design_multiplier: parseFloat(document.getElementById('design_multiplier').value) || 1.80, - gross_margin_pct: parseFloat(document.getElementById('gross_margin_pct').value) || 27.5, + gross_margin_pct: parseFloat(document.getElementById('gross_margin_pct').value ?? 27.5), discount_pct: parseFloat(document.getElementById('discount_pct').value) || 0, material_id: document.getElementById('materialSelect').value || null, machine_profile_id: document.getElementById('machineProfileSelect').value || null,