Commit e968f2b1 authored by chibrac's avatar chibrac

Gestion du solde en option

parent 6f9932ad
......@@ -46,10 +46,22 @@ class NewFactureForm(ModelForm):
banque = cleaned_data.get("banque")
if not paiement:
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")
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):
article = forms.ModelChoiceField(queryset=Article.objects.all(), label="Article", 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.dispatch import receiver
from dateutil.relativedelta import relativedelta
from django.forms import ValidationError
from django.core.validators import MinValueValidator
class Facture(models.Model):
PRETTY_NAME = "Factures émises"
......@@ -107,6 +109,11 @@ class Article(models.Model):
iscotisation = models.BooleanField()
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):
return self.name
......@@ -126,6 +133,9 @@ class Paiement(models.Model):
def __str__(self):
return self.moyen
def clean(self):
self.moyen = self.moyen.title()
class Cotisation(models.Model):
PRETTY_NAME = "Cotisations"
......
......@@ -30,6 +30,7 @@ urlpatterns = [
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'^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'^edit_article/(?P<articleid>[0-9]+)$', views.edit_article, name='edit-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 .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 .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 import settings
from preferences.models import GeneralOption
from preferences.models import OptionalUser, GeneralOption
from dateutil.relativedelta import relativedelta
from django.utils import timezone
......@@ -87,6 +87,19 @@ def new_facture(request, userid):
articles = article_formset
# Si au moins un article est rempli
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():
new_facture.save()
reversion.set_user(request.user)
......@@ -195,6 +208,33 @@ def del_facture(request, factureid):
return redirect("/cotisations/")
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
@permission_required('trésorier')
def add_article(request):
......
# -*- 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 cotisations.models import Paiement
class OptionalUser(models.Model):
is_tel_mandatory = models.BooleanField(default=True)
user_solde = models.BooleanField(default=False)
solde_negatif = models.DecimalField(max_digits=5, decimal_places=2, default=0)
gpg_fingerprint = models.BooleanField(default=True)
def clean(self):
if self.user_solde:
Paiement.objects.get_or_create(moyen="Solde")
class OptionalMachine(models.Model):
password_machine = models.BooleanField(default=False)
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 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 preferences.models import OptionalUser
......@@ -312,6 +312,18 @@ class User(AbstractBaseUser):
else:
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):
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>
<td>{{ user.comment }}</td>
</tr>
<tr>
<th>Date d'inscription</th>
<td>{{ user.registered }}</td>
......@@ -130,6 +129,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% else %}
<td>Aucun</td>
{% endif %}
</tr>
{% if user_solde %}
<tr>
<th>Solde</th>
<td>{{ user.solde }} €</td>
</tr>
{% endif %}
</table>
<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>
......@@ -139,7 +145,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<p>Aucune machine</p>
{% endif %}
<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 %}
{% include "cotisations/aff_cotisations.html" with facture_list=facture_list %}
{% else %}
......
......@@ -45,7 +45,7 @@ from cotisations.models import Facture
from machines.models import Machine, Interface
from users.forms import MassArchiveForm, PassForm, ResetPasswordForm
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.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)
whitelists = Whitelist.objects.filter(user__pseudo=users)
list_droits = Right.objects.filter(user=users)
options, created = OptionalUser.objects.get_or_create()
user_solde = options.user_solde
return render(
request,
'users/profil.html',
......@@ -704,6 +706,7 @@ def profil(request, userid):
'ban_list': bans,
'white_list': whitelists,
'list_droits': list_droits,
'user_solde': user_solde,
}
)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment