mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-26 22:52:26 +00:00
Gestion du solde en option
This commit is contained in:
parent
a66986741a
commit
5be77b04dd
9 changed files with 117 additions and 8 deletions
|
@ -46,10 +46,22 @@ class NewFactureForm(ModelForm):
|
||||||
banque = cleaned_data.get("banque")
|
banque = cleaned_data.get("banque")
|
||||||
if not paiement:
|
if not paiement:
|
||||||
raise forms.ValidationError("Le moyen de paiement est obligatoire")
|
raise forms.ValidationError("Le moyen de paiement est obligatoire")
|
||||||
elif paiement.moyen=="chèque" and not (cheque and banque):
|
elif paiement.moyen.lower()=="chèque" or paiement.moyen.lower()=="cheque" and not (cheque and banque):
|
||||||
raise forms.ValidationError("Le numero de chèque et la banque sont obligatoires")
|
raise forms.ValidationError("Le numero de chèque et la banque sont obligatoires")
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
class CreditSoldeForm(NewFactureForm):
|
||||||
|
class Meta(NewFactureForm.Meta):
|
||||||
|
model = Facture
|
||||||
|
fields = ['paiement','banque','cheque']
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(CreditSoldeForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['paiement'].queryset = Paiement.objects.exclude(moyen='solde').exclude(moyen="Solde")
|
||||||
|
|
||||||
|
|
||||||
|
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
|
||||||
|
|
||||||
class SelectArticleForm(Form):
|
class SelectArticleForm(Form):
|
||||||
article = forms.ModelChoiceField(queryset=Article.objects.all(), label="Article", required=True)
|
article = forms.ModelChoiceField(queryset=Article.objects.all(), label="Article", required=True)
|
||||||
quantity = forms.IntegerField(label="Quantité", validators=[MinValueValidator(1)], required=True)
|
quantity = forms.IntegerField(label="Quantité", validators=[MinValueValidator(1)], required=True)
|
||||||
|
|
|
@ -25,8 +25,10 @@ from django.db import models
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from django.forms import ValidationError
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
|
|
||||||
class Facture(models.Model):
|
class Facture(models.Model):
|
||||||
PRETTY_NAME = "Factures émises"
|
PRETTY_NAME = "Factures émises"
|
||||||
|
|
||||||
|
@ -107,6 +109,11 @@ class Article(models.Model):
|
||||||
iscotisation = models.BooleanField()
|
iscotisation = models.BooleanField()
|
||||||
duration = models.IntegerField(help_text="Durée exprimée en mois entiers", blank=True, null=True)
|
duration = models.IntegerField(help_text="Durée exprimée en mois entiers", blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.name.lower() == "solde":
|
||||||
|
raise ValidationError("Solde est un nom d'article invalide")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@ -126,6 +133,9 @@ class Paiement(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.moyen
|
return self.moyen
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.moyen = self.moyen.title()
|
||||||
|
|
||||||
class Cotisation(models.Model):
|
class Cotisation(models.Model):
|
||||||
PRETTY_NAME = "Cotisations"
|
PRETTY_NAME = "Cotisations"
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ urlpatterns = [
|
||||||
url(r'^del_facture/(?P<factureid>[0-9]+)$', views.del_facture, name='del-facture'),
|
url(r'^del_facture/(?P<factureid>[0-9]+)$', views.del_facture, name='del-facture'),
|
||||||
url(r'^facture_pdf/(?P<factureid>[0-9]+)$', views.facture_pdf, name='facture-pdf'),
|
url(r'^facture_pdf/(?P<factureid>[0-9]+)$', views.facture_pdf, name='facture-pdf'),
|
||||||
url(r'^new_facture_pdf/$', views.new_facture_pdf, name='new-facture-pdf'),
|
url(r'^new_facture_pdf/$', views.new_facture_pdf, name='new-facture-pdf'),
|
||||||
|
url(r'^credit_solde/(?P<userid>[0-9]+)$', views.credit_solde, name='credit-solde'),
|
||||||
url(r'^add_article/$', views.add_article, name='add-article'),
|
url(r'^add_article/$', views.add_article, name='add-article'),
|
||||||
url(r'^edit_article/(?P<articleid>[0-9]+)$', views.edit_article, name='edit-article'),
|
url(r'^edit_article/(?P<articleid>[0-9]+)$', views.edit_article, name='edit-article'),
|
||||||
url(r'^del_article/$', views.del_article, name='del-article'),
|
url(r'^del_article/$', views.del_article, name='del-article'),
|
||||||
|
|
|
@ -38,12 +38,12 @@ from reversion import revisions as reversion
|
||||||
from reversion.models import Version
|
from reversion.models import Version
|
||||||
|
|
||||||
from .models import Facture, Article, Vente, Cotisation, Paiement, Banque
|
from .models import Facture, Article, Vente, Cotisation, Paiement, Banque
|
||||||
from .forms import NewFactureForm, TrezEditFactureForm, EditFactureForm, ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm, BanqueForm, DelBanqueForm, NewFactureFormPdf, SelectArticleForm
|
from .forms import NewFactureForm, TrezEditFactureForm, EditFactureForm, ArticleForm, DelArticleForm, PaiementForm, DelPaiementForm, BanqueForm, DelBanqueForm, NewFactureFormPdf, CreditSoldeForm, SelectArticleForm
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from .tex import render_tex
|
from .tex import render_tex
|
||||||
from re2o.settings import ASSO_NAME, ASSO_ADDRESS_LINE1, ASSO_ADDRESS_LINE2, ASSO_SIRET, ASSO_EMAIL, ASSO_PHONE, LOGO_PATH
|
from re2o.settings import ASSO_NAME, ASSO_ADDRESS_LINE1, ASSO_ADDRESS_LINE2, ASSO_SIRET, ASSO_EMAIL, ASSO_PHONE, LOGO_PATH
|
||||||
from re2o import settings
|
from re2o import settings
|
||||||
from preferences.models import GeneralOption
|
from preferences.models import OptionalUser, GeneralOption
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -87,6 +87,19 @@ def new_facture(request, userid):
|
||||||
articles = article_formset
|
articles = article_formset
|
||||||
# Si au moins un article est rempli
|
# Si au moins un article est rempli
|
||||||
if any(art.cleaned_data for art in articles):
|
if any(art.cleaned_data for art in articles):
|
||||||
|
options, created = OptionalUser.objects.get_or_create()
|
||||||
|
user_solde = options.user_solde
|
||||||
|
solde_negatif = options.solde_negatif
|
||||||
|
# Si on paye par solde, que l'option est activée, on vérifie que le négatif n'est pas atteint
|
||||||
|
if user_solde:
|
||||||
|
if new_facture.paiement == Paiement.objects.get_or_create(moyen='solde')[0]:
|
||||||
|
prix_total = 0
|
||||||
|
for art_item in articles:
|
||||||
|
if art_item.cleaned_data:
|
||||||
|
prix_total += art_item.cleaned_data['article'].prix*art_item.cleaned_data['quantity']
|
||||||
|
if float(user.solde) - float(prix_total) < solde_negatif:
|
||||||
|
messages.error(request, "Le solde est insuffisant pour effectuer l'opération")
|
||||||
|
return redirect("/users/profil/" + userid)
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
new_facture.save()
|
new_facture.save()
|
||||||
reversion.set_user(request.user)
|
reversion.set_user(request.user)
|
||||||
|
@ -195,6 +208,33 @@ def del_facture(request, factureid):
|
||||||
return redirect("/cotisations/")
|
return redirect("/cotisations/")
|
||||||
return form({'objet': facture, 'objet_name': 'facture'}, 'cotisations/delete.html', request)
|
return form({'objet': facture, 'objet_name': 'facture'}, 'cotisations/delete.html', request)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required('cableur')
|
||||||
|
def credit_solde(request, userid):
|
||||||
|
""" Credit ou débit de solde """
|
||||||
|
try:
|
||||||
|
user = User.objects.get(pk=userid)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
messages.error(request, u"Utilisateur inexistant" )
|
||||||
|
return redirect("/cotisations/")
|
||||||
|
facture = CreditSoldeForm(request.POST or None)
|
||||||
|
if facture.is_valid():
|
||||||
|
facture_instance = facture.save(commit=False)
|
||||||
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
facture_instance.user = user
|
||||||
|
facture_instance.save()
|
||||||
|
reversion.set_user(request.user)
|
||||||
|
reversion.set_comment("Création")
|
||||||
|
new_vente = Vente.objects.create(facture=facture_instance, name="solde", prix=facture.cleaned_data['montant'], iscotisation=False, duration=0, number=1)
|
||||||
|
with transaction.atomic(), reversion.create_revision():
|
||||||
|
new_vente.save()
|
||||||
|
reversion.set_user(request.user)
|
||||||
|
reversion.set_comment("Création")
|
||||||
|
messages.success(request, "Solde modifié")
|
||||||
|
return redirect("/cotisations/")
|
||||||
|
return form({'factureform': facture}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('trésorier')
|
@permission_required('trésorier')
|
||||||
def add_article(request):
|
def add_article(request):
|
||||||
|
|
20
preferences/migrations/0003_optionaluser_solde_negatif.py
Normal file
20
preferences/migrations/0003_optionaluser_solde_negatif.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-06-26 01:33
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('preferences', '0002_auto_20170625_1923'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='optionaluser',
|
||||||
|
name='solde_negatif',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=0, max_digits=5),
|
||||||
|
),
|
||||||
|
]
|
|
@ -22,13 +22,18 @@
|
||||||
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from cotisations.models import Paiement
|
||||||
|
|
||||||
class OptionalUser(models.Model):
|
class OptionalUser(models.Model):
|
||||||
is_tel_mandatory = models.BooleanField(default=True)
|
is_tel_mandatory = models.BooleanField(default=True)
|
||||||
user_solde = models.BooleanField(default=False)
|
user_solde = models.BooleanField(default=False)
|
||||||
|
solde_negatif = models.DecimalField(max_digits=5, decimal_places=2, default=0)
|
||||||
gpg_fingerprint = models.BooleanField(default=True)
|
gpg_fingerprint = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
if self.user_solde:
|
||||||
|
Paiement.objects.get_or_create(moyen="Solde")
|
||||||
|
|
||||||
class OptionalMachine(models.Model):
|
class OptionalMachine(models.Model):
|
||||||
password_machine = models.BooleanField(default=False)
|
password_machine = models.BooleanField(default=False)
|
||||||
max_lambdauser_interfaces = models.IntegerField(default=10)
|
max_lambdauser_interfaces = models.IntegerField(default=10)
|
||||||
|
|
|
@ -40,7 +40,7 @@ from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
|
||||||
|
|
||||||
from django.core.validators import MinLengthValidator
|
from django.core.validators import MinLengthValidator
|
||||||
from topologie.models import Room
|
from topologie.models import Room
|
||||||
from cotisations.models import Cotisation, Facture, Vente
|
from cotisations.models import Cotisation, Facture, Paiement, Vente
|
||||||
from machines.models import Interface, Machine
|
from machines.models import Interface, Machine
|
||||||
from preferences.models import OptionalUser
|
from preferences.models import OptionalUser
|
||||||
|
|
||||||
|
@ -312,6 +312,18 @@ class User(AbstractBaseUser):
|
||||||
else:
|
else:
|
||||||
return max(self.end_adhesion, self.end_whitelist)
|
return max(self.end_adhesion, self.end_whitelist)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def solde(self):
|
||||||
|
options, created = OptionalUser.objects.get_or_create()
|
||||||
|
user_solde = options.user_solde
|
||||||
|
if user_solde:
|
||||||
|
solde_object, created=Paiement.objects.get_or_create(moyen='Solde')
|
||||||
|
somme_debit = Vente.objects.filter(facture__in=Facture.objects.filter(user=self, paiement=solde_object)).aggregate(total=models.Sum(models.F('prix')*models.F('number'), output_field=models.FloatField()))['total'] or 0
|
||||||
|
somme_credit =Vente.objects.filter(facture__in=Facture.objects.filter(user=self), name="solde").aggregate(total=models.Sum(models.F('prix')*models.F('number'), output_field=models.FloatField()))['total'] or 0
|
||||||
|
return somme_credit - somme_debit
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
def user_interfaces(self):
|
def user_interfaces(self):
|
||||||
return Interface.objects.filter(machine__in=Machine.objects.filter(user=self, active=True))
|
return Interface.objects.filter(machine__in=Machine.objects.filter(user=self, active=True))
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>Commentaire</th>
|
<th>Commentaire</th>
|
||||||
<td>{{ user.comment }}</td>
|
<td>{{ user.comment }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date d'inscription</th>
|
<th>Date d'inscription</th>
|
||||||
<td>{{ user.registered }}</td>
|
<td>{{ user.registered }}</td>
|
||||||
|
@ -130,6 +129,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>Aucun</td>
|
<td>Aucun</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% if user_solde %}
|
||||||
|
<tr>
|
||||||
|
<th>Solde</th>
|
||||||
|
<td>{{ user.solde }} €</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
<h2>Machines :</h2>
|
<h2>Machines :</h2>
|
||||||
<h4><a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:new-machine' user.id %}"><i class="glyphicon glyphicon-phone"></i> Ajouter une machine</a></h4>
|
<h4><a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:new-machine' user.id %}"><i class="glyphicon glyphicon-phone"></i> Ajouter une machine</a></h4>
|
||||||
|
@ -139,7 +145,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<p>Aucune machine</p>
|
<p>Aucune machine</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>Cotisations :</h2>
|
<h2>Cotisations :</h2>
|
||||||
{% if is_cableur %}<h4><a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:new-facture' user.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Ajouter une cotisation</a></h4>{% endif%}
|
{% if is_cableur %}<h4><a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:new-facture' user.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Ajouter une cotisation</a> {% if user_solde %}<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:credit-solde' user.id %}"><i class="glyphicon glyphicon-piggy-bank"></i> Modifier le solde</a>{% endif%}</h4>{% endif%}
|
||||||
{% if facture_list %}
|
{% if facture_list %}
|
||||||
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
|
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -45,7 +45,7 @@ from cotisations.models import Facture
|
||||||
from machines.models import Machine, Interface
|
from machines.models import Machine, Interface
|
||||||
from users.forms import MassArchiveForm, PassForm, ResetPasswordForm
|
from users.forms import MassArchiveForm, PassForm, ResetPasswordForm
|
||||||
from machines.views import unassign_ips, assign_ips
|
from machines.views import unassign_ips, assign_ips
|
||||||
from preferences.models import GeneralOption
|
from preferences.models import OptionalUser, GeneralOption
|
||||||
|
|
||||||
from re2o.login import hashNT
|
from re2o.login import hashNT
|
||||||
from re2o.settings import REQ_EXPIRE_STR, EMAIL_FROM, ASSO_NAME, ASSO_EMAIL, SITE_NAME
|
from re2o.settings import REQ_EXPIRE_STR, EMAIL_FROM, ASSO_NAME, ASSO_EMAIL, SITE_NAME
|
||||||
|
@ -694,6 +694,8 @@ def profil(request, userid):
|
||||||
bans = Ban.objects.filter(user__pseudo=users)
|
bans = Ban.objects.filter(user__pseudo=users)
|
||||||
whitelists = Whitelist.objects.filter(user__pseudo=users)
|
whitelists = Whitelist.objects.filter(user__pseudo=users)
|
||||||
list_droits = Right.objects.filter(user=users)
|
list_droits = Right.objects.filter(user=users)
|
||||||
|
options, created = OptionalUser.objects.get_or_create()
|
||||||
|
user_solde = options.user_solde
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'users/profil.html',
|
'users/profil.html',
|
||||||
|
@ -704,6 +706,7 @@ def profil(request, userid):
|
||||||
'ban_list': bans,
|
'ban_list': bans,
|
||||||
'white_list': whitelists,
|
'white_list': whitelists,
|
||||||
'list_droits': list_droits,
|
'list_droits': list_droits,
|
||||||
|
'user_solde': user_solde,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue