Merge branch 'skia/counter_rework' into 'master'

counter: make click page dynamic to avoid repetitive loading

See merge request ae/Sith!278
This commit is contained in:
Skia 2021-09-27 19:21:28 +00:00
commit cb3307509d
15 changed files with 288 additions and 164 deletions

View File

@ -79,9 +79,11 @@ class ComTest(TestCase):
)
r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200)
self.assertTrue(
"""<div id="alert_box">\\n <div class="markdown"><h3>ALERTE!</h3>\\n<p><strong>Caaaataaaapuuuulte!!!!</strong></p>"""
in str(r.content)
self.assertContains(
r,
"""<div id="alert_box">
<div class="markdown"><h3>ALERTE!</h3>
<p><strong>Caaaataaaapuuuulte!!!!</strong></p>""",
)
def test_info_msg(self):
@ -95,9 +97,10 @@ class ComTest(TestCase):
)
r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200)
self.assertTrue(
"""<div id="info_box">\\n <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>"""
in str(r.content)
self.assertContains(
r,
"""<div id="info_box">
<div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>""",
)
def test_birthday_non_subscribed_user(self):

File diff suppressed because one or more lines are too long

View File

@ -1125,7 +1125,7 @@ u, .underline {
overflow: auto;
}
#bar_ui {
#click_form {
float: left;
min-width: 57%;
}

View File

