Autres taxes CFE param, % marge sur HT, Prix HT/TVA/TTC séparés, clients sans coeff

This commit is contained in:
Jo
2026-07-06 13:23:19 +02:00
parent 274ec01239
commit dbbbc92d91
5 changed files with 104 additions and 57 deletions
+25 -15
View File
@@ -100,6 +100,7 @@ def init_db():
('cotisations_rate_pct','12.3'),
('vfl_rate_pct','1.0'),
('tva_rate_pct','20.0'),
('other_taxes_rate_pct','1.0'),
('printer_power_kw','0.35'),
]
conn.executemany('INSERT OR IGNORE INTO settings VALUES (?,?)', defaults)
@@ -115,6 +116,7 @@ def init_db():
'ALTER TABLE jobs ADD COLUMN cotisations_amount REAL',
'ALTER TABLE jobs ADD COLUMN vfl_amount REAL',
'ALTER TABLE jobs ADD COLUMN tva_amount REAL',
'ALTER TABLE jobs ADD COLUMN other_taxes_amount REAL',
]
for sql in migrations:
try:
@@ -214,10 +216,11 @@ def calc(weight_g, print_time_s, design_mult, gross_margin_pct, discount_pct,
total_marge= handling + design + margin
prix_ht = cout_fixe + total_marge # = sous_total + margin
cot_rate = s.get('cotisations_rate_pct', 12.3) / 100
vfl_rate = s.get('vfl_rate_pct', 0.0) / 100
tva_rate = s.get('tva_rate_pct', 0.0) / 100
total_charges = cot_rate + vfl_rate
cot_rate = s.get('cotisations_rate_pct', 12.3) / 100
vfl_rate = s.get('vfl_rate_pct', 0.0) / 100
tva_rate = s.get('tva_rate_pct', 0.0) / 100
other_taxes_rate = s.get('other_taxes_rate_pct', 1.0) / 100
total_charges = cot_rate + vfl_rate + other_taxes_rate
# Charges calculées sur le CA (prix de vente HT net)
# prix_ht_net = prix_ht / (1 - total_charges) so that charges = prix_ht_net * total_charges
@@ -226,11 +229,15 @@ def calc(weight_g, print_time_s, design_mult, gross_margin_pct, discount_pct,
else:
prix_ht_net = prix_ht
cot_amount = prix_ht_net * cot_rate
vfl_amount = prix_ht_net * vfl_rate
tva_amount = prix_ht_net * tva_rate
prix_ttc = prix_ht_net + tva_amount
final = prix_ttc * (1 - discount_pct / 100)
cot_amount = prix_ht_net * cot_rate
vfl_amount = prix_ht_net * vfl_rate
other_taxes_amount = prix_ht_net * other_taxes_rate
tva_amount = prix_ht_net * tva_rate
prix_ttc = prix_ht_net + tva_amount
final = prix_ttc * (1 - discount_pct / 100)
# % de marge effective sur le prix HT final
marge_pct_on_ht = (total_marge / prix_ht_net * 100) if prix_ht_net > 0 else 0
return {
# Lignes détaillées
@@ -248,20 +255,23 @@ def calc(weight_g, print_time_s, design_mult, gross_margin_pct, discount_pct,
'subtotal': round(sous_total, 4),
'margin_amount': round(margin, 4),
'total_marge': round(total_marge, 4),
'marge_pct_on_ht': round(marge_pct_on_ht, 1),
'prix_ht': round(prix_ht, 4),
# Fiscal
'cotisations_amount': round(cot_amount, 4),
'vfl_amount': round(vfl_amount, 4),
'tva_amount': round(tva_amount, 4),
'prix_ht_net': round(prix_ht_net, 4),
'prix_ttc': round(prix_ttc, 4),
'cotisations_amount': round(cot_amount, 4),
'vfl_amount': round(vfl_amount, 4),
'other_taxes_amount': round(other_taxes_amount, 4),
'tva_amount': round(tva_amount, 4),
'prix_ht_net': round(prix_ht_net, 4),
'prix_ttc': round(prix_ttc, 4),
# Compat
'tax_amount': round(cot_amount + vfl_amount, 4),
'tax_amount': round(cot_amount + vfl_amount + other_taxes_amount, 4),
'final_price': round(final, 2),
# Taux et paramètres appliqués (pour affichage)
'_cot_pct': round(cot_rate * 100, 2),
'_vfl_pct': round(vfl_rate * 100, 2),
'_tva_pct': round(tva_rate * 100, 2),
'_other_taxes_pct': round(other_taxes_rate * 100, 2),
'_material_margin_pct': s['material_margin_pct'],
'_handling_rate': s['handling_rate_per_hour'],
'_handling_minutes': s['handling_minutes_per_plate'],
+1 -9
View File
@@ -15,8 +15,7 @@
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Nom</th><th>Email</th><th>Coeff design par défaut</th>
<th>Commandes</th><th>CA total</th><th></th>
<th>Nom</th><th>Email</th><th>Commandes</th><th>CA total</th><th></th>
</tr>
</thead>
<tbody>
@@ -24,13 +23,6 @@
<tr>
<td class="fw-semibold">{{ c.name }}</td>
<td class="text-muted">{{ c.email or '—' }}</td>
<td>
{% if c.design_multiplier <= 0.80 %}
<span class="badge bg-success">×{{ c.design_multiplier }} (STL fourni)</span>
{% else %}
<span class="badge bg-primary">×{{ c.design_multiplier }}</span>
{% endif %}
</td>
<td>{{ c.job_count }}</td>
<td class="fw-semibold">{{ c.total_revenue or 0 }} €</td>
<td>
+25 -16
View File
@@ -62,15 +62,14 @@
<!-- Cout fixe -->
<div class="breakdown-row section-header"><span>Cout fixe</span></div>
<div class="breakdown-row"><span class="breakdown-muted ps-2">Matiere</span><span>{{ "%.4f"|format(job.material_cost) }} €</span></div>
<div class="breakdown-row"><span class="breakdown-muted ps-2">Matiere{% if job.material_name %} <small class="text-secondary">{{ job.material_name }}</small>{% endif %}</span><span>{{ "%.4f"|format(job.material_cost) }} €</span></div>
<div class="breakdown-row"><span class="breakdown-muted ps-2">Marge matiere</span><span>{{ "%.4f"|format(job.material_margin) }} €</span></div>
<div class="breakdown-row"><span class="breakdown-muted ps-2">Usure machine</span><span>{{ "%.4f"|format(job.wear_cost) }} €</span></div>
<div class="breakdown-row"><span class="breakdown-muted ps-2">Electricite</span><span>{{ "%.4f"|format(job.electricity_cost) }} €</span></div>
{% set cout_f = job.cout_fixe or (job.material_cost + job.material_margin + job.wear_cost + job.electricity_cost) %}
<div class="breakdown-row subtotal">
<span class="fw-semibold">Total cout fixe</span>
<span class="fw-semibold">
{% if job.cout_fixe %}{{ "%.2f"|format(job.cout_fixe) }}{% else %}{{ "%.2f"|format(job.material_cost + job.material_margin + job.wear_cost + job.electricity_cost) }}{% endif %} €
</span>
<span class="fw-semibold">{{ "%.2f"|format(cout_f) }} €</span>
</div>
<!-- Partie variable -->
@@ -78,18 +77,20 @@
<div class="breakdown-row"><span class="breakdown-muted ps-2">Manutention</span><span>{{ "%.4f"|format(job.handling_cost) }} €</span></div>
<div class="breakdown-row"><span class="breakdown-muted ps-2">Design (×{{ job.design_multiplier }})</span><span>{{ "%.4f"|format(job.design_cost) }} €</span></div>
<div class="breakdown-row"><span class="breakdown-muted ps-2">Marge brute ({{ job.gross_margin_pct }}%)</span><span>{{ "%.2f"|format(job.margin_amount) }} €</span></div>
{% set tot_m = job.total_marge or (job.handling_cost + job.design_cost + job.margin_amount) %}
{% set prix_ttc = job.final_price / (1 - job.discount_pct / 100) if job.discount_pct < 100 else job.final_price %}
{% set prix_ht_net = prix_ttc - (job.tva_amount or 0) %}
{% set marge_pct_ht = (tot_m / prix_ht_net * 100)|round(1) if prix_ht_net > 0 else 0 %}
<div class="breakdown-row subtotal">
<span class="fw-semibold">Total marge</span>
<span class="fw-semibold">
{% if job.total_marge %}{{ "%.2f"|format(job.total_marge) }}{% else %}{{ "%.2f"|format(job.handling_cost + job.design_cost + job.margin_amount) }}{% endif %} €
</span>
<span class="fw-semibold">Total marge <small class="text-muted fw-normal">({{ marge_pct_ht }}% du HT)</small></span>
<span class="fw-semibold">{{ "%.2f"|format(tot_m) }} €</span>
</div>
<!-- Fiscal -->
<div class="breakdown-row section-header"><span>Fiscal</span></div>
<div class="breakdown-row">
<span class="breakdown-muted ps-2">Cotisations sociales</span>
<span>{% if job.cotisations_amount %}{{ "%.2f"|format(job.cotisations_amount) }}{% else %}{{ "%.2f"|format(job.tax_amount) }}{% endif %} €</span>
<span class="breakdown-muted ps-2">Cotisations BIC vente</span>
<span>{% if job.cotisations_amount %}{{ "%.2f"|format(job.cotisations_amount) }}{% else %}{{ "%.2f"|format(job.tax_amount or 0) }}{% endif %} €</span>
</div>
{% if job.vfl_amount and job.vfl_amount > 0 %}
<div class="breakdown-row">
@@ -97,27 +98,35 @@
<span>{{ "%.2f"|format(job.vfl_amount) }} €</span>
</div>
{% endif %}
{% if job.other_taxes_amount and job.other_taxes_amount > 0 %}
<div class="breakdown-row">
<span class="breakdown-muted ps-2">Autres taxes / CFE</span>
<span>{{ "%.2f"|format(job.other_taxes_amount) }} €</span>
</div>
{% endif %}
<div class="breakdown-row total" style="border-top:none">
<span>Prix HT</span>
<span class="final-price">{{ "%.2f"|format(prix_ht_net) }} €</span>
</div>
{% if job.tva_amount and job.tva_amount > 0 %}
<div class="breakdown-row">
<span class="breakdown-muted ps-2">TVA</span>
<span class="ps-2">TVA</span>
<span>{{ "%.2f"|format(job.tva_amount) }} €</span>
</div>
{% endif %}
{% if job.discount_pct > 0 %}
<div class="breakdown-row text-danger">
<span class="ps-2">Remise ({{ job.discount_pct }}%)</span>
<span>{{ "%.2f"|format(job.final_price / (1 - job.discount_pct/100) * (job.discount_pct/100)) }} €</span>
<span>{{ "%.2f"|format(prix_ttc * job.discount_pct / 100) }} €</span>
</div>
{% endif %}
<div class="breakdown-row total">
<span>PRIX FINAL</span>
<span>PRIX FINAL TTC</span>
<span class="final-price">{{ "%.2f"|format(job.final_price) }} €</span>
</div>
<!-- Barre de composition -->
{% set cout_f = job.cout_fixe or (job.material_cost + job.material_margin + job.wear_cost + job.electricity_cost) %}
{% set tot_m = job.total_marge or (job.handling_cost + job.design_cost + job.margin_amount) %}
{% set fiscal = (job.cotisations_amount or job.tax_amount or 0) + (job.vfl_amount or 0) + (job.tva_amount or 0) %}
{% set fiscal = (job.cotisations_amount or job.tax_amount or 0) + (job.vfl_amount or 0) + (job.other_taxes_amount or 0) + (job.tva_amount or 0) %}
{% set total = job.final_price %}
{% if total > 0 %}
<div class="mt-4">
+35 -14
View File
@@ -178,10 +178,16 @@
<label class="form-label fw-semibold d-flex justify-content-between">
Marge brute <span id="marginDisplay" class="fw-bold" style="color:#ff6b35">27.5 %</span>
</label>
<div class="d-flex gap-2 mb-2" id="marginButtons">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setMargin(20)">20%</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setMargin(25)">25%</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="setMargin(30)">30%</button>
<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="20" max="35" step="0.5" value="27.5">
min="0" max="100" step="0.5" value="27.5">
<div class="d-flex justify-content-between" style="font-size:.75rem;color:#6b7280">
<span>20%</span><span>35%</span>
<span>0%</span><span>100%</span>
</div>
</div>
<div class="mb-3">
@@ -271,8 +277,19 @@ document.getElementById('design_multiplier').addEventListener('input', function(
});
// ── Marge ─────────────────────────────────────────────────────────────────────
function setMargin(val) {
document.getElementById('gross_margin_pct').value = val;
updateMarginDisplay(val);
recalc();
}
function updateMarginDisplay(val) {
document.getElementById('marginDisplay').textContent = parseFloat(val).toFixed(1) + ' %';
val = parseFloat(val);
document.getElementById('marginDisplay').textContent = val.toFixed(1) + ' %';
[20, 25, 30, 35].forEach(function(v) {
const btn = document.querySelector('#marginButtons button[onclick="setMargin(' + v + ')"]');
if (btn) btn.className = 'btn btn-sm ' + (Math.abs(val - v) < 0.01 ? 'btn-secondary' : 'btn-outline-secondary');
});
}
const marginSlider = document.getElementById('gross_margin_pct');
@@ -374,20 +391,22 @@ function renderBreakdown(d, body) {
const matOpt = matSel.options[matSel.selectedIndex];
const matName = matOpt.value ? (matOpt.dataset.name || matOpt.text.split('—')[0].trim()) : 'global';
// Remise
const dis = 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>` : '';
// TVA — toujours affichee
const tvaLabel = d._tva_pct > 0
? `TVA ${d._tva_pct}% — ${fmt2(d.tva_amount)}`
: `TVA — franchise en base`;
// 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>` : '';
document.getElementById('breakdown').innerHTML = `
<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>
@@ -402,13 +421,15 @@ function renderBreakdown(d, body) {
<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</span><span class="fw-semibold">${fmt2(d.total_marge)}</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>
<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}
<div class="breakdown-row ${tvaClass}"><span class="ps-2">${tvaLabel}</span></div>
${dis}
${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>
${disRow}
<div class="breakdown-row total"><span>PRIX FINAL TTC</span><span class="final-price">${fmt2(d.final_price)}</span></div>`;
}
+18 -3
View File
@@ -130,6 +130,18 @@
Mettre <strong>0</strong> si vous n'avez pas opte pour le VFL (impot sur declaration classique).
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Autres taxes a prevoir (%)</label>
<input type="number" name="other_taxes_rate_pct" class="form-control"
step="0.1" value="{{ settings.get('other_taxes_rate_pct', 1.0) }}">
<div class="form-text">
<strong>~1%</strong> — provision pour taxes diverses non incluses ci-dessus.
Exemples : <strong>CFE</strong> (Cotisation Fonciere des Entreprises, due des la 2e annee
d'activite, montant variable selon la commune),
frais bancaires, comptable, etc.
Mettre <strong>0</strong> si non concerne.
</div>
</div>
<hr>
<div class="mb-2">
<label class="form-label fw-semibold">TVA (%)</label>
@@ -140,11 +152,14 @@
La TVA s'ajoute au prix HT et est collectee pour l'Etat (non conservee).
</div>
</div>
{% set total_charges = settings.cotisations_rate_pct + settings.vfl_rate_pct %}
{% set other = settings.get('other_taxes_rate_pct', 1.0) %}
{% set total_charges = settings.cotisations_rate_pct + settings.vfl_rate_pct + other %}
<div class="alert alert-info py-2 mt-3 mb-0 small">
<i class="bi bi-info-circle me-1"></i>
Charges sur CA HT : <strong>{{ total_charges }} %</strong>
(cotisations {{ settings.cotisations_rate_pct }}% + VFL {{ settings.vfl_rate_pct }}%)
Charges sur CA HT : <strong>{{ "%.1f"|format(total_charges) }} %</strong>
(cotisations {{ settings.cotisations_rate_pct }}%
+ VFL {{ settings.vfl_rate_pct }}%
+ autres {{ other }}%)
{% if settings.tva_rate_pct > 0 %}
— TVA {{ settings.tva_rate_pct }}% collectee et reversee a l'Etat
{% else %}