forked from seb_vallee/BillManager
132 lines
5.5 KiB
HTML
132 lines
5.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ titre }}{% endblock %}
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1>{{ titre }}</h1>
|
|
<a href="/devis/" class="btn">← Retour</a>
|
|
</div>
|
|
|
|
<form method="post" class="form-card" id="form-devis">
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label>Client *</label>
|
|
<select name="client_id" required>
|
|
<option value="">— Sélectionner —</option>
|
|
{% for c in clients %}
|
|
<option value="{{ c.id }}" {% if devis and devis.client_id == c.id %}selected{% endif %}>
|
|
{{ c.nom }} — {{ c.ville }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Date d'émission *</label>
|
|
<input type="date" name="date_emission" required value="{{ date_aujourd_hui }}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Date de validité *</label>
|
|
<input type="date" name="date_validite" required value="{{ date_validite_defaut }}">
|
|
</div>
|
|
<div class="form-group full">
|
|
<label>Conditions</label>
|
|
<textarea name="conditions" rows="2">{{ devis.conditions if devis else '' }}</textarea>
|
|
</div>
|
|
<div class="form-group full">
|
|
<label>Notes internes</label>
|
|
<textarea name="notes" rows="2">{{ devis.notes if devis else '' }}</textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<h2>Lignes</h2>
|
|
<table id="table-lignes">
|
|
<thead>
|
|
<tr>
|
|
<th>Description</th>
|
|
<th>Qté</th>
|
|
<th>PU HT (€)</th>
|
|
<th>Total HT</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="lignes-body">
|
|
{% if devis and devis.lignes %}
|
|
{% for l in devis.lignes %}
|
|
<tr class="ligne-row">
|
|
<td><input type="text" class="ligne-desc" placeholder="Description" value="{{ l.description }}" required></td>
|
|
<td><input type="number" class="ligne-qte" step="0.01" min="0.01" value="{{ l.quantite }}" required></td>
|
|
<td><input type="number" class="ligne-pu" step="0.01" min="0" value="{{ l.prix_unitaire_ht }}" required></td>
|
|
<td class="ligne-total">{{ (l.quantite * l.prix_unitaire_ht) | round(2) }} €</td>
|
|
<td><button type="button" class="btn btn-sm btn-danger btn-suppr-ligne">✕</button></td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
<button type="button" id="btn-ajouter-ligne" class="btn">+ Ajouter une ligne</button>
|
|
|
|
<div class="total-bloc">
|
|
<strong>Total HT : <span id="total-ht">0,00 €</span></strong>
|
|
<br><small>TVA non applicable — art. 293B du CGI</small>
|
|
</div>
|
|
|
|
<input type="hidden" name="lignes_json" id="lignes_json">
|
|
|
|
<div class="form-actions">
|
|
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
|
<a href="/devis/" class="btn">Annuler</a>
|
|
</div>
|
|
</form>
|
|
|
|
<script>
|
|
function recalculer() {
|
|
let total = 0;
|
|
document.querySelectorAll('.ligne-row').forEach(row => {
|
|
const qte = parseFloat(row.querySelector('.ligne-qte').value) || 0;
|
|
const pu = parseFloat(row.querySelector('.ligne-pu').value) || 0;
|
|
const t = qte * pu;
|
|
row.querySelector('.ligne-total').textContent = t.toFixed(2).replace('.', ',') + ' €';
|
|
total += t;
|
|
});
|
|
document.getElementById('total-ht').textContent = total.toFixed(2).replace('.', ',') + ' €';
|
|
}
|
|
function ajouterLigne() {
|
|
const tbody = document.getElementById('lignes-body');
|
|
const tr = document.createElement('tr');
|
|
tr.className = 'ligne-row';
|
|
tr.innerHTML = `
|
|
<td><input type="text" class="ligne-desc" placeholder="Description" required></td>
|
|
<td><input type="number" class="ligne-qte" step="0.01" min="0.01" value="1" required></td>
|
|
<td><input type="number" class="ligne-pu" step="0.01" min="0" value="0" required></td>
|
|
<td class="ligne-total">0,00 €</td>
|
|
<td><button type="button" class="btn btn-sm btn-danger btn-suppr-ligne">✕</button></td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
tr.querySelectorAll('input').forEach(i => i.addEventListener('input', recalculer));
|
|
tr.querySelector('.btn-suppr-ligne').addEventListener('click', () => { tr.remove(); recalculer(); });
|
|
}
|
|
document.getElementById('btn-ajouter-ligne').addEventListener('click', ajouterLigne);
|
|
document.querySelectorAll('.ligne-row').forEach(row => {
|
|
row.querySelectorAll('input').forEach(i => i.addEventListener('input', recalculer));
|
|
row.querySelector('.btn-suppr-ligne').addEventListener('click', () => { row.remove(); recalculer(); });
|
|
});
|
|
document.getElementById('form-devis').addEventListener('submit', function(e) {
|
|
const lignes = [];
|
|
let valide = true;
|
|
document.querySelectorAll('.ligne-row').forEach(row => {
|
|
const desc = row.querySelector('.ligne-desc').value.trim();
|
|
const qte = parseFloat(row.querySelector('.ligne-qte').value);
|
|
const pu = parseFloat(row.querySelector('.ligne-pu').value);
|
|
if (!desc || isNaN(qte) || isNaN(pu)) { valide = false; return; }
|
|
lignes.push({ description: desc, quantite: qte, prix_unitaire_ht: pu });
|
|
});
|
|
if (!valide || lignes.length === 0) {
|
|
e.preventDefault();
|
|
alert('Veuillez saisir au moins une ligne valide.');
|
|
return;
|
|
}
|
|
document.getElementById('lignes_json').value = JSON.stringify(lignes);
|
|
});
|
|
recalculer();
|
|
</script>
|
|
{% endblock %}
|