Browse Source

Merge pull request #14 from nanoy42/release-3.6.3

Release 3.6.3
pull/17/head v3.6.3
Yoann Pietri 6 years ago
committed by GitHub
parent
commit
cb449ee414
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 5
      CHANGELOG.md
  3. 15
      coopeV3/management/commands/gencontributors.py
  4. 2
      coopeV3/urls.py
  5. 67
      coopeV3/views.py
  6. 4
      gestion/admin.py
  7. 53
      gestion/migrations/0012_auto_20190827_2119.py
  8. 10
      gestion/models.py
  9. 3
      gestion/templates/gestion/product_profile.html
  10. 6
      gestion/templates/gestion/products_list.html
  11. 41
      gestion/templates/gestion/stocks.html
  12. 3
      gestion/urls.py
  13. 92
      gestion/views.py
  14. 2
      preferences/views.py
  15. 16
      staticfiles/stocks.js
  16. 6
      templates/about.html
  17. 2
      templates/footer.html
  18. 9
      templates/nav.html
  19. 4
      templates/stats.html

1
.gitignore

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

5
CHANGELOG.md

@ -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

15
coopeV3/management/commands/gencontributors.py

@ -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)

2
coopeV3/urls.py

@ -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')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

67
coopeV3/views.py

@ -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})
@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,
})

4
gestion/admin.py

@ -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')

53
gestion/migrations/0012_auto_20190827_2119.py

@ -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',
),
]

10
gestion/models.py

@ -54,13 +54,9 @@ class Product(models.Model):
"""
The price of the product.
"""
stockHold = models.IntegerField(default=0, verbose_name="Stock en soute")
"""
Number of product in the hold.
"""
stockBar = models.IntegerField(default=0, verbose_name="Stock en bar")
stock = models.IntegerField(default=0, verbose_name="Stock")
"""
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) + "°)"

3
gestion/templates/gestion/product_profile.html

@ -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>

6
gestion/templates/gestion/products_list.html

@ -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>

41
gestion/templates/gestion/stocks.html

@ -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 %}

3
gestion/urls.py

@ -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"),
]

92
gestion/views.py

@ -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):

2
preferences/views.py

@ -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

@ -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);
});
}
});
});

6
templates/about.html

@ -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 %}

2
templates/footer.html

@ -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>

9
templates/nav.html

@ -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>

4
gestion/templates/gestion/stats.html → templates/stats.html

@ -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>
Loading…
Cancel
Save