3
0
Fork 0
mirror of https://github.com/nanoy42/coope synced 2024-05-03 08:02:24 +00:00

Merge pull request #14 from nanoy42/release-3.6.3

Release 3.6.3
This commit is contained in:
Yoann Pietri 2019-08-28 12:39:03 +02:00 committed by GitHub
commit cb449ee414
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 260 additions and 85 deletions

1
.gitignore vendored
View file

@ -45,3 +45,4 @@ static/
Pipfile
mediafiles
Pipfile.lock
contributors.txt

View file

@ -1,3 +1,8 @@
## v3.6.3
* Refonte totale du système de stocks
* Fix price profile
* Rajoute un nombre de citations sur la page de statistiques
* Fix les contributeurs sur la page about et gencontributors en commande
## v3.6.2
* Fix sur les prix des cotisations.
* Page À propose

View file

@ -0,0 +1,15 @@
from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
import subprocess
class Command(BaseCommand):
help = 'Generate the git contributors file'
def handle(self, *args, **options):
try:
subprocess.call("rm " + settings.BASE_DIR + "/contributors.txt", shell=True)
except:
pass
subprocess.call("git -C " + settings.BASE_DIR + " shortlog -n $@ | grep \"):\" | sed 's|:||' >> " + settings.BASE_DIR + "/contributors.txt", shell=True)
subprocess.call("cat " + settings.BASE_DIR + "/contributors.txt", shell=True)

View file

@ -25,11 +25,13 @@ urlpatterns = [
path('home', views.homepage, name="homepage"),
path('about', views.about, name="about"),
path('coope-runner', views.coope_runner, name="coope-runner"),
path('stats', views.stats, name="stats"),
path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
path('gestion/', include('gestion.urls')),
path('preferences/', include('preferences.urls')),
path('preferences/', include('preferences.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -3,9 +3,15 @@ import os
from django.shortcuts import redirect, render
from django.urls import reverse
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User, Group
from preferences.models import GeneralPreferences
from gestion.models import Keg
from preferences.models import GeneralPreferences, PaymentMethod, Cotisation
from gestion.models import Keg, ConsumptionHistory, Category, Product, Menu
from users.models import School
from .acl import active_required, admin_required
def home(request):
"""
@ -38,16 +44,59 @@ def about(request):
"""
A page about the project
"""
os.system("git -C " + settings.BASE_DIR + " shortlog -n $@ | grep \"):\" | sed 's|:||' >> " + settings.BASE_DIR + "/contributors.txt")
contributors = []
with open(settings.BASE_DIR + "/contributors.txt", "r") as f:
for line in f:
print(line)
print(line.split(" ")[0])
contributors.append((line.split(" ")[0], int(line.split(" ")[1].replace("(", "").replace(")", "").replace("\n", ""))))
os.system("rm " + settings.BASE_DIR + "/contributors.txt")
try:
with open(settings.BASE_DIR + "/contributors.txt", "r") as f:
for line in f:
contributors.append((line[:line.find('(')], int(line[(line.find('(') + 1):line.find(')')])))
except:
pass
license = []
with open(settings.BASE_DIR + "/LICENSE", "r") as f:
for line in f:
license.append(line)
return render(request, "about.html", {"contributors": contributors, "license": license})
return render(request, "about.html", {"contributors": contributors, "license": license})
@active_required
@login_required
@admin_required
def stats(request):
users = User.objects.all()
adherents = [x for x in users if x.profile.is_adherent]
transactions = ConsumptionHistory.objects.all()
categories = Category.objects.all()
categories_shown = Category.objects.exclude(order=0)
products = Product.objects.all()
active_products = Product.objects.filter(is_active=True)
active_kegs = Keg.objects.filter(is_active=True)
sum_positive_balance = sum([x.profile.balance for x in users if x.profile.balance > 0])
sum_balance = sum([x.profile.balance for x in users])
schools = School.objects.all()
groups = Group.objects.all()
admins = User.objects.filter(is_staff=True)
superusers = User.objects.filter(is_superuser=True)
menus = Menu.objects.all()
payment_methods = PaymentMethod.objects.all()
cotisations = Cotisation.objects.all()
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
nb_quotes = len(gp.global_message.split("\n"))
return render(request, "stats.html", {
"users": users,
"adherents": adherents,
"transactions": transactions,
"categories": categories,
"categories_shown": categories_shown,
"products": products,
"active_products": active_products,
"active_kegs": active_kegs,
"sum_positive_balance": sum_positive_balance,
"sum_balance": sum_balance,
"schools": schools,
"groups": groups,
"admins": admins,
"superusers": superusers,
"menus": menus,
"payment_methods": payment_methods,
"cotisations": cotisations,
"nb_quotes": nb_quotes,
})

View file

@ -59,8 +59,8 @@ class ProductAdmin(SimpleHistoryAdmin):
"""
The admin class for :class:`Products <gestion.models.Product>`.
"""
list_display = ('name', 'amount', 'is_active', 'category', 'adherentRequired', 'stockHold', 'stockBar', 'volume', 'deg')
ordering = ('name', 'amount', 'stockHold', 'stockBar', 'deg')
list_display = ('name', 'amount', 'is_active', 'category', 'adherentRequired', 'stock', 'volume', 'deg')
ordering = ('name', 'amount', 'stock', 'deg')
search_fields = ('name',)
list_filter = ('is_active', 'adherentRequired', 'category')

View file

@ -0,0 +1,53 @@
# Generated by Django 2.1 on 2019-08-27 19:19
from django.db import migrations, models
def update(apps, schema_editor):
Product = apps.get_model('gestion', 'Product')
for product in Product.objects.all():
product.stock = product.stockBar
product.save()
def reverse(apps, schema_editor):
Product = apps.get_model('gestion', 'Product')
for product in Product.objects.all():
product.stockBar = product.stock
product.save()
class Migration(migrations.Migration):
dependencies = [
('gestion', '0011_auto_20190623_1640'),
]
operations = [
migrations.AddField(
model_name='historicalproduct',
name='stock',
field=models.IntegerField(default=0, verbose_name='Stock'),
),
migrations.AddField(
model_name='product',
name='stock',
field=models.IntegerField(default=0, verbose_name='Stock'),
),
migrations.RunPython(update, reverse),
migrations.RemoveField(
model_name='historicalproduct',
name='stockBar',
),
migrations.RemoveField(
model_name='historicalproduct',
name='stockHold',
),
migrations.RemoveField(
model_name='product',
name='stockBar',
),
migrations.RemoveField(
model_name='product',
name='stockHold',
),
]

View file

@ -54,13 +54,9 @@ class Product(models.Model):
"""
The price of the product.
"""
stockHold = models.IntegerField(default=0, verbose_name="Stock en soute")
stock = models.IntegerField(default=0, verbose_name="Stock")
"""
Number of product in the hold.
"""
stockBar = models.IntegerField(default=0, verbose_name="Stock en bar")
"""
Number of product at the bar.
Number of product
"""
category = models.ForeignKey('Category', on_delete=models.PROTECT, verbose_name="Catégorie")
"""
@ -95,7 +91,7 @@ class Product(models.Model):
def __str__(self):
if self.draft_category == self.DRAFT_NONE:
return self.name + "(" + str(self.amount) + " €)"
return self.name + " (" + str(self.amount) + " €)"
else:
return self.name + " (" + str(self.amount) + " €, " + str(self.deg) + "°)"

View file

@ -14,8 +14,7 @@
{% if perms.gestion.change_product %}<a href="{% url 'gestion:switchActivate' product.pk %}" class="button small">{% if product.is_active %}Désa{% else %}A{% endif %}ctiver</a> <a href="{% url 'gestion:editProduct' product.pk %}" class="button small">Modifier</a><br>{% endif %}<br>
<strong>Nom</strong> : {{ product.name }}<br>
<strong>Prix de vente</strong> : {{ product.amount }}€<br>
<strong>Stock en soute</strong> : {{ product.stockHold }}<br>
<strong>Stock au bar</strong> : {{ product.stockBar }}<br>
<strong>Stock en soute</strong> : {{ product.stock }}<br>
<strong>Catégorie</strong> : <a href="{% url 'gestion:categoryProfile' product.category.pk %}">{{ product.category }}</a><br>
<strong>Actif</strong> : {{ product.is_active | yesno:"Oui, Non"}}<br>
<strong>Dégré</strong> : {{ product.deg }}<br>

View file

@ -19,8 +19,7 @@
<tr>
<th>Nom</th>
<th>Prix</th>
<th>Stock (soute)</th>
<th>Stock (bar)</th>
<th>Stock</th>
<th>Catégorie</th>
<th>Actif</th>
<th>Degré</th>
@ -33,8 +32,7 @@
<tr>
<td><a href="{% url 'gestion:productProfile' product.pk %}">{{ product.name }}</a></td>
<td>{{ product.amount}}</td>
<td>{{ product.stockHold }}</td>
<td>{{ product.stockBar }}</td>
<td>{{ product.stock }}</td>
<td>{{ product.category }}</td>
<td>{{ product.is_active | yesno:"Oui, Non"}}</td>
<td>{{ product.deg }}</td>

View file

@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load static %}
{% block entete %}Gestion des produits{% endblock %}
{% block navbar%}
<ul>
{% for category in categories %}
<li><a href="#{{category}}">Stocks {{category}}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% block content %}
{% for category in categories %}
<section id="{{category}}" class="main">
<header class="major">
<h2>Stocks {{category}}</h2>
</header>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Nom</th>
<th>Stock</th>
<th>Mettre à jour</th>
</tr>
</thead>
<tbody>
{% for product in category.active_products %}
<tr id="tr-{{product.pk}}">
<td><a href="{% url 'gestion:productProfile' product.pk %}">{{ product.name }}</a></td>
<td id="stock-{{product.pk}}">{{ product.stock }}</td>
<td><button class="update-stock" data-pk="{{product.pk}}" data-stock="{{product.stock}}" ><i class="fa fa-marker"></i></button></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endfor %}
<script src="{% static 'jquery.js' %}"></script>
<script src="{% static 'stocks.js' %}"></script>
{% endblock %}

