3
0
Fork 0
mirror of https://github.com/nanoy42/coope synced 2024-06-02 13:21:48 +00:00

Add price profiles

This commit is contained in:
Yoann Pétri 2019-06-23 14:53:18 +02:00
parent 8c44b4edc9
commit 94715e4f99
11 changed files with 249 additions and 7 deletions

7
coopeV3/utils.py Normal file
View file

@ -0,0 +1,7 @@
import math
def compute_price(price, a, b, c, alpha):
if price < a:
return price * (a + b * math.exp(-c/(price-alpha)**2))
else:
return price * a

View file

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin from simple_history.admin import SimpleHistoryAdmin
from .models import PaymentMethod, GeneralPreferences, Cotisation, DivideHistory from .models import PaymentMethod, GeneralPreferences, Cotisation, DivideHistory, PriceProfile
class CotisationAdmin(SimpleHistoryAdmin): class CotisationAdmin(SimpleHistoryAdmin):
""" """
@ -24,6 +24,15 @@ class PaymentMethodAdmin(SimpleHistoryAdmin):
search_fields = ('name',) search_fields = ('name',)
list_filter = ('is_active', 'is_usable_in_cotisation', 'is_usable_in_reload', 'affect_balance') list_filter = ('is_active', 'is_usable_in_cotisation', 'is_usable_in_reload', 'affect_balance')
class PriceProfileAdmin(SimpleHistoryAdmin):
"""
The admin class for :class:`Consumptions <preferences.models.PriceProfile>`.
"""
list_display = ('name', 'a', 'b', 'c', 'alpha', 'use_for_draft')
ordering = ('name',)
search_fields = ('name',)
list_filter = ('use_for_draft',)
class DivideHistoryAdmin(SimpleHistoryAdmin): class DivideHistoryAdmin(SimpleHistoryAdmin):
""" """
The admin class for Divide histories The admin class for Divide histories
@ -34,4 +43,5 @@ class DivideHistoryAdmin(SimpleHistoryAdmin):
admin.site.register(PaymentMethod, PaymentMethodAdmin) admin.site.register(PaymentMethod, PaymentMethodAdmin)
admin.site.register(GeneralPreferences, GeneralPreferencesAdmin) admin.site.register(GeneralPreferences, GeneralPreferencesAdmin)
admin.site.register(Cotisation, CotisationAdmin) admin.site.register(Cotisation, CotisationAdmin)
admin.site.register(PriceProfile, PriceProfileAdmin)
admin.site.register(DivideHistory, DivideHistoryAdmin) admin.site.register(DivideHistory, DivideHistoryAdmin)

View file

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from .models import Cotisation, PaymentMethod, GeneralPreferences from .models import Cotisation, PaymentMethod, GeneralPreferences, PriceProfile
class CotisationForm(forms.ModelForm): class CotisationForm(forms.ModelForm):
""" """
@ -25,6 +25,13 @@ class PaymentMethodForm(forms.ModelForm):
model = PaymentMethod model = PaymentMethod
fields = "__all__" fields = "__all__"
class PriceProfileForm(forms.ModelForm):
"""
Form to add and edit :class:`~preferences.models.PriceProfile`.
"""
class Meta:
model = PriceProfile
fields = "__all__"
class GeneralPreferencesForm(forms.ModelForm): class GeneralPreferencesForm(forms.ModelForm):
""" """

View file

@ -0,0 +1,25 @@
# Generated by Django 2.1 on 2019-06-23 12:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0015_dividehistory'),
]
operations = [
migrations.CreateModel(
name='PriceProfile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('a', models.DecimalField(decimal_places=2, max_digits=3, verbose_name='Marge constante')),
('b', models.DecimalField(decimal_places=2, max_digits=3, verbose_name='Marge constante')),
('c', models.DecimalField(decimal_places=2, max_digits=4, verbose_name='Marge constante')),
('alpha', models.DecimalField(decimal_places=2, max_digits=4, verbose_name='Marge constante')),
('use_for_draft', models.BooleanField(default=False)),
],
),
]

View file

@ -0,0 +1,38 @@
# Generated by Django 2.1 on 2019-06-23 12:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0016_priceprofile'),
]
operations = [
migrations.AlterField(
model_name='priceprofile',
name='alpha',
field=models.DecimalField(decimal_places=2, max_digits=4, verbose_name='Étendue'),
),
migrations.AlterField(
model_name='priceprofile',
name='b',
field=models.DecimalField(decimal_places=2, max_digits=3, verbose_name='Marge variable'),
),
migrations.AlterField(
model_name='priceprofile',
name='c',
field=models.DecimalField(decimal_places=2, max_digits=4, verbose_name='Paramètre de forme'),
),
migrations.AlterField(
model_name='priceprofile',
name='name',
field=models.CharField(max_length=255, verbose_name='Nom'),
),
migrations.AlterField(
model_name='priceprofile',
name='use_for_draft',
field=models.BooleanField(default=False, verbose_name='Utiliser pour les pressions ?'),
),
]

