Compare commits

...

4 commits

Author SHA1 Message Date
Ludovic Stephan 60831045b5 Tests + timezone fix 2020-06-10 22:26:13 +02:00
Ludovic Stephan 55f22f2250 Change party start to 21h 2020-06-10 22:26:13 +02:00
Ludovic Stephan 39a2d309b5 Fetch and display if drunk 2020-06-10 22:26:13 +02:00
Ludovic Stephan e533966f55 Add abv and volume to Article model 2020-06-10 22:26:13 +02:00
9 changed files with 193 additions and 2 deletions

View file

@ -288,6 +288,8 @@ class ArticleForm(forms.ModelForm):
"hidden",
"price",
"stock",
"abv",
"volume",
"category",
"box_type",
"box_capacity",
@ -301,6 +303,8 @@ class ArticleRestrictForm(ArticleForm):
"is_sold",
"hidden",
"price",
"abv",
"volume",
"category",
"box_type",
"box_capacity",

View file

@ -0,0 +1,33 @@
# Generated by Django 2.2.12 on 2020-05-15 15:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("kfet", "0071_promo_2020"),
]
operations = [
migrations.AddField(
model_name="article",
name="abv",
field=models.DecimalField(
decimal_places=1,
default=0,
max_digits=6,
verbose_name="Degré d'alcool (en %)",
),
),
migrations.AddField(
model_name="article",
name="volume",
field=models.DecimalField(
decimal_places=1,
default=0,
max_digits=6,
verbose_name="Volume d'une unité (en cL)",
),
),
]

View file

@ -520,6 +520,13 @@ class Article(models.Model):
"capacité du contenant", blank=True, null=True, default=None
)
abv = models.DecimalField(
_("Degré d'alcool (en %)"), max_digits=6, default=0, decimal_places=1
)
volume = models.DecimalField(
_("Volume d'une unité (en cL)"), max_digits=6, default=0, decimal_places=1
)
def __str__(self):
return "%s - %s" % (self.category.name, self.name)

View file