View file

@ -52,8 +52,9 @@ urlpatterns = [
path('categoryProfile/<int:pk>', views.categoryProfile, name="categoryProfile"),
path('categoriesList', views.categoriesList, name="categoriesList"),
path('categories-autocomplete', views.CategoriesAutocomplete.as_view(), name="categories-autocomplete"),
path('stats', views.stats, name="stats"),
path('divide', views.divide, name="divide"),
path('gen_invoice', views.gen_invoice, name="gen_invoice"),
path('compute-price', views.compute_price_view, name="compute-price"),
path('stocks', views.stocks, name="stocks"),
path('updateStock/<int:pk>', views.update_stock, name="updateStock"),
]

View file

@ -161,9 +161,11 @@ def order(request):
kegHistory.amountSold += Decimal(quantity * product.amount)
kegHistory.save()
else:
if(product.stockHold > 0):
product.stockHold -= 1
if(product.stock > quantity):
product.stock -= quantity
product.save()
else:
raise Exception("Le stock du produit n'autorise pas l'opération")
consumption, _ = Consumption.objects.get_or_create(customer=user, product=product)
consumption.quantity += quantity
consumption.save()
@ -195,9 +197,11 @@ def order(request):
consumption, _ = Consumption.objects.get_or_create(customer=user, product=article)
consumption.quantity += quantity
consumption.save()
if(article.stockHold > 0):
article.stockHold -= 1
if(article.stock > quantity):
article.stock -= quantity
article.save()
else:
raise Exception("Le stock du produit " + article.name + "n'autorise pas l'opération")
user.profile.alcohol += Decimal(quantity * float(product.deg) * product.volume * 0.79 /10 /1000)
user.save()
return HttpResponse("La commande a bien été effectuée")
@ -282,6 +286,7 @@ def cancel_consumption(request, pk):
"""
consumption = get_object_or_404(ConsumptionHistory, pk=pk)
user = consumption.customer
product = consumption.product
if consumption.paymentMethod.affect_balance:
user.profile.debit -= consumption.amount
else:
@ -291,6 +296,8 @@ def cancel_consumption(request, pk):
consumptionT = Consumption.objects.get(customer=user, product=consumption.product)
consumptionT.quantity -= consumption.quantity
consumptionT.save()
product.stock += consumption.quantity
product.save()
consumption.delete()
messages.success(request, "La consommation a bien été annulée")
return redirect(reverse('users:profile', kwargs={'pk': user.pk}))
@ -311,7 +318,9 @@ def cancel_menu(request, pk):
user.profile.debit -= menu_history.amount
else:
user.profile.direct_debit -= menu_history.amount
for product in manu_history.menu.articles:
for product in menu_history.menu.articles:
product.stock += menu_history.quantity
product.save()
consumptionT = Consumption.objects.get(customer=user, product=product)
consumptionT -= menu_history.quantity
consumptionT.save()
@ -322,6 +331,7 @@ def cancel_menu(request, pk):
return redirect(reverse('users:profile', kwargs={'pk': user.pk}))
########## Products ##########
@active_required
@login_required
@acl_or('gestion.add_product', 'gestion.view_product', 'gestion.add_keg', 'gestion.view_keg', 'gestion.change_keg', 'gestion.view_menu', 'gestion.add_menu')
@ -451,6 +461,26 @@ class ActiveProductsAutocomplete(autocomplete.Select2QuerySetView):
qs = qs.filter(name__icontains=self.q)
return qs
@active_required
@login_required
@permission_required('gestion.change_product')
def update_stock(request, pk):
product = get_object_or_404(Product, pk=pk)
if("stock" in request.GET):
product.stock = request.GET.get("stock")
product.save()
return HttpResponse("Le stock a bien été mis à jour")
@active_required
@login_required
@permission_required('gestion.change_product')
def stocks(request):
"""
View to update stocks of active products
"""
categories = Category.objects.exclude(order=0).order_by("order")
return render(request, "gestion/stocks.html", {"categories": categories})
########## Kegs ##########
@active_required
@ -471,8 +501,7 @@ def addKeg(request):
pinte = Product(
name = "Pinte " + name,
amount = pinte_price,
stockHold = 0,
stockBar = 0,
stock = 0,
category = form.cleaned_data["category"],
needQuantityButton = False,
is_active = True,
@ -487,8 +516,7 @@ def addKeg(request):
demi = Product(
name = "Demi " + name,
amount = ceil(5*pinte_price)/10,
stockHold = 0,
stockBar = 0,
stock = 0,
category = form.cleaned_data["category"],
needQuantityButton = False,
is_active = True,
@ -504,8 +532,7 @@ def addKeg(request):
galopin = Product(
name = "Galopin " + name,
amount = ceil(2.5 * pinte_price)/10,
stockHold = 0,
stockBar = 0,
stock = 0,
category = form.cleaned_data["category"],
needQuantityButton = False,
is_active = True,
@ -1013,7 +1040,9 @@ def divide(request):
"divide_histories": divide_histories,
}
)
########## categories ##########
@active_required
@login_required
@permission_required('gestion.add_category')
@ -1091,47 +1120,6 @@ class CategoriesAutocomplete(autocomplete.Select2QuerySetView):
qs = qs.filter(name__icontains=self.q)
return qs
@active_required
@login_required
@admin_required
def stats(request):
users = User.objects.all()
adherents = [x for x in users if x.profile.is_adherent]
transactions = ConsumptionHistory.objects.all()
categories = Category.objects.all()
categories_shown = Category.objects.exclude(order=0)
products = Product.objects.all()
active_products = Product.objects.filter(is_active=True)
active_kegs = Keg.objects.filter(is_active=True)
sum_positive_balance = sum([x.profile.balance for x in users if x.profile.balance > 0])
sum_balance = sum([x.profile.balance for x in users])
schools = School.objects.all()
groups = Group.objects.all()
admins = User.objects.filter(is_staff=True)
superusers = User.objects.filter(is_superuser=True)
menus = Menu.objects.all()
payment_methods = PaymentMethod.objects.all()
cotisations = Cotisation.objects.all()
return render(request, "gestion/stats.html", {
"users": users,
"adherents": adherents,
"transactions": transactions,
"categories": categories,
"categories_shown": categories_shown,
"products": products,
"active_products": active_products,
"active_kegs": active_kegs,
"sum_positive_balance": sum_positive_balance,
"sum_balance": sum_balance,
"schools": schools,
"groups": groups,
"admins": admins,
"superusers": superusers,
"menus": menus,
"payment_methods": payment_methods,
"cotisations": cotisations,
})
########## Compute price ##########
def compute_price_view(request):

View file

@ -242,6 +242,6 @@ def delete_price_profile(request,pk):
"""
price_profile = get_object_or_404(PriceProfile, pk=pk)
message = "Le profil de prix " + price_profile.name + " a bien été supprimé"
price_pofile.delete()
price_profile.delete()
messages.success(request, message)
return redirect(reverse('preferences:priceProfilesIndex'))

