From 0a4ba2001bb337ef7cde77e6d69b28aa3543d4e5 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 11 Jul 2026 20:27:51 +0200 Subject: [PATCH] Changes to be committed: modified: .env.example new file: compta/__pycache__/__init__.cpython-310.pyc new file: compta/__pycache__/admin.cpython-310.pyc new file: compta/__pycache__/apps.cpython-310.pyc new file: compta/__pycache__/models.cpython-310.pyc modified: compta/admin.py new file: compta/categories_indy.py modified: compta/indy.py modified: compta/management/commands/seed_reference.py new file: compta/migrations/0010_categorie_compte_pcg.py modified: compta/models.py modified: compta/templates/compta/base.html new file: compta/templates/compta/gestion.html modified: compta/urls.py new file: compta/views_gestion.py new file: config/__pycache__/__init__.cpython-310.pyc new file: config/__pycache__/settings.cpython-310.pyc modified: config/settings.py modified: entrypoint.sh --- .env.example | 12 +- compta/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 159 bytes compta/__pycache__/admin.cpython-310.pyc | Bin 0 -> 5767 bytes compta/__pycache__/apps.cpython-310.pyc | Bin 0 -> 483 bytes compta/__pycache__/models.cpython-310.pyc | Bin 0 -> 13613 bytes compta/admin.py | 6 +- compta/categories_indy.py | 84 +++++ compta/indy.py | 44 +-- compta/management/commands/seed_reference.py | 32 +- .../migrations/0010_categorie_compte_pcg.py | 18 + compta/models.py | 3 + compta/templates/compta/base.html | 1 + compta/templates/compta/gestion.html | 323 ++++++++++++++++++ compta/urls.py | 2 + compta/views_gestion.py | 267 +++++++++++++++ config/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 159 bytes config/__pycache__/settings.cpython-310.pyc | Bin 0 -> 3531 bytes config/settings.py | 11 + entrypoint.sh | 3 +- 19 files changed, 743 insertions(+), 63 deletions(-) create mode 100644 compta/__pycache__/__init__.cpython-310.pyc create mode 100644 compta/__pycache__/admin.cpython-310.pyc create mode 100644 compta/__pycache__/apps.cpython-310.pyc create mode 100644 compta/__pycache__/models.cpython-310.pyc create mode 100644 compta/categories_indy.py create mode 100644 compta/migrations/0010_categorie_compte_pcg.py create mode 100644 compta/templates/compta/gestion.html create mode 100644 compta/views_gestion.py create mode 100644 config/__pycache__/__init__.cpython-310.pyc create mode 100644 config/__pycache__/settings.cpython-310.pyc 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 0000000000000000000000000000000000000000..682e4f39598a4630212c60da82ef4035c001cee6 GIT binary patch literal 159 zcmd1j<>g`k0=UAH(VHMO8pH#;>aC$&gFH?KtB%Sl03Avr&{pd?WrO2x-#=4F<|$LkeT W-r}&y%}*)KNwou+UCaa|SQr3TPbB{U literal 0 HcmV?d00001 diff --git a/compta/__pycache__/admin.cpython-310.pyc b/compta/__pycache__/admin.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be0ecd7db9c6a7655d02e988bad83032de558361 GIT binary patch literal 5767 zcma)ATW{RP73Pp!lFPl=l4V(ToK$GiCfn9>)22l)LF2@^G-;xywvhshwhYA?NnCTe z>lsq9)J=iZ27(rF|3Fbd0{Wu&{sX`CH_U6F@|?Fm^*ckZc6Cd-!W<3faAwY&%XiLj z)ais4e*gOH&fwpdEbBk?F#l`d;TD>Dk69MCILoXd>oZ1uJF|yQ-$CEuZsraf{l?Ji zdqcnP4}*R%Z1$VOupbUv{noJEZ!_y1i#NFU*y5gGgU%zT-xX~Cf@mKwaC{zs6I7f> ztbft)8iv>8A$Z{tZ^7_9!)x(2cRr7AkynB^j1Ls=(?lr>+ z4d)qt9h~bm=NbO&U90!p7o_DL(~X!9(;S_kNQYu?l#4H*X{#4#?~PcBq0r%5c_GDE zrb=ki|p654V8=hP4C z`{)R(&$!jMIqN&z?z^8`n~d!GIOsP_pDY*jz0WPz>ial>uUmJM(OB@;=>X+xSmSm! zO4TUO#cC$hDo5|>Av)t&u0oXh46EZ&Da{M{D8He~v5-auYE6f;w-S3!QWDfhqyOqv zkivRo?Xw9xV4vC(Hn0bdY)|aF*2EsT+)k~DHE8@D&iW6#@b-OrW$T8G#nT4&HA5F)Y^DgE@G7qnhHSLEMP8sf$go#8Dwq#M9I|D zc-x|ocF|gB>LCcUW(}Bnxv=*g>O-q8bmnq)-T$xF09+8BtWQ1UWy>6mhX##c5+a=4j_j7R8E69}IBVz<|j zSLua-Z$ySKuhFA-|C3m5Deb_*@;WU;#*@zx`4$L?p~w;eYqQY&ErBkwWw!UiyeZDz zH_$;8k>F9J!zdb#c$rbZ9l;sntmgPp#77ByXPI`U4bAqtQ+rhcpQ$~olF-O%#3UbS zqA5c8z;fXyZF|X+G@hUx`w{NkLQ^#VBpz_5-{5ZF;|+w6r@Ke6V!|IIPCgpyMycX0 zp)ZJsNd|a|k~mAsOzF@#Fv2F;0^%h_x~Ur@$))HylF&!KOyq|ku*GcP2h&mc3bAZ? zlLlyXz30mwJ$#4AcR_SJOH~o^RE@KESGUYl9kdG)j5V}>w<-@3yO2wOc zA>N3K!T>mgRM4XK4Sy%off8k!L9@DB^@^Q1+K82adVYre<)A!FUl(QAEK}?7)^ZcIvECBvG`~>9bZ&GM6xX9`b-UAG3a7Qb!ZoXz2@Y9p!^s zBb8}McHP;Oah_}=-$gi^zPux(s&tX18^{*LC`IPGn3qH10~`t&!@sn@Q&uw?Vi>0x zT#J^IZxFc+f&ua$U_Ry-@=f#)ZQU-56z+j63FT{lOOB9|)T#VK=ccA!068!J6z+bb z$~JecL$==Y4DeJTdj@F96|8jOD5*$+oRw$0b@)&|uDk{j;V@aK=uc?3g(`ZZZDdFkMn#-en*0Dvd4tHzlV_0kIv5nCic$-CGX4zg zEqao83EHvW5$usQ&h}{Ntf5;~?N#}!Tlp*PA>StQ4oHugd{+C}Y@8h7+-Rhu&K+bH zlD`IneAhUFIS2s|jh=smc>oelA1NFGX-uvc=PSi`^DNDUq%zv?*qgcr;di8{~snQdXw|W++hpC2^`0%E8r_;&OlrkiWQHraja~EskiD z>9{~SbjpuOK;y@hkf}D|tYG{0fwk`(z?YxcbP+Kgt()~80zjxeRm4SE=mn0rj;0(c zhylf3Ii_LhU`Tw@cciTW`2L}ywyDs)CKWu6bI4I=Ec%EFv zCc0A{dI*I>r}DQLlOGWS$3qn5MYLV$*0fI$J9{XSo?9(NGb#X$&;v~TftF}h1sbZB z(NG$OBR{4$V3|SLZdmHoteT`_=KQyGG^PDY6p^i(L~2+D}cCDdQ8`r&N!MUtc&0h9t@Pc=mg6 z?~NfPga2>HFL@xYnsJjWs6F)LtO__@{kJxVVt$ zW&#tKTcmLb9ZIo<>#E7mfvHU3M#lWc5e}L8w~lpA;2zbX@;5~8&<40wp&VCoNFy^e zpMhWw2AytNhvCeq!)!Sn2W$2tOWs0LAA@{dm<5v zglch9L;R0PRQC$mq~i-RDe4e zyoIJ-0XfgrkwttF4B*n-hoe&&PG7h}9>RTFx?Kl;<-INAW;5@VW2{^yfCto*L^g;J z)Z@N_(mO7TQIde^LKopr+enx~ApY!aQ zQU44Ey{nZ>fk{tsyh zuS!%VYnkj)xj4C@5&x`H>)=%gijNn+AP`dD18L*C)JyhaBUoNyfsf{)U0P&|Uf2j( P;G%aS2o_mzd71qWh+zx7 literal 0 HcmV?d00001 diff --git a/compta/__pycache__/apps.cpython-310.pyc b/compta/__pycache__/apps.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba6425b55beba79b9d51cbc8fed0d472725bcbab GIT binary patch literal 483 zcmYjNy-ve05VqqKg*IIf5-dnrGbDKdRP{#!iJ=Qy7AvjYD8-45oglR}?RekZxd z{AU!8exM4)*j_G_TFGWtm0K4%Ck)OU*xZ8PD8~#zH_i$3a-WfWG$7et#rUr1L`w^Rrn?^7cR3rralMg-_ZXi@Phk6A{LV7Njj3c~vVuun zHDzK1=jxEWa3Q$P>ZZ+}mQfN7OJ*lkC}mQ0&;*d&n@s>BKtA?yfcxk=CU$Z<+*NDO a4R!VZ;|D8*m<{fSt7h)S#zBVYn*0GJ<9t#8 literal 0 HcmV?d00001 diff --git a/compta/__pycache__/models.cpython-310.pyc b/compta/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c592ad5d610779f25956d5b9231e56dc25b6ca7f GIT binary patch literal 13613 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 literal 0 HcmV?d00001 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 %} + + + + +JB Compta — Entrées / sorties + + + + +{% if not entreprise %} +

