mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-25 22:22:26 +00:00
Supprime tout ce qui ne sert plus pour les cotisations
This commit is contained in:
parent
ab6babd45f
commit
812661cadd
12 changed files with 77 additions and 578 deletions
|
@ -5,6 +5,7 @@
|
||||||
# Copyright © 2017 Gabriel Détraz
|
# Copyright © 2017 Gabriel Détraz
|
||||||
# Copyright © 2017 Goulven Kermarec
|
# Copyright © 2017 Goulven Kermarec
|
||||||
# Copyright © 2017 Augustin Lemesle
|
# Copyright © 2017 Augustin Lemesle
|
||||||
|
# Copyright © 2018 Hugo Levy-Falk
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -78,24 +79,6 @@ class NewFactureForm(FormRevMixin, ModelForm):
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class CreditSoldeForm(NewFactureForm):
|
|
||||||
"""
|
|
||||||
Form used to make some operations on the user's balance if the option is
|
|
||||||
activated.
|
|
||||||
"""
|
|
||||||
class Meta(NewFactureForm.Meta):
|
|
||||||
model = Facture
|
|
||||||
fields = ['paiement', 'banque', 'cheque']
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(CreditSoldeForm, self).__init__(*args, **kwargs)
|
|
||||||
# TODO : change solde to balance
|
|
||||||
self.fields['paiement'].queryset = Paiement.objects.exclude(
|
|
||||||
is_balance=True)
|
|
||||||
|
|
||||||
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
|
|
||||||
|
|
||||||
|
|
||||||
class SelectUserArticleForm(FormRevMixin, Form):
|
class SelectUserArticleForm(FormRevMixin, Form):
|
||||||
"""
|
"""
|
||||||
Form used to select an article during the creation of an invoice for a
|
Form used to select an article during the creation of an invoice for a
|
||||||
|
@ -300,57 +283,6 @@ class DelBanqueForm(FormRevMixin, Form):
|
||||||
self.fields['banques'].queryset = Banque.objects.all()
|
self.fields['banques'].queryset = Banque.objects.all()
|
||||||
|
|
||||||
|
|
||||||
# TODO : change facture to Invoice
|
|
||||||
class NewFactureSoldeForm(NewFactureForm):
|
|
||||||
"""
|
|
||||||
Form used to create an invoice
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
|
||||||
super(NewFactureSoldeForm, self).__init__(
|
|
||||||
*args,
|
|
||||||
prefix=prefix,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
self.fields['cheque'].required = False
|
|
||||||
self.fields['banque'].required = False
|
|
||||||
self.fields['cheque'].label = _('Cheque number')
|
|
||||||
self.fields['banque'].empty_label = _("Not specified")
|
|
||||||
self.fields['paiement'].empty_label = \
|
|
||||||
_("Select a payment method")
|
|
||||||
# TODO : change paiement to payment
|
|
||||||
paiement_list = Paiement.objects.filter(type_paiement=1)
|
|
||||||
if paiement_list:
|
|
||||||
self.fields['paiement'].widget\
|
|
||||||
.attrs['data-cheque'] = paiement_list.first().id
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
# TODO : change facture to invoice
|
|
||||||
model = Facture
|
|
||||||
# TODO : change paiement to payment and baque to bank
|
|
||||||
fields = ['paiement', 'banque']
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super(NewFactureSoldeForm, self).clean()
|
|
||||||
# TODO : change paiement to payment
|
|
||||||
paiement = cleaned_data.get("paiement")
|
|
||||||
cheque = cleaned_data.get("cheque")
|
|
||||||
# TODO : change banque to bank
|
|
||||||
banque = cleaned_data.get("banque")
|
|
||||||
# TODO : change paiement to payment
|
|
||||||
if not paiement:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
_("A payment method must be specified.")
|
|
||||||
)
|
|
||||||
# TODO : change paiement and banque to payment and bank
|
|
||||||
elif paiement.type_paiement == "check" and not (cheque and banque):
|
|
||||||
raise forms.ValidationError(
|
|
||||||
_("A cheque number and a bank must be specified.")
|
|
||||||
)
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
# TODO : Better name and docstring
|
# TODO : Better name and docstring
|
||||||
class RechargeForm(FormRevMixin, Form):
|
class RechargeForm(FormRevMixin, Form):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
# Copyright © 2017 Gabriel Détraz
|
# Copyright © 2017 Gabriel Détraz
|
||||||
# Copyright © 2017 Goulven Kermarec
|
# Copyright © 2017 Goulven Kermarec
|
||||||
# Copyright © 2017 Augustin Lemesle
|
# Copyright © 2017 Augustin Lemesle
|
||||||
|
# Copyright © 2018 Hugo Levy-Falk
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
{% 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%}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Invoices creation and edition" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% bootstrap_form_errors venteform.management_form %}
|
|
||||||
|
|
||||||
<form class="form" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<h3>{% trans "New invoice" %}</h3>
|
|
||||||
{{ venteform.management_form }}
|
|
||||||
<!-- TODO: FIXME to include data-type="check" for right option in id_cheque select -->
|
|
||||||
<h3>{% trans "Invoice's articles" %}</h3>
|
|
||||||
<div id="form_set" class="form-group">
|
|
||||||
{% for form in venteform.forms %}
|
|
||||||
<div class='product_to_sell form-inline'>
|
|
||||||
{% trans "Article" %} :
|
|
||||||
{% bootstrap_form form label_class='sr-only' %}
|
|
||||||
|
|
||||||
<button class="btn btn-danger btn-sm" id="id_form-0-article-remove" type="button">
|
|
||||||
<span class="fa fa-times"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<input class="btn btn-primary btn-sm" role="button" value="{% trans "Add an article"%}" id="add_one">
|
|
||||||
<p>
|
|
||||||
{% blocktrans %}
|
|
||||||
Total price : <span id="total_price">0,00</span> €
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
{% trans "Confirm" as tr_confirm %}
|
|
||||||
{% bootstrap_button tr_confirm button_type='submit' icon='star' %}
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
var prices = {};
|
|
||||||
{% for article in articlelist %}
|
|
||||||
prices[{{ article.id|escapejs }}] = {{ article.prix }};
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
var template = `Article :
|
|
||||||
{% bootstrap_form venteform.empty_form label_class='sr-only' %}
|
|
||||||
|
|
||||||
<button class="btn btn-danger btn-sm" id="id_form-__prefix__-article-remove" type="button">
|
|
||||||
<span class="fa fa-times"></span>
|
|
||||||
</button>`
|
|
||||||
|
|
||||||
function add_article(){
|
|
||||||
// Index start at 0 => new_index = number of items
|
|
||||||
var new_index =
|
|
||||||
document.getElementsByClassName('product_to_sell').length;
|
|
||||||
document.getElementById('id_form-TOTAL_FORMS').value ++;
|
|
||||||
var new_article = document.createElement('div');
|
|
||||||
new_article.className = 'product_to_sell form-inline';
|
|
||||||
new_article.innerHTML = template.replace(/__prefix__/g, new_index);
|
|
||||||
document.getElementById('form_set').appendChild(new_article);
|
|
||||||
add_listenner_for_id(new_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_price(){
|
|
||||||
var price = 0;
|
|
||||||
var product_count =
|
|
||||||
document.getElementsByClassName('product_to_sell').length;
|
|
||||||
var article, article_price, quantity;
|
|
||||||
for (i = 0; i < product_count; ++i){
|
|
||||||
article = document.getElementById(
|
|
||||||
'id_form-' + i.toString() + '-article').value;
|
|
||||||
if (article == '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
article_price = prices[article];
|
|
||||||
quantity = document.getElementById(
|
|
||||||
'id_form-' + i.toString() + '-quantity').value;
|
|
||||||
price += article_price * quantity;
|
|
||||||
}
|
|
||||||
document.getElementById('total_price').innerHTML =
|
|
||||||
price.toFixed(2).toString().replace('.', ',');
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_listenner_for_id(i){
|
|
||||||
document.getElementById('id_form-' + i.toString() + '-article')
|
|
||||||
.addEventListener("change", update_price, true);
|
|
||||||
document.getElementById('id_form-' + i.toString() + '-article')
|
|
||||||
.addEventListener("onkeypress", update_price, true);
|
|
||||||
document.getElementById('id_form-' + i.toString() + '-quantity')
|
|
||||||
.addEventListener("change", update_price, true);
|
|
||||||
document.getElementById('id_form-' + i.toString() + '-article-remove')
|
|
||||||
.addEventListener("click", function(event) {
|
|
||||||
var article = event.target.parentNode;
|
|
||||||
article.parentNode.removeChild(article);
|
|
||||||
document.getElementById('id_form-TOTAL_FORMS').value --;
|
|
||||||
update_price();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_cheque_info_visibility() {
|
|
||||||
var paiement = document.getElementById("id_Facture-paiement");
|
|
||||||
var visible = paiement.value == paiement.getAttribute('data-cheque');
|
|
||||||
p = document.getElementById("id_Facture-paiement");
|
|
||||||
var display = 'none';
|
|
||||||
if (visible) {
|
|
||||||
display = 'block';
|
|
||||||
}
|
|
||||||
document.getElementById("id_Facture-cheque")
|
|
||||||
.parentNode.style.display = display;
|
|
||||||
document.getElementById("id_Facture-banque")
|
|
||||||
.parentNode.style.display = display;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add events manager when DOM is fully loaded
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
document.getElementById("add_one")
|
|
||||||
.addEventListener("click", add_article, true);
|
|
||||||
var product_count =
|
|
||||||
document.getElementsByClassName('product_to_sell').length;
|
|
||||||
for (i = 0; i < product_count; ++i){
|
|
||||||
add_listenner_for_id(i);
|
|
||||||
}
|
|
||||||
document.getElementById("id_Facture-paiement")
|
|
||||||
.addEventListener("change", set_cheque_info_visibility, true);
|
|
||||||
set_cheque_info_visibility();
|
|
||||||
update_price();
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
{% 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%}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block title %}{% trans "Balance refill" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>{% trans "Balance refill" %}</h2>
|
|
||||||
<h3>
|
|
||||||
{% blocktrans %}
|
|
||||||
Balance : <span class="label label-default">{{ solde }} €</span>
|
|
||||||
{% endblocktrans %}
|
|
||||||
</h3>
|
|
||||||
<form class="form" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% bootstrap_form rechargeform %}
|
|
||||||
{% trans "Confirm" as tr_confirm %}
|
|
||||||
{% bootstrap_button tr_confirm button_type='submit' icon='piggy-bank' %}
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -133,11 +133,5 @@ urlpatterns = [
|
||||||
views.control,
|
views.control,
|
||||||
name='control'
|
name='control'
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r'^new_facture_solde/(?P<userid>[0-9]+)$',
|
|
||||||
views.new_facture_solde,
|
|
||||||
name='new_facture_solde'
|
|
||||||
),
|
|
||||||
url(r'^$', views.index, name='index'),
|
url(r'^$', views.index, name='index'),
|
||||||
] + payment_methods.urls.urlpatterns
|
] + payment_methods.urls.urlpatterns
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
# Copyright © 2017 Gabriel Détraz
|
# Copyright © 2017 Gabriel Détraz
|
||||||
# Copyright © 2017 Goulven Kermarec
|
# Copyright © 2017 Goulven Kermarec
|
||||||
# Copyright © 2017 Augustin Lemesle
|
# Copyright © 2017 Augustin Lemesle
|
||||||
|
# Copyright © 2018 Hugo Levy-Falk
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -56,7 +57,7 @@ from re2o.acl import (
|
||||||
can_delete_set,
|
can_delete_set,
|
||||||
can_change,
|
can_change,
|
||||||
)
|
)
|
||||||
from preferences.models import OptionalUser, AssoOption, GeneralOption
|
from preferences.models import AssoOption, GeneralOption
|
||||||
from .models import Facture, Article, Vente, Paiement, Banque
|
from .models import Facture, Article, Vente, Paiement, Banque
|
||||||
from .forms import (
|
from .forms import (
|
||||||
NewFactureForm,
|
NewFactureForm,
|
||||||
|
@ -70,7 +71,6 @@ from .forms import (
|
||||||
NewFactureFormPdf,
|
NewFactureFormPdf,
|
||||||
SelectUserArticleForm,
|
SelectUserArticleForm,
|
||||||
SelectClubArticleForm,
|
SelectClubArticleForm,
|
||||||
CreditSoldeForm,
|
|
||||||
RechargeForm
|
RechargeForm
|
||||||
)
|
)
|
||||||
from .tex import render_invoice
|
from .tex import render_invoice
|
||||||
|
@ -88,10 +88,6 @@ def new_facture(request, user, userid):
|
||||||
A bit of JS is used in the template to add articles in a fancier way.
|
A bit of JS is used in the template to add articles in a fancier way.
|
||||||
If everything is correct, save each one of the articles, save the
|
If everything is correct, save each one of the articles, save the
|
||||||
purchase object associated and finally the newly created invoice.
|
purchase object associated and finally the newly created invoice.
|
||||||
|
|
||||||
TODO : The whole verification process should be moved to the model. This
|
|
||||||
function should only act as a dumb interface between the model and the
|
|
||||||
user.
|
|
||||||
"""
|
"""
|
||||||
invoice = Facture(user=user)
|
invoice = Facture(user=user)
|
||||||
# The template needs the list of articles (for the JS part)
|
# The template needs the list of articles (for the JS part)
|
||||||
|
@ -670,118 +666,6 @@ def index(request):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# TODO : merge this function with new_facture() which is nearly the same
|
|
||||||
# TODO : change facture to invoice
|
|
||||||
@login_required
|
|
||||||
def new_facture_solde(request, userid):
|
|
||||||
"""
|
|
||||||
View called to create a new invoice when using the balance to pay.
|
|
||||||
Currently, send the list of available articles for the user along with
|
|
||||||
a formset of a new invoice (based on the `:forms:NewFactureForm()` form.
|
|
||||||
A bit of JS is used in the template to add articles in a fancier way.
|
|
||||||
If everything is correct, save each one of the articles, save the
|
|
||||||
purchase object associated and finally the newly created invoice.
|
|
||||||
|
|
||||||
TODO : The whole verification process should be moved to the model. This
|
|
||||||
function should only act as a dumb interface between the model and the
|
|
||||||
user.
|
|
||||||
"""
|
|
||||||
user = request.user
|
|
||||||
invoice = Facture(user=user)
|
|
||||||
payment, _created = Paiement.objects.get_or_create(is_balance=True)
|
|
||||||
invoice.paiement = payment
|
|
||||||
# The template needs the list of articles (for the JS part)
|
|
||||||
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
|
|
||||||
# Check if at leat one article has been selected
|
|
||||||
if any(art.cleaned_data for art in articles):
|
|
||||||
user_balance = OptionalUser.get_cached_value('user_solde')
|
|
||||||
negative_balance = OptionalUser.get_cached_value('solde_negatif')
|
|
||||||
# If the paiement using balance has been activated,
|
|
||||||
# checking that the total price won't get the user under
|
|
||||||
# the authorized minimum (negative_balance)
|
|
||||||
if user_balance:
|
|
||||||
total_price = 0
|
|
||||||
for art_item in articles:
|
|
||||||
if art_item.cleaned_data:
|
|
||||||
total_price += art_item.cleaned_data['article']\
|
|
||||||
.prix*art_item.cleaned_data['quantity']
|
|
||||||
if float(user.solde) - float(total_price) < negative_balance:
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
_("The balance is too low for this operation.")
|
|
||||||
)
|
|
||||||
return redirect(reverse(
|
|
||||||
'users:profil',
|
|
||||||
kwargs={'userid': userid}
|
|
||||||
))
|
|
||||||
# Saving the invoice
|
|
||||||
invoice.save()
|
|
||||||
|
|
||||||
# Building a purchase for each article sold
|
|
||||||
for art_item in articles:
|
|
||||||
if art_item.cleaned_data:
|
|
||||||
article = art_item.cleaned_data['article']
|
|
||||||
quantity = art_item.cleaned_data['quantity']
|
|
||||||
new_purchase = Vente.objects.create(
|
|
||||||
facture=invoice,
|
|
||||||
name=article.name,
|
|
||||||
prix=article.prix,
|
|
||||||
type_cotisation=article.type_cotisation,
|
|
||||||
duration=article.duration,
|
|
||||||
number=quantity
|
|
||||||
)
|
|
||||||
new_purchase.save()
|
|
||||||
|
|
||||||
# In case a cotisation was bought, inform the user, the
|
|
||||||
# cotisation time has been extended too
|
|
||||||
if any(art_item.cleaned_data['article'].type_cotisation
|
|
||||||
for art_item in articles if art_item.cleaned_data):
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
_("The cotisation of %(member_name)s has been successfully \
|
|
||||||
extended to %(end_date)s.") % {
|
|
||||||
'member_name': user.pseudo,
|
|
||||||
'end_date': user.end_adhesion()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
# Else, only tell the invoice was created
|
|
||||||
else:
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
_("The invoice has been successuflly created.")
|
|
||||||
)
|
|
||||||
return redirect(reverse(
|
|
||||||
'users:profil',
|
|
||||||
kwargs={'userid': userid}
|
|
||||||
))
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
_("You need to choose at least one article.")
|
|
||||||
)
|
|
||||||
return redirect(reverse(
|
|
||||||
'users:profil',
|
|
||||||
kwargs={'userid': userid}
|
|
||||||
))
|
|
||||||
|
|
||||||
return form({
|
|
||||||
'venteform': article_formset,
|
|
||||||
'articlelist': article_list
|
|
||||||
}, 'cotisations/new_facture_solde.html', request)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO : change solde to balance
|
# TODO : change solde to balance
|
||||||
@login_required
|
@login_required
|
||||||
@can_create(Facture)
|
@can_create(Facture)
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
# coding:utf-8
|
|
||||||
# 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
|
|
||||||
# Copyright © 2018 Maël Kervella
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# App de gestion des machines pour re2o
|
|
||||||
# Gabriel Détraz, Augustin Lemesle
|
|
||||||
# Gplv2
|
|
||||||
"""preferences.aes_field
|
|
||||||
Module defining a AESEncryptedField object that can be used in forms
|
|
||||||
to handle the use of properly encrypting and decrypting AES keys
|
|
||||||
"""
|
|
||||||
|
|
||||||
import string
|
|
||||||
import binascii
|
|
||||||
from random import choice
|
|
||||||
from Crypto.Cipher import AES
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
EOD = '`%EofD%`' # This should be something that will not occur in strings
|
|
||||||
|
|
||||||
|
|
||||||
def genstring(length=16, chars=string.printable):
|
|
||||||
""" Generate a random string of length `length` and composed of
|
|
||||||
the characters in `chars` """
|
|
||||||
return ''.join([choice(chars) for i in range(length)])
|
|
||||||
|
|
||||||
|
|
||||||
def encrypt(key, s):
|
|
||||||
""" AES Encrypt a secret `s` with the key `key` """
|
|
||||||
obj = AES.new(key)
|
|
||||||
datalength = len(s) + len(EOD)
|
|
||||||
if datalength < 16:
|
|
||||||
saltlength = 16 - datalength
|
|
||||||
else:
|
|
||||||
saltlength = 16 - datalength % 16
|
|
||||||
ss = ''.join([s, EOD, genstring(saltlength)])
|
|
||||||
return obj.encrypt(ss)
|
|
||||||
|
|
||||||
|
|
||||||
def decrypt(key, s):
|
|
||||||
""" AES Decrypt a secret `s` with the key `key` """
|
|
||||||
obj = AES.new(key)
|
|
||||||
ss = obj.decrypt(s)
|
|
||||||
return ss.split(bytes(EOD, 'utf-8'))[0]
|
|
||||||
|
|
||||||
|
|
||||||
class AESEncryptedField(models.CharField):
|
|
||||||
""" A Field that can be used in forms for adding the support
|
|
||||||
of AES ecnrypted fields """
|
|
||||||
def save_form_data(self, instance, data):
|
|
||||||
setattr(instance, self.name,
|
|
||||||
binascii.b2a_base64(encrypt(settings.AES_KEY, data)))
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
return decrypt(settings.AES_KEY,
|
|
||||||
binascii.a2b_base64(value)).decode('utf-8')
|
|
||||||
|
|
||||||
def from_db_value(self, value, *args, **kwargs):
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
return decrypt(settings.AES_KEY,
|
|
||||||
binascii.a2b_base64(value)).decode('utf-8')
|
|
||||||
|
|
||||||
def get_prep_value(self, value):
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
return binascii.b2a_base64(encrypt(
|
|
||||||
settings.AES_KEY,
|
|
||||||
value
|
|
||||||
))
|
|
47
preferences/migrations/0036_auto_20180705_0840.py
Normal file
47
preferences/migrations/0036_auto_20180705_0840.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-07-05 13:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('preferences', '0035_optionaluser_allow_self_subscription'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='assooption',
|
||||||
|
name='payment',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='assooption',
|
||||||
|
name='payment_id',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='assooption',
|
||||||
|
name='payment_pass',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='optionaluser',
|
||||||
|
name='allow_self_subscription',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='optionaluser',
|
||||||
|
name='max_solde',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='optionaluser',
|
||||||
|
name='min_online_payment',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='optionaluser',
|
||||||
|
name='solde_negatif',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='optionaluser',
|
||||||
|
name='user_solde',
|
||||||
|
),
|
||||||
|
]
|
|
@ -3,7 +3,6 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import preferences.aes_field
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import preferences.aes_field
|
try:
|
||||||
|
import preferences.aes_field as aes_field
|
||||||
|
except ImportError:
|
||||||
|
import cotisations.payment_methods.comnpay.aes_field as aes_field
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -16,7 +19,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='assooption',
|
model_name='assooption',
|
||||||
name='payment_pass',
|
name='payment_pass',
|
||||||
field=preferences.aes_field.AESEncryptedField(blank=True, max_length=255, null=True),
|
field=aes_field.AESEncryptedField(blank=True, max_length=255, null=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='assooption',
|
model_name='assooption',
|
||||||
|
|
|
@ -35,8 +35,6 @@ import cotisations.models
|
||||||
import machines.models
|
import machines.models
|
||||||
from re2o.mixins import AclMixin
|
from re2o.mixins import AclMixin
|
||||||
|
|
||||||
from .aes_field import AESEncryptedField
|
|
||||||
|
|
||||||
|
|
||||||
class PreferencesModel(models.Model):
|
class PreferencesModel(models.Model):
|
||||||
""" Base object for the Preferences objects
|
""" Base object for the Preferences objects
|
||||||
|
@ -67,22 +65,6 @@ class OptionalUser(AclMixin, PreferencesModel):
|
||||||
PRETTY_NAME = "Options utilisateur"
|
PRETTY_NAME = "Options utilisateur"
|
||||||
|
|
||||||
is_tel_mandatory = models.BooleanField(default=True)
|
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
|
|
||||||
)
|
|
||||||
max_solde = models.DecimalField(
|
|
||||||
max_digits=5,
|
|
||||||
decimal_places=2,
|
|
||||||
default=50
|
|
||||||
)
|
|
||||||
min_online_payment = models.DecimalField(
|
|
||||||
max_digits=5,
|
|
||||||
decimal_places=2,
|
|
||||||
default=10
|
|
||||||
)
|
|
||||||
gpg_fingerprint = models.BooleanField(default=True)
|
gpg_fingerprint = models.BooleanField(default=True)
|
||||||
all_can_create_club = models.BooleanField(
|
all_can_create_club = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
|
@ -96,13 +78,6 @@ class OptionalUser(AclMixin, PreferencesModel):
|
||||||
default=False,
|
default=False,
|
||||||
help_text="Un nouvel utilisateur peut se créer son compte sur re2o"
|
help_text="Un nouvel utilisateur peut se créer son compte sur re2o"
|
||||||
)
|
)
|
||||||
allow_self_subscription = models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text=(
|
|
||||||
"Autoriser les utilisateurs à cotiser par eux mêmes via les"
|
|
||||||
" moyens de paiement permettant l'auto-cotisation."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
shell_default = models.OneToOneField(
|
shell_default = models.OneToOneField(
|
||||||
'users.ListShell',
|
'users.ListShell',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -298,25 +273,6 @@ class AssoOption(AclMixin, PreferencesModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
PAYMENT = (
|
|
||||||
('NONE', 'NONE'),
|
|
||||||
('COMNPAY', 'COMNPAY'),
|
|
||||||
)
|
|
||||||
payment = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
choices=PAYMENT,
|
|
||||||
default='NONE',
|
|
||||||
)
|
|
||||||
payment_id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
default='',
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
payment_pass = AESEncryptedField(
|
|
||||||
max_length=255,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
|
@ -31,46 +31,30 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h4>Préférences utilisateur</h4>
|
<h4>Préférences utilisateur</h4>
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalUser' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalUser' %}">
|
||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-edit"></i>
|
||||||
Editer
|
Editer
|
||||||
</a>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
</p>
|
</p>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Téléphone obligatoirement requis</th>
|
<th>Téléphone obligatoirement requis</th>
|
||||||
<td>{{ useroptions.is_tel_mandatory }}</td>
|
<td>{{ useroptions.is_tel_mandatory }}</td>
|
||||||
<th>Activation du solde pour les utilisateurs</th>
|
|
||||||
<td>{{ useroptions.user_solde }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Champ gpg fingerprint</th>
|
<th>Champ gpg fingerprint</th>
|
||||||
<td>{{ useroptions.gpg_fingerprint }}</td>
|
<td>{{ useroptions.gpg_fingerprint }}</td>
|
||||||
{% if useroptions.user_solde %}
|
|
||||||
<th>Solde négatif</th>
|
|
||||||
<td>{{ useroptions.solde_negatif }}</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Creations d'adhérents par tous</th>
|
<th>Creations d'adhérents par tous</th>
|
||||||
<td>{{ useroptions.all_can_create_adherent }}</td>
|
<td>{{ useroptions.all_can_create_adherent }}</td>
|
||||||
<th>Creations de clubs par tous</th>
|
<th>Creations de clubs par tous</th>
|
||||||
<td>{{ useroptions.all_can_create_club }}</td>
|
<td>{{ useroptions.all_can_create_club }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if useroptions.user_solde %}
|
|
||||||
<tr>
|
|
||||||
<th>Solde maximum</th>
|
|
||||||
<td>{{ useroptions.max_solde }}</td>
|
|
||||||
<th>Montant minimal de rechargement en ligne</th>
|
|
||||||
<td>{{ useroptions.min_online_payment }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Auto inscription</th>
|
<th>Auto inscription</th>
|
||||||
<td>{{ useroptions.self_adhesion }}</td>
|
<td>{{ useroptions.self_adhesion }}</td>
|
||||||
<th>Shell par défaut des utilisateurs</th>
|
<th>Shell par défaut des utilisateurs</th>
|
||||||
<td>{{ useroptions.shell_default }}</td>
|
<td>{{ useroptions.shell_default }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<h4>Préférences machines</h4>
|
<h4>Préférences machines</h4>
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalMachine' %}">
|
||||||
|
@ -91,11 +75,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{ machineoptions.max_lambdauser_aliases }}</td>
|
<td>{{ machineoptions.max_lambdauser_aliases }}</td>
|
||||||
<th>Support de l'ipv6</th>
|
<th>Support de l'ipv6</th>
|
||||||
<td>{{ machineoptions.ipv6_mode }}</td>
|
<td>{{ machineoptions.ipv6_mode }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Creation de machines</th>
|
<th>Creation de machines</th>
|
||||||
<td>{{ machineoptions.create_machine }}</td>
|
<td>{{ machineoptions.create_machine }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<h4>Préférences topologie</h4>
|
<h4>Préférences topologie</h4>
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalTopologie' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'OptionalTopologie' %}">
|
||||||
|
@ -108,7 +92,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Politique générale de placement de vlan</th>
|
<th>Politique générale de placement de vlan</th>
|
||||||
<td>{{ topologieoptions.radius_general_policy }}</td>
|
<td>{{ topologieoptions.radius_general_policy }}</td>
|
||||||
<th> Ce réglage défini la politique vlan après acceptation radius : soit sur le vlan de la plage d'ip de la machine, soit sur un vlan prédéfini dans "Vlan où placer les machines après acceptation RADIUS"</th>
|
<th> Ce réglage défini la politique vlan après acceptation radius : soit sur le vlan de la plage d'ip de la machine, soit sur un vlan prédéfini dans "Vlan où placer les machines après acceptation RADIUS"</th>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -144,12 +128,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>Temps avant expiration du lien de reinitialisation de mot de passe (en heures)</th>
|
<th>Temps avant expiration du lien de reinitialisation de mot de passe (en heures)</th>
|
||||||
<td>{{ generaloptions.req_expire_hrs }}</td>
|
<td>{{ generaloptions.req_expire_hrs }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Message global affiché sur le site</th>
|
<th>Message global affiché sur le site</th>
|
||||||
<td>{{ generaloptions.general_message }}</td>
|
<td>{{ generaloptions.general_message }}</td>
|
||||||
<th>Résumé des CGU</th>
|
<th>Résumé des CGU</th>
|
||||||
<td>{{ generaloptions.GTU_sum_up }}</td>
|
<td>{{ generaloptions.GTU_sum_up }}</td>
|
||||||
<tr>
|
<tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>CGU</th>
|
<th>CGU</th>
|
||||||
<td>{{generaloptions.GTU}}</th>
|
<td>{{generaloptions.GTU}}</th>
|
||||||
|
@ -171,8 +155,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Adresse</th>
|
<th>Adresse</th>
|
||||||
<td>{{ assooptions.adresse1 }}<br>
|
<td>{{ assooptions.adresse1 }}<br>
|
||||||
{{ assooptions.adresse2 }}</td>
|
{{ assooptions.adresse2 }}</td>
|
||||||
<th>Contact mail</th>
|
<th>Contact mail</th>
|
||||||
<td>{{ assooptions.contact }}</td>
|
<td>{{ assooptions.contact }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -185,13 +169,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Objet utilisateur de l'association</th>
|
<th>Objet utilisateur de l'association</th>
|
||||||
<td>{{ assooptions.utilisateur_asso }}</td>
|
<td>{{ assooptions.utilisateur_asso }}</td>
|
||||||
<th>Moyen de paiement automatique</th>
|
<th>Description de l'association</th>
|
||||||
<td>{{ assooptions.payment }}</td>
|
<td>{{ assooptions.description | safe }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th>Description de l'association</th>
|
|
||||||
<td colspan="3">{{ assooptions.description | safe }}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
<h4>Messages personalisé dans les mails</h4>
|
<h4>Messages personalisé dans les mails</h4>
|
||||||
|
@ -205,7 +185,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>Mail de bienvenue (Français)</th>
|
<th>Mail de bienvenue (Français)</th>
|
||||||
<td>{{ mailmessageoptions.welcome_mail_fr | safe }}</td>
|
<td>{{ mailmessageoptions.welcome_mail_fr | safe }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Mail de bienvenue (Anglais)</th>
|
<th>Mail de bienvenue (Anglais)</th>
|
||||||
<td>{{ mailmessageoptions.welcome_mail_en | safe }}</td>
|
<td>{{ mailmessageoptions.welcome_mail_en | safe }}</td>
|
||||||
|
|
Loading…
Reference in a new issue