@ -126,6 +126,7 @@
</header>
<div id="info_boxes">
{% block info_boxes %}
{% set sith = get_sith() %}
{% if sith.alert_msg %}
<div id="alert_box">
@ -137,6 +138,7 @@
{{ sith.info_msg|markdown }}
</div>
{% endif %}
{% endblock %}
</div>
{% else %}{# if not popup #}

View File

@ -4,6 +4,12 @@
{% trans obj=object %}Edit {{ obj }}{% endtrans %}
{% endblock %}
{% block info_boxes %}
{% endblock %}
{% block nav %}
{% endblock %}
{% block content %}
<h2>{% trans %}Make a cash register summary{% endtrans %}</h2>
<form action="" method="post" id="cash_summary_form">

View File

@ -1,27 +1,16 @@
{% extends "core/base.jinja" %}
{% from "core/macros.jinja" import user_mini_profile, user_subscription %}
{% macro add_product(id, content, class="") %}
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="{{ class }}">
{% csrf_token %}
<input type="hidden" name="action" value="add_product">
<button type="submit" name="product_id" value="{{ id }}"> {{ content|safe }} </button>
</form>
{% endmacro %}
{% macro del_product(id, content, class="") %}
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="{{ class }}">
{% csrf_token %}
<input type="hidden" name="action" value="del_product">
<button type="submit" name="product_id" value="{{ id }}"> {{ content }} </button>
</form>
{% endmacro %}
{% block title %}
{{ counter }}
{% endblock %}
{% block info_boxes %}
{% endblock %}
{% block nav %}
{% endblock %}
{% block content %}
<h4 id="click_interface">{{ counter }}</h4>
@ -52,56 +41,57 @@
{% endif %}
</div>
<div id="bar_ui">
<noscript>
<p class="important">Javascript is required for the counter UI.</p>
</noscript>
<div id="click_form">
<h5>{% trans %}Selling{% endtrans %}</h5>
<div>
<div class="important">
{% if request.session['too_young'] %}
<p><strong>{% trans %}Too young for that product{% endtrans %}</strong></p>
{% endif %}
{% if request.session['not_allowed'] %}
<p><strong>{% trans %}Not allowed for that product{% endtrans %}</strong></p>
{% endif %}
{% if request.session['no_age'] %}
<p><strong>{% trans %}No date of birth provided{% endtrans %}</strong></p>
{% endif %}
{% if request.session['not_enough'] %}
<p><strong>{% trans %}Not enough money{% endtrans %}</strong></p>
{% endif %}
<p v-for="error in errors"><strong>{{ error }}</strong></p>
</div>
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="code_form" @submit.prevent="handle_code">
{% csrf_token %}
<input type="hidden" name="action" value="code">
<input type="input" name="code" value="" class="focus" id="code_field"/>
<input type="submit" value="{% trans %}Go{% endtrans %}" />
</form>
<p>{% trans %}Basket: {% endtrans %}</p>
{% raw %}
<ul>
{% for id,infos in request.session['basket']|dictsort %}
{% set product = counter.products.filter(id=id).first() %}
{% set s = infos['qty'] * infos['price'] / 100 %}
<li>{{ del_product(id, '-', "inline") }} {{ infos['qty'] + infos['bonus_qty'] }} {{ add_product(id, '+', "inline") }}
{{ product.name }}: {{ "%0.2f"|format(s) }}
{% if infos['bonus_qty'] %}
P
{% endif %}
<li v-for="p_info,p_id in basket">
<form method="post" action="" class="inline del_product_form" @submit.prevent="handle_action">
<input type="hidden" name="csrfmiddlewaretoken" v-bind:value="js_csrf_token">
<input type="hidden" name="action" value="del_product">
<input type="hidden" name="product_id" v-bind:value="p_id">
<button type="submit"> - </button>
</form>
{{ p_info["qty"] + p_info["bonus_qty"] }}
<form method="post" action="" class="inline add_product_form" @submit.prevent="handle_action">
<input type="hidden" name="csrfmiddlewaretoken" v-bind:value="js_csrf_token">
<input type="hidden" name="action" value="add_product">
<input type="hidden" name="product_id" v-bind:value="p_id">
<button type="submit"> + </button>
</form>
{{ products[p_id].name }}: {{ (p_info["qty"]*p_info["price"]/100).toLocaleString(undefined, { minimumFractionDigits: 2 }) }} € <span v-if="p_info['bonus_qty'] > 0">P</span>
</li>
{% endfor %}
</ul>
<p><strong>{% trans %}Total: {% endtrans %}{{ "%0.2f"|format(basket_total) }} €</strong></p>
<p>
<strong>Total: {{ sum_basket().toLocaleString(undefined, { minimumFractionDigits: 2 }) }} €</strong>
</p>
{% endraw %}
<div class="important">
{% if request.session['too_young'] %}
<p><strong>{% trans %}Too young for that product{% endtrans %}</strong></p>
{% endif %}
{% if request.session['not_allowed'] %}
<p><strong>{% trans %}Not allowed for that product{% endtrans %}</strong></p>
{% endif %}
{% if request.session['no_age'] %}
<p><strong>{% trans %}No date of birth provided{% endtrans %}</strong></p>
{% endif %}
{% if request.session['not_enough'] %}
<p><strong>{% trans %}Not enough money{% endtrans %}</strong></p>
{% endif %}
<p v-for="error in errors"><strong>{{ error }}</strong></p>
</div>
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
{% csrf_token %}
<input type="hidden" name="action" value="finish">
@ -141,23 +131,89 @@
{% else %}
{% set file = static('core/img/na.gif') %}
{% endif %}
{% set prod = '<strong>%s</strong><hr><img src="%s" /><span>%s €<br>%s</span>' % (p.name, file, p.selling_price, p.code) %}
{{ add_product(p.id, prod, "form_button") }}
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="form_button add_product_form" @submit.prevent="handle_action">
{% csrf_token %}
<input type="hidden" name="action" value="add_product">
<input type="hidden" name="product_id" value="{{ p.id }}">
<button type="submit"><strong>{{ p.name }}</strong><hr><img src="{{ file }}" /><span>{{ p.selling_price }} €<br>{{ p.code }}</span></button>
</form>
{%- endfor %}
</div>
{%- endfor %}
</div>
</div>
{% endblock %}
{% block script %}
<script>
document.getElementById("click_interface").scrollIntoView();
</script>
{{ super() }}
<script src="{{ static('core/js/vue.global.prod.js') }}"></script>
<script>
$( function() {
var products = [
/* Vue.JS dynamic form */
const click_form_vue = Vue.createApp({
data() {
return {
js_csrf_token: "{{ csrf_token }}",
products: {
{% for p in products -%}
{{ p.id }}: {
code: "{{ p.code }}",
name: "{{ p.name }}",
selling_price: "{{ p.selling_price }}",
special_selling_price: "{{ p.special_selling_price }}",
},
{%- endfor %}
},
basket: {{ request.session["basket"]|tojson }},
errors: [],
}
},
methods: {
sum_basket() {
var vm = this;
var total = 0;
for(idx in vm.basket) {
var item = vm.basket[idx];
console.log(item);
total += item["qty"] * item["price"];
}
return total / 100;
},
handle_code(event) {
var vm = this;
var code = $(event.target).find("#code_field").val().toUpperCase();
console.log("Code:");
console.log(code);
if(code == "{% trans %}END{% endtrans %}" || code == "{% trans %}CAN{% endtrans %}") {
$(event.target).submit();
} else {
vm.handle_action(event);
}
},
handle_action(event) {
var vm = this;
var payload = $(event.target).serialize();
$.ajax({
type: 'post',
dataType: 'json',
data: payload,
success: function(response) {
vm.basket = response.basket;
vm.errors = [];
},
error: function(error) {
vm.basket = error.responseJSON.basket;
vm.errors = error.responseJSON.errors;
}
});
$('form.code_form #code_field').val("").focus();
}
}
}).mount('#bar_ui');
/* Autocompletion in the code field */
var products_autocomplete = [
{% for p in products -%}
{
value: "{{ p.code }}",
@ -166,6 +222,7 @@ $( function() {
},
{%- endfor %}
];
var quantity = "";
var search = "";
var pattern = /^(\d+x)?(.*)/i;
@ -183,21 +240,22 @@ $( function() {
quantity = res[1] || "";
search = res[2];
var matcher = new RegExp( $.ui.autocomplete.escapeRegex( search ), "i" );
response($.grep( products, function( value ) {
response($.grep( products_autocomplete, function( value ) {
value = value.tags;
return matcher.test( value );
}));
},
});
});
$( function() {
$("#bar_ui").accordion({
/* Accordion UI between basket and refills */
$("#click_form").accordion({
heightStyle: "content",
activate: function(event, ui){
$(".focus").focus();
}
});
$("#products").tabs();
$("#code_field").focus();
});
</script>

View File

@ -12,6 +12,12 @@
{% trans counter_name=counter %}{{ counter_name }} counter{% endtrans %}
{% endblock %}
{% block info_boxes %}
{% endblock %}
{% block nav %}
{% endblock %}
{% block content %}
<h3>{% trans counter_name=counter %}{{ counter_name }} counter{% endtrans %}</h3>

View File

@ -5,6 +5,12 @@
{% trans counter_name=counter %}{{ counter_name }} last operations{% endtrans %}
{% endblock %}
{% block info_boxes %}
{% endblock %}
{% block nav %}
{% endblock %}
{% block content %}
<h3>{% trans counter_name=counter %}{{ counter_name }} last operations{% endtrans %}</h3>
<h4>{% trans %}Refillings{% endtrans %}</h4>

View File

@ -68,18 +68,29 @@ class CounterTest(TestCase):
location,
{
"action": "refill",
"amount": "10",
"amount": "5",
"payment_method": "CASH",
"bank": "OTHER",
},
)
response = self.client.post(location, {"action": "code", "code": "BARB"})
response = self.client.post(
location, {"action": "add_product", "product_id": "4"}
)
response = self.client.post(
location, {"action": "del_product", "product_id": "4"}
)
response = self.client.post(location, {"action": "code", "code": "2xdeco"})
response = self.client.post(location, {"action": "code", "code": "1xbarb"})
response = self.client.post(location, {"action": "code", "code": "fin"})
response_get = self.client.get(response.get("location"))
response_content = response_get.content.decode("utf-8")
self.assertTrue("<li>2 x Barbar" in str(response_content))
self.assertTrue("<li>2 x Déconsigne Eco-cup" in str(response_content))
self.assertTrue(
"<p>Client : Richard Batsbak - Nouveau montant : 8.30"
in str(response_get.content)
"<p>Client : Richard Batsbak - Nouveau montant : 3.60"
in str(response_content)
)

View File

@ -38,7 +38,7 @@ from django.views.generic.edit import (
from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple
from django.urls import reverse_lazy, reverse
from django.http import HttpResponseRedirect, HttpResponse
from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
from django.utils import timezone
from django import forms
from django.utils.translation import ugettext_lazy as _
@ -48,6 +48,7 @@ from django.db import DataError, transaction, models
import re
import pytz
from datetime import date, timedelta, datetime
from http import HTTPStatus
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
from ajax_select import make_ajax_field
@ -357,6 +358,34 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
pk_url_kwarg = "counter_id"
current_tab = "counter"
def render_to_response(self, *args, **kwargs):
if self.request.is_ajax(): # JSON response for AJAX requests
response = {"errors": []}
status = HTTPStatus.OK
if self.request.session["too_young"]:
response["errors"].append(_("Too young for that product"))
status = HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS
if self.request.session["not_allowed"]:
response["errors"].append(_("Not allowed for that product"))
status = HTTPStatus.FORBIDDEN
if self.request.session["no_age"]:
response["errors"].append(_("No date of birth provided"))
status = HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS
if self.request.session["not_enough"]:
response["errors"].append(_("Not enough money"))
status = HTTPStatus.PAYMENT_REQUIRED
if len(response["errors"]) > 1:
status = HTTPStatus.BAD_REQUEST
response["basket"] = self.request.session["basket"]
return JsonResponse(response, status=status)
else: # Standard HTML page
return super().render_to_response(*args, **kwargs)
def dispatch(self, request, *args, **kwargs):
self.customer = get_object_or_404(Customer, user__id=self.kwargs["user_id"])
obj = self.get_object()
@ -370,7 +399,9 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
)
or len(obj.get_barmen_list()) < 1
):
raise PermissionDenied
return HttpResponseRedirect(
reverse_lazy("counter:details", kwargs={"counter_id": obj.id})
)
else:
if not request.user.is_authenticated:
raise PermissionDenied