JB Compta v1.1 - multi-activité
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
from django.contrib import admin
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import (
|
||||
Bareme, Entreprise, Client, Facture, LigneFacture, Depense,
|
||||
)
|
||||
|
||||
|
||||
@admin.register(Bareme)
|
||||
class BaremeAdmin(admin.ModelAdmin):
|
||||
list_display = ("annee", "activite", "taux_urssaf", "abattement_fiscal",
|
||||
"taux_vl", "seuil_ca", "seuil_tva_base")
|
||||
list_filter = ("annee", "activite")
|
||||
|
||||
|
||||
@admin.register(Entreprise)
|
||||
class EntrepriseAdmin(admin.ModelAdmin):
|
||||
list_display = ("nom", "activite", "franchise_tva",
|
||||
"versement_liberatoire", "email")
|
||||
list_filter = ("activite", "franchise_tva", "versement_liberatoire")
|
||||
search_fields = ("nom", "siret")
|
||||
|
||||
|
||||
@admin.register(Client)
|
||||
class ClientAdmin(admin.ModelAdmin):
|
||||
list_display = ("nom", "entreprise", "email", "siret")
|
||||
list_filter = ("entreprise",)
|
||||
search_fields = ("nom", "email", "siret")
|
||||
|
||||
|
||||
class LigneFactureInline(admin.TabularInline):
|
||||
model = LigneFacture
|
||||
extra = 1
|
||||
|
||||
|
||||
@admin.action(description="Marquer comme encaissée (aujourd'hui)")
|
||||
def marquer_encaissee(modeladmin, request, queryset):
|
||||
queryset.update(statut="encaissee", date_encaissement=timezone.now().date())
|
||||
|
||||
|
||||
@admin.register(Facture)
|
||||
class FactureAdmin(admin.ModelAdmin):
|
||||
list_display = ("numero", "entreprise", "client", "date_emission",
|
||||
"date_encaissement", "statut", "montant_ht",
|
||||
"montant_tva", "montant_ttc")
|
||||
list_filter = ("entreprise", "statut", "activite", "date_emission")
|
||||
search_fields = ("numero", "client__nom")
|
||||
date_hierarchy = "date_emission"
|
||||
inlines = [LigneFactureInline]
|
||||
actions = [marquer_encaissee]
|
||||
readonly_fields = ("montant_ht", "montant_tva", "montant_ttc")
|
||||
|
||||
def save_related(self, request, form, formsets, change):
|
||||
super().save_related(request, form, formsets, change)
|
||||
# Recalcule les totaux à partir des lignes après sauvegarde
|
||||
form.instance.recompute_totaux()
|
||||
|
||||
|
||||
@admin.register(Depense)
|
||||
class DepenseAdmin(admin.ModelAdmin):
|
||||
list_display = ("date", "entreprise", "libelle", "activite", "categorie",
|
||||
"montant_ttc", "montant_tva")
|
||||
list_filter = ("entreprise", "activite", "categorie", "date")
|
||||
search_fields = ("libelle", "categorie")
|
||||
date_hierarchy = "date"
|
||||
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ComptaConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "compta"
|
||||
verbose_name = "Compta micro-entreprise"
|
||||
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Charge les barèmes 2026 et un jeu de données d'exemple (multi-activité).
|
||||
Usage : python manage.py seed_demo
|
||||
"""
|
||||
from decimal import Decimal
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from compta.models import (
|
||||
Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Activite,
|
||||
StatutFacture,
|
||||
)
|
||||
|
||||
# Barèmes officiels 2026 (voir docs/regles-fiscales-2026.md)
|
||||
BAREMES_2026 = [
|
||||
# activite, taux_urssaf, abattement, taux_vl, seuil_ca, tva_base, tva_majore
|
||||
(Activite.VENTE, Decimal("12.30"), Decimal("71"), Decimal("1.0"),
|
||||
Decimal("203100"), Decimal("85000"), Decimal("93500")),
|
||||
(Activite.SERVICE_BIC, Decimal("21.20"), Decimal("50"), Decimal("1.7"),
|
||||
Decimal("83600"), Decimal("37500"), Decimal("41250")),
|
||||
(Activite.SERVICE_BNC, Decimal("25.60"), Decimal("34"), Decimal("2.2"),
|
||||
Decimal("83600"), Decimal("37500"), Decimal("41250")),
|
||||
]
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Charge les barèmes 2026 et des données d'exemple."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for act, urssaf, ab, vl, sca, tb, tm in BAREMES_2026:
|
||||
Bareme.objects.update_or_create(
|
||||
activite=act, annee=2026,
|
||||
defaults=dict(taux_urssaf=urssaf, abattement_fiscal=ab,
|
||||
taux_vl=vl, seuil_ca=sca,
|
||||
seuil_tva_base=tb, seuil_tva_majore=tm),
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS("Barèmes 2026 chargés."))
|
||||
|
||||
# Entreprise multi-activité : impression 3D (Vente BIC) + aide info (Presta BIC)
|
||||
ent, _ = Entreprise.objects.get_or_create(
|
||||
nom="JB Conseil",
|
||||
defaults=dict(activite=Activite.SERVICE_BIC, franchise_tva=False,
|
||||
taux_tva_defaut=Decimal("20.00"),
|
||||
versement_liberatoire=True, email="jb@example.fr"),
|
||||
)
|
||||
|
||||
clients = {}
|
||||
for nom, mail in [("Acme SARL", "contact@acme.fr"),
|
||||
("Studio Lumiere", "hello@studiolumiere.fr"),
|
||||
("Mairie de Ville", "compta@ville.fr")]:
|
||||
c, _ = Client.objects.get_or_create(entreprise=ent, nom=nom,
|
||||
defaults=dict(email=mail))
|
||||
clients[nom] = c
|
||||
|
||||
V, S = Activite.VENTE, Activite.SERVICE_BIC
|
||||
factures = [
|
||||
# numero, client, activite, emission, encaissement, statut, lignes
|
||||
("2026-001", "Acme SARL", S, date(2026, 1, 10), date(2026, 1, 25),
|
||||
StatutFacture.ENCAISSEE, [("Dépannage informatique", 2, 600)]),
|
||||
("2026-002", "Studio Lumiere", V, date(2026, 1, 18), date(2026, 2, 5),
|
||||
StatutFacture.ENCAISSEE, [("Impression 3D - lot pièces", 1, 800)]),
|
||||
("2026-003", "Mairie de Ville", S, date(2026, 2, 2), date(2026, 2, 20),
|
||||
StatutFacture.ENCAISSEE, [("Maintenance parc", 1, 2500)]),
|
||||
("2026-004", "Acme SARL", V, date(2026, 3, 1), date(2026, 3, 15),
|
||||
StatutFacture.ENCAISSEE, [("Impression 3D - prototype", 1, 1500)]),
|
||||
("2026-005", "Studio Lumiere", S, date(2026, 3, 20), None,
|
||||
StatutFacture.ENVOYEE, [("Audit poste de travail", 1, 950)]),
|
||||
]
|
||||
for numero, client_nom, activite, emis, enc, statut, lignes in factures:
|
||||
f, created = Facture.objects.get_or_create(
|
||||
entreprise=ent, numero=numero,
|
||||
defaults=dict(client=clients[client_nom], activite=activite,
|
||||
date_emission=emis, date_encaissement=enc,
|
||||
statut=statut),
|
||||
)
|
||||
if created:
|
||||
for des, qte, pu in lignes:
|
||||
LigneFacture.objects.create(
|
||||
facture=f, designation=des, quantite=Decimal(qte),
|
||||
prix_unitaire_ht=Decimal(pu), taux_tva=Decimal("20"))
|
||||
f.recompute_totaux()
|
||||
|
||||
Depense.objects.get_or_create(
|
||||
entreprise=ent, libelle="Bobines PLA/PETG", date=date(2026, 1, 15),
|
||||
defaults=dict(categorie="Matiere", activite=Activite.VENTE,
|
||||
montant_ttc=Decimal("96.00"), montant_tva=Decimal("16.00")))
|
||||
Depense.objects.get_or_create(
|
||||
entreprise=ent, libelle="Abonnement outils", date=date(2026, 2, 10),
|
||||
defaults=dict(categorie="Logiciel", activite=Activite.SERVICE_BIC,
|
||||
montant_ttc=Decimal("34.80"), montant_tva=Decimal("5.80")))
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Données d'exemple chargées."))
|
||||
@@ -0,0 +1,130 @@
|
||||
# Generated by Django 5.2.15 on 2026-06-29 20:53
|
||||
|
||||
import django.db.models.deletion
|
||||
from decimal import Decimal
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Entreprise',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nom', models.CharField(max_length=200)),
|
||||
('siret', models.CharField(blank=True, max_length=20)),
|
||||
('activite', models.CharField(choices=[('vente', 'Vente de marchandises (BIC)'), ('service_bic', 'Prestation de services (BIC)'), ('service_bnc', 'Prestation de services / libéral (BNC)')], default='service_bnc', max_length=20)),
|
||||
('franchise_tva', models.BooleanField(default=False, help_text='Décochez si vous facturez et déclarez la TVA.', verbose_name='En franchise de TVA')),
|
||||
('taux_tva_defaut', models.DecimalField(decimal_places=2, default=Decimal('20.00'), max_digits=4, verbose_name='Taux de TVA par défaut (%)')),
|
||||
('versement_liberatoire', models.BooleanField(default=False, verbose_name='Option versement libératoire')),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('adresse', models.TextField(blank=True)),
|
||||
('cree_le', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entreprise',
|
||||
'verbose_name_plural': 'Entreprises',
|
||||
'ordering': ['nom'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Bareme',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('activite', models.CharField(choices=[('vente', 'Vente de marchandises (BIC)'), ('service_bic', 'Prestation de services (BIC)'), ('service_bnc', 'Prestation de services / libéral (BNC)')], max_length=20)),
|
||||
('annee', models.PositiveIntegerField()),
|
||||
('taux_urssaf', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Taux cotisations URSSAF (%)')),
|
||||
('abattement_fiscal', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Abattement forfaitaire (%)')),
|
||||
('taux_vl', models.DecimalField(decimal_places=2, max_digits=4, verbose_name='Taux versement libératoire (%)')),
|
||||
('seuil_ca', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Plafond CA micro (€)')),
|
||||
('seuil_tva_base', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Seuil franchise TVA (€)')),
|
||||
('seuil_tva_majore', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Seuil franchise TVA majoré (€)')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Barème',
|
||||
'verbose_name_plural': 'Barèmes',
|
||||
'ordering': ['-annee', 'activite'],
|
||||
'unique_together': {('activite', 'annee')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Depense',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField()),
|
||||
('categorie', models.CharField(blank=True, max_length=100)),
|
||||
('libelle', models.CharField(max_length=255)),
|
||||
('montant_ttc', models.DecimalField(decimal_places=2, max_digits=12)),
|
||||
('montant_tva', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=12, verbose_name='TVA déductible (€)')),
|
||||
('cree_le', models.DateTimeField(auto_now_add=True)),
|
||||
('entreprise', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='depenses', to='compta.entreprise')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Dépense',
|
||||
'verbose_name_plural': 'Dépenses',
|
||||
'ordering': ['-date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Client',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nom', models.CharField(max_length=200)),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('siret', models.CharField(blank=True, max_length=20)),
|
||||
('adresse', models.TextField(blank=True)),
|
||||
('cree_le', models.DateTimeField(auto_now_add=True)),
|
||||
('entreprise', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='clients', to='compta.entreprise')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Client',
|
||||
'verbose_name_plural': 'Clients',
|
||||
'ordering': ['nom'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Facture',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('numero', models.CharField(max_length=50)),
|
||||
('activite', models.CharField(choices=[('vente', 'Vente de marchandises (BIC)'), ('service_bic', 'Prestation de services (BIC)'), ('service_bnc', 'Prestation de services / libéral (BNC)')], max_length=20)),
|
||||
('date_emission', models.DateField()),
|
||||
('date_encaissement', models.DateField(blank=True, help_text="À renseigner une fois la facture payée. Le CA micro est compté à l'encaissement.", null=True)),
|
||||
('statut', models.CharField(choices=[('brouillon', 'Brouillon'), ('envoyee', 'Envoyée'), ('encaissee', 'Encaissée'), ('annulee', 'Annulée')], default='brouillon', max_length=12)),
|
||||
('montant_ht', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=12)),
|
||||
('montant_tva', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=12)),
|
||||
('montant_ttc', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=12)),
|
||||
('notes', models.TextField(blank=True)),
|
||||
('cree_le', models.DateTimeField(auto_now_add=True)),
|
||||
('client', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='factures', to='compta.client')),
|
||||
('entreprise', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='factures', to='compta.entreprise')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Facture',
|
||||
'verbose_name_plural': 'Factures',
|
||||
'ordering': ['-date_emission', 'numero'],
|
||||
'unique_together': {('entreprise', 'numero')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LigneFacture',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('designation', models.CharField(max_length=255)),
|
||||
('quantite', models.DecimalField(decimal_places=3, default=Decimal('1'), max_digits=12)),
|
||||
('prix_unitaire_ht', models.DecimalField(decimal_places=2, max_digits=12)),
|
||||
('taux_tva', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=4)),
|
||||
('facture', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lignes', to='compta.facture')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Ligne de facture',
|
||||
'verbose_name_plural': 'Lignes de facture',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.15 on 2026-06-29 21:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('compta', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='depense',
|
||||
name='activite',
|
||||
field=models.CharField(blank=True, choices=[('vente', 'Vente de marchandises (BIC)'), ('service_bic', 'Prestation de services (BIC)'), ('service_bnc', 'Prestation de services / libéral (BNC)')], help_text='Activité concernée (facultatif, pour la ventilation).', max_length=20),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entreprise',
|
||||
name='activite',
|
||||
field=models.CharField(choices=[('vente', 'Vente de marchandises (BIC)'), ('service_bic', 'Prestation de services (BIC)'), ('service_bnc', 'Prestation de services / libéral (BNC)')], default='service_bnc', help_text='Activité par défaut des nouvelles factures. Une entreprise peut avoir plusieurs activités : chaque facture/dépense porte la sienne.', max_length=20, verbose_name='Activité principale'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
Modèles de données — JB Compta.
|
||||
|
||||
Conçus pour rester valables si le statut évolue : les taux et seuils
|
||||
ne sont jamais codés en dur, ils vivent dans le modèle `Bareme`
|
||||
(par activité et par année).
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
|
||||
|
||||
class Activite(models.TextChoices):
|
||||
VENTE = "vente", "Vente de marchandises (BIC)"
|
||||
SERVICE_BIC = "service_bic", "Prestation de services (BIC)"
|
||||
SERVICE_BNC = "service_bnc", "Prestation de services / libéral (BNC)"
|
||||
|
||||
|
||||
class StatutFacture(models.TextChoices):
|
||||
BROUILLON = "brouillon", "Brouillon"
|
||||
ENVOYEE = "envoyee", "Envoyée"
|
||||
ENCAISSEE = "encaissee", "Encaissée"
|
||||
ANNULEE = "annulee", "Annulée"
|
||||
|
||||
|
||||
class Bareme(models.Model):
|
||||
"""Taux et seuils officiels, par activité et par année (paramétrable)."""
|
||||
activite = models.CharField(max_length=20, choices=Activite.choices)
|
||||
annee = models.PositiveIntegerField()
|
||||
taux_urssaf = models.DecimalField(
|
||||
"Taux cotisations URSSAF (%)", max_digits=5, decimal_places=2)
|
||||
abattement_fiscal = models.DecimalField(
|
||||
"Abattement forfaitaire (%)", max_digits=5, decimal_places=2)
|
||||
taux_vl = models.DecimalField(
|
||||
"Taux versement libératoire (%)", max_digits=4, decimal_places=2)
|
||||
seuil_ca = models.DecimalField(
|
||||
"Plafond CA micro (€)", max_digits=12, decimal_places=2)
|
||||
seuil_tva_base = models.DecimalField(
|
||||
"Seuil franchise TVA (€)", max_digits=12, decimal_places=2)
|
||||
seuil_tva_majore = models.DecimalField(
|
||||
"Seuil franchise TVA majoré (€)", max_digits=12, decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Barème"
|
||||
verbose_name_plural = "Barèmes"
|
||||
unique_together = ("activite", "annee")
|
||||
ordering = ["-annee", "activite"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.get_activite_display()} — {self.annee}"
|
||||
|
||||
|
||||
class Entreprise(models.Model):
|
||||
nom = models.CharField(max_length=200)
|
||||
siret = models.CharField(max_length=20, blank=True)
|
||||
activite = models.CharField(
|
||||
"Activité principale", max_length=20, choices=Activite.choices,
|
||||
default=Activite.SERVICE_BNC,
|
||||
help_text="Activité par défaut des nouvelles factures. "
|
||||
"Une entreprise peut avoir plusieurs activités : "
|
||||
"chaque facture/dépense porte la sienne.")
|
||||
franchise_tva = models.BooleanField(
|
||||
"En franchise de TVA", default=False,
|
||||
help_text="Décochez si vous facturez et déclarez la TVA.")
|
||||
taux_tva_defaut = models.DecimalField(
|
||||
"Taux de TVA par défaut (%)", max_digits=4, decimal_places=2,
|
||||
default=Decimal("20.00"))
|
||||
versement_liberatoire = models.BooleanField(
|
||||
"Option versement libératoire", default=False)
|
||||
email = models.EmailField(blank=True)
|
||||
adresse = models.TextField(blank=True)
|
||||
cree_le = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Entreprise"
|
||||
verbose_name_plural = "Entreprises"
|
||||
ordering = ["nom"]
|
||||
|
||||
def __str__(self):
|
||||
return self.nom
|
||||
|
||||
def bareme(self, annee):
|
||||
return Bareme.objects.filter(activite=self.activite, annee=annee).first()
|
||||
|
||||
|
||||
class Client(models.Model):
|
||||
entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE,
|
||||
related_name="clients")
|
||||
nom = models.CharField(max_length=200)
|
||||
email = models.EmailField(blank=True)
|
||||
siret = models.CharField(max_length=20, blank=True)
|
||||
adresse = models.TextField(blank=True)
|
||||
cree_le = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Client"
|
||||
verbose_name_plural = "Clients"
|
||||
ordering = ["nom"]
|
||||
|
||||
def __str__(self):
|
||||
return self.nom
|
||||
|
||||
|
||||
class Facture(models.Model):
|
||||
entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE,
|
||||
related_name="factures")
|
||||
client = models.ForeignKey(Client, on_delete=models.PROTECT,
|
||||
related_name="factures")
|
||||
numero = models.CharField(max_length=50)
|
||||
activite = models.CharField(max_length=20, choices=Activite.choices)
|
||||
date_emission = models.DateField()
|
||||
date_encaissement = models.DateField(
|
||||
null=True, blank=True,
|
||||
help_text="À renseigner une fois la facture payée. "
|
||||
"Le CA micro est compté à l'encaissement.")
|
||||
statut = models.CharField(max_length=12, choices=StatutFacture.choices,
|
||||
default=StatutFacture.BROUILLON)
|
||||
montant_ht = models.DecimalField(max_digits=12, decimal_places=2,
|
||||
default=Decimal("0"))
|
||||
montant_tva = models.DecimalField(max_digits=12, decimal_places=2,
|
||||
default=Decimal("0"))
|
||||
montant_ttc = models.DecimalField(max_digits=12, decimal_places=2,
|
||||
default=Decimal("0"))
|
||||
notes = models.TextField(blank=True)
|
||||
cree_le = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Facture"
|
||||
verbose_name_plural = "Factures"
|
||||
unique_together = ("entreprise", "numero")
|
||||
ordering = ["-date_emission", "numero"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.numero} — {self.client}"
|
||||
|
||||
def recompute_totaux(self, save=True):
|
||||
"""Recalcule HT/TVA/TTC à partir des lignes."""
|
||||
ht = Decimal("0")
|
||||
tva = Decimal("0")
|
||||
for ligne in self.lignes.all():
|
||||
l_ht = ligne.montant_ht
|
||||
ht += l_ht
|
||||
tva += (l_ht * ligne.taux_tva / Decimal("100"))
|
||||
self.montant_ht = ht.quantize(Decimal("0.01"))
|
||||
self.montant_tva = tva.quantize(Decimal("0.01"))
|
||||
self.montant_ttc = (self.montant_ht + self.montant_tva)
|
||||
if save:
|
||||
super().save(update_fields=["montant_ht", "montant_tva", "montant_ttc"])
|
||||
|
||||
|
||||
class LigneFacture(models.Model):
|
||||
facture = models.ForeignKey(Facture, on_delete=models.CASCADE,
|
||||
related_name="lignes")
|
||||
designation = models.CharField(max_length=255)
|
||||
quantite = models.DecimalField(max_digits=12, decimal_places=3,
|
||||
default=Decimal("1"))
|
||||
prix_unitaire_ht = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
taux_tva = models.DecimalField(max_digits=4, decimal_places=2,
|
||||
default=Decimal("0"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Ligne de facture"
|
||||
verbose_name_plural = "Lignes de facture"
|
||||
|
||||
def __str__(self):
|
||||
return self.designation
|
||||
|
||||
@property
|
||||
def montant_ht(self):
|
||||
return (self.quantite * self.prix_unitaire_ht).quantize(Decimal("0.01"))
|
||||
|
||||
|
||||
class Depense(models.Model):
|
||||
entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE,
|
||||
related_name="depenses")
|
||||
date = models.DateField()
|
||||
activite = models.CharField(
|
||||
max_length=20, choices=Activite.choices, blank=True,
|
||||
help_text="Activité concernée (facultatif, pour la ventilation).")
|
||||
categorie = models.CharField(max_length=100, blank=True)
|
||||
libelle = models.CharField(max_length=255)
|
||||
montant_ttc = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
montant_tva = models.DecimalField(
|
||||
"TVA déductible (€)", max_digits=12, decimal_places=2,
|
||||
default=Decimal("0"))
|
||||
cree_le = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Dépense"
|
||||
verbose_name_plural = "Dépenses"
|
||||
ordering = ["-date"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} — {self.libelle}"
|
||||
|
||||
@property
|
||||
def montant_ht(self):
|
||||
return (self.montant_ttc - self.montant_tva).quantize(Decimal("0.01"))
|
||||
@@ -0,0 +1,154 @@
|
||||
"""
|
||||
Logique fiscale — JB Compta.
|
||||
|
||||
Bases de calcul : CA HORS TAXES (HT) encaissé.
|
||||
- Base des cotisations URSSAF et du versement libératoire (assujetti TVA).
|
||||
- En franchise de TVA, HT = TTC : le calcul reste correct.
|
||||
|
||||
Multi-activité : une entreprise peut exercer plusieurs activités (ex. Vente BIC
|
||||
+ Prestation BIC). Les taux URSSAF, abattements, taux de versement libératoire et
|
||||
seuils diffèrent par activité, donc tout est ventilé par activité puis totalisé.
|
||||
|
||||
TVA à reverser = TVA collectée (ventes encaissées) − TVA déductible (dépenses).
|
||||
"""
|
||||
from decimal import Decimal
|
||||
from django.db.models import Sum
|
||||
from django.db.models.functions import TruncMonth
|
||||
|
||||
from .models import Facture, Depense, Bareme, StatutFacture, Activite
|
||||
|
||||
CENT = Decimal("0.01")
|
||||
|
||||
|
||||
def _q(value):
|
||||
return (value or Decimal("0")).quantize(CENT)
|
||||
|
||||
|
||||
def factures_encaissees(entreprise, annee):
|
||||
return Facture.objects.filter(
|
||||
entreprise=entreprise,
|
||||
statut=StatutFacture.ENCAISSEE,
|
||||
date_encaissement__year=annee,
|
||||
)
|
||||
|
||||
|
||||
def ca_ht_par_activite(entreprise, annee):
|
||||
"""Dict {activite: CA HT encaissé}."""
|
||||
rows = (
|
||||
factures_encaissees(entreprise, annee)
|
||||
.values("activite")
|
||||
.annotate(total=Sum("montant_ht"))
|
||||
)
|
||||
return {r["activite"]: _q(r["total"]) for r in rows}
|
||||
|
||||
|
||||
def ca_ht(entreprise, annee):
|
||||
agg = factures_encaissees(entreprise, annee).aggregate(s=Sum("montant_ht"))
|
||||
return _q(agg["s"])
|
||||
|
||||
|
||||
def tva_collectee(entreprise, annee):
|
||||
agg = factures_encaissees(entreprise, annee).aggregate(s=Sum("montant_tva"))
|
||||
return _q(agg["s"])
|
||||
|
||||
|
||||
def tva_deductible(entreprise, annee):
|
||||
agg = Depense.objects.filter(
|
||||
entreprise=entreprise, date__year=annee
|
||||
).aggregate(s=Sum("montant_tva"))
|
||||
return _q(agg["s"])
|
||||
|
||||
|
||||
def ca_ht_mensuel(entreprise, annee):
|
||||
"""Liste de 12 valeurs (index 0 = janvier) du CA HT encaissé (toutes activités)."""
|
||||
rows = (
|
||||
factures_encaissees(entreprise, annee)
|
||||
.annotate(mois=TruncMonth("date_encaissement"))
|
||||
.values("mois")
|
||||
.annotate(total=Sum("montant_ht"))
|
||||
)
|
||||
mensuel = [Decimal("0")] * 12
|
||||
for r in rows:
|
||||
if r["mois"]:
|
||||
mensuel[r["mois"].month - 1] = _q(r["total"])
|
||||
return mensuel
|
||||
|
||||
|
||||
def detail_activite(activite, ca, bareme):
|
||||
"""Calculs fiscaux pour une activité donnée."""
|
||||
label = dict(Activite.choices).get(activite, activite)
|
||||
d = {"activite": activite, "label": label, "ca_ht": ca, "bareme": bareme}
|
||||
if bareme:
|
||||
d.update({
|
||||
"taux_urssaf": bareme.taux_urssaf,
|
||||
"cotisations": _q(ca * bareme.taux_urssaf / Decimal("100")),
|
||||
"taux_vl": bareme.taux_vl,
|
||||
"versement_liberatoire": _q(ca * bareme.taux_vl / Decimal("100")),
|
||||
"abattement_fiscal": bareme.abattement_fiscal,
|
||||
"revenu_imposable": _q(
|
||||
ca * (Decimal("100") - bareme.abattement_fiscal) / Decimal("100")),
|
||||
"seuil_ca": bareme.seuil_ca,
|
||||
"pct_seuil_ca": (_q(ca / bareme.seuil_ca * Decimal("100"))
|
||||
if bareme.seuil_ca else Decimal("0")),
|
||||
"seuil_tva_base": bareme.seuil_tva_base,
|
||||
"depasse_seuil_tva": ca > bareme.seuil_tva_base,
|
||||
"depasse_seuil_ca": ca > bareme.seuil_ca,
|
||||
})
|
||||
else:
|
||||
d.update({
|
||||
"taux_urssaf": None, "cotisations": None,
|
||||
"taux_vl": None, "versement_liberatoire": None,
|
||||
"abattement_fiscal": None, "revenu_imposable": None,
|
||||
"seuil_ca": None, "pct_seuil_ca": None,
|
||||
"seuil_tva_base": None, "depasse_seuil_tva": False,
|
||||
"depasse_seuil_ca": False,
|
||||
})
|
||||
return d
|
||||
|
||||
|
||||
def synthese(entreprise, annee):
|
||||
"""Tableau de bord chiffré, ventilé par activité puis totalisé."""
|
||||
ca_par_act = ca_ht_par_activite(entreprise, annee)
|
||||
|
||||
# On inclut toujours l'activité principale, même sans CA encore.
|
||||
activites = set(ca_par_act) | {entreprise.activite}
|
||||
baremes = {b.activite: b for b in Bareme.objects.filter(annee=annee)}
|
||||
|
||||
lignes = []
|
||||
for act in sorted(activites):
|
||||
ca = ca_par_act.get(act, Decimal("0"))
|
||||
lignes.append(detail_activite(act, ca, baremes.get(act)))
|
||||
|
||||
# Totaux (on additionne ce qui est calculable)
|
||||
def somme(cle):
|
||||
vals = [l[cle] for l in lignes if l.get(cle) is not None]
|
||||
return _q(sum(vals, Decimal("0"))) if vals else None
|
||||
|
||||
tva_col = tva_collectee(entreprise, annee)
|
||||
tva_ded = tva_deductible(entreprise, annee)
|
||||
|
||||
return {
|
||||
"entreprise": entreprise,
|
||||
"annee": annee,
|
||||
"lignes_activite": lignes,
|
||||
"bareme_manquant": any(l["bareme"] is None and l["ca_ht"] for l in lignes),
|
||||
"ca_ht": ca_ht(entreprise, annee),
|
||||
"cotisations_urssaf": somme("cotisations"),
|
||||
"versement_liberatoire": (
|
||||
somme("versement_liberatoire") if entreprise.versement_liberatoire else None),
|
||||
"revenu_imposable": somme("revenu_imposable"),
|
||||
"tva_collectee": tva_col,
|
||||
"tva_deductible": tva_ded,
|
||||
"tva_a_reverser": _q(tva_col - tva_ded),
|
||||
"ca_mensuel": ca_ht_mensuel(entreprise, annee),
|
||||
}
|
||||
|
||||
|
||||
def annees_disponibles(entreprise):
|
||||
qs = (
|
||||
Facture.objects.filter(entreprise=entreprise,
|
||||
statut=StatutFacture.ENCAISSEE,
|
||||
date_encaissement__isnull=False)
|
||||
.dates("date_encaissement", "year")
|
||||
)
|
||||
return sorted({d.year for d in qs}, reverse=True)
|
||||
@@ -0,0 +1,169 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>JB Compta — Tableau de bord</title>
|
||||
<style>
|
||||
:root { --bg:#0f172a; --card:#1e293b; --txt:#e2e8f0; --muted:#94a3b8;
|
||||
--accent:#38bdf8; --ok:#34d399; --warn:#fbbf24; --bad:#f87171; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; font-family:system-ui,Segoe UI,Roboto,sans-serif;
|
||||
background:var(--bg); color:var(--txt); padding:24px; }
|
||||
h1 { margin:0 0 4px; font-size:22px; }
|
||||
h2 { font-size:15px; color:var(--muted); margin:24px 0 10px; font-weight:600; }
|
||||
a { color:var(--accent); }
|
||||
.sub { color:var(--muted); margin-bottom:20px; }
|
||||
form.filters { display:flex; gap:12px; flex-wrap:wrap; margin-bottom:24px; }
|
||||
select, button { background:var(--card); color:var(--txt); border:1px solid #334155;
|
||||
border-radius:8px; padding:8px 12px; font-size:14px; }
|
||||
button { cursor:pointer; }
|
||||
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
|
||||
gap:16px; margin-bottom:8px; }
|
||||
.card { background:var(--card); border:1px solid #334155; border-radius:12px;
|
||||
padding:16px; }
|
||||
.card .label { color:var(--muted); font-size:13px; }
|
||||
.card .value { font-size:26px; font-weight:600; margin-top:6px; }
|
||||
.card .hint { color:var(--muted); font-size:12px; margin-top:4px; }
|
||||
.ok{color:var(--ok)} .warn{color:var(--warn)} .bad{color:var(--bad)}
|
||||
.bar { height:8px; background:#334155; border-radius:99px; overflow:hidden; margin-top:8px;}
|
||||
.bar > span { display:block; height:100%; background:var(--accent); }
|
||||
.row { display:grid; grid-template-columns:2fr 1fr; gap:16px; margin-top:8px; }
|
||||
@media(max-width:800px){ .row{grid-template-columns:1fr;} }
|
||||
.exports a { display:inline-block; margin-right:12px; }
|
||||
canvas { max-width:100%; }
|
||||
table { width:100%; border-collapse:collapse; font-size:14px; }
|
||||
td,th { padding:8px; border-bottom:1px solid #334155; text-align:right; }
|
||||
th:first-child, td:first-child { text-align:left; }
|
||||
tr.total td { font-weight:600; border-top:2px solid #475569; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% if not entreprise %}
|
||||
<h1>Aucune entreprise</h1>
|
||||
<p class="sub">Crée une entreprise et des factures dans
|
||||
<a href="/admin/">l'administration</a>, puis reviens ici.</p>
|
||||
{% else %}
|
||||
<h1>{{ entreprise.nom }}</h1>
|
||||
<div class="sub">{{ entreprise.get_activite_display }} (principale) ·
|
||||
{% if entreprise.franchise_tva %}Franchise de TVA{% else %}Assujetti TVA{% endif %}
|
||||
{% if entreprise.versement_liberatoire %}· Versement libératoire{% endif %}
|
||||
· Exercice {{ annee }}</div>
|
||||
|
||||
<form class="filters" method="get">
|
||||
<select name="entreprise" onchange="this.form.submit()">
|
||||
{% for e in entreprises %}
|
||||
<option value="{{ e.pk }}" {% if e.pk == entreprise.pk %}selected{% endif %}>{{ e.nom }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="annee" onchange="this.form.submit()">
|
||||
{% for a in annees %}
|
||||
<option value="{{ a }}" {% if a == annee %}selected{% endif %}>{{ a }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<a href="/admin/"><button type="button">Saisie / Admin</button></a>
|
||||
</form>
|
||||
|
||||
{% if bareme_manquant %}
|
||||
<div class="card bad" style="margin-bottom:16px;">
|
||||
Une activité avec du CA n'a pas de barème {{ annee }}. Ajoute-le dans
|
||||
<a href="/admin/compta/bareme/">Barèmes</a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Totaux entreprise -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="label">CA HT encaissé (total)</div>
|
||||
<div class="value">{{ ca_ht }} €</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Cotisations URSSAF (total)</div>
|
||||
<div class="value">{{ cotisations_urssaf|default:"—" }} €</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">Impôt — versement libératoire</div>
|
||||
<div class="value">{{ versement_liberatoire|default:"—" }} €</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label">TVA à reverser</div>
|
||||
<div class="value">{{ tva_a_reverser }} €</div>
|
||||
<div class="hint">{{ tva_collectee }} collectée − {{ tva_deductible }} déductible</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ventilation par activité -->
|
||||
<h2>Ventilation par activité</h2>
|
||||
<div class="card">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Activité</th><th>CA HT</th><th>% plafond</th>
|
||||
<th>URSSAF</th><th>Impôt (VL)</th><th>Revenu imposable</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for l in lignes_activite %}
|
||||
<tr>
|
||||
<td>{{ l.label }}</td>
|
||||
<td>{{ l.ca_ht }} €</td>
|
||||
<td class="{% if l.depasse_seuil_ca %}bad{% endif %}">
|
||||
{% if l.pct_seuil_ca is not None %}{{ l.pct_seuil_ca }} %{% else %}—{% endif %}
|
||||
</td>
|
||||
<td>{% if l.cotisations is not None %}{{ l.cotisations }} €{% else %}—{% endif %}</td>
|
||||
<td>{% if l.versement_liberatoire is not None %}{{ l.versement_liberatoire }} €{% else %}—{% endif %}</td>
|
||||
<td>{% if l.revenu_imposable is not None %}{{ l.revenu_imposable }} €{% else %}—{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="total">
|
||||
<td>Total</td>
|
||||
<td>{{ ca_ht }} €</td>
|
||||
<td></td>
|
||||
<td>{{ cotisations_urssaf|default:"—" }} €</td>
|
||||
<td>{{ versement_liberatoire|default:"—" }} €</td>
|
||||
<td>{{ revenu_imposable|default:"—" }} €</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="card">
|
||||
<div class="label" style="margin-bottom:8px;">CA HT mensuel (toutes activités)</div>
|
||||
<canvas id="caChart" height="120"></canvas>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label" style="margin-bottom:8px;">À provisionner (année)</div>
|
||||
<table>
|
||||
<tr><th>Poste</th><th>Montant</th></tr>
|
||||
<tr><td>URSSAF</td><td>{{ cotisations_urssaf|default:"—" }} €</td></tr>
|
||||
<tr><td>Impôt (VL)</td><td>{{ versement_liberatoire|default:"—" }} €</td></tr>
|
||||
<tr><td>TVA</td><td>{{ tva_a_reverser }} €</td></tr>
|
||||
</table>
|
||||
<div class="exports" style="margin-top:16px;">
|
||||
<div class="label" style="margin-bottom:6px;">Exports CSV</div>
|
||||
<a href="{% url 'export_ventes' %}?entreprise={{ entreprise.pk }}&annee={{ annee }}">Ventes</a>
|
||||
<a href="{% url 'export_tva' %}?entreprise={{ entreprise.pk }}&annee={{ annee }}">TVA</a>
|
||||
<a href="{% url 'export_urssaf' %}?entreprise={{ entreprise.pk }}&annee={{ annee }}">URSSAF</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
const ctx = document.getElementById('caChart');
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: {{ mois_labels|safe }},
|
||||
datasets: [{ label: 'CA HT (€)', data: {{ ca_mensuel_json|safe }},
|
||||
backgroundColor: '#38bdf8' }]
|
||||
},
|
||||
options: { plugins:{legend:{display:false}},
|
||||
scales:{ x:{ticks:{color:'#94a3b8'}}, y:{ticks:{color:'#94a3b8'}} } }
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("dashboard/", views.dashboard, name="dashboard"),
|
||||
path("export/ventes/", views.export_ventes, name="export_ventes"),
|
||||
path("export/tva/", views.export_tva, name="export_tva"),
|
||||
path("export/urssaf/", views.export_urssaf, name="export_urssaf"),
|
||||
]
|
||||
@@ -0,0 +1,89 @@
|
||||
import csv
|
||||
from datetime import date
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
|
||||
from .models import Entreprise
|
||||
from . import services
|
||||
|
||||
|
||||
def _entreprise_courante(request):
|
||||
eid = request.GET.get("entreprise")
|
||||
qs = Entreprise.objects.all()
|
||||
if eid:
|
||||
return get_object_or_404(Entreprise, pk=eid)
|
||||
return qs.first()
|
||||
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
entreprise = _entreprise_courante(request)
|
||||
if entreprise is None:
|
||||
return render(request, "compta/dashboard.html", {"entreprise": None})
|
||||
|
||||
annee = int(request.GET.get("annee", date.today().year))
|
||||
ctx = services.synthese(entreprise, annee)
|
||||
ctx.update({
|
||||
"entreprises": Entreprise.objects.all(),
|
||||
"annees": services.annees_disponibles(entreprise) or [annee],
|
||||
"mois_labels": ["Jan", "Fév", "Mar", "Avr", "Mai", "Juin",
|
||||
"Juil", "Août", "Sep", "Oct", "Nov", "Déc"],
|
||||
"ca_mensuel_json": [float(v) for v in ctx["ca_mensuel"]],
|
||||
})
|
||||
return render(request, "compta/dashboard.html", ctx)
|
||||
|
||||
|
||||
def _csv_response(filename):
|
||||
resp = HttpResponse(content_type="text/csv; charset=utf-8")
|
||||
resp["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||
resp.write("") # BOM pour Excel
|
||||
return resp
|
||||
|
||||
|
||||
@login_required
|
||||
def export_ventes(request):
|
||||
entreprise = _entreprise_courante(request)
|
||||
annee = int(request.GET.get("annee", date.today().year))
|
||||
resp = _csv_response(f"ventes_{annee}.csv")
|
||||
w = csv.writer(resp, delimiter=";")
|
||||
w.writerow(["Numero", "Client", "Activite", "Date emission",
|
||||
"Date encaissement", "Statut", "HT", "TVA", "TTC"])
|
||||
for f in services.factures_encaissees(entreprise, annee).select_related("client"):
|
||||
w.writerow([f.numero, f.client.nom, f.get_activite_display(),
|
||||
f.date_emission, f.date_encaissement, f.get_statut_display(),
|
||||
f.montant_ht, f.montant_tva, f.montant_ttc])
|
||||
return resp
|
||||
|
||||
|
||||
@login_required
|
||||
def export_tva(request):
|
||||
entreprise = _entreprise_courante(request)
|
||||
annee = int(request.GET.get("annee", date.today().year))
|
||||
s = services.synthese(entreprise, annee)
|
||||
resp = _csv_response(f"tva_{annee}.csv")
|
||||
w = csv.writer(resp, delimiter=";")
|
||||
w.writerow(["Annee", "TVA collectee", "TVA deductible", "TVA a reverser"])
|
||||
w.writerow([annee, s["tva_collectee"], s["tva_deductible"],
|
||||
s["tva_a_reverser"]])
|
||||
return resp
|
||||
|
||||
|
||||
@login_required
|
||||
def export_urssaf(request):
|
||||
entreprise = _entreprise_courante(request)
|
||||
annee = int(request.GET.get("annee", date.today().year))
|
||||
s = services.synthese(entreprise, annee)
|
||||
resp = _csv_response(f"urssaf_{annee}.csv")
|
||||
w = csv.writer(resp, delimiter=";")
|
||||
w.writerow(["Annee", "Activite", "CA HT", "Taux URSSAF", "Cotisations",
|
||||
"Taux VL", "Versement liberatoire", "Abattement",
|
||||
"Revenu imposable"])
|
||||
for l in s["lignes_activite"]:
|
||||
w.writerow([annee, l["label"], l["ca_ht"], l["taux_urssaf"],
|
||||
l["cotisations"], l["taux_vl"], l["versement_liberatoire"],
|
||||
l["abattement_fiscal"], l["revenu_imposable"]])
|
||||
w.writerow([annee, "TOTAL", s["ca_ht"], "", s["cotisations_urssaf"],
|
||||
"", s["versement_liberatoire"], "", s["revenu_imposable"]])
|
||||
return resp
|
||||
Reference in New Issue
Block a user