@ -112,6 +112,14 @@ input[type=number]::-webkit-outer-spin-button {
right:0;
}
#account #alcohol-warning {
padding: 5px;
position: absolute;
top:0;
right:0;
font-size:35px;
}
@media (min-width: 600px) {
#account_form input { font-size:60px; }

View file

@ -11,6 +11,7 @@ var Account = Backbone.Model.extend({
'departement': '',
'nickname': '',
'trigramme': '',
'n_units': 0,
},
url: function () {
@ -45,6 +46,7 @@ var AccountView = Backbone.View.extend({
el: '#account',
input: '#id_trigramme',
buttons: '.buttons',
alcohol_warning: '#alcohol-warning',
props: _.keys(Account.prototype.defaults),
@ -98,6 +100,15 @@ var AccountView = Backbone.View.extend({
return buttons
},
get_alcohol_warning: function () {
n_units = parseFloat(this.model.get("n_units")).toFixed(2);
if (n_units < 4)
return "";
return `<i class="fa fa-beer" title="${n_units} unités"></i>`
},
render: function () {
for (let prop of this.props) {
var selector = "#account-" + prop;
@ -106,6 +117,7 @@ var AccountView = Backbone.View.extend({
this.$el.attr("data-balance", this.attr_data_balance());
this.$(this.buttons).html(this.get_buttons());
this.$(this.alcohol_warning).html(this.get_alcohol_warning());
},
reset: function () {
@ -124,6 +136,10 @@ var LIQView = AccountView.extend({
return "";
},
get_alcohol_warning: function () {
return ""
},
attr_data_balance: function () {
return 'ok';
}

View file

@ -17,6 +17,7 @@
<link rel="stylesheet" type="text/css" href="{% static 'vendor/jquery/jquery-ui.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'kfet/vendor/jquery/jquery-confirm.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/index.css' %}">
<link type="text/css" rel="stylesheet" href="{% static 'gestioncof/vendor/font-awesome/css/font-awesome.min.css' %}">
{# JS #}
<script type="text/javascript" src="{% static 'kfet/vendor/js.cookie.js' %}"></script>

View file

@ -86,6 +86,7 @@
<span id="account-promo"></span>
</div>
<div id="account-email" class="data_line"></div>
<div id="alcohol-warning"></div>
<div class="buttons">
</div>
</div>

View file

@ -1,5 +1,5 @@
import json
from datetime import datetime, timedelta
from datetime import timedelta
from decimal import Decimal
from unittest import mock
@ -1365,6 +1365,8 @@ class ArticleCreateViewTests(ViewTestCaseMixin, TestCase):
"category": self.category.pk,
"stock": 5,
"price": "2.5",
"volume": "33",
"abv": "6.3",
}
def setUp(self):
@ -1451,6 +1453,8 @@ class ArticleUpdateViewTests(ViewTestCaseMixin, TestCase):
"price": "3.5",
"box_type": "carton",
# 'hidden': not checked
"volume": "33",
"abv": "6.3",
}
def setUp(self):
@ -4220,6 +4224,74 @@ class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase):
def url_expected(self):
return "/k-fet/accounts/{}/.json".format(self.accounts["user"].trigramme)
def setUp(self):
super().setUp()
# A Checkout, curently usable, balance=100
self.checkout = Checkout.objects.create(
created_by=self.accounts["team"],
name="Checkout",
valid_from=self.now - timedelta(days=7),
valid_to=self.now + timedelta(days=7),
balance=Decimal("100.00"),
)
# An Article, price=2.5, stock=20
self.article = Article.objects.create(
category=ArticleCategory.objects.create(name="Category"),
name="Article",
price=Decimal("2.5"),
stock=20,
volume=Decimal("33"),
abv=Decimal("7.3"),
)
now = timezone.localtime(self.now)
ope_party1 = OperationGroup.objects.create(
on_acc=self.accounts["user"],
checkout=self.checkout,
at=now.replace(hour=1),
)
ope_party2 = OperationGroup.objects.create(
on_acc=self.accounts["user"],
checkout=self.checkout,
at=now.replace(day=now.day - 1, hour=22),
)
ope_noparty = OperationGroup.objects.create(
on_acc=self.accounts["user"],
checkout=self.checkout,
at=now.replace(day=now.day - 1, hour=19),
)
Operation.objects.create(
group=ope_party1,
type=Operation.PURCHASE,
article=self.article,
article_nb=2,
)
Operation.objects.create(
group=ope_party2,
type=Operation.PURCHASE,
article=self.article,
article_nb=1,
)
Operation.objects.create(
group=ope_party2,
type=Operation.PURCHASE,
article=self.article,
article_nb=1,
)
Operation.objects.create(
group=ope_noparty,
type=Operation.PURCHASE,
article=self.article,
article_nb=3,
)
def test_ok(self):
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
@ -4243,10 +4315,33 @@ class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase):
"nickname",
"promo",
"trigramme",
"n_units",
]
),
)
@mock.patch("django.utils.timezone.now")
def test_without_party(self, mock_now):
now = timezone.localtime(self.now)
mock_now.return_value = now.replace(hour=20).astimezone(timezone.utc)
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
content = json.loads(r.content.decode("utf-8"))
self.assertEqual(content["n_units"], 0)
@mock.patch("django.utils.timezone.now")
def test_with_party(self, mock_now):
now = timezone.localtime(self.now)
mock_now.return_value = now.replace(hour=5).astimezone(timezone.utc)
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
content = json.loads(r.content.decode("utf-8"))
self.assertAlmostEqual(float(content["n_units"]), 3 * 33 * 7.3 * 0.8 / 100)
class SettingsListViewTests(ViewTestCaseMixin, TestCase):
url_name = "kfet.settings"

View file

@ -2,7 +2,7 @@ import ast
import heapq
import statistics
from collections import defaultdict
from datetime import timedelta
from datetime import datetime, time, timedelta
from decimal import Decimal
from typing import List
from urllib.parse import urlencode
@ -916,6 +916,31 @@ def account_read_json(request, trigramme):
account = get_object_or_404(Account, trigramme=trigramme)
if not account.readable:
raise Http404
# Calcul des unités d'alcool consommées
# 1UA = 10g d'alcool (éthanol) pur
alcohol_density = 0.8
now = timezone.localtime(timezone.now())
# une soirée va de XXh à 06h
if time(21) <= now.time() or now.time() <= time(6):
begin_time = now.replace(hour=21, minute=0, second=0, microsecond=0)
# si on est après minuit, il faut retrancher un jour
if now.time() <= time(6):
begin_time -= timedelta(days=1)
qs = Operation.objects.filter(
group__on_acc=account,
type=Operation.PURCHASE,
group__at__gte=begin_time.astimezone(timezone.utc),
).annotate(
units=F("article__volume") * F("article__abv") * alcohol_density / 100
)
# Sum retourne None sur un queryset vide :
# https://docs.djangoproject.com/en/3.0/ref/models/querysets/#aggregation-functions
n_units = qs.aggregate(agg=Sum("units"))["agg"] or 0
else:
n_units = 0
data = {
"id": account.pk,
"name": account.name,
@ -927,6 +952,7 @@ def account_read_json(request, trigramme):
"departement": account.departement,
"nickname": account.nickname,
"trigramme": account.trigramme,
"n_units": n_units,
}
return JsonResponse(data)