Saisie unifiée + 4 vues dashboard (mensuel, déclarations URSSAF/TVA)
This commit is contained in:
+144
-74
@@ -1,5 +1,8 @@
|
||||
"""
|
||||
Page de saisie / import : éclater un paiement bancaire en plusieurs dépenses.
|
||||
Saisie unifiée : une opération bancaire signée.
|
||||
- Montant négatif -> dépense(s) (éclatement possible).
|
||||
- Montant positif -> facture (vente) avec création de client à la volée,
|
||||
OU mouvement neutre (ex. remboursement de TVA).
|
||||
"""
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from datetime import date
|
||||
@@ -8,7 +11,10 @@ from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render, redirect
|
||||
|
||||
from .models import Entreprise, Paiement, Depense, Activite, RegimeTva, Categorie
|
||||
from .models import (
|
||||
Entreprise, Client, Facture, LigneFacture, Paiement, Depense,
|
||||
Activite, RegimeTva, Categorie,
|
||||
)
|
||||
|
||||
|
||||
def _dec(val, defaut="0"):
|
||||
@@ -18,85 +24,149 @@ def _dec(val, defaut="0"):
|
||||
return Decimal(defaut)
|
||||
|
||||
|
||||
@login_required
|
||||
def saisie_paiement(request):
|
||||
entreprises = Entreprise.objects.all()
|
||||
|
||||
if request.method == "POST":
|
||||
entreprise = entreprises.filter(pk=request.POST.get("entreprise")).first()
|
||||
d = request.POST.get("date") or date.today().isoformat()
|
||||
libelle = (request.POST.get("libelle") or "").strip()
|
||||
montant_total = _dec(request.POST.get("montant_total"))
|
||||
|
||||
libelles = request.POST.getlist("l_libelle")
|
||||
categories = request.POST.getlist("l_categorie")
|
||||
activites = request.POST.getlist("l_activite")
|
||||
regimes = request.POST.getlist("l_regime")
|
||||
taux = request.POST.getlist("l_taux")
|
||||
montants = request.POST.getlist("l_montant")
|
||||
tvas = request.POST.getlist("l_tva")
|
||||
|
||||
lignes = []
|
||||
for i in range(len(libelles)):
|
||||
m = _dec(montants[i] if i < len(montants) else "0")
|
||||
lib = (libelles[i] or "").strip()
|
||||
if m == 0 and not lib:
|
||||
continue
|
||||
lignes.append({
|
||||
"libelle": lib or "(sans libellé)",
|
||||
"categorie_id": categories[i] if i < len(categories) else "",
|
||||
"activite": activites[i] if i < len(activites) else "",
|
||||
"regime": regimes[i] if i < len(regimes) else RegimeTva.NATIONALE,
|
||||
"taux": _dec(taux[i] if i < len(taux) else "0"),
|
||||
"montant": m,
|
||||
"tva": _dec(tvas[i] if i < len(tvas) else "0"),
|
||||
})
|
||||
|
||||
somme = sum((l["montant"] for l in lignes), Decimal("0"))
|
||||
erreurs = []
|
||||
if not entreprise:
|
||||
erreurs.append("Sélectionne une entreprise.")
|
||||
if not libelle:
|
||||
erreurs.append("Renseigne le libellé du paiement.")
|
||||
if not lignes:
|
||||
erreurs.append("Ajoute au moins une ligne de dépense.")
|
||||
if lignes and abs(somme - montant_total) >= Decimal("0.01"):
|
||||
erreurs.append(
|
||||
f"La somme des lignes ({somme} €) ne correspond pas au "
|
||||
f"montant total ({montant_total} €). Écart : {somme - montant_total} €.")
|
||||
|
||||
if erreurs:
|
||||
for e in erreurs:
|
||||
messages.error(request, e)
|
||||
return render(request, "compta/saisie_paiement.html",
|
||||
_context(entreprises, request.POST))
|
||||
|
||||
cats = {str(c.pk): c for c in Categorie.objects.all()}
|
||||
paiement = Paiement.objects.create(
|
||||
entreprise=entreprise, date=d, libelle=libelle,
|
||||
montant_total=montant_total, sens="debit")
|
||||
for l in lignes:
|
||||
Depense.objects.create(
|
||||
entreprise=entreprise, paiement=paiement, date=d,
|
||||
libelle=l["libelle"], categorie=cats.get(l["categorie_id"]),
|
||||
activite=l["activite"] or "", regime_tva=l["regime"],
|
||||
taux_tva=l["taux"], montant_ttc=l["montant"], montant_tva=l["tva"])
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
f"Paiement « {libelle} » enregistré et éclaté en {len(lignes)} dépense(s).")
|
||||
return redirect("saisie_paiement")
|
||||
|
||||
return render(request, "compta/saisie_paiement.html", _context(entreprises))
|
||||
def _numero_unique(entreprise, annee):
|
||||
base = Facture.objects.filter(
|
||||
entreprise=entreprise, numero__startswith=f"{annee}-").count() + 1
|
||||
while True:
|
||||
numero = f"{annee}-{base:03d}"
|
||||
if not Facture.objects.filter(entreprise=entreprise, numero=numero).exists():
|
||||
return numero
|
||||
base += 1
|
||||
|
||||
|
||||
def _context(entreprises, post=None):
|
||||
return {
|
||||
"entreprises": entreprises,
|
||||
"clients": Client.objects.select_related("entreprise"),
|
||||
"activites": Activite.choices,
|
||||
"regimes": RegimeTva.choices,
|
||||
"categories": Categorie.objects.filter(
|
||||
actif=True).exclude(usage="vente"),
|
||||
"categories_depense": Categorie.objects.filter(actif=True).exclude(usage="vente"),
|
||||
"categories_vente": Categorie.objects.filter(actif=True, usage__in=["vente", "neutre"]),
|
||||
"today": date.today().isoformat(),
|
||||
"post": post,
|
||||
}
|
||||
|
||||
|
||||
@login_required
|
||||
def saisie_paiement(request):
|
||||
entreprises = Entreprise.objects.all()
|
||||
if request.method != "POST":
|
||||
return render(request, "compta/saisie_paiement.html", _context(entreprises))
|
||||
|
||||
entreprise = entreprises.filter(pk=request.POST.get("entreprise")).first()
|
||||
d = request.POST.get("date") or date.today().isoformat()
|
||||
libelle = (request.POST.get("libelle") or "").strip()
|
||||
montant_total = _dec(request.POST.get("montant_total"))
|
||||
annee = int(d[:4])
|
||||
|
||||
erreurs = []
|
||||
if not entreprise:
|
||||
erreurs.append("Sélectionne une entreprise.")
|
||||
if not libelle:
|
||||
erreurs.append("Renseigne le libellé du paiement.")
|
||||
if montant_total == 0:
|
||||
erreurs.append("Le montant ne peut pas être nul.")
|
||||
|
||||
if erreurs:
|
||||
for e in erreurs:
|
||||
messages.error(request, e)
|
||||
return render(request, "compta/saisie_paiement.html",
|
||||
_context(entreprises, request.POST))
|
||||
|
||||
cats = {str(c.pk): c for c in Categorie.objects.all()}
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# CRÉDIT (montant positif) -> facture (vente) ou mouvement neutre
|
||||
# ---------------------------------------------------------------
|
||||
if montant_total > 0:
|
||||
cat = cats.get(request.POST.get("v_categorie"))
|
||||
if cat and cat.exclure_calculs:
|
||||
# Mouvement neutre (ex. remboursement de TVA) : pas de CA.
|
||||
Paiement.objects.create(
|
||||
entreprise=entreprise, date=d, libelle=libelle,
|
||||
montant_total=montant_total, sens="credit", categorie=cat)
|
||||
messages.success(request, f"Mouvement neutre « {libelle} » enregistré.")
|
||||
return redirect("saisie_paiement")
|
||||
|
||||
# Vente -> facture encaissée
|
||||
client = None
|
||||
nouveau = (request.POST.get("v_client_nouveau") or "").strip()
|
||||
if nouveau:
|
||||
client, _ = Client.objects.get_or_create(entreprise=entreprise, nom=nouveau)
|
||||
elif request.POST.get("v_client"):
|
||||
client = Client.objects.filter(
|
||||
entreprise=entreprise, pk=request.POST.get("v_client")).first()
|
||||
if not client:
|
||||
messages.error(request, "Choisis un client existant ou saisis-en un nouveau.")
|
||||
return render(request, "compta/saisie_paiement.html",
|
||||
_context(entreprises, request.POST))
|
||||
|
||||
activite = request.POST.get("v_activite") or entreprise.activite
|
||||
taux = _dec(request.POST.get("v_taux"), "0")
|
||||
designation = (request.POST.get("v_designation") or libelle).strip()
|
||||
ttc = montant_total
|
||||
ht = (ttc / (Decimal("1") + taux / Decimal("100"))).quantize(Decimal("0.01"))
|
||||
|
||||
f = Facture.objects.create(
|
||||
entreprise=entreprise, client=client,
|
||||
numero=_numero_unique(entreprise, annee), activite=activite,
|
||||
date_emission=d, date_encaissement=d, statut="encaissee")
|
||||
LigneFacture.objects.create(
|
||||
facture=f, designation=designation, quantite=Decimal("1"),
|
||||
prix_unitaire_ht=ht, taux_tva=taux)
|
||||
f.recompute_totaux()
|
||||
messages.success(request, f"Vente « {libelle} » enregistrée (facture {f.numero}).")
|
||||
return redirect("saisie_paiement")
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# DÉBIT (montant négatif) -> dépense(s), éclatement possible
|
||||
# ---------------------------------------------------------------
|
||||
total_abs = -montant_total
|
||||
libelles = request.POST.getlist("l_libelle")
|
||||
categories = request.POST.getlist("l_categorie")
|
||||
activites = request.POST.getlist("l_activite")
|
||||
regimes = request.POST.getlist("l_regime")
|
||||
taux = request.POST.getlist("l_taux")
|
||||
montants = request.POST.getlist("l_montant")
|
||||
tvas = request.POST.getlist("l_tva")
|
||||
|
||||
lignes = []
|
||||
for i in range(len(libelles)):
|
||||
m = _dec(montants[i] if i < len(montants) else "0")
|
||||
lib = (libelles[i] or "").strip()
|
||||
if m == 0 and not lib:
|
||||
continue
|
||||
lignes.append({
|
||||
"libelle": lib or "(sans libellé)",
|
||||
"categorie": cats.get(categories[i] if i < len(categories) else ""),
|
||||
"activite": activites[i] if i < len(activites) else "",
|
||||
"regime": regimes[i] if i < len(regimes) else RegimeTva.NATIONALE,
|
||||
"taux": _dec(taux[i] if i < len(taux) else "0"),
|
||||
"montant": m,
|
||||
"tva": _dec(tvas[i] if i < len(tvas) else "0"),
|
||||
})
|
||||
|
||||
somme = sum((l["montant"] for l in lignes), Decimal("0"))
|
||||
if not lignes:
|
||||
messages.error(request, "Ajoute au moins une ligne de dépense.")
|
||||
return render(request, "compta/saisie_paiement.html",
|
||||
_context(entreprises, request.POST))
|
||||
if abs(somme - total_abs) >= Decimal("0.01"):
|
||||
messages.error(request,
|
||||
f"La somme des lignes ({somme} €) ne correspond pas au montant "
|
||||
f"({total_abs} €). Écart : {somme - total_abs} €.")
|
||||
return render(request, "compta/saisie_paiement.html",
|
||||
_context(entreprises, request.POST))
|
||||
|
||||
paiement = Paiement.objects.create(
|
||||
entreprise=entreprise, date=d, libelle=libelle,
|
||||
montant_total=total_abs, sens="debit")
|
||||
for l in lignes:
|
||||
Depense.objects.create(
|
||||
entreprise=entreprise, paiement=paiement, date=d,
|
||||
libelle=l["libelle"], categorie=l["categorie"],
|
||||
activite=l["activite"] or "", regime_tva=l["regime"],
|
||||
taux_tva=l["taux"], montant_ttc=l["montant"], montant_tva=l["tva"])
|
||||
messages.success(request,
|
||||
f"Paiement « {libelle} » enregistré ({len(lignes)} dépense(s)).")
|
||||
return redirect("saisie_paiement")
|
||||
|
||||
Reference in New Issue
Block a user