Aucune entreprise

+

Crée une entreprise dans l'administration.

+{% else %} + + +

Entrées / sorties

+
{{ entreprise.nom }} — vue unifiée de toutes les opérations importées
+ + {% for m in messages %}
{{ m }}
{% endfor %} + +
+
Opérations
{{ nb }}
+
Entrées
{{ entrees|floatformat:2|unlocalize }} €
+
Sorties
{{ sorties|floatformat:2|unlocalize }} €
+
Solde
{{ total|floatformat:2|unlocalize }} €
+
+ +
+ + + + + + + + + + + Réinitialiser +
+ + {% if rows %} + + + + + + + {% for r in rows %} + + + + + + + + + + {% endfor %} + +
DateTypeLibelléCatégorieMontantTVA
{{ 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 %} + +
+ {% else %} +
Aucune opération ne correspond à ces filtres.
+ {% endif %} +{% endif %} + + +
+ +
+ + + + diff --git a/compta/urls.py b/compta/urls.py index 152c844..5ca3508 100644 --- a/compta/urls.py +++ b/compta/urls.py @@ -3,9 +3,11 @@ from . import views from . import views_saisie from . import views_import from . import views_bookmarklet +from . import views_gestion urlpatterns = [ path("dashboard/", views.dashboard, name="dashboard"), + path("gestion/", views_gestion.gestion, name="gestion"), path("dashboard/mensuel/", views.vue_mensuelle, name="vue_mensuelle"), path("declaration/urssaf/", views.declaration_urssaf, name="declaration_urssaf"), path("declaration/tva/", views.declaration_tva, name="declaration_tva"), diff --git a/compta/views_gestion.py b/compta/views_gestion.py new file mode 100644 index 0000000..0b5d053 --- /dev/null +++ b/compta/views_gestion.py @@ -0,0 +1,267 @@ +""" +Page de gestion des entrées / sorties : vue unifiée de TOUTES les opérations +importées (recettes = factures, dépenses, opérations bancaires neutres). + +- Filtrer (type, catégorie, régime, texte, dates), ordonner (date, montant, libellé). +- Modifier via une popup (avec explications sur les catégories) : la TVA est + recalculée automatiquement. +- Supprimer. + +Tout est cloisonné par entreprise (Entreprise.accessibles). +""" +from decimal import Decimal, InvalidOperation +from datetime import datetime + +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import redirect, render +from django.urls import reverse + +from .models import (Entreprise, Facture, LigneFacture, Depense, Paiement, + Categorie, Activite, RegimeTva, StatutFacture, TAUX_TVA_CHOICES) + +CENT = Decimal("0.01") + +# Explications spécifiques (affichées dans la popup). Sinon, texte générique par usage. +EXPLIC = { + "vente": "Recette imposable : entre dans le chiffre d'affaires (CA) et dans " + "l'assiette des cotisations URSSAF. La TVA collectée est due.", + "depense": "Charge professionnelle : ne réduit PAS le CA micro, mais la TVA " + "payée est récupérable (TVA déductible) si vous êtes assujetti.", + "neutre": "Mouvement neutre : n'entre ni dans le CA, ni dans les cotisations, " + "ni dans la TVA. Pur mouvement de trésorerie (apport, prélèvement, " + "impôt, virement interne, remboursement…).", +} +EXPLIC_SPE = { + "Apport personnel": "Argent que vous injectez depuis vos fonds perso. Ce n'est " + "pas une recette : aucun impact fiscal.", + "Prélèvement personnel": "Argent que vous sortez pour vous. En micro, la " + "rémunération n'est pas une charge : mouvement neutre.", + "Remboursement reçu": "Remboursement d'un tiers (trop-perçu, avoir…). Ce n'est " + "pas un revenu d'activité : neutre.", + "Cotisation sociale Urssaf": "Vos cotisations URSSAF. Déjà prises en compte via " + "le taux URSSAF : ne pas les recompter en charge.", + "TVA payée": "Reversement de TVA à l'État. Neutre : c'est de la trésorerie, " + "pas une charge.", + "Remboursement de TVA": "Crédit de TVA remboursé par l'État. Neutre.", + "Virement interne": "Transfert entre vos propres comptes. Aucun impact.", + "CFE": "Cotisation Foncière des Entreprises. Impôt : suivi mais neutre côté TVA/CA.", +} + + +def _explication(cat): + if cat is None: + return ("Non catégorisé : pensez à choisir une catégorie pour un calcul " + "fiscal correct.") + return EXPLIC_SPE.get(cat.nom, EXPLIC.get(cat.usage, EXPLIC["depense"])) + + +def _entreprise(request, pk=None): + acc = Entreprise.accessibles(request.user) + pk = pk or request.GET.get("entreprise") or request.POST.get("entreprise") + if pk: + return acc.filter(pk=pk).first() + return acc.first() + + +def _dec(v, d="0"): + try: + return Decimal(str(v).replace(",", ".").strip() or d) + except (InvalidOperation, AttributeError): + return Decimal(d) + + +def _date(s): + for fmt in ("%Y-%m-%d", "%d/%m/%Y"): + try: + return datetime.strptime((s or "").strip(), fmt).date() + except ValueError: + continue + return None + + +def _rows(entreprise): + """Liste unifiée des opérations de l'entreprise (recettes, dépenses, neutres).""" + rows = [] + for f in (Facture.objects.filter(entreprise=entreprise) + .prefetch_related("lignes").select_related("client")): + une_ligne = f.lignes.count() == 1 + rows.append({ + "type": "recette", "type_label": "Recette", "id": f.pk, + "date": f.date_encaissement or f.date_emission, + "libelle": str(f.client), "categorie": f"Facture {f.numero}", + "categorie_id": "", "activite": f.activite, + "regime": "nationale", "taux": f.lignes.first().taux_tva if une_ligne else "", + "montant": f.montant_ttc, "tva": f.montant_tva, "neutre": False, + "signe": Decimal("1"), "statut": f.statut, + "date_encaissement": f.date_encaissement, "date_emission": f.date_emission, + "editable_montant": une_ligne, + }) + for d in Depense.objects.filter(entreprise=entreprise).select_related("categorie"): + rows.append({ + "type": "depense", "type_label": "Dépense", "id": d.pk, + "date": d.date, "libelle": d.libelle, + "categorie": d.categorie.nom if d.categorie else "(à catégoriser)", + "categorie_id": d.categorie_id or "", "activite": d.activite, + "regime": d.regime_tva, "taux": d.taux_tva, + "montant": -d.montant_ttc, "tva": d.tva_deductible_value, + "neutre": d.neutre, "signe": Decimal("-1"), "editable_montant": True, + }) + for p in Paiement.objects.filter(entreprise=entreprise).select_related("categorie"): + rows.append({ + "type": "operation", "type_label": "Opération", "id": p.pk, + "date": p.date, "libelle": p.libelle, + "categorie": p.categorie.nom if p.categorie else "(non catégorisée)", + "categorie_id": p.categorie_id or "", "activite": "", + "regime": "", "taux": "", + "montant": p.montant_total if p.sens == "credit" else -p.montant_total, + "tva": Decimal("0"), + "neutre": bool(p.categorie and p.categorie.exclure_calculs), + "signe": Decimal("1") if p.sens == "credit" else Decimal("-1"), + "sens": p.sens, "editable_montant": True, + }) + return rows + + +def _filtrer_trier(rows, request): + q = (request.GET.get("q") or "").strip().lower() + ftype = request.GET.get("type") or "" + fcat = request.GET.get("cat") or "" + fregime = request.GET.get("regime") or "" + dfrom = _date(request.GET.get("du")) + dto = _date(request.GET.get("au")) + tri = request.GET.get("tri") or "date" + sens = request.GET.get("sens") or "desc" + + def garde(r): + if ftype and r["type"] != ftype: + return False + if fcat and str(r["categorie_id"]) != fcat: + return False + if fregime and r["regime"] != fregime: + return False + if q and q not in (r["libelle"] + " " + r["categorie"]).lower(): + return False + if dfrom and r["date"] and r["date"] < dfrom: + return False + if dto and r["date"] and r["date"] > dto: + return False + return True + + rows = [r for r in rows if garde(r)] + cles = {"date": lambda r: (r["date"] or datetime(1900, 1, 1).date()), + "montant": lambda r: r["montant"], + "libelle": lambda r: r["libelle"].lower()} + rows.sort(key=cles.get(tri, cles["date"]), reverse=(sens == "desc")) + return rows, {"q": q, "type": ftype, "cat": fcat, "regime": fregime, + "du": request.GET.get("du") or "", "au": request.GET.get("au") or "", + "tri": tri, "sens": sens} + + +@login_required +def gestion(request): + if request.method == "POST": + return _traiter_post(request) + + entreprise = _entreprise(request) + if entreprise is None: + return render(request, "compta/gestion.html", {"entreprise": None}) + + rows, filtres = _filtrer_trier(_rows(entreprise), request) + total = sum((r["montant"] for r in rows), Decimal("0")) + entrees = sum((r["montant"] for r in rows if r["montant"] > 0), Decimal("0")) + sorties = sum((r["montant"] for r in rows if r["montant"] < 0), Decimal("0")) + + cats = list(Categorie.objects.filter(actif=True)) + cats_json = {str(c.pk): {"nom": c.nom, "usage": c.usage, + "neutre": c.exclure_calculs, "compte": c.compte_pcg, + "explication": _explication(c)} for c in cats} + + ctx = { + "entreprise": entreprise, + "entreprises": Entreprise.accessibles(request.user), + "rows": rows, "filtres": filtres, + "total": total, "entrees": entrees, "sorties": sorties, "nb": len(rows), + "categories": cats, "cats_json": cats_json, + "activites": Activite.choices, "regimes": RegimeTva.choices, + "statuts": StatutFacture.choices, "taux_choices": TAUX_TVA_CHOICES, + "vue": "gestion", + } + return render(request, "compta/gestion.html", ctx) + + +def _retour(request, entreprise): + base = reverse("gestion") + qs = request.POST.get("retour_qs", "") + url = f"{base}?entreprise={entreprise.pk}" + if qs: + url += "&" + qs + return redirect(url) + + +def _traiter_post(request): + entreprise = _entreprise(request) + if entreprise is None: + return redirect(reverse("gestion")) + action = request.POST.get("action") + typ = request.POST.get("type") + oid = request.POST.get("id") + + modeles = {"recette": Facture, "depense": Depense, "operation": Paiement} + Modele = modeles.get(typ) + if not Modele: + return _retour(request, entreprise) + obj = Modele.objects.filter(entreprise=entreprise, pk=oid).first() + if not obj: + messages.error(request, "Opération introuvable.") + return _retour(request, entreprise) + + if action == "delete": + obj.delete() + messages.success(request, "Opération supprimée.") + return _retour(request, entreprise) + + # action == "edit" + dt = _date(request.POST.get("date")) + libelle = (request.POST.get("libelle") or "").strip() + montant = _dec(request.POST.get("montant")) + cat_id = request.POST.get("categorie") or "" + cat = Categorie.objects.filter(pk=cat_id).first() if cat_id else None + + if typ == "depense": + if dt: + obj.date = dt + obj.libelle = libelle or obj.libelle + obj.categorie = cat + obj.activite = request.POST.get("activite") or "" + obj.regime_tva = request.POST.get("regime") or RegimeTva.NATIONALE + obj.taux_tva = _dec(request.POST.get("taux"), "0") + obj.montant_ttc = abs(montant) + obj.save() # recalcule la TVA + elif typ == "operation": + if dt: + obj.date = dt + obj.libelle = libelle or obj.libelle + obj.categorie = cat + obj.sens = request.POST.get("sens") or obj.sens + obj.montant_total = abs(montant) + obj.save() + elif typ == "recette": + de = _date(request.POST.get("date_emission")) or _date(request.POST.get("date")) + if de: + obj.date_emission = de + obj.date_encaissement = _date(request.POST.get("date_encaissement")) + obj.statut = request.POST.get("statut") or obj.statut + ligne = obj.lignes.first() + if ligne and obj.lignes.count() == 1: + taux = _dec(request.POST.get("taux"), str(ligne.taux_tva)) + ttc = abs(montant) if montant else obj.montant_ttc + ligne.prix_unitaire_ht = (ttc / (Decimal("1") + taux / Decimal("100"))).quantize(CENT) + ligne.taux_tva = taux + ligne.quantite = Decimal("1") + ligne.save() + obj.save() + obj.recompute_totaux() + + messages.success(request, "Opération modifiée.") + return _retour(request, entreprise) diff --git a/config/__pycache__/__init__.cpython-310.pyc b/config/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b12e9c4eb514c80b3bf150e2c6d7eb6cc4d7ecc GIT binary patch literal 159 zcmd1j<>g`kf)6*HvOx4>5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;!HMesOAXab|vA zv3_DoeoQ#%P1D_M8d{=lHL@s(bes}caB0n0rbCg+ z3}vs0EKneQ+P4MvF$Lm2ZTDqAqJJ@u+oyg)i=y5$BiT`$02>LMIdg7v=FGWFG&z}< z;FtY-d*{xSB>lt}<3CQl4Ogrhh0Ywv&KWd~X1fZlN~qD!!vL+`@)-uJREqtbJ=D?9V(edisq)8zx1e1~%A zgQJz0KSUoLC<6MJTOyz<=;{b6qlE(neS)q%&-$`+DVzZLMeu_*FQLkFh2zk5bOUy~ zjy@IRO)=i`<*w?yi#~JSL${q0SpPnJm(l0W2OxcCUUEJ}U!Xq#?-%HgFy4KxI3J-u zIUl2Y&K2~fa~0+?N?B4_AN&VyEnkhoEx$V;UhGFdi~hb z@^&e{$A85HV2VTPZ*uIekHZj$aS7qlu19=NV9^yE?)oGGQV)pvt6P3& z+sCA&0ltL868iCnT^vOHzMx+l6KzE#_WTfdfvry>H0W^a%3A(sFj*?gOl^7bb`yX? z5P8T&Q4C5Gm8)K6YUuSa%Obqx4T6|u2#yCN>?mAz^y6?EzAAil4u)8IAwQL3<*EEk zhOkK;K$R|6iz@Z9*T$`b{A=YaesgDF%AMuD!nkSfWH?@9pUjxM_dX>#W65bt-sH5 zu#dYLMFH1Hx&3o8IRg_rQ~OG9u9&W6RNIE_J}|yc&LOnm(_w9*?5bHk{)tEOdJOkVg^zSevMso=Sht{7rr;6L8o+ zlqZTcJgGQtSz(HHoyn0{4F@P363gEWd>Zp3-a7|t$}2}kQf1{(ku!(n0_T|@*;KXK z%dXvCw`?%XY}Z%nO^ZovW<*{xR_o20+iIJSzJ~qIt=e|WedI2i?G3$M0}-IuCQEO} zai7}9VwTl#+eWS4HmY_xo6I5+?i#o|CjO@8p`ITmGbgcwcsrRtf#f0()$x8G)8zaK z;?ZrtM(9BpcwLaVcv2#U{OfG_kWMT!41#hlSsZcoe1rnL=Mk)lEA``j&Ej_pz}hh_ zS=ih5!KlcmSQ``B;MgX|j`7|)X}~Bz#|5n+ZcM8Dx>;c1f>3 zFq$={LONM2r?*uX-W4?Y$b$g;I1x2dlg$)(ZOX!E7} z`Vo!0&|jVef^Xe2+DzHmWCajv>6W!&wrhgIs}-9FqK+4AgN(qn%(l(c&u-oP^yj-! zP0Cr8F`7_145so8nJU!fj`gTQi7Ld3RkPW2*YroOzG56F=(uTM6J*lOBoD|D4As5@ z8>_(*s`Tc?=lF~6Tdj4i+C2hcj>{f9%2h$Q#kR@0gv z`n^G~fx~XR{ZjtRx0N5}Zfj)YO9l7eRzY*n!^H2rt|7fe7Pm-JGzKK<<4OxoNRuoT z#je5%LqJqLav3~07PwIA1YV3Qe@^;{YYuN)UDZu#yxi$fMhvkw;LaEH9TASk{H- zM#ptowhwQRz~3aiKav6rEHiwF5Z*0W&UN__I477C1-qDK-~k1djOCVe%i!rkCON0d z`?_f6q{!F#m#v(ohrK#YA%9{v`D(A8ePf$E58~@>W7r*C-t$%sy#{$S3*rAtmlWZD z)-o&$GS;n{=7YMy-w`0qriJcTaWf;$<9(E!g4S$1~g)W72Bq1 zyX)-+B-b*Vu#L4=L$?i!O>I~!bT$Na5A{YJh$fVo z$p$=i)*(T`*qQ+f>uZMVn2_K(ezy9JJ5841knMmMZO~lhyR*Uw=H{@R1-d|o#inb< zvcBH1UD(icm+MBO#wHpjR16_CJp_+l3<5U;ICI@TenxQQ*h7cE&WL#J7os)~olv0%EQbjc{Ps HTTPS seulement si demandé (laisser 0 si Traefik le fait déjà). + SECURE_SSL_REDIRECT = env_bool("DJANGO_SSL_REDIRECT", False) + INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", diff --git a/entrypoint.sh b/entrypoint.sh index 6ad933e..5fed5c7 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -20,4 +20,5 @@ if [ "$SEED_DEMO" = "1" ]; then fi echo "→ Démarrage de Gunicorn sur :8000" -exec gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 60 +exec gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 60 \ + --forwarded-allow-ips="*" --proxy-allow-from="*"