Taux TVA auto + seed enrichi + import photo collee
This commit is contained in:
+3
-8
@@ -28,8 +28,6 @@ class ScopedAdmin(admin.ModelAdmin):
|
|||||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# ----- Référentiels mutualisés (visibles par tous) -----
|
|
||||||
|
|
||||||
@admin.register(Categorie)
|
@admin.register(Categorie)
|
||||||
class CategorieAdmin(admin.ModelAdmin):
|
class CategorieAdmin(admin.ModelAdmin):
|
||||||
list_display = ("nom", "usage", "exclure_calculs", "actif", "ordre")
|
list_display = ("nom", "usage", "exclure_calculs", "actif", "ordre")
|
||||||
@@ -45,8 +43,6 @@ class BaremeAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ("annee", "activite")
|
list_filter = ("annee", "activite")
|
||||||
|
|
||||||
|
|
||||||
# ----- Entreprise (accès) -----
|
|
||||||
|
|
||||||
@admin.register(Entreprise)
|
@admin.register(Entreprise)
|
||||||
class EntrepriseAdmin(admin.ModelAdmin):
|
class EntrepriseAdmin(admin.ModelAdmin):
|
||||||
list_display = ("nom", "activite", "franchise_tva", "versement_liberatoire",
|
list_display = ("nom", "activite", "franchise_tva", "versement_liberatoire",
|
||||||
@@ -62,14 +58,11 @@ class EntrepriseAdmin(admin.ModelAdmin):
|
|||||||
return Entreprise.accessibles(request.user)
|
return Entreprise.accessibles(request.user)
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
# Seul un superadmin gère les droits d'accès.
|
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
return ("utilisateurs", "groupes")
|
return ("utilisateurs", "groupes")
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|
||||||
# ----- Données cloisonnées par entreprise -----
|
|
||||||
|
|
||||||
@admin.register(Client)
|
@admin.register(Client)
|
||||||
class ClientAdmin(ScopedAdmin):
|
class ClientAdmin(ScopedAdmin):
|
||||||
list_display = ("nom", "entreprise", "email", "siret")
|
list_display = ("nom", "entreprise", "email", "siret")
|
||||||
@@ -108,6 +101,7 @@ class DepenseInline(admin.TabularInline):
|
|||||||
extra = 0
|
extra = 0
|
||||||
fields = ("date", "libelle", "categorie", "activite", "regime_tva",
|
fields = ("date", "libelle", "categorie", "activite", "regime_tva",
|
||||||
"taux_tva", "montant_ttc", "montant_tva")
|
"taux_tva", "montant_ttc", "montant_tva")
|
||||||
|
readonly_fields = ("montant_tva",)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Paiement)
|
@admin.register(Paiement)
|
||||||
@@ -135,10 +129,11 @@ class PaiementAdmin(ScopedAdmin):
|
|||||||
@admin.register(Depense)
|
@admin.register(Depense)
|
||||||
class DepenseAdmin(ScopedAdmin):
|
class DepenseAdmin(ScopedAdmin):
|
||||||
list_display = ("date", "entreprise", "libelle", "categorie", "activite",
|
list_display = ("date", "entreprise", "libelle", "categorie", "activite",
|
||||||
"regime_tva", "montant_ttc", "montant_tva", "tva_autoliq", "paiement")
|
"regime_tva", "taux_tva", "montant_ttc", "montant_tva", "tva_autoliq", "paiement")
|
||||||
list_filter = ("entreprise", "categorie", "regime_tva", "activite", "date")
|
list_filter = ("entreprise", "categorie", "regime_tva", "activite", "date")
|
||||||
search_fields = ("libelle",)
|
search_fields = ("libelle",)
|
||||||
date_hierarchy = "date"
|
date_hierarchy = "date"
|
||||||
|
readonly_fields = ("montant_tva",)
|
||||||
|
|
||||||
@admin.display(description="TVA auto-liquidée")
|
@admin.display(description="TVA auto-liquidée")
|
||||||
def tva_autoliq(self, obj):
|
def tva_autoliq(self, obj):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Charge les données de RÉFÉRENCE (barèmes + catégories) puis un jeu de
|
Référence (barèmes + catégories + groupe) PUIS un jeu de données d'exemple
|
||||||
données d'EXEMPLE (entreprise, clients, factures, dépenses).
|
couvrant tous les modèles : entreprise, clients, factures + lignes, dépenses,
|
||||||
Pour la prod, préférez `seed_reference` (référence seule, sans démo).
|
et opérations bancaires (débit éclaté, crédit neutre).
|
||||||
|
|
||||||
Usage : python manage.py seed_demo
|
Usage : python manage.py seed_demo
|
||||||
"""
|
"""
|
||||||
@@ -11,14 +11,14 @@ from datetime import date
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from compta.models import (
|
from compta.models import (
|
||||||
Entreprise, Client, Facture, LigneFacture, Depense, Activite,
|
Entreprise, Client, Facture, LigneFacture, Depense, Paiement, Activite,
|
||||||
StatutFacture, RegimeTva,
|
StatutFacture, RegimeTva,
|
||||||
)
|
)
|
||||||
from compta.management.commands.seed_reference import charger_reference
|
from compta.management.commands.seed_reference import charger_reference
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Charge la référence + des données d'exemple."
|
help = "Charge la référence + des données d'exemple (tous les modèles)."
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
cat = charger_reference(self.stdout, self.style)
|
cat = charger_reference(self.stdout, self.style)
|
||||||
@@ -26,16 +26,14 @@ class Command(BaseCommand):
|
|||||||
ent, _ = Entreprise.objects.get_or_create(
|
ent, _ = Entreprise.objects.get_or_create(
|
||||||
nom="JB Conseil",
|
nom="JB Conseil",
|
||||||
defaults=dict(activite=Activite.SERVICE_BIC, franchise_tva=False,
|
defaults=dict(activite=Activite.SERVICE_BIC, franchise_tva=False,
|
||||||
taux_tva_defaut=Decimal("20.00"),
|
taux_tva_defaut=Decimal("20.00"), versement_liberatoire=True,
|
||||||
versement_liberatoire=True, email="jb@example.fr",
|
email="jb@example.fr", num_tva_intracom="FR00123456789"))
|
||||||
num_tva_intracom="FR00123456789"))
|
|
||||||
|
|
||||||
clients = {}
|
clients = {}
|
||||||
for nom, mail in [("Acme SARL", "contact@acme.fr"),
|
for nom, mail in [("Acme SARL", "contact@acme.fr"),
|
||||||
("Studio Lumiere", "hello@studiolumiere.fr"),
|
("Studio Lumiere", "hello@studiolumiere.fr"),
|
||||||
("Mairie de Ville", "compta@ville.fr")]:
|
("Mairie de Ville", "compta@ville.fr")]:
|
||||||
c, _ = Client.objects.get_or_create(entreprise=ent, nom=nom,
|
c, _ = Client.objects.get_or_create(entreprise=ent, nom=nom, defaults=dict(email=mail))
|
||||||
defaults=dict(email=mail))
|
|
||||||
clients[nom] = c
|
clients[nom] = c
|
||||||
|
|
||||||
V, S = Activite.VENTE, Activite.SERVICE_BIC
|
V, S = Activite.VENTE, Activite.SERVICE_BIC
|
||||||
@@ -55,32 +53,44 @@ class Command(BaseCommand):
|
|||||||
f, created = Facture.objects.get_or_create(
|
f, created = Facture.objects.get_or_create(
|
||||||
entreprise=ent, numero=numero,
|
entreprise=ent, numero=numero,
|
||||||
defaults=dict(client=clients[client_nom], activite=activite,
|
defaults=dict(client=clients[client_nom], activite=activite,
|
||||||
date_emission=emis, date_encaissement=enc,
|
date_emission=emis, date_encaissement=enc, statut=statut))
|
||||||
statut=statut))
|
|
||||||
if created:
|
if created:
|
||||||
for des, qte, pu in lignes:
|
for des, qte, pu in lignes:
|
||||||
LigneFacture.objects.create(
|
LigneFacture.objects.create(facture=f, designation=des, quantite=Decimal(qte),
|
||||||
facture=f, designation=des, quantite=Decimal(qte),
|
prix_unitaire_ht=Decimal(pu), taux_tva=Decimal("20"))
|
||||||
prix_unitaire_ht=Decimal(pu), taux_tva=Decimal("20"))
|
|
||||||
f.recompute_totaux()
|
f.recompute_totaux()
|
||||||
|
|
||||||
Depense.objects.get_or_create(
|
def depense(libelle, jour, montant_ttc, cat_nom, taux="20",
|
||||||
entreprise=ent, libelle="Bobines PLA/PETG", date=date(2026, 1, 15),
|
regime=RegimeTva.NATIONALE, activite="", paiement=None):
|
||||||
defaults=dict(categorie=cat["Matière première"], activite=Activite.VENTE,
|
# montant_tva est calculé automatiquement dans Depense.save().
|
||||||
montant_ttc=Decimal("96.00"), montant_tva=Decimal("16.00")))
|
Depense.objects.get_or_create(
|
||||||
Depense.objects.get_or_create(
|
entreprise=ent, libelle=libelle, date=date(2026, *jour),
|
||||||
entreprise=ent, libelle="Abonnement outils", date=date(2026, 2, 10),
|
defaults=dict(categorie=cat[cat_nom], activite=activite, paiement=paiement,
|
||||||
defaults=dict(categorie=cat["Abonnement logiciel"],
|
regime_tva=regime, taux_tva=Decimal(taux),
|
||||||
activite=Activite.SERVICE_BIC,
|
montant_ttc=Decimal(montant_ttc)))
|
||||||
montant_ttc=Decimal("34.80"), montant_tva=Decimal("5.80")))
|
|
||||||
Depense.objects.get_or_create(
|
|
||||||
entreprise=ent, libelle="Imprimante 3D (DE)", date=date(2026, 2, 20),
|
|
||||||
defaults=dict(categorie=cat["Matériel et outillage"], activite=Activite.VENTE,
|
|
||||||
regime_tva=RegimeTva.INTRACOM, taux_tva=Decimal("20"),
|
|
||||||
montant_ttc=Decimal("500.00"), montant_tva=Decimal("0")))
|
|
||||||
Depense.objects.get_or_create(
|
|
||||||
entreprise=ent, libelle="URSSAF DE LORRAINE", date=date(2026, 3, 3),
|
|
||||||
defaults=dict(categorie=cat["Cotisation sociale Urssaf"],
|
|
||||||
montant_ttc=Decimal("73.00"), montant_tva=Decimal("0")))
|
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS("Données d'exemple chargées."))
|
# Opération bancaire DÉBIT éclatée : un paiement Amazon = 2 dépenses
|
||||||
|
amazon, _ = Paiement.objects.get_or_create(
|
||||||
|
entreprise=ent, libelle="AMAZON PAYMENTS", date=date(2026, 6, 22),
|
||||||
|
defaults=dict(montant_total=Decimal("24.52"), sens="debit"))
|
||||||
|
depense("Amazon - visserie", (6, 22), "15.82", "Matériel et outillage",
|
||||||
|
activite=Activite.VENTE, paiement=amazon)
|
||||||
|
depense("Amazon - filament", (6, 18), "8.70", "Matériel et outillage",
|
||||||
|
activite=Activite.VENTE, paiement=amazon)
|
||||||
|
|
||||||
|
# Dépenses variées (taux 20 / 10 / intracom / neutre)
|
||||||
|
depense("Bobines PLA/PETG", (1, 15), "96.00", "Matière première", activite=Activite.VENTE)
|
||||||
|
depense("Burger King", (6, 11), "16.15", "Frais de repas hors domicile", taux="10")
|
||||||
|
depense("Abonnement outils", (2, 10), "34.80", "Abonnement logiciel",
|
||||||
|
activite=Activite.SERVICE_BIC)
|
||||||
|
depense("Imprimante 3D (DE)", (2, 20), "500.00", "Matériel et outillage",
|
||||||
|
regime=RegimeTva.INTRACOM, activite=Activite.VENTE)
|
||||||
|
depense("URSSAF DE LORRAINE", (6, 3), "73.00", "Cotisation sociale Urssaf", taux="0")
|
||||||
|
|
||||||
|
# Opération bancaire CRÉDIT neutre : remboursement de TVA
|
||||||
|
Paiement.objects.get_or_create(
|
||||||
|
entreprise=ent, libelle="SIE EPINAL", date=date(2026, 6, 15),
|
||||||
|
defaults=dict(montant_total=Decimal("103.00"), sens="credit",
|
||||||
|
categorie=cat["Remboursement de TVA"]))
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS("Données d'exemple chargées (tous les modèles)."))
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 5.2.15 on 2026-06-30 07:05
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('compta', '0007_alter_paiement_options_entreprise_groupes_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='depense',
|
||||||
|
name='montant_tva',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=Decimal('0'), help_text='Calculée automatiquement depuis le montant payé et le taux.', max_digits=12, verbose_name='TVA déductible (€)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='depense',
|
||||||
|
name='taux_tva',
|
||||||
|
field=models.DecimalField(choices=[(Decimal('0'), '0 %'), (Decimal('5.5'), '5,5 %'), (Decimal('10'), '10 %'), (Decimal('20'), '20 %')], decimal_places=2, default=Decimal('20'), help_text='Achat national : la TVA déductible est calculée. UE/import : taux auto-liquidé.', max_digits=4, verbose_name='Taux de TVA (%)'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='lignefacture',
|
||||||
|
name='taux_tva',
|
||||||
|
field=models.DecimalField(choices=[(Decimal('0'), '0 %'), (Decimal('5.5'), '5,5 %'), (Decimal('10'), '10 %'), (Decimal('20'), '20 %')], decimal_places=2, default=Decimal('0'), max_digits=4),
|
||||||
|
),
|
||||||
|
]
|
||||||
+23
-7
@@ -27,6 +27,10 @@ class RegimeTva(models.TextChoices):
|
|||||||
IMPORT = "import", "Import hors UE (autoliquidation)"
|
IMPORT = "import", "Import hors UE (autoliquidation)"
|
||||||
|
|
||||||
|
|
||||||
|
TAUX_TVA_CHOICES = [(Decimal("0"), "0 %"), (Decimal("5.5"), "5,5 %"),
|
||||||
|
(Decimal("10"), "10 %"), (Decimal("20"), "20 %")]
|
||||||
|
|
||||||
|
|
||||||
class Categorie(models.Model):
|
class Categorie(models.Model):
|
||||||
"""Catégorie globale (mutualisée). exclure_calculs=True => mouvement neutre."""
|
"""Catégorie globale (mutualisée). exclure_calculs=True => mouvement neutre."""
|
||||||
USAGE = [("depense", "Dépense"), ("vente", "Vente"), ("neutre", "Mouvement neutre")]
|
USAGE = [("depense", "Dépense"), ("vente", "Vente"), ("neutre", "Mouvement neutre")]
|
||||||
@@ -84,7 +88,6 @@ class Entreprise(models.Model):
|
|||||||
num_tva_intracom = models.CharField("N° TVA intracommunautaire", max_length=20, blank=True)
|
num_tva_intracom = models.CharField("N° TVA intracommunautaire", max_length=20, blank=True)
|
||||||
vue_tva = models.CharField(
|
vue_tva = models.CharField(
|
||||||
"Mode d'affichage de la déclaration TVA", max_length=10, choices=VUE_TVA, default="simplifie")
|
"Mode d'affichage de la déclaration TVA", max_length=10, choices=VUE_TVA, default="simplifie")
|
||||||
# --- Droits d'accès (multi-utilisateur) ---
|
|
||||||
utilisateurs = models.ManyToManyField(
|
utilisateurs = models.ManyToManyField(
|
||||||
settings.AUTH_USER_MODEL, blank=True, related_name="entreprises",
|
settings.AUTH_USER_MODEL, blank=True, related_name="entreprises",
|
||||||
verbose_name="Utilisateurs autorisés",
|
verbose_name="Utilisateurs autorisés",
|
||||||
@@ -110,7 +113,6 @@ class Entreprise(models.Model):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def accessibles(cls, user):
|
def accessibles(cls, user):
|
||||||
"""Entreprises visibles par un utilisateur (toutes si superadmin)."""
|
|
||||||
if not user or not user.is_authenticated:
|
if not user or not user.is_authenticated:
|
||||||
return cls.objects.none()
|
return cls.objects.none()
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
@@ -179,7 +181,8 @@ class LigneFacture(models.Model):
|
|||||||
designation = models.CharField(max_length=255)
|
designation = models.CharField(max_length=255)
|
||||||
quantite = models.DecimalField(max_digits=12, decimal_places=3, default=Decimal("1"))
|
quantite = models.DecimalField(max_digits=12, decimal_places=3, default=Decimal("1"))
|
||||||
prix_unitaire_ht = models.DecimalField(max_digits=12, decimal_places=2)
|
prix_unitaire_ht = models.DecimalField(max_digits=12, decimal_places=2)
|
||||||
taux_tva = models.DecimalField(max_digits=4, decimal_places=2, default=Decimal("0"))
|
taux_tva = models.DecimalField(max_digits=4, decimal_places=2, default=Decimal("0"),
|
||||||
|
choices=TAUX_TVA_CHOICES)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Ligne de facture"
|
verbose_name = "Ligne de facture"
|
||||||
@@ -244,13 +247,14 @@ class Depense(models.Model):
|
|||||||
"Montant payé (€)", max_digits=12, decimal_places=2,
|
"Montant payé (€)", max_digits=12, decimal_places=2,
|
||||||
help_text="Pour un achat UE/import, c'est le montant HT (sans TVA facturée).")
|
help_text="Pour un achat UE/import, c'est le montant HT (sans TVA facturée).")
|
||||||
montant_tva = models.DecimalField(
|
montant_tva = models.DecimalField(
|
||||||
"TVA déductible facturée (€)", max_digits=12, decimal_places=2, default=Decimal("0"),
|
"TVA déductible (€)", max_digits=12, decimal_places=2, default=Decimal("0"),
|
||||||
help_text="TVA payée au fournisseur (achats nationaux uniquement).")
|
help_text="Calculée automatiquement depuis le montant payé et le taux.")
|
||||||
regime_tva = models.CharField(
|
regime_tva = models.CharField(
|
||||||
"Régime de TVA", max_length=12, choices=RegimeTva.choices, default=RegimeTva.NATIONALE)
|
"Régime de TVA", max_length=12, choices=RegimeTva.choices, default=RegimeTva.NATIONALE)
|
||||||
taux_tva = models.DecimalField(
|
taux_tva = models.DecimalField(
|
||||||
"Taux TVA autoliquidation (%)", max_digits=4, decimal_places=2, default=Decimal("0"),
|
"Taux de TVA (%)", max_digits=4, decimal_places=2, default=Decimal("20"),
|
||||||
help_text="Taux français à auto-liquider pour les achats UE/import (ex. 20).")
|
choices=TAUX_TVA_CHOICES,
|
||||||
|
help_text="Achat national : la TVA déductible est calculée. UE/import : taux auto-liquidé.")
|
||||||
cree_le = models.DateTimeField(auto_now_add=True)
|
cree_le = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -261,6 +265,18 @@ class Depense(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.date} — {self.libelle}"
|
return f"{self.date} — {self.libelle}"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
# TVA déductible calculée : incluse dans le TTC (national), nulle en autoliq.
|
||||||
|
t = self.taux_tva or Decimal("0")
|
||||||
|
if self.autoliquidee:
|
||||||
|
self.montant_tva = Decimal("0.00")
|
||||||
|
elif t:
|
||||||
|
self.montant_tva = ((self.montant_ttc or Decimal("0")) * t
|
||||||
|
/ (Decimal("100") + t)).quantize(Decimal("0.01"))
|
||||||
|
else:
|
||||||
|
self.montant_tva = Decimal("0.00")
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def neutre(self):
|
def neutre(self):
|
||||||
return bool(self.categorie and self.categorie.exclure_calculs)
|
return bool(self.categorie and self.categorie.exclure_calculs)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not lignes %}
|
{% if not lignes %}
|
||||||
<form method="post" enctype="multipart/form-data" class="card">
|
<form method="post" enctype="multipart/form-data" class="card" id="srcForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="grid3">
|
<div class="grid3">
|
||||||
<div>
|
<div>
|
||||||
@@ -59,8 +59,9 @@
|
|||||||
<input type="number" name="annee" value="{{ annee_defaut }}">
|
<input type="number" name="annee" value="{{ annee_defaut }}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Capture d'écran (OCR)</label>
|
<label>Photo / capture (OCR) — fichier, appareil photo, ou <b>coller</b> (Ctrl/Cmd+V)</label>
|
||||||
<input type="file" name="image" accept="image/*">
|
<input type="file" name="image" id="imgInput" accept="image/*" capture="environment">
|
||||||
|
<div id="pasteInfo" class="sub" style="margin-top:4px"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label>…ou fichier CSV</label>
|
<label>…ou fichier CSV</label>
|
||||||
@@ -110,5 +111,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.addEventListener('paste', function(e){
|
||||||
|
var input = document.getElementById('imgInput');
|
||||||
|
if(!input || !e.clipboardData) return;
|
||||||
|
for(var i=0; i<e.clipboardData.items.length; i++){
|
||||||
|
var it = e.clipboardData.items[i];
|
||||||
|
if(it.type && it.type.indexOf('image') === 0){
|
||||||
|
var dt = new DataTransfer();
|
||||||
|
dt.items.add(it.getAsFile());
|
||||||
|
input.files = dt.files;
|
||||||
|
var info = document.getElementById('pasteInfo');
|
||||||
|
if(info){ info.textContent = 'Image collée — clique « Analyser ».'; info.style.color = '#34d399'; }
|
||||||
|
e.preventDefault();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>Saisie d'une opération</h1>
|
<h1>Saisie d'une opération</h1>
|
||||||
<div class="sub">Montant <b>négatif</b> = dépense · <b>positif</b> = vente/encaissement.
|
<div class="sub">Montant <b>négatif</b> = dépense · <b>positif</b> = vente/encaissement.
|
||||||
· <a href="/dashboard/">← Tableau de bord</a></div>
|
· <a href="/dashboard/">← Tableau de bord</a> · <a href="/import/">Importer un relevé</a></div>
|
||||||
|
|
||||||
{% if messages %}{% for m in messages %}<div class="msg {{ m.tags }}">{{ m }}</div>{% endfor %}{% endif %}
|
{% if messages %}{% for m in messages %}<div class="msg {{ m.tags }}">{{ m }}</div>{% endfor %}{% endif %}
|
||||||
|
|
||||||
@@ -83,13 +83,12 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:22%">Libellé / vendeur</th>
|
<th style="width:24%">Libellé / vendeur</th>
|
||||||
<th style="width:16%">Catégorie</th>
|
<th style="width:16%">Catégorie</th>
|
||||||
<th style="width:14%">Activité</th>
|
<th style="width:15%">Activité</th>
|
||||||
<th style="width:16%">Régime TVA</th>
|
<th style="width:16%">Régime TVA</th>
|
||||||
<th style="width:7%">Taux %</th>
|
<th style="width:9%">Taux TVA</th>
|
||||||
<th style="width:11%">Montant €</th>
|
<th style="width:13%">Montant €</th>
|
||||||
<th style="width:11%">TVA déduct.</th>
|
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -125,7 +124,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Taux TVA (%)</label>
|
<label>Taux TVA (%)</label>
|
||||||
<input type="number" step="0.01" name="v_taux" value="20">
|
<select name="v_taux">
|
||||||
|
<option value="20">20 %</option><option value="10">10 %</option>
|
||||||
|
<option value="5.5">5,5 %</option><option value="0">0 %</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Désignation</label>
|
<label>Désignation</label>
|
||||||
@@ -139,9 +141,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">Le montant TTC encaissé = le montant signé saisi plus haut. HT et TVA
|
<div class="hint">Le montant TTC encaissé = le montant signé saisi plus haut.
|
||||||
sont calculés depuis le taux. Si tu choisis une catégorie <b>neutre</b>, aucune facture
|
Une catégorie <b>neutre</b> n'enregistre pas de vente (mouvement de trésorerie).</div>
|
||||||
n'est créée (simple mouvement de trésorerie).</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
@@ -169,9 +170,13 @@
|
|||||||
{% for val,lab in regimes %}<option value="{{ val }}">{{ lab }}</option>{% endfor %}
|
{% for val,lab in regimes %}<option value="{{ val }}">{{ lab }}</option>{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td><input type="number" step="0.01" name="l_taux" value="0" class="num"></td>
|
<td>
|
||||||
|
<select name="l_taux">
|
||||||
|
<option value="20">20 %</option><option value="10">10 %</option>
|
||||||
|
<option value="5.5">5,5 %</option><option value="0">0 %</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
<td><input type="number" step="0.01" name="l_montant" class="num montant" oninput="recalc()"></td>
|
<td><input type="number" step="0.01" name="l_montant" class="num montant" oninput="recalc()"></td>
|
||||||
<td><input type="number" step="0.01" name="l_tva" value="0" class="num"></td>
|
|
||||||
<td><button type="button" class="mini" onclick="this.closest('tr').remove();recalc()">✕</button></td>
|
<td><button type="button" class="mini" onclick="this.closest('tr').remove();recalc()">✕</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+4
-10
@@ -19,13 +19,13 @@ CENT = Decimal("0.01")
|
|||||||
MOIS = {"janvier": 1, "février": 2, "fevrier": 2, "mars": 3, "avril": 4, "mai": 5,
|
MOIS = {"janvier": 1, "février": 2, "fevrier": 2, "mars": 3, "avril": 4, "mai": 5,
|
||||||
"juin": 6, "juillet": 7, "août": 8, "aout": 8, "septembre": 9,
|
"juin": 6, "juillet": 7, "août": 8, "aout": 8, "septembre": 9,
|
||||||
"octobre": 10, "novembre": 11, "décembre": 12, "decembre": 12}
|
"octobre": 10, "novembre": 11, "décembre": 12, "decembre": 12}
|
||||||
AMOUNT_RE = re.compile(r"^-?\d[\d .]*[.,]\d{2}$")
|
AMOUNT_RE = re.compile(r"^-?\d[\d .]*[.,]\d{2}$")
|
||||||
|
|
||||||
|
|
||||||
def _dec(v, d="0"):
|
def _dec(v, d="0"):
|
||||||
try:
|
try:
|
||||||
s = str(v or d)
|
s = str(v or d)
|
||||||
for ch in (" ", " ", " "):
|
for ch in (" ", " ", " "):
|
||||||
s = s.replace(ch, "")
|
s = s.replace(ch, "")
|
||||||
s = s.replace(",", ".").strip()
|
s = s.replace(",", ".").strip()
|
||||||
return Decimal(s or d)
|
return Decimal(s or d)
|
||||||
@@ -224,16 +224,10 @@ def _creer(entreprise, dt, libelle, montant, taux, regime, categorie):
|
|||||||
prix_unitaire_ht=ht, taux_tva=taux)
|
prix_unitaire_ht=ht, taux_tva=taux)
|
||||||
f.recompute_totaux()
|
f.recompute_totaux()
|
||||||
return "vente"
|
return "vente"
|
||||||
abs_m = -montant
|
# montant_tva est calculé automatiquement dans Depense.save() depuis le taux.
|
||||||
if regime in (RegimeTva.INTRACOM, RegimeTva.IMPORT) or neutre:
|
|
||||||
ttc, tva = abs_m, Decimal("0")
|
|
||||||
else:
|
|
||||||
ttc = abs_m
|
|
||||||
tva = (abs_m * taux / (Decimal("100") + taux)).quantize(CENT) if taux else Decimal("0")
|
|
||||||
Depense.objects.create(entreprise=entreprise, date=dt, libelle=libelle,
|
Depense.objects.create(entreprise=entreprise, date=dt, libelle=libelle,
|
||||||
categorie=categorie, regime_tva=regime,
|
categorie=categorie, regime_tva=regime,
|
||||||
taux_tva=taux if regime != RegimeTva.NATIONALE else Decimal("0"),
|
taux_tva=taux, montant_ttc=-montant)
|
||||||
montant_ttc=ttc, montant_tva=tva)
|
|
||||||
return "depense"
|
return "depense"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,6 @@ def saisie_paiement(request):
|
|||||||
regimes = request.POST.getlist("l_regime")
|
regimes = request.POST.getlist("l_regime")
|
||||||
taux = request.POST.getlist("l_taux")
|
taux = request.POST.getlist("l_taux")
|
||||||
montants = request.POST.getlist("l_montant")
|
montants = request.POST.getlist("l_montant")
|
||||||
tvas = request.POST.getlist("l_tva")
|
|
||||||
|
|
||||||
lignes = []
|
lignes = []
|
||||||
for i in range(len(libelles)):
|
for i in range(len(libelles)):
|
||||||
@@ -132,7 +131,6 @@ def saisie_paiement(request):
|
|||||||
"regime": regimes[i] if i < len(regimes) else RegimeTva.NATIONALE,
|
"regime": regimes[i] if i < len(regimes) else RegimeTva.NATIONALE,
|
||||||
"taux": _dec(taux[i] if i < len(taux) else "0"),
|
"taux": _dec(taux[i] if i < len(taux) else "0"),
|
||||||
"montant": m,
|
"montant": m,
|
||||||
"tva": _dec(tvas[i] if i < len(tvas) else "0"),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
somme = sum((l["montant"] for l in lignes), Decimal("0"))
|
somme = sum((l["montant"] for l in lignes), Decimal("0"))
|
||||||
@@ -148,9 +146,10 @@ def saisie_paiement(request):
|
|||||||
paiement = Paiement.objects.create(
|
paiement = Paiement.objects.create(
|
||||||
entreprise=entreprise, date=d, libelle=libelle, montant_total=total_abs, sens="debit")
|
entreprise=entreprise, date=d, libelle=libelle, montant_total=total_abs, sens="debit")
|
||||||
for l in lignes:
|
for l in lignes:
|
||||||
|
# montant_tva est calculé automatiquement dans Depense.save() depuis le taux.
|
||||||
Depense.objects.create(
|
Depense.objects.create(
|
||||||
entreprise=entreprise, paiement=paiement, date=d, libelle=l["libelle"],
|
entreprise=entreprise, paiement=paiement, date=d, libelle=l["libelle"],
|
||||||
categorie=l["categorie"], activite=l["activite"] or "", regime_tva=l["regime"],
|
categorie=l["categorie"], activite=l["activite"] or "", regime_tva=l["regime"],
|
||||||
taux_tva=l["taux"], montant_ttc=l["montant"], montant_tva=l["tva"])
|
taux_tva=l["taux"], montant_ttc=l["montant"])
|
||||||
messages.success(request, f"Opération « {libelle} » enregistrée ({len(lignes)} dépense(s)).")
|
messages.success(request, f"Opération « {libelle} » enregistrée ({len(lignes)} dépense(s)).")
|
||||||
return redirect("saisie_paiement")
|
return redirect("saisie_paiement")
|
||||||
|
|||||||
Reference in New Issue
Block a user