View file

@ -173,3 +173,28 @@ class DivideHistory(models.Model):
def __str__(self): def __str__(self):
return "Répartition du " + str(self.date) return "Répartition du " + str(self.date)
class PriceProfile(models.Model):
"""
Stores parameters to compute price
"""
name = models.CharField(max_length=255, verbose_name="Nom")
a = models.DecimalField(verbose_name="Marge constante", max_digits=3, decimal_places=2)
b = models.DecimalField(verbose_name="Marge variable", max_digits=3, decimal_places=2)
c = models.DecimalField(verbose_name="Paramètre de forme", max_digits=4, decimal_places=2)
alpha = models.DecimalField(verbose_name="Étendue", max_digits=4, decimal_places=2)
use_for_draft = models.BooleanField(default=False, verbose_name="Utiliser pour les pressions ?")
def save(self, *args, **kwargs):
if self.use_for_draft:
try:
temp = PriceProfile.objects.get(use_for_draft=True)
if self != temp:
temp.use_for_draft = False
temp.save()
except PriceProfile.DoesNotExist:
pass
super(PriceProfile, self).save(*args, **kwargs)
def __str__(self):
return self.name

View file

@ -0,0 +1,45 @@
{% extends "base.html" %}
{% block entete %}Gestion des profils de prix{% endblock %}
{% block navbar %}
<ul>
<li><a href="#first">Liste des profils de prix</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des profils de prix</h2>
</header>
{% if perms.preferences.add_priceprofile %}
<a class="button" href="{% url 'preferences:addPriceProfile' %}"><i class="fa fa-plus-square"></i> Créer un profil de prix</a><br><br>
{% endif %}
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Nom</th>
<th>a (marge constante)</th>
<th>b (marge variable)</th>
<th>c (paramètre de forme)</th>
<th>alpha (étendue)</th>
<th>Pression ?</th>
<th>Administration</th>
</tr>
</thead>
<tbody>
{% for pp in price_profiles %}
<tr>
<td>{{ pp.name }} </td>
<td>{{ pp.a }}</td>
<td>{{ pp.b }}</td>
<td>{{ pp.c }}</td>
<td>{{ pp.alpha }}</td>
<td>{{ pp.use_for_draft | yesno:"Oui,Non"}}</td>
<td>{% if perms.preferences.change_priceprofile %}<a class="button small" href="{% url 'preferences:editPriceProfile' pp.pk %}"><i class="fa fa-pencil-alt"></i> Modifier</a> {% endif %}{% if perms.preferences.delete_priceprofile %}<a class="button small" href="{% url 'preferences:deletePriceProfile' pp.pk %}"><i class="fa fa-trash"></i> Supprimer</a>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View file

@ -13,6 +13,10 @@ urlpatterns = [
path('addPaymentMethod', views.addPaymentMethod, name="addPaymentMethod"), path('addPaymentMethod', views.addPaymentMethod, name="addPaymentMethod"),
path('editPaymentMethod/<int:pk>', views.editPaymentMethod, name="editPaymentMethod"), path('editPaymentMethod/<int:pk>', views.editPaymentMethod, name="editPaymentMethod"),
path('deletePaymentMethod/<int:pk>', views.deletePaymentMethod, name="deletePaymentMethod"), path('deletePaymentMethod/<int:pk>', views.deletePaymentMethod, name="deletePaymentMethod"),
path('priceProfilesIndex', views.price_profiles_index, name="priceProfilesIndex"),
path('addPriceProfile', views.add_price_profile, name="addPriceProfile"),
path('editPriceProfile/<int:pk>', views.edit_price_profile, name="editPriceProfile"),
path('deletePriceProfile/<int:pk>', views.delete_price_profile, name="deletePriceProfile"),
path('inactive', views.inactive, name="inactive"), path('inactive', views.inactive, name="inactive"),
path('getConfig', views.get_config, name="getConfig"), path('getConfig', views.get_config, name="getConfig"),
path('getCotisation/<int:pk>', views.get_cotisation, name="getCotisation") path('getCotisation/<int:pk>', views.get_cotisation, name="getCotisation")

View file

@ -10,9 +10,9 @@ from django.http import Http404
from coopeV3.acl import active_required from coopeV3.acl import active_required
from .models import GeneralPreferences, Cotisation, PaymentMethod from .models import GeneralPreferences, Cotisation, PaymentMethod, PriceProfile
from .forms import CotisationForm, PaymentMethodForm, GeneralPreferencesForm from .forms import CotisationForm, PaymentMethodForm, GeneralPreferencesForm, PriceProfileForm
@active_required @active_required
@login_required @login_required
@ -186,3 +186,62 @@ def get_config(request):
data = json.dumps(gp_dict) data = json.dumps(gp_dict)
return HttpResponse(data, content_type='application/json') return HttpResponse(data, content_type='application/json')
########## Price Profiles ##########
@active_required
@login_required
@permission_required('preferences.view_priceprofile')
def price_profiles_index(request):
"""
View which lists all the :class:`~preferences.models.PriceProfile`.
"""
price_profiles = PriceProfile.objects.all()
return render(request, "preferences/price_profiles_index.html", {"price_profiles": price_profiles})
@active_required
@login_required
@permission_required('preferences.add_priceprofile')
def add_price_profile(request):
"""
View which displays a :class:`~preferences.forms.PriceProfileForm` to create a :class:`~preferences.models.PriceProfile`.
"""
form = PriceProfileForm(request.POST or None)
if form.is_valid():
price_profile = form.save()
messages.success(request, "Le profil de prix " + price_profile.name + " a bien été crée")
return redirect(reverse('preferences:priceProfilesIndex'))
return render(request, "form.html", {"form": form, "form_title": "Création d'un profil de prix", "form_button": "Créer", "form_button_icon": "plus-square"})
@active_required
@login_required
@permission_required('preferences.change_priceprofile')
def edit_price_profile(request, pk):
"""
View which displays a :class:`~preferences.forms.PriceProfile` to edit a :class:`~preferences.models.PriceProfile`.
pk
The primary key of the :class:`~preferences.models.PriceProfile` to edit.
"""
price_profile = get_object_or_404(PriceProfile, pk=pk)
form = PriceProfileForm(request.POST or None, instance=price_profile)
if form.is_valid():
price_profile = form.save()
messages.success(request, "Le profil de prix " + price_profile.name + " a bien été modifié")
return redirect(reverse('preferences:priceProfilesIndex'))
return render(request, "form.html", {"form": form, "form_title": "Modification d'un profil de prix", "form_button": "Modifier", "form_button_icon": "pencil-alt"})
@active_required
@login_required
@permission_required('preferences.delete_priceprofile')
def delete_price_profile(request,pk):
"""
Delete a :class:`~preferences.models.PriceProfile`.
pk
The primary key of the :class:`~preferences.models.PriceProfile` to delete.
"""
price_profile = get_object_or_404(PriceProfile, pk=pk)
message = "Le profil de prix " + price_profile.name + " a bien été supprimé"
price_pofile.delete()
messages.success(request, message)
return redirect(reverse('preferences:priceProfilesIndex'))

View file

@ -52,11 +52,16 @@
<i class="fa fa-calendar-check"></i> <a href="{% url 'preferences:cotisationsIndex' %}">Cotisations</a> <i class="fa fa-calendar-check"></i> <a href="{% url 'preferences:cotisationsIndex' %}">Cotisations</a>
</span> </span>
{% endif %} {% endif %}
{% if perms.preferences.view_cotisation %} {% if perms.preferences.view_paymentmethod %}
<span class="tabulation2"> <span class="tabulation2">
<i class="fa fa-comments-dollar"></i> <a href="{% url 'preferences:paymentMethodsIndex' %}">Moyens de paiement</a> <i class="fa fa-comments-dollar"></i> <a href="{% url 'preferences:paymentMethodsIndex' %}">Moyens de paiement</a>
</span> </span>
{% endif %} {% endif %}
{% if perms.preferences.view_priceprofile %}
<span class="tabulation2">
<i class="fa fa-search-dollar"></i> <a href="{% url 'preferences:priceProfilesIndex' %}">Profils de prix</a>
</span>
{% endif %}
<span class="tabulation2"> <span class="tabulation2">
<i class="fa fa-bed"></i> <a href="{% url 'users:logout' %}">Deconnexion</a> <i class="fa fa-bed"></i> <a href="{% url 'users:logout' %}">Deconnexion</a>
</span> </span>

View file

@ -0,0 +1,17 @@
# Generated by Django 2.1 on 2019-06-23 12:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('users', '0008_auto_20190623_1105'),
]
operations = [
migrations.AlterModelOptions(
name='profile',
options={'permissions': (('can_generate_invoices', 'Can generate invocies'),), 'verbose_name': 'Profil'},
),
]