mirror of
https://github.com/nanoy42/coope
synced 2024-11-10 20:06:26 +00:00
Add price profiles
This commit is contained in:
parent
8c44b4edc9
commit
94715e4f99
11 changed files with 249 additions and 7 deletions
7
coopeV3/utils.py
Normal file
7
coopeV3/utils.py
Normal 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
|
|
@ -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)
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
25
preferences/migrations/0016_priceprofile.py
Normal file
25
preferences/migrations/0016_priceprofile.py
Normal 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
38
preferences/migrations/0017_auto_20190623_1453.py
Normal file
38
preferences/migrations/0017_auto_20190623_1453.py
Normal 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 ?'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
45
preferences/templates/preferences/price_profiles_index.html
Normal file
45
preferences/templates/preferences/price_profiles_index.html
Normal 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 %}
|
|
@ -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")
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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>
|
||||||
|
|
17
users/migrations/0009_auto_20190623_1437.py
Normal file
17
users/migrations/0009_auto_20190623_1437.py
Normal 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'},
|
||||||
|
),
|
||||||
|
]
|
Loading…
Reference in a new issue