From 688fa3fc97cf27a720f36f8681e9f3cb173044ef Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 30 Jun 2026 08:33:05 +0200 Subject: [PATCH] Multi-utilisateur : droits par entreprise + renommage Operation bancaire --- compta/__pycache__/admin.cpython-310.pyc | Bin 4222 -> 5658 bytes compta/__pycache__/models.cpython-310.pyc | Bin 11824 -> 12716 bytes compta/__pycache__/views.cpython-310.pyc | Bin 4834 -> 4952 bytes .../__pycache__/views_saisie.cpython-310.pyc | Bin 5423 -> 5515 bytes compta/admin.py | 58 +++++++++++++++--- ...ent_options_entreprise_groupes_and_more.py | 46 ++++++++++++++ compta/models.py | 50 ++++++++++----- compta/views.py | 18 +++--- compta/views_saisie.py | 56 ++++++----------- 9 files changed, 157 insertions(+), 71 deletions(-) create mode 100644 compta/migrations/0007_alter_paiement_options_entreprise_groupes_and_more.py diff --git a/compta/__pycache__/admin.cpython-310.pyc b/compta/__pycache__/admin.cpython-310.pyc index 7a98e87d5078d54eee51d528b00ee9e0ddcd3cd8..6f05201011cb2f1aeef40d1eb005da1127b5db7a 100644 GIT binary patch literal 5658 zcma)ATXWmS6~+P}0D`1s(XuQ%PO3I(8@9C)_ufpVRpKU@q=}lwX=aLE5D2@Hz<>n3 z3n-S#UhSDo9^C$bPG_W@2wqG%s6P<$SM64aE3EV^Q74MS`4 z5VY`wwq$6Yp|yA$wDt+DYiPcqb@&2k3u57+gB7o0mqmUBJy^THjQy|Se}ymc?qkNU z^5sVsMqM}a12g{`U%}{=$^2E!zs^_DvpSi7!;Ed2u{ZclP;O4f-ZYfZP@dwqK)E%c zJjI{BXZ4=>jHKLSx{>ffHbf&Rvw=7$hT=0w+Uf<`do___AawZJu#{pXGbOZlH_ybd z)c)&9T2@l%_S@OcP)y(apNf$fVqEZUl3^}JHt!~-*ePTtQrdC;aV9V7MKpvJF>XaR zXOYA0$o<6HW~A3gLDVp9(p(UEpIELH`8a{ETldmpB={?Ifa)h$<8EGLsu&K%S}xQY zN9)Nk8lyz6ftAS_)<#7o&46JsysfH{kVXV*U5B$#sXfOjG3ukxzj_%}X+5+K*_a)% zKiFf|xBHH4kL`Qb*zUXB&aAQ3Z~Ozs`cJ#`4t*MN#4`3T_BRH`olWh=aW*_AAvX*I zy1gUH_#^QzCPNQUG#QPRDi0@0_L*k*um!Pwp<>&V<`A#YxuP3bI|u} z+30(Hf9!JiF`G~q47CC3oiaE}Et?OmemG`hdz+-~OeE`V$_4DNgA+-5sj*j>_rgrK zBzzr=CKlRj$*Y(ofj*XzsJu>VxWi;1Bxzmy#a3UWrIOcZ2pv-{QUB_8AqU%;$a#zw z6#-}XNbL6-@&*kE_*QJ_@+Q4{AAOw29i<&;Ox~hpNEz}Os=kVf*ifX2fVEj@e#@x4 zY=s>>H?N9I_YHIq$HaIX>oAT71+Q{yw__Mrl20gp9P=WDEv(Saw4vEvcP3VniaeiK zO3k6+)v!r6(nL{&+JWW5PTKaeC#gS0IrR{ZY@jHbf7ZXb6E(OSdAtFa@btn7Oc;0W zHMBD<2D(wHWJl;l@gU6sH*uQeX_YIWR$6A;x=~0jMbD9hAo31X-$vDQ&veM|P%o1& z;BT|%%N~vV236lgrQ3O?%9v+rlqdVTW!@(4)-f-FXJrBh(;X!eneNtFRkd`C5@(uR z$BJJ@nTp6JAq7lQ|K%bg>pL$Zh{n~e8i#AS_&7QM4hTyU*vm?xTV+x`h%2d7vaOer zt)wgs^nytRd1>F!_i`O5QDr$~s2A#1xtGLSi4r|q{s{Zamr!lOcu$K*ewS$9qiS~4 z*?E5m0!>haMlcCGuqE}KJL?oP)b~L>f7aR@=Aho^ArE--5sLx?`b|iqr59g2LGp=2 zj8+Zry0a~lVY-V*72|CB+MbZAmPMXz0q4siL(IA|tOi5}43shf6l#C3s%JFBAjxtV z6p2E=BX5We09s>>or=^i%f zIC0N9W}7|FKuqnN7r>KT#ad@Egglw}o&)hVJGga5V5fk5^AwU3m*li`K1CV^M}*`a zN8I9wZ-`y;4fGpBfYqFE1_2S`)i~z>FXv2!fJ0xU@F7Klpq%ed+-C!X+G>oC96Wmw zr)TEKw=k&}UT&g4pxu^g?~QkncOa!Hlf0JX+aSu@RLv}T25uW55PT>KB4o+<8G!40 zl4vQ)spkaXNE;VhG-TG$tvYL}?bWUAmG+RYQS~}1U=wvr>}=LghH$Aj(oyFQGIPmI z5Z**FhF}gt07Si)A7LJVgcBiH!y%2zb$33;dvlm)Lm?^PU%*@w-MWFeBXZpk58z%G zGWc^0dlzMj8N?p-RbNB(P9iCOufZ_~0*PCiWJ)2CTYEmK`pDh**LSO|hpeW>;cYS- zm02-7XUD{#v11Col!b6svP1jGI&_X;%a3ikLKutI&B-1DK&U-cCS_IXB@VxirxZ$M z0mWW5qHgJ6O$tySYnR@8jt4$cr$E2MXn^@!te+M8bny5qGh*}0jJ?JMsTlH; zVjvB{kui<@kg5b#N}1&Jp*BSn5`bDmHAT*`J$A?=hwC64LaW0R&@Tn0z73r6I-8KM&si+$q;+Ru!aDPt4br%+!Em0mmxhQ!I`c=j7m zBjS?+!wkrp*8W37Z3Ba&>2N)tle}P3r~nSNPLD1=}3iQZt44 z&E2mK&!H4MxMZ5x9GI*GCS+7^jNp7`CMnI7gcR}@v@$M1$hI%ey9OB0N11|Nopn*~ zxiga9HfZRFsJ_Ul`fw@k;eu{H5a5E|5>50qO<%}c=F;9aeQk~l|6{wpDY}c{;XK0^6Fhf6}skDw~ zA6z?ivh@>|-y@bU-B{8RwZAg?F|~hBl?h;`BD6GwKT>0i>fq|xov^|C6uewu?5b!>i-kIRhKAWM{Fpps;#{mFI1 zh_n`h;bSI*nIse@+(%KAXUsctXycmbgskl>FiE4%nYx3{4HWevs>@BiADl7rCZ}{e z)a{9z*OnU^6PsDCEU;<~^&@H#iW^tPr3CqPQk6xX-IrfMH-Od{t(jp4zeIxxFy#KU zRS{CX|G_J3H1js5RvAa+|9mr^w5|cHZ_V}oj>FH+ya|llxWtP zC4I_2QAO*{nej}6e+J8hdXyu&l8 zhLRK^>kk(6l|qu=OUl1$&@`VZ+IZFBJCu$Y1)6)h4hV+fKGNJt&1Fq)(8Ns|-g!US zs`5lmfSuAZ?bQ*;gd-C+YHKEoGwfe)dSN4Efs58+;CESYZH4_0cSq}y literal 4222 zcma)9S#upn6`tkJ+_`fXEtYIa4wR?@;v0w(prDE(1TP`15#YE23{d0IbW0jBi`+dU zY?t2bsuT|{{(}9e-{F7JuRP`1Z+YT7XYOW8N|;-Hrsteq&VJ6VhQrXp^Pg9@kKP|y z)_>_^@$v9+6GPv$ElXI!&aJ|ZY@6mz?i6n1V(yAg-YLAuEBweWx>2_XqM+zSy&{al zq965(K{T)}X+Iyz!3+CWmhgoC)Dph5k48`2Cw8>NG>>Ur5r7uVXv<9Vnbs2_XyJ^u z!n7{a`eFduU`AWTJwq`9ZUo$wCvLQc_qtdT%g=1FB37STxcMrd5bz0C#2Qx9s+Oy3 zIAL8}1rDqDgbiNZ=hfH51}MNxR&O#bWZI_K0&QzX+Y;A5wZ?CKLAo8=#!Ezzm6!x| zR>;RyDZhYzt#M%d+li8eG~qAHTFGOTX=(gBc_z!+bU#kgx>3>$exDtba{AT%NFK`) z>w-Tf8TMjj?@m(7gGyyGr5hI?y5}Z_ehWZaku9vq5q9JXC+Y|{@q0SQDPbSd&q-EG)31}}VcaOKlYO(2>?L*0h6A@6+B99JpX4Uc zvdMC2*o@j)eUij`iI!tWeFOKaZvyO&J#~dXh$FR5;3|P@1ZHBlv+IN-)06VG4?yw; z4&yGB_0nBp_R@ywUG;<2xV_ zQ3n8Qeh0=U?4=4(-v$+e_uJ5Hh3>TcgowaN=&EYhEzfiOuKGSLxrnWI zLHHKNoGtQ6!d}XjUi3-nBA{*9$bpSqv$~ixepBXIDOCrD?T*jstOvC55P~u{o_q*< zT*%;iSVmf(x)HHQxQ0yhNunM!QfP9K^RgLg1ADd^NWLItYr=gtf3x%&bY=$S*6h{W49y~!OCv0;2&Cv{Vs z6@f$;Pbrie5uJW>OlUCiWH6tc{m7fe@42%u{dkQ|RRTx@P73L~>pv@*`l zmt-960Bkwx=Xj+UuRU}nJ^BNHivue1W%t020@(u$g%|ZdxAtw}i|*4P8hj3wL_QzCuJQU)}}w5Q+&=p>J&Mf zyq0&g(RT}<$UVQHBl_(fNZVDRnBc10w45xaJ_1Oof0#Zrr-VZ~(AxmD^~8E^pW3I+ zse9Ua(Xq+G4q3Lf>Y2R@Yk9gkMtUFUgqzl?M z4%AQaGF?jEISOqrg+;j6Xw!YDgR?TqQeYNq}s;Ld1KZ65uO4xZg3pF?^3Ox!%qUy|e3aJLlM9#^e zS?-@W^!|aptno8j(K$SCs8Zd1dB%GcnLnkhGks`IwjxcvqL4eakB}wR$C#oJ#dw`L z>UVe>PH~`e;g!Oc_9Zx_!|+KgKMvIFf+&Q*G0Sf z6P-bxsO}OV-I?yBQ)QL2tcv-VD`D>1f5(R3VrYuVwhL(kIdnohbVGaKj%-D%&bhh^ zxWi!NoB7cR$UDKE6{ zKXFY)m$G#?76si|IAC`izL}M-(k^bHH)+P^C8H`nj?P{1<+&M;K)8>gM*#DD96B%_ z(RRnzQ5cR;gsPnsC0HTY*;fV=WK(CkZhI4-+Yzm(aw?xdmjDI7q8?3g=A`gY0{|0H z>P5&X6MjO?8{3or#c+GiEiE^YW_T~zYw|=*5<7Km#&2s87bY$ntv8d$G3OVFYl08J pBmSvagXU$$+X8ihw{XaZ7rOp5V diff --git a/compta/__pycache__/models.cpython-310.pyc b/compta/__pycache__/models.cpython-310.pyc index 42213d9a2cf58c7e491eb9b6a1160476bbd8cae1..92e79de986559572a9d02230b98737fce1882efe 100644 GIT binary patch delta 6303 zcma)AYiwM_72dmh_vKyhuGjDP`VrfkhslEw9w9ifo$zq%Bz6LIL7Vl?#9p%xXYSpU zxRDkW+Df2Qg#l3zg+NrmKwD_~qiUfPT=gX=Dzrc^eMq!woBl#V?GL1qerN7x*NGg! z);)XXo5!6w^PMwi?hCg*p9jU}WGNE2%Y(p*C7fwZtzAgv{& zfwfIK;_b3qySCkDaMMiZW{tRJd9r!NGe+ELxh4x)OWQAh>TEvqHO;AcqA-q#%5OLu z8hua$cs?S<=g>7>^J&5-V~ut4P3Mz=AT_Nf5t1id>znPaR90FoY4s2_@>N%@R$uDp za|*Bg$o0$x-JjCCdRFV+iSA7_P92F(HN;S@UwYk-hGGNRf>}sr(#G-o@&~Jg5{s%nT3SO?=Y7%L5R>WT$_B?Sud~h9=c#Uk9aq& zUu8O$zL%THwdiY9OcK<@cKK3Uo2>QKtl37d)hylMnIpCp%BHZqiG*Q_MB*}egRg7r z3ggl2P|5PCM8a|(oZPu*ROkfyhv$-FcbaFIpn=6!`MB@Ofh$PePGT2{Rpu%BP|+dJ z`Fgaz^Cl}k28=tVHRhOhGHu$$^?5gQf+$|*I_aGDas8D43CDC5@Bq`9oA`gyH62uC zU%6bZ%6>54t`Dhl0KB)$VO6d!mm{hiV#V_fi0)w)0ZSuyIj5tnhDGmoOxLnnFtyI| z>`Drr<%Mf7Td2#=WO=Nz&9Q-nq%f1S%S$nyGX;;C`GU!fSO$**7DU?MMttxr@ny*= z*5gNPgjhWDu|Kb|o1u;`dBug+7gtfUt0Am#A(uW{;0ZH7%gs411WjaB8`BQ#!1EEs z6Wcpkx^`9x11weH+1JI3en^#gM(|!4g5n<8>ELkXkEFh~1g#KIb802NNiU@o>vf@AF40^;( z#G-%~O@^p#ohCEYj}>|Sd;@m9m9?>U)^XB3-H5%eW1R|%u`Y!*0c!@*eNvllf!F-&TTc3b`+bW5bX=eCz`kuBktfo?PzSzb!zQ*(SVW~5`s@&zMyB$+Y`0zf7i zn>w^}P;8dzaBs&X8jA^tMfc{7gBv#@knAVu0EtcVxp1F$xqK@;Q5+;-j>KgUR$v7O z3ynYynrVcmq{$1yh$RbV9`syZG73bs3+s+2b7m}=N-aNQ#8_JJlnE~Yw0tU-Lfqyn z=sg(MMV4L{EDD~%;4G0#X1Psb+RMc%ylQT6kH{Am3Pe4n!9`Zg;5xulWCC)XEYHpe z{4$Q08L?UFh~Io=N~=v{Npp&<+!9G#KTER-U`qtI9Z-djh>O_tuXpWckpEOkt%ifxau|rVfZb4MGy!n}V7|*agN75M+{;M3seIzXm zGwu{Ssh9H03tiIk%&54Nk+0OW7gOYy=l@9e;2Fka+z4?(6Ydk*yu+wE;h5J>IiGM$ z0-JY^O8ISkcVef#QQ_zs!LkC9_@XQslZyVum z13qG|h=Vmi)n1D|R9}5WBy*`b90m#VSW+C8+oEHahxaCP$EWfn?L*Z?O~X46PFY6-Fd;&v$>o2aiJoKcX;RI@XnD@s|r&sg%tbT zqrDg3Byk@JHSqnUNfHl`c#uS8!zyl0T0p4qLknM&$@*69p^`22GY{4yMSU0*XE)Ob zWKY|Yd*#aw+xsctiY+9jNKm*}CxoSK6ges=)04s5iw9ATdl!Sd?bF3J_TvC8UkO6y zEhrzeJh=iQcYZOnfx$BqJe!99l+qyVKlxH)--cVE z<0UkXbczho#qCBsc`Xs6|$n-1*|ccO=Qj=K5GWZ@2SAIo>Nfa58>$i?$i`(3Ja ztEhoYH+hW5J$7I{Bab%sw<(jWfDpksmd`FAMMFMCqn(yNY#!RT3JJucG%)SH_%;bL zPKlrU4opmq4o``v2~yb)Eoz19-z-O3HjU9mL$pFz9>YwU1ydX*grc`vPG#!iern~; z<&inA%9$U_S6gcLsAVYeBN{^yhc@wEJVq~s&k@z4a81gXbi_B$xjJW^M*l(EVLc5l z@py?~etC84LsxvCMp7g@N17rT@#(CJMA{~sN)uGKkHjR2(bUGxxptv7bOEubQ+@G>oxgkeZPFU*I#6wlV05EU8e%L+XuO8 zz8YV&Li1tfqOVye_2~#Oo!3D6fw}M)s3TTHIG(dRk`(;Z0g&!aXoe2NJ4o%)qu~d^|V8Zyr-|tqcVjF z`F!8rz<%m_fW#K5_YdH(-OxWAsi2$esyq7|waetU`n$DB`IG)JtsGNsJ|9zprN!7u z#Q?e$XxnkMwf$ARjnZdC3U+eiYSK#FytUF_tclpK9Os8>H3t<#T#0L;KBui(IxG1dosEK0q&7}2_P&-(Ocgm7#)@rVu zjttuLP{V(aP*!<>vm<~Y)oLjf5NgT)CauCKr72qd9Tn8pI0Qj?6>ds0 zykTFFD#qiDVg$dIm+@3Oi?fF2gsf#gnVZcIrt-NX!bOO`gs>U=Vi75q2kr0O_6g=u zy{GUYcz7M&vux+CZc;=&ivqOPbX>cvx!|IN{wXkWCpq38b<{gs*k delta 5394 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 diff --git a/compta/__pycache__/views.cpython-310.pyc b/compta/__pycache__/views.cpython-310.pyc index 329267b4f9b825c561495281cbc59f1dd4ad832d..1ed4767780fb933fb3845876b9612fc48937c326 100644 GIT binary patch delta 1357 zcma)5J8u&~5ccey&*zJMc{vb*6d7cK93UY`JcW2iL_$Fbkf5_J_8Ocxwwb*%ARVHh zq5vUn0Tl`*9Vi`?lr%^*uq8hOQ7~g-6I&FVG_!NNJKucs?e33#-)8(~HtQwmJGgRj z^<(b6zYdQr`nAa}lBvVw!o&u|iB<6i9`R^x*cG5y!~%0+GKN@G#4~Xt5yOV_CUGNC zw4xLjeJ?w>r&-|a#<=>Tj97>4=`WfABDAg zO$DKhaL#zL2UP?kT5H7=x>UgoZOb%vq$C>a^5RWn-H{C@+lnT#mOm zrJ&J}awo-#b0z_&XehIm-H&4~*U|O}1u^_pHx?mj8Fy_ zxrg+uJ(?zBbgb4BTVJrhA7NZlK3T0dgRm%!m^*BsJy^~=O-B}%o9nGJ3wk+uduW2v zFv)I8zf0~t-%+hc*;qtu5F=d))ZLY4fR!1_Q{0*>To|E)$v{qzJF{?|NY1-Ge#%+y zEA-hZ%n#_0ZAY@SE*mQ1`*w_hS&J^PucuQ5{mYuut<73+NDh~bPk-3B2#)P^X0@q#bv@XaNj_6 zplOqp4_ZwOOKp0{QdKvvWgqo`zWRmZ{Xhm3G8V3~8=&HCL51xg+MFAn6=(aCG2*0N zx`Fy2LYGX0N(I#?`sL_{Zd13$7DoQo>qI&IYOJ^??b>39Ud}L?N0#xx9T6#te6p+$ FzX4r}3}^rV delta 1178 zcma)*O=uHA6vy|?Y<810o7Q~T^dMG=fmCQ(5nDxV{U}wmBGiKy3EN~+lWvnZvxz-y z5h-3oL7a;R4+#h&f}p(#dh{lE@L=aCdi3l`aV9pgS$c3`-@cuh_kX|tTlP!l>!E1P zvcj6Y_WwLv`5k{9U4=VHzUuNmCvTRK!P6>Q3yRQ&b?|^jUF2d9is&I))LdNATYA9W z$zzx*4XQfPeJc1!Lir_|k$k~jQvggABNXJb`T_pZoZ?UPJ*Jc|;tkB-=vzlrwH#WY z%1q~cX02v}s^8rMEI{@1|DoPB=~YD+2IICj(ee%|Kmu6bMw1#%qfoDU@pjw%L-=am{Smw zIQPR_$$oU9=L@WPV{>g_7L3kZM)>>4r)giu1*s?Cwt$k)y0FMvy++-9Q~rpA6*oUv z>ph{1gXyV$ApwjkEm~2*hZ5eFa+@~Wd^R|SX8Mb1LlyPP1vq|%E=+?mW|!`AEB3Z) W>SJtj=wH817U$7;7EC@JAN>Qa6Xy2- diff --git a/compta/__pycache__/views_saisie.cpython-310.pyc b/compta/__pycache__/views_saisie.cpython-310.pyc index 0c915b5f51c1255677bdb5fc833e33e0eab3e38e..01ad69af42c6603562eebad47c603187df9d04aa 100644 GIT binary patch delta 1981 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 diff --git a/compta/admin.py b/compta/admin.py index 442734a..5beb84a 100644 --- a/compta/admin.py +++ b/compta/admin.py @@ -2,11 +2,34 @@ from django.contrib import admin from django.utils import timezone from .models import ( - Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Paiement, - Categorie, + Bareme, Entreprise, Client, Facture, LigneFacture, Depense, Paiement, Categorie, ) +class ScopedAdmin(admin.ModelAdmin): + """Cloisonne les données par entreprise pour les non-superadmins.""" + scope_path = "entreprise" + + def get_queryset(self, request): + qs = super().get_queryset(request) + if request.user.is_superuser: + return qs + return qs.filter(**{f"{self.scope_path}__in": Entreprise.accessibles(request.user)}) + + def formfield_for_foreignkey(self, db_field, request, **kwargs): + if not request.user.is_superuser: + acc = Entreprise.accessibles(request.user) + if db_field.name == "entreprise": + kwargs["queryset"] = acc + elif db_field.name == "client": + kwargs["queryset"] = Client.objects.filter(entreprise__in=acc) + elif db_field.name == "paiement": + kwargs["queryset"] = Paiement.objects.filter(entreprise__in=acc) + return super().formfield_for_foreignkey(db_field, request, **kwargs) + + +# ----- Référentiels mutualisés (visibles par tous) ----- + @admin.register(Categorie) class CategorieAdmin(admin.ModelAdmin): list_display = ("nom", "usage", "exclure_calculs", "actif", "ordre") @@ -22,16 +45,33 @@ class BaremeAdmin(admin.ModelAdmin): list_filter = ("annee", "activite") +# ----- Entreprise (accès) ----- + @admin.register(Entreprise) class EntrepriseAdmin(admin.ModelAdmin): list_display = ("nom", "activite", "franchise_tva", "versement_liberatoire", "num_tva_intracom", "vue_tva", "email") list_filter = ("activite", "franchise_tva", "versement_liberatoire") search_fields = ("nom", "siret", "num_tva_intracom") + filter_horizontal = ("utilisateurs", "groupes") + def get_queryset(self, request): + qs = super().get_queryset(request) + if request.user.is_superuser: + return qs + return Entreprise.accessibles(request.user) + + def get_readonly_fields(self, request, obj=None): + # Seul un superadmin gère les droits d'accès. + if not request.user.is_superuser: + return ("utilisateurs", "groupes") + return () + + +# ----- Données cloisonnées par entreprise ----- @admin.register(Client) -class ClientAdmin(admin.ModelAdmin): +class ClientAdmin(ScopedAdmin): list_display = ("nom", "entreprise", "email", "siret") list_filter = ("entreprise",) search_fields = ("nom", "email", "siret") @@ -48,10 +88,9 @@ def marquer_encaissee(modeladmin, request, queryset): @admin.register(Facture) -class FactureAdmin(admin.ModelAdmin): +class FactureAdmin(ScopedAdmin): list_display = ("numero", "entreprise", "client", "date_emission", - "date_encaissement", "statut", "montant_ht", - "montant_tva", "montant_ttc") + "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" @@ -72,7 +111,7 @@ class DepenseInline(admin.TabularInline): @admin.register(Paiement) -class PaiementAdmin(admin.ModelAdmin): +class PaiementAdmin(ScopedAdmin): list_display = ("date", "entreprise", "libelle", "sens", "montant_total", "reparti", "reste", "equilibre") list_filter = ("entreprise", "sens", "date") @@ -94,10 +133,9 @@ class PaiementAdmin(admin.ModelAdmin): @admin.register(Depense) -class DepenseAdmin(admin.ModelAdmin): +class DepenseAdmin(ScopedAdmin): list_display = ("date", "entreprise", "libelle", "categorie", "activite", - "regime_tva", "montant_ttc", "montant_tva", "tva_autoliq", - "paiement") + "regime_tva", "montant_ttc", "montant_tva", "tva_autoliq", "paiement") list_filter = ("entreprise", "categorie", "regime_tva", "activite", "date") search_fields = ("libelle",) date_hierarchy = "date" diff --git a/compta/migrations/0007_alter_paiement_options_entreprise_groupes_and_more.py b/compta/migrations/0007_alter_paiement_options_entreprise_groupes_and_more.py new file mode 100644 index 0000000..bf6ec06 --- /dev/null +++ b/compta/migrations/0007_alter_paiement_options_entreprise_groupes_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 5.2.15 on 2026-06-30 06:30 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('compta', '0006_paiement_categorie_alter_depense_paiement_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterModelOptions( + name='paiement', + options={'ordering': ['-date'], 'verbose_name': 'Opération bancaire', 'verbose_name_plural': 'Opérations bancaires'}, + ), + migrations.AddField( + model_name='entreprise', + name='groupes', + field=models.ManyToManyField(blank=True, help_text='Tous les membres de ces groupes ont accès à cette entreprise.', related_name='entreprises', to='auth.group', verbose_name='Groupes autorisés'), + ), + migrations.AddField( + model_name='entreprise', + name='utilisateurs', + field=models.ManyToManyField(blank=True, help_text='Utilisateurs ayant accès directement à cette entreprise.', related_name='entreprises', to=settings.AUTH_USER_MODEL, verbose_name='Utilisateurs autorisés'), + ), + migrations.AlterField( + model_name='depense', + name='paiement', + field=models.ForeignKey(blank=True, help_text='Opération bancaire dont cette dépense fait partie.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='depenses', to='compta.paiement', verbose_name='Opération bancaire'), + ), + migrations.AlterField( + model_name='entreprise', + name='franchise_tva', + field=models.BooleanField(default=False, help_text='Décochez si vous facturez la TVA.', verbose_name='En franchise de TVA'), + ), + migrations.AlterField( + model_name='paiement', + name='categorie', + field=models.ForeignKey(blank=True, help_text='Surtout pour les mouvements neutres.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paiements', to='compta.categorie'), + ), + ] diff --git a/compta/models.py b/compta/models.py index 40535d7..93ec728 100644 --- a/compta/models.py +++ b/compta/models.py @@ -3,8 +3,9 @@ Modèles de données — JB Compta. """ from decimal import Decimal +from django.conf import settings from django.db import models -from django.db.models import Sum +from django.db.models import Sum, Q class Activite(models.TextChoices): @@ -27,7 +28,7 @@ class RegimeTva(models.TextChoices): class Categorie(models.Model): - """Catégorie globale. exclure_calculs=True => mouvement neutre (hors fiscal).""" + """Catégorie globale (mutualisée). exclure_calculs=True => mouvement neutre.""" USAGE = [("depense", "Dépense"), ("vente", "Vente"), ("neutre", "Mouvement neutre")] nom = models.CharField(max_length=100, unique=True) usage = models.CharField(max_length=10, choices=USAGE, default="depense") @@ -48,6 +49,7 @@ class Categorie(models.Model): class Bareme(models.Model): + """Barème mutualisé (partagé entre toutes les entreprises).""" 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) @@ -73,18 +75,24 @@ class Entreprise(models.Model): 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.") + default=Activite.SERVICE_BNC, help_text="Activité par défaut des nouvelles factures.") franchise_tva = models.BooleanField( - "En franchise de TVA", default=False, - help_text="Décochez si vous facturez et déclarez la TVA.") + "En franchise de TVA", default=False, help_text="Décochez si vous facturez 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) num_tva_intracom = models.CharField("N° TVA intracommunautaire", max_length=20, blank=True) 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( + settings.AUTH_USER_MODEL, blank=True, related_name="entreprises", + verbose_name="Utilisateurs autorisés", + help_text="Utilisateurs ayant accès directement à cette entreprise.") + groupes = models.ManyToManyField( + "auth.Group", blank=True, related_name="entreprises", + verbose_name="Groupes autorisés", + help_text="Tous les membres de ces groupes ont accès à cette entreprise.") email = models.EmailField(blank=True) adresse = models.TextField(blank=True) cree_le = models.DateTimeField(auto_now_add=True) @@ -100,6 +108,16 @@ class Entreprise(models.Model): def bareme(self, annee): return Bareme.objects.filter(activite=self.activite, annee=annee).first() + @classmethod + def accessibles(cls, user): + """Entreprises visibles par un utilisateur (toutes si superadmin).""" + if not user or not user.is_authenticated: + return cls.objects.none() + if user.is_superuser: + return cls.objects.all() + return cls.objects.filter( + Q(utilisateurs=user) | Q(groupes__in=user.groups.all())).distinct() + class Client(models.Model): entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE, related_name="clients") @@ -125,8 +143,7 @@ class Facture(models.Model): activite = models.CharField(max_length=20, choices=Activite.choices) date_emission = models.DateField() date_encaissement = models.DateField( - null=True, blank=True, - help_text="Le CA micro est compté à l'encaissement.") + null=True, blank=True, help_text="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")) @@ -184,15 +201,14 @@ class Paiement(models.Model): libelle = models.CharField("Libellé bancaire", max_length=255) montant_total = models.DecimalField(max_digits=12, decimal_places=2) sens = models.CharField(max_length=6, choices=SENS, default="debit") - categorie = models.ForeignKey(Categorie, on_delete=models.SET_NULL, - null=True, blank=True, related_name="paiements", - help_text="Surtout pour les mouvements neutres " - "(remboursement TVA, virement…).") + categorie = models.ForeignKey(Categorie, on_delete=models.SET_NULL, null=True, blank=True, + related_name="paiements", + help_text="Surtout pour les mouvements neutres.") cree_le = models.DateTimeField(auto_now_add=True) class Meta: - verbose_name = "Paiement" - verbose_name_plural = "Paiements" + verbose_name = "Opération bancaire" + verbose_name_plural = "Opérations bancaires" ordering = ["-date"] def __str__(self): @@ -215,7 +231,7 @@ class Paiement(models.Model): class Depense(models.Model): entreprise = models.ForeignKey(Entreprise, on_delete=models.CASCADE, related_name="depenses") paiement = models.ForeignKey(Paiement, on_delete=models.SET_NULL, null=True, blank=True, - related_name="depenses", + related_name="depenses", verbose_name="Opération bancaire", help_text="Opération bancaire dont cette dépense fait partie.") date = models.DateField() activite = models.CharField( diff --git a/compta/views.py b/compta/views.py index 47b03d2..2a24cac 100644 --- a/compta/views.py +++ b/compta/views.py @@ -12,20 +12,22 @@ MOIS = services.MOIS_FR def _entreprise(request): + """Entreprise courante, restreinte à celles accessibles par l'utilisateur.""" + accessibles = Entreprise.accessibles(request.user) eid = request.GET.get("entreprise") if eid: - return get_object_or_404(Entreprise, pk=eid) - return Entreprise.objects.first() + return get_object_or_404(accessibles, pk=eid) + return accessibles.first() def _annee(request): return int(request.GET.get("annee", date.today().year)) -def _base_ctx(entreprise, annee, vue): +def _base_ctx(request, entreprise, annee, vue): return { "entreprise": entreprise, - "entreprises": Entreprise.objects.all(), + "entreprises": Entreprise.accessibles(request.user), "annee": annee, "annees": services.annees_disponibles(entreprise) or [annee], "vue": vue, @@ -40,7 +42,7 @@ def dashboard(request): return render(request, "compta/dashboard.html", {"entreprise": None}) annee = _annee(request) ctx = services.synthese(entreprise, annee) - ctx.update(_base_ctx(entreprise, annee, "global")) + ctx.update(_base_ctx(request, entreprise, annee, "global")) ctx.update({ "mois_labels": ["Jan", "Fév", "Mar", "Avr", "Mai", "Juin", "Juil", "Août", "Sep", "Oct", "Nov", "Déc"], @@ -55,7 +57,7 @@ def vue_mensuelle(request): if entreprise is None: return render(request, "compta/mensuel.html", {"entreprise": None}) annee = _annee(request) - ctx = _base_ctx(entreprise, annee, "mensuel") + ctx = _base_ctx(request, entreprise, annee, "mensuel") ctx["rows"] = services.tableau_mensuel(entreprise, annee) return render(request, "compta/mensuel.html", ctx) @@ -67,7 +69,7 @@ def declaration_urssaf(request): return render(request, "compta/decl_urssaf.html", {"entreprise": None}) annee = _annee(request) mois = int(request.GET.get("mois", date.today().month)) - ctx = _base_ctx(entreprise, annee, "urssaf") + ctx = _base_ctx(request, entreprise, annee, "urssaf") ctx["mois_sel"] = mois ctx["decl"] = services.declaration_urssaf(entreprise, annee, mois) return render(request, "compta/decl_urssaf.html", ctx) @@ -81,7 +83,7 @@ def declaration_tva(request): annee = _annee(request) mois = int(request.GET.get("mois", date.today().month)) mode = request.GET.get("mode") or entreprise.vue_tva - ctx = _base_ctx(entreprise, annee, "tva") + ctx = _base_ctx(request, entreprise, annee, "tva") ctx["mois_sel"] = mois ctx["mode"] = mode ctx["decl"] = services.declaration_tva(entreprise, annee, mois) diff --git a/compta/views_saisie.py b/compta/views_saisie.py index 1b73ca7..9756d14 100644 --- a/compta/views_saisie.py +++ b/compta/views_saisie.py @@ -1,8 +1,9 @@ """ Saisie unifiée : une opération bancaire signée. - Montant négatif -> dépense(s) (éclatement possible). -- Montant positif -> facture (vente) avec création de client à la volée, +- Montant positif -> facture (vente) + création de client à la volée, OU mouvement neutre (ex. remboursement de TVA). +Cloisonnée : un utilisateur ne voit que ses entreprises. """ from decimal import Decimal, InvalidOperation from datetime import date @@ -37,7 +38,7 @@ def _numero_unique(entreprise, annee): def _context(entreprises, post=None): return { "entreprises": entreprises, - "clients": Client.objects.select_related("entreprise"), + "clients": Client.objects.filter(entreprise__in=entreprises).select_related("entreprise"), "activites": Activite.choices, "regimes": RegimeTva.choices, "categories_depense": Categorie.objects.filter(actif=True).exclude(usage="vente"), @@ -49,7 +50,7 @@ def _context(entreprises, post=None): @login_required def saisie_paiement(request): - entreprises = Entreprise.objects.all() + entreprises = Entreprise.accessibles(request.user) if request.method != "POST": return render(request, "compta/saisie_paiement.html", _context(entreprises)) @@ -61,34 +62,28 @@ def saisie_paiement(request): erreurs = [] if not entreprise: - erreurs.append("Sélectionne une entreprise.") + erreurs.append("Sélectionne une entreprise autorisée.") if not libelle: - erreurs.append("Renseigne le libellé du paiement.") + erreurs.append("Renseigne le libellé de l'opération.") if montant_total == 0: erreurs.append("Le montant ne peut pas être nul.") - if erreurs: for e in erreurs: messages.error(request, e) - return render(request, "compta/saisie_paiement.html", - _context(entreprises, request.POST)) + return render(request, "compta/saisie_paiement.html", _context(entreprises, request.POST)) cats = {str(c.pk): c for c in Categorie.objects.all()} - # --------------------------------------------------------------- - # CRÉDIT (montant positif) -> facture (vente) ou mouvement neutre - # --------------------------------------------------------------- + # --- CRÉDIT (positif) -> facture (vente) ou mouvement neutre --- if montant_total > 0: cat = cats.get(request.POST.get("v_categorie")) if cat and cat.exclure_calculs: - # Mouvement neutre (ex. remboursement de TVA) : pas de CA. Paiement.objects.create( entreprise=entreprise, date=d, libelle=libelle, montant_total=montant_total, sens="credit", categorie=cat) messages.success(request, f"Mouvement neutre « {libelle} » enregistré.") return redirect("saisie_paiement") - # Vente -> facture encaissée client = None nouveau = (request.POST.get("v_client_nouveau") or "").strip() if nouveau: @@ -98,29 +93,23 @@ def saisie_paiement(request): entreprise=entreprise, pk=request.POST.get("v_client")).first() if not client: messages.error(request, "Choisis un client existant ou saisis-en un nouveau.") - return render(request, "compta/saisie_paiement.html", - _context(entreprises, request.POST)) + return render(request, "compta/saisie_paiement.html", _context(entreprises, request.POST)) activite = request.POST.get("v_activite") or entreprise.activite taux = _dec(request.POST.get("v_taux"), "0") designation = (request.POST.get("v_designation") or libelle).strip() ttc = montant_total ht = (ttc / (Decimal("1") + taux / Decimal("100"))).quantize(Decimal("0.01")) - f = Facture.objects.create( - entreprise=entreprise, client=client, - numero=_numero_unique(entreprise, annee), activite=activite, - date_emission=d, date_encaissement=d, statut="encaissee") - LigneFacture.objects.create( - facture=f, designation=designation, quantite=Decimal("1"), - prix_unitaire_ht=ht, taux_tva=taux) + entreprise=entreprise, client=client, numero=_numero_unique(entreprise, annee), + activite=activite, date_emission=d, date_encaissement=d, statut="encaissee") + LigneFacture.objects.create(facture=f, designation=designation, quantite=Decimal("1"), + prix_unitaire_ht=ht, taux_tva=taux) f.recompute_totaux() messages.success(request, f"Vente « {libelle} » enregistrée (facture {f.numero}).") return redirect("saisie_paiement") - # --------------------------------------------------------------- - # DÉBIT (montant négatif) -> dépense(s), éclatement possible - # --------------------------------------------------------------- + # --- DÉBIT (négatif) -> dépense(s), éclatement possible --- total_abs = -montant_total libelles = request.POST.getlist("l_libelle") categories = request.POST.getlist("l_categorie") @@ -149,24 +138,19 @@ def saisie_paiement(request): somme = sum((l["montant"] for l in lignes), Decimal("0")) if not lignes: messages.error(request, "Ajoute au moins une ligne de dépense.") - return render(request, "compta/saisie_paiement.html", - _context(entreprises, request.POST)) + return render(request, "compta/saisie_paiement.html", _context(entreprises, request.POST)) if abs(somme - total_abs) >= Decimal("0.01"): messages.error(request, f"La somme des lignes ({somme} €) ne correspond pas au montant " f"({total_abs} €). Écart : {somme - total_abs} €.") - return render(request, "compta/saisie_paiement.html", - _context(entreprises, request.POST)) + return render(request, "compta/saisie_paiement.html", _context(entreprises, request.POST)) paiement = Paiement.objects.create( - entreprise=entreprise, date=d, libelle=libelle, - montant_total=total_abs, sens="debit") + entreprise=entreprise, date=d, libelle=libelle, montant_total=total_abs, sens="debit") for l in lignes: Depense.objects.create( - entreprise=entreprise, paiement=paiement, date=d, - libelle=l["libelle"], categorie=l["categorie"], - activite=l["activite"] or "", regime_tva=l["regime"], + entreprise=entreprise, paiement=paiement, date=d, libelle=l["libelle"], + categorie=l["categorie"], activite=l["activite"] or "", regime_tva=l["regime"], taux_tva=l["taux"], montant_ttc=l["montant"], montant_tva=l["tva"]) - messages.success(request, - f"Paiement « {libelle} » enregistré ({len(lignes)} dépense(s)).") + messages.success(request, f"Opération « {libelle} » enregistrée ({len(lignes)} dépense(s)).") return redirect("saisie_paiement")