feat: colonnes prix/pièce et prix/commande dans les deux récaps
This commit is contained in:
+100
-62
@@ -73,10 +73,10 @@
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Poids filament (g)</label>
|
||||
<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">
|
||||
<div class="form-text">Champ Modele dans Bambu</div>
|
||||
<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>
|
||||
@@ -417,59 +417,77 @@ function fmt2(n) { return parseFloat(n).toFixed(2) + ' €'; }
|
||||
function renderBreakdown(d, body) {
|
||||
const dm = parseFloat(body.design_multiplier).toFixed(2);
|
||||
const mgn = parseFloat(body.gross_margin_pct).toFixed(1);
|
||||
const qty = body.order_qty || 1;
|
||||
const ppp = body.pieces_per_plate || 1;
|
||||
const multi = qty > 1;
|
||||
|
||||
// Nom du materiau selectionne
|
||||
const matSel = document.getElementById('materialSelect');
|
||||
const matOpt = matSel.options[matSel.selectedIndex];
|
||||
// Nom du materiau
|
||||
const matSel = document.getElementById('materialSelect');
|
||||
const matOpt = matSel.options[matSel.selectedIndex];
|
||||
const matName = matOpt.value ? (matOpt.dataset.name || matOpt.text.split('—')[0].trim()) : 'global';
|
||||
|
||||
// TVA
|
||||
const tvaLabel = d._tva_pct > 0 ? `TVA (${d._tva_pct}%)` : `TVA — franchise en base`;
|
||||
const tvaClass = d._tva_pct > 0 ? '' : 'text-muted fst-italic';
|
||||
|
||||
// VFL
|
||||
const vflRow = d._vfl_pct > 0
|
||||
? `<div class="breakdown-row"><span class="breakdown-muted ps-2">VFL impot (${d._vfl_pct}%)</span><span>${fmt2(d.vfl_amount)}</span></div>` : '';
|
||||
|
||||
// Autres taxes
|
||||
const otherRow = d._other_taxes_pct > 0
|
||||
? `<div class="breakdown-row"><span class="breakdown-muted ps-2">Autres taxes / CFE (${d._other_taxes_pct}%)</span><span>${fmt2(d.other_taxes_amount)}</span></div>` : '';
|
||||
|
||||
// Remise
|
||||
const disRow = body.discount_pct > 0
|
||||
? `<div class="breakdown-row text-danger"><span class="ps-2">Remise (${body.discount_pct}%)</span><span>-${fmt2(d.prix_ttc * body.discount_pct / 100)}</span></div>` : '';
|
||||
|
||||
// Multi-piece header
|
||||
const qty = body.order_qty || 1;
|
||||
const ppp = body.pieces_per_plate || 1;
|
||||
const nbPlateaux = Math.ceil(qty / ppp);
|
||||
const lastPlate = qty % ppp;
|
||||
const priceOrder = d.price_order || (d.final_price * qty);
|
||||
let plateauAlert = '';
|
||||
if (lastPlate !== 0) {
|
||||
plateauAlert = `<div class="alert alert-warning py-2 mb-2 small">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||
Dernier plateau incomplet : <strong>${lastPlate}/${ppp} pièces</strong>.
|
||||
Vous pourriez imprimer jusqu'à <strong>${nbPlateaux * ppp}</strong> pièces pour le même coût fixe.
|
||||
// Helper : génère une ligne avec 1 ou 2 colonnes de valeurs
|
||||
// cls : classes CSS supplémentaires sur le div
|
||||
function row(label, pieceVal, extraCls = '', isFmt4 = false) {
|
||||
const fmtFn = isFmt4 ? fmt : fmt2;
|
||||
const p = fmtFn(pieceVal);
|
||||
const o = multi ? `<span class="text-end" style="min-width:80px">${fmt2(pieceVal * qty)}</span>` : '';
|
||||
return `<div class="breakdown-row${extraCls ? ' '+extraCls : ''}">
|
||||
<span class="flex-fill">${label}</span>
|
||||
<span class="text-end" style="min-width:80px">${p}</span>
|
||||
${o}
|
||||
</div>`;
|
||||
}
|
||||
function rowFiscal(label, pieceVal, extraCls = '') {
|
||||
const p = fmt2(pieceVal);
|
||||
const o = multi ? `<span class="text-end" style="min-width:80px">${fmt2(pieceVal * qty)}</span>` : '';
|
||||
return `<div class="breakdown-row${extraCls ? ' '+extraCls : ''}">
|
||||
<span class="flex-fill">${label}</span>
|
||||
<span class="text-end" style="min-width:80px">${p}</span>
|
||||
${o}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Prix header (1 ou 2 colonnes)
|
||||
const multiPriceHeader = qty > 1
|
||||
? `<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="text-center flex-fill border-end">
|
||||
<div class="small text-muted">Prix / pièce</div>
|
||||
<div class="fw-bold fs-5" style="color:#ff6b35">${fmt2(d.final_price)}</div>
|
||||
</div>
|
||||
<div class="text-center flex-fill">
|
||||
<div class="small text-muted">Prix commande (×${qty})</div>
|
||||
<div class="fw-bold fs-5" style="color:#ff6b35">${fmt2(priceOrder)}</div>
|
||||
</div>
|
||||
</div>`
|
||||
: '';
|
||||
// Alerte plateau incomplet
|
||||
const nbPlateaux = Math.ceil(qty / ppp);
|
||||
const lastPlate = qty % ppp;
|
||||
let plateauAlert = '';
|
||||
if (lastPlate !== 0) {
|
||||
plateauAlert = `<div class="alert alert-warning py-2 mb-2 small">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||
Dernier plateau incomplet : <strong>${lastPlate}/${ppp} pièces</strong> —
|
||||
vous pourriez imprimer <strong>${nbPlateaux * ppp}</strong> pièces pour le même coût fixe.
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// En-tête colonnes (multi uniquement)
|
||||
const colHeader = multi
|
||||
? `<div class="d-flex justify-content-end gap-0 small text-muted mb-1 border-bottom pb-1">
|
||||
<span style="min-width:80px;text-align:right">/ pièce</span>
|
||||
<span style="min-width:80px;text-align:right">/ commande ×${qty}</span>
|
||||
</div>` : '';
|
||||
|
||||
// VFL / autres taxes
|
||||
const vflRow = d._vfl_pct > 0 ? rowFiscal(`<span class="breakdown-muted ps-2">VFL impot (${d._vfl_pct}%)</span>`, d.vfl_amount) : '';
|
||||
const otherRow = d._other_taxes_pct > 0 ? rowFiscal(`<span class="breakdown-muted ps-2">Autres taxes / CFE (${d._other_taxes_pct}%)</span>`, d.other_taxes_amount) : '';
|
||||
|
||||
// Remise (sur prix TTC/pièce)
|
||||
let disRow = '';
|
||||
if (body.discount_pct > 0) {
|
||||
const remisePiece = d.prix_ttc * body.discount_pct / 100;
|
||||
const o = multi ? `<span class="text-end text-danger" style="min-width:80px">-${fmt2(remisePiece * qty)}</span>` : '';
|
||||
disRow = `<div class="breakdown-row text-danger">
|
||||
<span class="flex-fill">Remise (${body.discount_pct}%)</span>
|
||||
<span class="text-end" style="min-width:80px">-${fmt2(remisePiece)}</span>${o}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Capacité
|
||||
const cap = d.capacity || {};
|
||||
const cap = d.capacity || {};
|
||||
const totalMin = d.total_print_min || 0;
|
||||
const capBlock = `
|
||||
<div class="breakdown-row section-header mt-1"><span>Capacite de production</span></div>
|
||||
@@ -479,31 +497,51 @@ function renderBreakdown(d, body) {
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Pieces / semaine</span><span>${cap.pieces_per_week||'—'}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Pieces / mois (30j)</span><span>${cap.pieces_per_month||'—'}</span></div>`;
|
||||
|
||||
document.getElementById('breakdown').innerHTML = plateauAlert + multiPriceHeader + `
|
||||
// Prix finaux
|
||||
const pricePiece = d.final_price;
|
||||
const priceOrder = pricePiece * qty;
|
||||
const finalLabel = multi
|
||||
? `<div class="d-flex justify-content-between align-items-center mt-2 pt-2" style="border-top:2px solid #dee2e6">
|
||||
<span class="fw-bold">PRIX FINAL TTC</span>
|
||||
<div class="d-flex gap-2">
|
||||
<span class="final-price" style="font-size:1.4rem">${fmt2(pricePiece)}</span>
|
||||
<span class="final-price" style="font-size:1.4rem;opacity:.7">× ${qty} = ${fmt2(priceOrder)}</span>
|
||||
</div>
|
||||
</div>`
|
||||
: `<div class="breakdown-row total"><span>PRIX FINAL TTC</span><span class="final-price">${fmt2(pricePiece)}</span></div>`;
|
||||
|
||||
document.getElementById('breakdown').innerHTML = plateauAlert + colHeader + `
|
||||
<div class="breakdown-row section-header"><span>Cout fixe</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Matiere <small class="text-secondary">${matName}</small></span><span>${fmt(d.material_cost)}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Marge matiere (${d._material_margin_pct}%)</span><span>${fmt(d.material_margin)}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Usure — imprimante</span><span>${fmt(d.printer_wear)}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Usure — buse</span><span>${fmt(d.nozzle_wear)}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Usure — plateau</span><span>${fmt(d.plate_wear)}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Electricite <small class="text-secondary">${parseFloat(d.kwh_used).toFixed(3)} kWh × ${d._elec_price} €</small></span><span>${fmt(d.electricity_cost)}</span></div>
|
||||
<div class="breakdown-row subtotal"><span class="fw-semibold">Total cout fixe</span><span class="fw-semibold">${fmt2(d.cout_fixe)}</span></div>
|
||||
${row(`<span class="breakdown-muted ps-2">Matiere <small class="text-secondary">${matName}</small></span>`, d.material_cost, '', true)}
|
||||
${row(`<span class="breakdown-muted ps-2">Marge matiere (${d._material_margin_pct}%)</span>`, d.material_margin, '', true)}
|
||||
${row(`<span class="breakdown-muted ps-2">Usure — imprimante</span>`, d.printer_wear, '', true)}
|
||||
${row(`<span class="breakdown-muted ps-2">Usure — buse</span>`, d.nozzle_wear, '', true)}
|
||||
${row(`<span class="breakdown-muted ps-2">Usure — plateau</span>`, d.plate_wear, '', true)}
|
||||
${row(`<span class="breakdown-muted ps-2">Electricite <small class="text-secondary">${parseFloat(d.kwh_used).toFixed(3)} kWh × ${d._elec_price} €</small></span>`, d.electricity_cost, '', true)}
|
||||
${row(`<span class="fw-semibold">Total cout fixe</span>`, d.cout_fixe, 'subtotal')}
|
||||
|
||||
<div class="breakdown-row section-header"><span>Partie variable</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Manutention <small class="text-secondary">${d._handling_minutes} min × ${d._handling_rate} €/h</small></span><span>${fmt(d.handling_cost)}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Design (x${dm})</span><span>${fmt(d.design_cost)}</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Marge brute (${mgn}%)</span><span>${fmt2(d.margin_amount)}</span></div>
|
||||
<div class="breakdown-row subtotal"><span class="fw-semibold">Total marge <small class="text-muted fw-normal">(${d.marge_pct_on_ht}% du HT)</small></span><span class="fw-semibold">${fmt2(d.total_marge)}</span></div>
|
||||
${row(`<span class="breakdown-muted ps-2">Manutention <small class="text-secondary">${d._handling_minutes} min × ${d._handling_rate} €/h</small></span>`, d.handling_cost, '', true)}
|
||||
${row(`<span class="breakdown-muted ps-2">Design (x${dm})</span>`, d.design_cost, '', true)}
|
||||
${row(`<span class="breakdown-muted ps-2">Marge brute (${mgn}%)</span>`, d.margin_amount)}
|
||||
${row(`<span class="fw-semibold">Total marge <small class="text-muted fw-normal">(${d.marge_pct_on_ht}% du HT)</small></span>`, d.total_marge, 'subtotal')}
|
||||
|
||||
<div class="breakdown-row section-header"><span>Fiscal</span></div>
|
||||
<div class="breakdown-row"><span class="breakdown-muted ps-2">Cotisations BIC vente (${d._cot_pct}%)</span><span>${fmt2(d.cotisations_amount)}</span></div>
|
||||
${vflRow}
|
||||
${otherRow}
|
||||
<div class="breakdown-row total" style="border-top:none"><span>Prix HT</span><span class="final-price">${fmt2(d.prix_ht_net)}</span></div>
|
||||
<div class="breakdown-row ${tvaClass}"><span class="ps-2">${tvaLabel}</span><span>${d._tva_pct > 0 ? fmt2(d.tva_amount) : ''}</span></div>
|
||||
${rowFiscal(`<span class="breakdown-muted ps-2">Cotisations BIC vente (${d._cot_pct}%)</span>`, d.cotisations_amount)}
|
||||
${vflRow}${otherRow}
|
||||
<div class="breakdown-row" style="border-top:none">
|
||||
<span class="fw-bold">Prix HT</span>
|
||||
<span class="final-price" style="font-size:1.3rem;text-align:right;min-width:80px">${fmt2(d.prix_ht_net)}</span>
|
||||
${multi ? `<span class="final-price" style="font-size:1.3rem;text-align:right;min-width:80px;opacity:.75">${fmt2(d.prix_ht_net * qty)}</span>` : ''}
|
||||
</div>
|
||||
<div class="breakdown-row ${tvaClass}">
|
||||
<span class="ps-2">${tvaLabel}</span>
|
||||
<span class="text-end" style="min-width:80px">${d._tva_pct > 0 ? fmt2(d.tva_amount) : ''}</span>
|
||||
${multi && d._tva_pct > 0 ? `<span class="text-end" style="min-width:80px">${fmt2(d.tva_amount * qty)}</span>` : (multi ? '<span style="min-width:80px"></span>' : '')}
|
||||
</div>
|
||||
${disRow}
|
||||
<div class="breakdown-row total"><span>PRIX FINAL TTC</span><span class="final-price">${fmt2(d.final_price)}</span></div>
|
||||
` + capBlock;
|
||||
${finalLabel}
|
||||
` + capBlock;
|
||||
}
|
||||
|
||||
// Init
|
||||
|
||||
Reference in New Issue
Block a user