Commit 812661ca authored by Hugo LEVY-FALK's avatar Hugo LEVY-FALK

Supprime tout ce qui ne sert plus pour les cotisations

parent ab6babd4
......@@ -5,6 +5,7 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# Copyright © 2018 Hugo Levy-Falk
#
# 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
......@@ -78,24 +79,6 @@ class NewFactureForm(FormRevMixin, ModelForm):
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):
"""
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()
# 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
class RechargeForm(FormRevMixin, Form):
"""
......
......@@ -6,6 +6,7 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# Copyright © 2018 Hugo Levy-Falk
#
# 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
......
{% 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" %} : &nbsp;
{% bootstrap_form form label_class='sr-only' %}
&nbsp;
<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 : &nbsp;
{% bootstrap_form venteform.empty_form label_class='sr-only' %}
&nbsp;
<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 %}
{% 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,
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'),
] + payment_methods.urls.urlpatterns
......@@ -5,6 +5,7 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# Copyright © 2018 Hugo Levy-Falk
#
# 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
......@@ -56,7 +57,7 @@ from re2o.acl import (
can_delete_set,
can_change,
)
from preferences.models import OptionalUser, AssoOption, GeneralOption
from preferences.models import AssoOption, GeneralOption
from .models import Facture, Article, Vente, Paiement, Banque
from .forms import (
NewFactureForm,
......@@ -70,7 +71,6 @@ from .forms import (
NewFactureFormPdf,
SelectUserArticleForm,
SelectClubArticleForm,
CreditSoldeForm,
RechargeForm
)
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.
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.
"""
invoice = Facture(user=user)
# 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
@login_required
@can_create(Facture)
......
# 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
))
# -*- 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 django.db import migrations, models
import preferences.aes_field
class Migration(migrations.Migration):
......
......@@ -3,7 +3,10 @@
from __future__ import unicode_literals
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):
......@@ -16,7 +19,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='assooption',
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(
model_name='assooption',
......
......@@ -35,8 +35,6 @@ import cotisations.models
import machines.models
from re2o.mixins import AclMixin
from .aes_field import AESEncryptedField
class PreferencesModel(models.Model):
""" Base object for the Preferences objects
......@@ -67,22 +65,6 @@ class OptionalUser(AclMixin, PreferencesModel):
PRETTY_NAME = "Options utilisateur"
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)
all_can_create_club = models.BooleanField(
default=False,
......@@ -96,13 +78,6 @@ class OptionalUser(AclMixin, PreferencesModel):
default=False,
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(
'users.ListShell',
on_delete=models.PROTECT,
......@@ -298,25 +273,6 @@ class AssoOption(AclMixin, PreferencesModel):
blank=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(
null=True,
blank=True,
......
......@@ -31,46 +31,30 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block content %}
<h4>Préférences utilisateur</h4>
<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
</a>
</a>
<p>
</p>
<table class="table table-striped">
<tr>
<th>Téléphone obligatoirement requis</th>
<td>{{ useroptions.is_tel_mandatory }}</td>
<th>Activation du solde pour les utilisateurs</th>
<td>{{ useroptions.user_solde }}</td>