diff --git a/app.py b/app.py index 2a34303..09aab38 100644 --- a/app.py +++ b/app.py @@ -151,7 +151,7 @@ def init_db(): handling_profile_id INTEGER REFERENCES handling_profiles(id) ON DELETE SET NULL, material_profile_id INTEGER REFERENCES material_profiles(id) ON DELETE SET NULL, pricing_profile_id INTEGER REFERENCES pricing_profiles(id) ON DELETE SET NULL, - source_file TEXT DEFAULT '', name TEXT NOT NULL, + source_file TEXT DEFAULT '', plate_name TEXT DEFAULT '', name TEXT NOT NULL, description TEXT DEFAULT '', weight_g REAL NOT NULL, print_time_s INTEGER NOT NULL, design_multiplier REAL NOT NULL, gross_margin_pct REAL NOT NULL DEFAULT 27.5, discount_pct REAL DEFAULT 0, @@ -259,6 +259,7 @@ def init_db(): 'ALTER TABLE print_slots ADD COLUMN wasted_g REAL DEFAULT 0', 'ALTER TABLE materials ADD COLUMN total_wasted_g REAL DEFAULT 0', 'ALTER TABLE jobs ADD COLUMN client_token TEXT DEFAULT NULL', + 'ALTER TABLE jobs ADD COLUMN plate_name TEXT DEFAULT ""', ] for sql in migrations: try: @@ -304,6 +305,7 @@ def parse_3mf(file_stream): meta = {m.get('key'): m.get('value') for m in plate_el.findall('metadata')} plate['index'] = int(meta.get('index', 0)) plate['print_time_s'] = int(meta.get('prediction', 0)) + plate['name'] = meta.get('name', '') or '' filaments = [] total_g = 0.0 @@ -317,12 +319,26 @@ def parse_3mf(file_stream): 'used_g': round(used_g, 2), 'used_m': round(float(fil.get('used_m', 0) or 0), 2), }) - plate['weight_g'] = round(total_g, 2) - plate['filaments'] = filaments + plate['weight_g'] = round(total_g, 2) + plate['filaments'] = filaments + # Pièces : compter les non skippés + objs = [o for o in plate_el.findall('object') if o.get('skipped') != 'true'] + plate['pieces'] = len(objs) + # Noms uniques des modèles (sans extension) + plate['object_names'] = list(dict.fromkeys( + o.get('name', '').rsplit('.', 1)[0] + for o in objs if o.get('name') + )) # Libellé lisible h = plate['print_time_s'] // 3600 mn = (plate['print_time_s'] % 3600) // 60 - plate['label'] = f"Plateau {plate['index']} — {plate['weight_g']} g — {h}h{mn:02d}m" + name_part = f" «{plate['name']}»" if plate['name'] else '' + plate['label'] = ( + f"Plateau {plate['index']}{name_part}" + f" — {plate['weight_g']} g" + f" — {h}h{mn:02d}m" + f" — {plate['pieces']} pce(s)" + ) result['plates'].append(plate) result['plates'].sort(key=lambda p: p['index']) except Exception: @@ -1157,6 +1173,7 @@ def new_job(): project_id = request.form.get('project_id') or None pieces_per_plate = max(1, int(request.form.get('pieces_per_plate', 1))) order_qty = max(1, int(request.form.get('order_qty', 1))) + plate_name = request.form.get('plate_name', '').strip() s = dict(settings) if machine_pid: @@ -1189,16 +1206,16 @@ def new_job(): r = calc(weight_g, print_time_s, design_mult, gross_margin, discount, price_kg, s, pieces_per_plate) conn.execute('''INSERT INTO jobs(client_id,material_id,machine_profile_id,handling_profile_id, - material_profile_id,pricing_profile_id,project_id,source_file,name,description, + material_profile_id,pricing_profile_id,project_id,source_file,plate_name,name,description, weight_g,print_time_s,design_multiplier,gross_margin_pct,discount_pct, pieces_per_plate,order_qty, material_cost,material_margin,design_cost,handling_cost,wear_cost,electricity_cost, cout_fixe,total_marge,subtotal,margin_amount, cotisations_amount,vfl_amount,tva_amount,other_taxes_amount,tax_amount, price_per_piece,final_price,notes) - VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', (client_id,material_id,machine_pid,handling_pid,material_pid,pricing_pid,project_id, - source_file,request.form['name'],request.form.get('description',''), + source_file,plate_name,request.form['name'],request.form.get('description',''), weight_g,print_time_s,design_mult,gross_margin,discount, pieces_per_plate,order_qty, r['material_cost'],r['material_margin'],r['design_cost'],r['handling_cost'], diff --git a/templates/job_detail.html b/templates/job_detail.html index ba29608..c8ee1e5 100644 --- a/templates/job_detail.html +++ b/templates/job_detail.html @@ -75,6 +75,10 @@ {% endif %} {% else %}—{% endif %} + {% if job.plate_name %} + Référence plateau + {{ job.plate_name }} + {% endif %} Date{{ job.created_at[:10] }} Poids plateau{{ job.weight_g }} g Pieces / plateau{{ job.pieces_per_plate or 1 }} diff --git a/templates/new_job.html b/templates/new_job.html index 26ec053..e959a3c 100644 --- a/templates/new_job.html +++ b/templates/new_job.html @@ -152,6 +152,15 @@
Nombre total de pièces à produire.
+
+
+ + +
+
+
@@ -387,7 +396,11 @@ function parse3mf() { return; } // ── Plaque unique ou fallback ── - applyPlateData(d.weight_g, d.hours, d.minutes); + const p0 = (d.plates && d.plates.length === 1) ? d.plates[0] : null; + applyPlateData(d.weight_g, d.hours, d.minutes, + p0 ? p0.pieces : null, + p0 ? (p0.name || `Plateau ${p0.index}`) : null, + p0 ? p0.object_names : null); const ok = d.weight_g || d.hours != null; status.innerHTML = ok ? 'Données extraites' @@ -396,10 +409,19 @@ function parse3mf() { .catch(() => { status.innerHTML = 'Erreur de lecture'; }); } -function applyPlateData(weight_g, hours, minutes) { - if (weight_g != null) document.getElementById('weight_g').value = weight_g; - if (hours != null) document.getElementById('hours').value = hours; - if (minutes != null) document.getElementById('minutes').value = minutes; +function applyPlateData(weight_g, hours, minutes, pieces, plate_name, object_names) { + if (weight_g != null) document.getElementById('weight_g').value = weight_g; + if (hours != null) document.getElementById('hours').value = hours; + if (minutes != null) document.getElementById('minutes').value = minutes; + if (pieces != null) document.getElementById('pieces_per_plate').value = pieces; + if (plate_name != null) document.getElementById('plate_name').value = plate_name; + // Afficher les noms des modèles comme aide + const hint = document.getElementById('plateObjectsHint'); + if (hint && object_names && object_names.length) { + hint.innerHTML = `Modèles : ${object_names.join(', ')}`; + } else if (hint) { + hint.textContent = ''; + } recalc(); } @@ -420,11 +442,20 @@ function renderPlateSelector(plates) { const dots = p.filaments.map(f => `` ).join(''); - btn.innerHTML = `${dots}Plateau ${p.index} — ${p.weight_g} g — ${Math.floor(p.print_time_s/3600)}h${String(Math.floor((p.print_time_s%3600)/60)).padStart(2,'0')}m`; + const piecesLabel = p.pieces != null ? ` — ${p.pieces} pce` : ''; + const nameLabel = p.name ? ` «${p.name}»` : ''; + btn.innerHTML = `${dots}Plateau ${p.index}${nameLabel} — ${p.weight_g} g — ${Math.floor(p.print_time_s/3600)}h${String(Math.floor((p.print_time_s%3600)/60)).padStart(2,'0')}m${piecesLabel}`; btn.addEventListener('click', () => { document.querySelectorAll('#plateSelectorWrap button').forEach(b => b.classList.replace('btn-warning','btn-outline-warning')); btn.classList.replace('btn-outline-warning','btn-warning'); - applyPlateData(p.weight_g, Math.floor(p.print_time_s/3600), Math.floor((p.print_time_s%3600)/60)); + applyPlateData( + p.weight_g, + Math.floor(p.print_time_s/3600), + Math.floor((p.print_time_s%3600)/60), + p.pieces, + p.name || `Plateau ${p.index}`, + p.object_names + ); document.getElementById('parseStatus').innerHTML = `Plateau ${p.index} appliqué`; });