Autres taxes CFE param, % marge sur HT, Prix HT/TVA/TTC séparés, clients sans coeff
This commit is contained in:
@@ -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'],
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user