16
staticfiles/stocks.js Normal file
View file

@ -0,0 +1,16 @@
$(document).ready(function(){
$(".update-stock").click(function(){
var pk = $(this).attr('data-pk');
var current_value = $(this).attr('data-stock');
var ok = false;
while(!ok){
var new_stock = prompt("Nouveau stock ? (entier attendu)", current_value);
ok = new_stock == null || !(isNaN(parseInt(new_stock)));
}
if(new_stock != null){
$.get("/gestion/updateStock/" + pk, {"stock": new_stock}, function(data){
$("#stock-"+pk).html(new_stock);
});
}
});
});

View file

@ -28,8 +28,6 @@
{% endfor %}
(<a href="https://github.com/nanoy42/coope/blob/master/LICENSE" target="_blank">https://github.com/nanoy42/coope/blob/master/LICENSE</a>).
<br><br>
Version 3.6.2.
</section>
</section>
<section id="third" class="main">
@ -37,12 +35,16 @@
<h2>Contributeurs</h2>
</header>
<section>
{% if contributors %}
Les contributeurs, triés par ordre décroissant de nombre de commits, sont:
<ol>
{% for contributor in contributors %}
<li>{{contributor.0}} ({{contributor.1}} commits)</li>
{% endfor %}
</ol>
{% else %}
Impossible d'afficher la liste des contributeurs
{% endif %}
</section>
</section>
{% endblock %}

View file

@ -42,6 +42,6 @@
<li><a href="https://www.facebook.com/coopesmetz/" class="icon fa-facebook alt"><span class="label">Facebook</span></a></li>
</ul>
</section>
<p class="copyright">coope.rez v3.6.2 (release stable) &copy; 2018-2019 Yoann Pietri. <a href="{% url 'about'%}">À propos du projet</a>.</p>
<p class="copyright">coope.rez v3.6.3 (release stable) &copy; 2018-2019 Yoann Pietri. <a href="{% url 'about'%}">À propos du projet</a>.</p>

View file

@ -20,8 +20,13 @@
<i class="fa fa-dolly-flatbed"></i> <a href="{% url 'gestion:productsIndex' %}">Gestion des produits</a>
</span>
{% endif %}
{% if perms.gestion.change_product %}
<span class="tabulation2">
<br>
<i class="fa fa-boxes"></i> <a href="{% url 'gestion:stocks' %}">Stocks</a>
</span>
{% endif %}
<span class="tabulation2">
<br>
<i class="fa fa-list-ol"></i> <a href="{% url 'gestion:ranking' %}">Classement</a>
</span>
{% if perms.preferences.change_generalpreferences %}
@ -31,7 +36,7 @@
{% endif %}
{% if request.user.is_staff %}
<span class="tabulation2">
<i class="fa fa-chart-bar"></i> <a href="{% url 'gestion:stats' %}">Stats</a>
<i class="fa fa-chart-bar"></i> <a href="{% url 'stats' %}">Stats</a>
</span>
<span class="tabulation2">
<i class="fa fa-business-time"></i> <a href="{% url 'gestion:gen_releve' %}">Relevé</a>

View file

@ -63,6 +63,10 @@
<td>Nombre de superusers</td>
<td>{{superusers.count}}</td>
</tr>
<tr>
<td>Nombre de citations</td>
<td>{{nb_quotes}}</td>
</tr>
<tr>
<td>Nombre 8</td>
<td>8</td>