8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 23:43:47 +00:00

Gestion du solde en option

This commit is contained in:
chibrac 2017-06-26 19:23:01 +02:00
parent 6f9932add4
commit e968f2b12f
9 changed files with 117 additions and 8 deletions

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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