mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-26 14:42:25 +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")
|
||||
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):
|
||||
|
|
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 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,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue