diff --git a/cotisations/forms.py b/cotisations/forms.py index 5845611a..bbe50b69 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -26,9 +26,8 @@ importé par les views. Permet de créer une nouvelle facture pour un user (NewFactureForm), et de l'editer (soit l'user avec EditFactureForm, soit le trésorier avec TrezEdit qui a plus de possibilités que self -notamment sur le controle trésorier) - -SelectArticleForm est utilisée lors de la creation d'une facture en +notamment sur le controle trésorier SelectArticleForm est utilisée +lors de la creation d'une facture en parrallèle de NewFacture pour le choix des articles désirés. (la vue correspondante est unique) @@ -40,7 +39,7 @@ from __future__ import unicode_literals from django import forms from django.db.models import Q from django.forms import ModelForm, Form -from django.core.validators import MinValueValidator +from django.core.validators import MinValueValidator,MaxValueValidator from .models import Article, Paiement, Facture, Banque from re2o.field_permissions import FieldPermissionFormMixin @@ -246,3 +245,37 @@ class DelBanqueForm(Form): self.fields['banques'].queryset = instances else: self.fields['banques'].queryset = Banque.objects.all() + + +class NewFactureSoldeForm(NewFactureForm): + """Creation d'une facture, moyen de paiement, banque et numero + de cheque""" + def __init__(self, *args, **kwargs): + prefix = kwargs.pop('prefix', self.Meta.model.__name__) + self.fields['cheque'].required = False + self.fields['banque'].required = False + self.fields['cheque'].label = 'Numero de chèque' + self.fields['banque'].empty_label = "Non renseigné" + self.fields['paiement'].empty_label = "Séléctionner\ + une bite de paiement" + paiement_list = Paiement.objects.filter(type_paiement=1) + if paiement_list: + self.fields['paiement'].widget\ + .attrs['data-cheque'] = paiement_list.first().id + + class Meta: + model = Facture + fields = ['paiement', 'banque'] + + + def clean(self): + cleaned_data = super(NewFactureSoldeForm, self).clean() + paiement = cleaned_data.get("paiement") + cheque = cleaned_data.get("cheque") + banque = cleaned_data.get("banque") + if not paiement: + raise forms.ValidationError("Le moyen de paiement est obligatoire") + elif paiement.type_paiement == "check" and not (cheque and banque): + raise forms.ValidationError("Le numéro de chèque et\ + la banque sont obligatoires.") + return cleaned_data diff --git a/cotisations/templates/cotisations/new_facture.html b/cotisations/templates/cotisations/new_facture.html index f2586e8b..9d466cee 100644 --- a/cotisations/templates/cotisations/new_facture.html +++ b/cotisations/templates/cotisations/new_facture.html @@ -34,6 +34,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% csrf_token %}

Nouvelle facture

+

+ Solde de l'utilisateur : {{ user.solde }} € +

{% bootstrap_form factureform %} {{ venteform.management_form }} diff --git a/cotisations/templates/cotisations/new_facture_solde.html b/cotisations/templates/cotisations/new_facture_solde.html new file mode 100644 index 00000000..2efd8e81 --- /dev/null +++ b/cotisations/templates/cotisations/new_facture_solde.html @@ -0,0 +1,157 @@ + +{% extends "cotisations/sidebar.html" %} +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Goulven Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load bootstrap3 %} +{% load staticfiles%} + +{% block title %}Création et modification de factures{% endblock %} + +{% block content %} +{% bootstrap_form_errors venteform.management_form %} + + + {% csrf_token %} +

Nouvelle facture

+ {{ venteform.management_form }} + +

Articles de la facture

+
+ {% for form in venteform.forms %} +
+ Article :   + {% bootstrap_form form label_class='sr-only' %} +   + +
+ {% endfor %} +
+ +

+ Prix total : 0,00 € +

