diff --git a/.env.example b/.env.example index 015cf76..cf86b35 100644 --- a/.env.example +++ b/.env.example @@ -4,8 +4,16 @@ # Sécurité Django DJANGO_SECRET_KEY=change-moi-avec-une-longue-chaine-aleatoire DJANGO_DEBUG=0 -DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,192.168.1.50 -DJANGO_CSRF_TRUSTED_ORIGINS=http://192.168.1.50:8001 +# Domaine Traefik + accès direct éventuel (IP de l'hôte Docker / localhost) +DJANGO_ALLOWED_HOSTS=compta.lespatas.ovh,localhost,127.0.0.1 +# Origine HTTPS servie par Traefik — le schéma https:// est OBLIGATOIRE +DJANGO_CSRF_TRUSTED_ORIGINS=https://compta.lespatas.ovh + +# Derrière Traefik (reverse-proxy distant qui termine le TLS) : mets 1. +# Django reconnaît alors le HTTPS via X-Forwarded-Proto et sécurise les cookies. +DJANGO_BEHIND_PROXY=1 +# Laisse 0 : c'est Traefik qui redirige HTTP->HTTPS. +DJANGO_SSL_REDIRECT=0 # PostgreSQL EXISTANT (ne pas héberger de base ici) DB_NAME=compta diff --git a/compta/__pycache__/__init__.cpython-310.pyc b/compta/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..682e4f3 Binary files /dev/null and b/compta/__pycache__/__init__.cpython-310.pyc differ diff --git a/compta/__pycache__/admin.cpython-310.pyc b/compta/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000..be0ecd7 Binary files /dev/null and b/compta/__pycache__/admin.cpython-310.pyc differ diff --git a/compta/__pycache__/apps.cpython-310.pyc b/compta/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000..ba6425b Binary files /dev/null and b/compta/__pycache__/apps.cpython-310.pyc differ diff --git a/compta/__pycache__/models.cpython-310.pyc b/compta/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000..c592ad5 Binary files /dev/null and b/compta/__pycache__/models.cpython-310.pyc differ diff --git a/compta/admin.py b/compta/admin.py index d62025c..d8b21f4 100644 --- a/compta/admin.py +++ b/compta/admin.py @@ -30,10 +30,10 @@ class ScopedAdmin(admin.ModelAdmin): @admin.register(Categorie) class CategorieAdmin(admin.ModelAdmin): - list_display = ("nom", "usage", "exclure_calculs", "actif", "ordre") + list_display = ("nom", "usage", "exclure_calculs", "compte_pcg", "actif", "ordre") list_filter = ("usage", "exclure_calculs", "actif") - list_editable = ("usage", "exclure_calculs", "actif", "ordre") - search_fields = ("nom",) + list_editable = ("usage", "exclure_calculs", "compte_pcg", "actif", "ordre") + search_fields = ("nom", "compte_pcg") @admin.register(Bareme) diff --git a/compta/categories_indy.py b/compta/categories_indy.py new file mode 100644 index 0000000..0c18457 --- /dev/null +++ b/compta/categories_indy.py @@ -0,0 +1,84 @@ +""" +Catalogue des catégories Indy (référentiel complet), source de vérité unique +utilisée par le seed (création des Catégorie) et par la sync Indy (mapping +numéro de compte PCG -> catégorie). + +Chaque entrée : (nom, usage, exclure_calculs, compte_pcg) +- usage : "vente" | "depense" | "neutre" +- exclure_calculs=True : mouvement neutre (hors calcul du bénéfice / non déductible / + trésorerie / impôts) — exclu des calculs fiscaux. +""" + +# nom, usage, exclure_calculs, compte_pcg +CATALOGUE = [ + # --- Revenus (ventes) --- + ("Vente de produits finis", "vente", False, "701000"), + ("Prestation de services", "vente", False, "706001"), + ("Vente de services annexes", "vente", False, "706002"), + ("Vente de marchandise", "vente", False, "707000"), + ("Autre gain divers", "vente", False, "710000"), + + # --- Dépenses --- + ("Matériel et outillage", "depense", False, "606000"), + ("Matière première", "depense", False, ""), # (nôtre, pas de compte Indy) + ("Achat", "depense", False, "605000"), + ("Immobilisation", "depense", False, "210000"), + ("Frais divers", "depense", False, "600000"), + ("Télécom, fournitures, documentation", "depense", False, "626000"), + ("Frais d'acte et de contentieux", "depense", False, "622700"), + ("Honoraires payés", "depense", False, "622000"), + ("Formation", "depense", False, "618000"), + ("Réception et congrès", "depense", False, "625600"), + ("Restaurant et repas d'affaires", "depense", False, "625700"), + ("Frais de repas hors domicile", "depense", False, "625300"), + ("Frais de déplacement", "depense", False, "625100"), + ("Véhicule et carburant", "depense", False, "625200"), + ("Location de matériel", "depense", False, "613500"), + ("Loyers et charges locatives", "depense", False, "613200"), + ("Entretien et réparation", "depense", False, "615000"), + ("Abonnement logiciel", "depense", False, "613600"), + ("Eau, gaz, électricité", "depense", False, "606100"), + ("Assurance professionnelle", "depense", False, "616000"), + ("Frais bancaires", "depense", False, "661000"), + ("Cotisations professionnelles", "depense", False, "628100"), + ("Personnel intérimaire", "depense", False, "621000"), + ("[Salariés] Salaire net", "depense", False, "641000"), + ("[Salariés] Charge sociale", "depense", False, "645000"), + + # --- Neutres (hors calcul du bénéfice / non déductible / trésorerie / impôts) --- + ("Apport personnel", "neutre", True, "108900"), + ("Prélèvement personnel", "neutre", True, "108100"), + ("Dépense personnelle", "neutre", True, "108200"), + ("[Salariés] Prélèvement à la source", "neutre", True, "641100"), + ("Amende et pénalité", "neutre", True, "108500"), + ("Emprunt", "neutre", True, "164000"), + ("Caution reçue", "neutre", True, "275000"), + ("Caution versée", "neutre", True, ""), # même compte 275000 + ("Compte commun ou SCM", "neutre", True, "467000"), + ("Débours pour vos clients", "neutre", True, "709800"), + ("Virement interne", "neutre", True, "580000"), + ("Carte à débit différé", "neutre", True, "512500"), + ("Remboursement de TVA", "neutre", True, "445830"), + ("Remboursement reçu", "neutre", True, ""), # (nôtre, générique) + ("TVA payée", "neutre", True, "445510"), + ("TVA déductible", "neutre", True, "445610"), + ("TVA sur cession d'immobilisation", "neutre", True, "445775"), + ("Vente d'une immobilisation", "neutre", True, "462000"), + ("Cotisation sociale Urssaf", "neutre", True, "646000"), + ("Cotisation retraite", "neutre", True, "646200"), + ("Cotisation facultative", "neutre", True, "646100"), + ("CFE", "neutre", True, "635120"), + ("Autre impôt", "neutre", True, "635100"), +] + +# Compte PCG -> nom de catégorie (pour la sync Indy). Premier gagnant si doublon. +COMPTE_VERS_NOM = {} +for _nom, _u, _e, _c in CATALOGUE: + if _c and _c not in COMPTE_VERS_NOM: + COMPTE_VERS_NOM[_c] = _nom + +# Compte de vente -> activité (multi-activité) +COMPTE_ACTIVITE = { + "701000": "vente", "707000": "vente", + "706001": "service_bic", "706002": "service_bic", +} diff --git a/compta/indy.py b/compta/indy.py index 44498f7..c78a291 100644 --- a/compta/indy.py +++ b/compta/indy.py @@ -1,9 +1,10 @@ """ Synchronisation Indy : récupère les transactions via l'API privée d'Indy et les -normalise au format attendu par l'import (réutilisé par la page /import/). +normalise au format d'import. -La « catégorie » Indy est un numéro de compte du plan comptable (PCG) ; on le -mappe vers nos catégories. Montants en centimes, signés. `tva_intracom` => UE. +La « catégorie » Indy = un numéro de compte du plan comptable (PCG). On mappe ce +numéro vers nos catégories via le catalogue (categories_indy). Montants en +centimes, signés. `tva_intracom` => UE (autoliquidation). API non officielle d'Indy : pour les données de l'utilisateur. Le token JWT expire (~1 h) et doit être renouvelé. @@ -12,43 +13,18 @@ from decimal import Decimal import httpx +from .categories_indy import COMPTE_VERS_NOM, COMPTE_ACTIVITE + BASE_URL = "https://app.indy.fr" TRANSACTIONS_PATH = "/api/transactions/transactions-list" -COMPTE_CATEGORIE = [ - ("701", "Vente de produits finis"), ("707", "Vente de produits finis"), - ("706", "Prestation de services"), - ("605", "Matériel et outillage"), ("606", "Matériel et outillage"), - ("607", "Matériel et outillage"), - ("6256", "Frais de repas hors domicile"), ("6253", "Frais de repas hors domicile"), - ("626", "Télécom, fournitures, documentation"), - ("6181", "Télécom, fournitures, documentation"), - ("6183", "Télécom, fournitures, documentation"), - ("6136", "Abonnement logiciel"), ("6226", "Abonnement logiciel"), - ("646", "Cotisation sociale Urssaf"), - ("44551", "TVA payée"), ("44558", "TVA payée"), - ("44583", "Remboursement de TVA"), - ("108", "Prélèvement personnel"), -] - -COMPTE_ACTIVITE = [("701", "vente"), ("707", "vente"), ("706", "service_bic")] - def _cat(num): - num = str(num or "") - best = None - for pref, cat in COMPTE_CATEGORIE: - if num.startswith(pref) and (best is None or len(pref) > len(best[0])): - best = (pref, cat) - return best[1] if best else None + return COMPTE_VERS_NOM.get(str(num or "")) def _act(num): - num = str(num or "") - for pref, act in COMPTE_ACTIVITE: - if num.startswith(pref): - return act - return "" + return COMPTE_ACTIVITE.get(str(num or ""), "") def normaliser(tx): @@ -109,12 +85,12 @@ def fetch_transactions(token, date_debut=None, date_fin=None, search="", page=1, return r.status_code, [], ( "Reponse non-JSON (probablement Cloudflare). Le conteneur n'arrive " "pas a joindre Indy directement. Recupere le CSV depuis ton poste " - "(MCP/CLI) et colle-le ci-dessous.") + "(favori/MCP) et colle-le ci-dessous.") data = r.json() return 200, data.get("transactions", []), None except httpx.TimeoutException: return None, [], ("Delai depasse en joignant Indy : le conteneur n'a peut-etre " - "pas d'acces Internet, ou Cloudflare bloque. Utilise le CSV " + "pas d'acces Internet, ou Cloudflare bloque. Utilise le favori " "depuis ton poste.") except Exception as ex: # noqa: BLE001 return None, [], f"{type(ex).__name__}: {ex}" diff --git a/compta/management/commands/seed_reference.py b/compta/management/commands/seed_reference.py index c5f6d1f..46747b6 100644 --- a/compta/management/commands/seed_reference.py +++ b/compta/management/commands/seed_reference.py @@ -1,5 +1,5 @@ """ -Données de RÉFÉRENCE (barèmes + catégories + groupe Gestionnaire) — sans démo. +Données de RÉFÉRENCE (barèmes + catégories complètes Indy + groupe Gestionnaire). Idempotent : sûr à relancer en production. Appelé par l'entrypoint au démarrage. Usage : python manage.py seed_reference @@ -10,6 +10,7 @@ from django.core.management.base import BaseCommand from django.contrib.auth.models import Group, Permission from compta.models import Bareme, Categorie, Activite +from compta.categories_indy import CATALOGUE BAREMES_2026 = [ (Activite.VENTE, Decimal("12.30"), Decimal("71"), Decimal("1.0"), @@ -20,26 +21,10 @@ BAREMES_2026 = [ Decimal("83600"), Decimal("37500"), Decimal("41250")), ] -CATEGORIES = [ - ("Vente de produits finis", "vente", False, 1), - ("Prestation de services", "vente", False, 2), - ("Matériel et outillage", "depense", False, 1), - ("Matière première", "depense", False, 2), - ("Télécom, fournitures, documentation", "depense", False, 3), - ("Frais de repas hors domicile", "depense", False, 4), - ("Abonnement logiciel", "depense", False, 5), - ("TVA payée", "neutre", True, 1), - ("Cotisation sociale Urssaf", "neutre", True, 2), - ("Remboursement de TVA", "neutre", True, 3), - ("Prélèvement personnel", "neutre", True, 4), - ("Virement interne", "neutre", True, 5), -] - -# Permissions du groupe "Gestionnaire entreprise" GROUPE_NOM = "Gestionnaire entreprise" -PERMS_COMPLETES = ["client", "facture", "lignefacture", "depense", "paiement"] # add/change/delete/view -PERMS_CHANGE = ["entreprise"] # view + change (pas add/delete, ni gestion des accès) -PERMS_VUE = ["categorie", "bareme"] # view seulement +PERMS_COMPLETES = ["client", "facture", "lignefacture", "depense", "paiement"] +PERMS_CHANGE = ["entreprise"] +PERMS_VUE = ["categorie", "bareme"] def charger_groupes(): @@ -67,9 +52,10 @@ def charger_reference(stdout=None, style=None): defaults=dict(taux_urssaf=urssaf, abattement_fiscal=ab, taux_vl=vl, seuil_ca=sca, seuil_tva_base=tb, seuil_tva_majore=tm)) cat = {} - for nom, usage, excl, ordre in CATEGORIES: + for i, (nom, usage, excl, compte) in enumerate(CATALOGUE): c, _ = Categorie.objects.update_or_create( - nom=nom, defaults=dict(usage=usage, exclure_calculs=excl, ordre=ordre)) + nom=nom, defaults=dict(usage=usage, exclure_calculs=excl, + compte_pcg=compte, ordre=i)) cat[nom] = c g = charger_groupes() if stdout and style: @@ -80,7 +66,7 @@ def charger_reference(stdout=None, style=None): class Command(BaseCommand): - help = "Charge barèmes, catégories et le groupe Gestionnaire (sans démo)." + help = "Charge barèmes, catégories (référentiel Indy) et le groupe Gestionnaire." def handle(self, *args, **options): charger_reference(self.stdout, self.style) diff --git a/compta/migrations/0010_categorie_compte_pcg.py b/compta/migrations/0010_categorie_compte_pcg.py new file mode 100644 index 0000000..1b06cd9 --- /dev/null +++ b/compta/migrations/0010_categorie_compte_pcg.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.15 on 2026-07-11 17:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('compta', '0009_depense_indy_id_entreprise_indy_token_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='categorie', + name='compte_pcg', + field=models.CharField(blank=True, db_index=True, help_text='Numéro de compte du plan comptable, pour le mapping Indy.', max_length=10, verbose_name='Compte PCG (Indy)'), + ), + ] diff --git a/compta/models.py b/compta/models.py index f93bae0..3ba769e 100644 --- a/compta/models.py +++ b/compta/models.py @@ -40,6 +40,9 @@ class Categorie(models.Model): "Neutre (exclue des calculs)", default=False, help_text="Cochez pour les mouvements non fiscaux : TVA payée, " "cotisations URSSAF, remboursement de TVA, virements…") + compte_pcg = models.CharField( + "Compte PCG (Indy)", max_length=10, blank=True, db_index=True, + help_text="Numéro de compte du plan comptable, pour le mapping Indy.") actif = models.BooleanField(default=True) ordre = models.IntegerField(default=0) diff --git a/compta/templates/compta/base.html b/compta/templates/compta/base.html index 6e4451a..66e1552 100644 --- a/compta/templates/compta/base.html +++ b/compta/templates/compta/base.html @@ -53,6 +53,7 @@ Mensuel Déclaration URSSAF Déclaration TVA + Entrées / sorties + Saisir Importer {% if user.is_staff %}Admin{% endif %} diff --git a/compta/templates/compta/gestion.html b/compta/templates/compta/gestion.html new file mode 100644 index 0000000..61f836a --- /dev/null +++ b/compta/templates/compta/gestion.html @@ -0,0 +1,323 @@ +{% load l10n %} + +
+ + +Crée une entreprise dans l'administration.
+{% else %} + + +| Date | Type | Libellé | Catégorie | +Montant | TVA | + |
|---|---|---|---|---|---|---|
| {{ r.date|date:"d/m/Y" }} | +{{ r.type_label }} | +{{ r.libelle }} | +{{ r.categorie }}{% if r.neutre %}neutre{% endif %} | +{{ r.montant|floatformat:2|unlocalize }} € | +{% if r.tva %}{{ r.tva|floatformat:2|unlocalize }}{% else %}—{% endif %} | ++ + | +