8
0
Fork 0
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:
Gabriel Detraz 2017-06-26 19:23:01 +02:00
parent a66986741a
commit 5be77b04dd
9 changed files with 117 additions and 8 deletions

View file

@ -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)

View file

@ -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"

View file

@ -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'),

View file

@ -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):

View 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),
),
]

View file

@ -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)

View file

@ -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))

View file

@ -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 %}

View file

@ -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,
} }
) )