+ {% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %} +
+ + + +{% endblock %} + diff --git a/cotisations/urls.py b/cotisations/urls.py index 2a0c0163..cbeeb8eb 100644 --- a/cotisations/urls.py +++ b/cotisations/urls.py @@ -26,6 +26,7 @@ from django.conf.urls import url import re2o from . import views +from . import payment urlpatterns = [ url(r'^new_facture/(?P[0-9]+)$', @@ -110,5 +111,9 @@ urlpatterns = [ views.control, name='control' ), + url(r'^new_facture_solde/(?P[0-9]+)$', + views.new_facture_solde, + name='new_facture_solde' + ), url(r'^$', views.index, name='index'), ] diff --git a/cotisations/views.py b/cotisations/views.py index d7d953c9..e338943e 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -29,6 +29,7 @@ import os from django.urls import reverse from django.shortcuts import render, redirect from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.core.validators import MaxValueValidator from django.contrib.auth.decorators import login_required, permission_required from django.contrib import messages from django.db.models import ProtectedError @@ -67,7 +68,9 @@ from .forms import ( NewFactureFormPdf, SelectUserArticleForm, SelectClubArticleForm, - CreditSoldeForm + CreditSoldeForm, + NewFactureSoldeForm, + RechargeForm ) from .tex import render_invoice @@ -584,3 +587,98 @@ def index(request): return render(request, 'cotisations/index.html', { 'facture_list': facture_list }) + + +@login_required +def new_facture_solde(request, userid): + """Creation d'une facture pour un user. Renvoie la liste des articles + et crée des factures dans un formset. Utilise un peu de js coté template + pour ajouter des articles. + Parse les article et boucle dans le formset puis save les ventes, + enfin sauve la facture parente. + TODO : simplifier cette fonction, déplacer l'intelligence coté models + Facture et Vente.""" + user = request.user + facture = Facture(user=user) + paiement, _created = Paiement.objects.get_or_create(moyen='Solde') + facture.paiement = paiement + # Le template a besoin de connaitre les articles pour le js + article_list = Article.objects.filter( + Q(type_user='All') | Q(type_user=request.user.class_name) + ) + if request.user.is_class_club: + article_formset = formset_factory(SelectClubArticleForm)(request.POST or None) + else: + article_formset = formset_factory(SelectUserArticleForm)(request.POST or None) + if article_formset.is_valid(): + 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: + 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(reverse( + 'users:profil', + kwargs={'userid': userid} + )) + with transaction.atomic(), reversion.create_revision(): + facture.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + for art_item in articles: + if art_item.cleaned_data: + article = art_item.cleaned_data['article'] + quantity = art_item.cleaned_data['quantity'] + new_vente = Vente.objects.create( + facture=facture, + name=article.name, + prix=article.prix, + type_cotisation=article.type_cotisation, + duration=article.duration, + number=quantity + ) + with transaction.atomic(), reversion.create_revision(): + new_vente.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + if any(art_item.cleaned_data['article'].type_cotisation + for art_item in articles if art_item.cleaned_data): + messages.success( + request, + "La cotisation a été prolongée\ + pour l'adhérent %s jusqu'au %s" % ( + user.pseudo, user.end_adhesion() + ) + ) + else: + messages.success(request, "La facture a été crée") + return redirect(reverse( + 'users:profil', + kwargs={'userid': userid} + )) + messages.error( + request, + u"Il faut au moins un article valide pour créer une facture" + ) + return redirect(reverse( + 'users:profil', + kwargs={'userid': userid} + )) + + return form({ + 'venteform': article_formset, + 'articlelist': article_list + }, 'cotisations/new_facture_solde.html', request) + + diff --git a/preferences/forms.py b/preferences/forms.py index 7dda8620..2f79b6fb 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -48,6 +48,7 @@ class EditOptionalUserForm(ModelForm): téléphone' self.fields['user_solde'].label = 'Activation du solde pour\ les utilisateurs' + self.fields['max_recharge'].label = 'Rechargement max' class EditOptionalMachineForm(ModelForm): diff --git a/preferences/migrations/0028_auto_20180111_1129.py b/preferences/migrations/0028_auto_20180111_1129.py new file mode 100644 index 00000000..c6c03719 --- /dev/null +++ b/preferences/migrations/0028_auto_20180111_1129.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2018-01-11 10:29 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('preferences', '0027_merge_20180106_2019'), + ] + + operations = [ + migrations.AddField( + model_name='optionaluser', + name='max_recharge', + field=models.DecimalField(decimal_places=2, default=100, max_digits=5), + ), + ] diff --git a/preferences/models.py b/preferences/models.py index 8dfc4260..1d4b9bd2 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -41,6 +41,11 @@ class OptionalUser(models.Model): decimal_places=2, default=0 ) + max_recharge = models.DecimalField( + max_digits=5, + decimal_places=2, + default=100 + ) gpg_fingerprint = models.BooleanField(default=True) all_can_create = models.BooleanField( default=False, @@ -107,7 +112,10 @@ class OptionalUser(models.Model): def clean(self): """Creation du mode de paiement par solde""" if self.user_solde: - cotisations.models.Paiement.objects.get_or_create(moyen="Solde") + p = cotisations.models.Paiement.objects.filter(moyen="Solde") + if not len(p): + c = cotisations.models.Paiement(moyen="Solde") + c.save() class OptionalMachine(models.Model): @@ -436,7 +444,14 @@ class AssoOption(models.Model): blank=True, null=True ) - + PAYMENT = ( + ('NONE', 'NONE'), + ('COMNPAY', 'COMNPAY'), + ) + payment = models.CharField(max_length=255, + choices=PAYMENT, + default='NONE', + ) class Meta: permissions = ( ("view_assooption", "Peut voir les options de l'asso"), diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html index 7802929d..38649e16 100644 --- a/preferences/templates/preferences/display_preferences.html +++ b/preferences/templates/preferences/display_preferences.html @@ -54,6 +54,10 @@ with this program; if not, write to the Free Software Foundation, Inc., Creations d'users par tous {{ useroptions.all_can_create }} + {% if useroptions.user_solde %} + Rechargement max + {{ useroptions.max_recharge }} + {% endif %}

Préférences machines

@@ -159,7 +163,10 @@ with this program; if not, write to the Free Software Foundation, Inc., Objet utilisateur de l'association {{ assooptions.utilisateur_asso }} + Moyen de paiement automatique + {{ assooptions.payment }} +

Messages personalisé dans les mails

diff --git a/users/models.py b/users/models.py index 998678cd..1852c687 100644 --- a/users/models.py +++ b/users/models.py @@ -153,7 +153,7 @@ class UserManager(BaseUserManager): user.set_password(password) if su: user.is_superuser=True - user.save(using=self._db) + user.save(using=self._db) return user def create_user(self, pseudo, surname, email, password=None): @@ -409,13 +409,11 @@ class User(FieldPermissionModelMixin, AbstractBaseUser, PermissionsMixin): 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' - ) + solde_objects = Paiement.objects.filter(moyen='Solde') somme_debit = Vente.objects.filter( facture__in=Facture.objects.filter( user=self, - paiement=solde_object + paiement__in=solde_objects ) ).aggregate( total=models.Sum( diff --git a/users/templates/users/profil.html b/users/templates/users/profil.html index 0e5f30e4..8f399043 100644 --- a/users/templates/users/profil.html +++ b/users/templates/users/profil.html @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% block title %}Profil{% endblock %} {% block content %} -

{{ users.class_name }}

+

{{ users.class_name }} : {{ users.surname }} {{users.name}}

@@ -135,13 +135,18 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if user_solde %} Solde - {{ users.solde }} € - + {{ users.solde }} € + + + Recharger + + {% endif %} {% if users.shell %} Shell {{ users.shell }} {% endif %} + {% if users.is_class_club %} @@ -191,7 +196,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

Aucune machine

{% endif %}

Cotisations

-

{% can_create Facture %} Ajouter une cotisation{% acl_end %} {% if user_solde %} Modifier le solde{% endif%}

+

{% can_create Facture %} Ajouter une cotisation {% if user_solde %} Modifier le solde{% endif%}{% acl_else %} Ajouter une cotisation par solde{% acl_end %}

{% if facture_list %} {% include "cotisations/aff_cotisations.html" with facture_list=facture_list %} {% else %}