Merge pull request #923 from ae-utbm/counter-click-step-2

Casser counter click step 2 : separate refilling from counter clicks with fragments
This commit is contained in:
Bartuccio Antoine 2024-12-17 10:58:34 +01:00 committed by GitHub
commit 6416de237f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 552 additions and 366 deletions

View File

@ -24,6 +24,12 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
PAYMENT_METHOD = [
("CHECK", _("Check")),
("CASH", _("Cash")),
("CARD", _("Credit card")),
]
class CounterConfig(AppConfig): class CounterConfig(AppConfig):
name = "counter" name = "counter"

View File

@ -111,6 +111,8 @@ class GetUserForm(forms.Form):
class RefillForm(forms.ModelForm): class RefillForm(forms.ModelForm):
allowed_refilling_methods = ["CASH", "CARD"]
error_css_class = "error" error_css_class = "error"
required_css_class = "required" required_css_class = "required"
amount = forms.FloatField( amount = forms.FloatField(
@ -120,6 +122,21 @@ class RefillForm(forms.ModelForm):
class Meta: class Meta:
model = Refilling model = Refilling
fields = ["amount", "payment_method", "bank"] fields = ["amount", "payment_method", "bank"]
widgets = {"payment_method": forms.RadioSelect}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["payment_method"].choices = (
method
for method in self.fields["payment_method"].choices
if method[0] in self.allowed_refilling_methods
)
if self.fields["payment_method"].initial not in self.allowed_refilling_methods:
self.fields["payment_method"].initial = self.allowed_refilling_methods[0]
if "CHECK" not in self.allowed_refilling_methods:
del self.fields["bank"]
class CounterEditForm(forms.ModelForm): class CounterEditForm(forms.ModelForm):

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2.17 on 2024-12-15 22:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("counter", "0026_alter_studentcard_customer"),
]
operations = [
migrations.AlterField(
model_name="refilling",
name="payment_method",
field=models.CharField(
choices=[("CHECK", "Check"), ("CASH", "Cash"), ("CARD", "Credit card")],
default="CARD",
max_length=255,
verbose_name="payment method",
),
),
]

View File

@ -42,7 +42,8 @@ from club.models import Club
from core.fields import ResizedImageField from core.fields import ResizedImageField
from core.models import Group, Notification, User from core.models import Group, Notification, User
from core.utils import get_start_of_semester from core.utils import get_start_of_semester
from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB from counter.apps import PAYMENT_METHOD
from sith.settings import SITH_MAIN_CLUB
from subscription.models import Subscription from subscription.models import Subscription
@ -558,9 +559,6 @@ class Counter(models.Model):
"""Show if the counter authorize the refilling with physic money.""" """Show if the counter authorize the refilling with physic money."""
if self.type != "BAR": if self.type != "BAR":
return False return False
if self.id in SITH_COUNTER_OFFICES:
# If the counter is either 'AE' or 'BdF', refills are authorized
return True
# at least one of the barmen is in the AE board # at least one of the barmen is in the AE board
ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"]) ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"])
return any(ae.get_membership_for(barman) for barman in self.barmen_list) return any(ae.get_membership_for(barman) for barman in self.barmen_list)
@ -650,6 +648,14 @@ class Counter(models.Model):
) )
)["total"] )["total"]
def customer_is_barman(self, customer: Customer | User) -> bool:
"""Check if this counter is a `bar` and if the customer is currently logged in.
This is useful to compute special prices."""
# Customer and User are two different tables,
# but they share the same primary key
return self.type == "BAR" and any(b.pk == customer.pk for b in self.barmen_list)
class RefillingQuerySet(models.QuerySet): class RefillingQuerySet(models.QuerySet):
def annotate_total(self) -> Self: def annotate_total(self) -> Self:
@ -688,8 +694,8 @@ class Refilling(models.Model):
payment_method = models.CharField( payment_method = models.CharField(
_("payment method"), _("payment method"),
max_length=255, max_length=255,
choices=settings.SITH_COUNTER_PAYMENT_METHOD, choices=PAYMENT_METHOD,
default="CASH", default="CARD",
) )
bank = models.CharField( bank = models.CharField(
_("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER" _("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER"

View File

@ -0,0 +1,129 @@
import { exportToHtml } from "#core:utils/globals";
interface CounterConfig {
csrfToken: string;
clickApiUrl: string;
sessionBasket: Record<number, BasketItem>;
customerBalance: number;
customerId: number;
}
interface BasketItem {
// biome-ignore lint/style/useNamingConvention: talking with python
bonus_qty: number;
price: number;
qty: number;
}
exportToHtml("loadCounter", (config: CounterConfig) => {
document.addEventListener("alpine:init", () => {
Alpine.data("counter", () => ({
basket: config.sessionBasket,
errors: [],
customerBalance: config.customerBalance,
sumBasket() {
if (!this.basket || Object.keys(this.basket).length === 0) {
return 0;
}
const total = Object.values(this.basket).reduce(
(acc: number, cur: BasketItem) => acc + cur.qty * cur.price,
0,
) as number;
return total / 100;
},
onRefillingSuccess(event: CustomEvent) {
if (event.type !== "htmx:after-request" || event.detail.failed) {
return;
}
this.customerBalance += Number.parseFloat(
(event.detail.target.querySelector("#id_amount") as HTMLInputElement).value,
);
document.getElementById("selling-accordion").click();
},
async handleCode(event: SubmitEvent) {
const code = (
$(event.target).find("#code_field").val() as string
).toUpperCase();
if (["FIN", "ANN"].includes(code)) {
$(event.target).submit();
} else {
await this.handleAction(event);
}
},
async handleAction(event: SubmitEvent) {
const payload = $(event.target).serialize();
const request = new Request(config.clickApiUrl, {
method: "POST",
body: payload,
headers: {
// biome-ignore lint/style/useNamingConvention: this goes into http headers
Accept: "application/json",
"X-CSRFToken": config.csrfToken,
},
});
const response = await fetch(request);
const json = await response.json();
this.basket = json.basket;
this.errors = json.errors;
$("form.code_form #code_field").val("").focus();
},
}));
});
});
interface Product {
value: string;
label: string;
tags: string;
}
declare global {
const productsAutocomplete: Product[];
}
$(() => {
/* Autocompletion in the code field */
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
const codeField: any = $("#code_field");
let quantity = "";
codeField.autocomplete({
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
select: (event: any, ui: any) => {
event.preventDefault();
codeField.val(quantity + ui.item.value);
},
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
focus: (event: any, ui: any) => {
event.preventDefault();
codeField.val(quantity + ui.item.value);
},
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
source: (request: any, response: any) => {
// biome-ignore lint/performance/useTopLevelRegex: performance impact is minimal
const res = /^(\d+x)?(.*)/i.exec(request.term);
quantity = res[1] || "";
const search = res[2];
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
const matcher = new RegExp(($ as any).ui.autocomplete.escapeRegex(search), "i");
response(
$.grep(productsAutocomplete, (value: Product) => {
return matcher.test(value.tags);
}),
);
},
});
/* Accordion UI between basket and refills */
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
($("#click_form") as any).accordion({
heightStyle: "content",
activate: () => $(".focus").focus(),
});
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
($("#products") as any).tabs();
codeField.focus();
});

View File

@ -1,86 +0,0 @@
document.addEventListener("alpine:init", () => {
Alpine.data("counter", () => ({
// biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja
basket: sessionBasket,
errors: [],
sumBasket() {
if (!this.basket || Object.keys(this.basket).length === 0) {
return 0;
}
const total = Object.values(this.basket).reduce(
(acc, cur) => acc + cur.qty * cur.price,
0,
);
return total / 100;
},
async handleCode(event) {
const code = $(event.target).find("#code_field").val().toUpperCase();
if (["FIN", "ANN"].includes(code)) {
$(event.target).submit();
} else {
await this.handleAction(event);
}
},
async handleAction(event) {
const payload = $(event.target).serialize();
// biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja
const request = new Request(clickApiUrl, {
method: "POST",
body: payload,
headers: {
// biome-ignore lint/style/useNamingConvention: this goes into http headers
Accept: "application/json",
// biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja
"X-CSRFToken": csrfToken,
},
});
const response = await fetch(request);
const json = await response.json();
this.basket = json.basket;
this.errors = json.errors;
$("form.code_form #code_field").val("").focus();
},
}));
});
$(() => {
/* Autocompletion in the code field */
const codeField = $("#code_field");
let quantity = "";
codeField.autocomplete({
select: (event, ui) => {
event.preventDefault();
codeField.val(quantity + ui.item.value);
},
focus: (event, ui) => {
event.preventDefault();
codeField.val(quantity + ui.item.value);
},
source: (request, response) => {
// biome-ignore lint/performance/useTopLevelRegex: performance impact is minimal
const res = /^(\d+x)?(.*)/i.exec(request.term);
quantity = res[1] || "";
const search = res[2];
const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i");
response(
// biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja
$.grep(productsAutocomplete, (value) => {
return matcher.test(value.tags);
}),
);
},
});
/* Accordion UI between basket and refills */
$("#click_form").accordion({
heightStyle: "content",
activate: () => $(".focus").focus(),
});
$("#products").tabs();
codeField.focus();
});

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block additional_js %} {% block additional_js %}
<script src="{{ static('counter/js/counter_click.js') }}" defer></script> <script type="module" src="{{ static('bundled/counter/counter-click-index.ts') }}"></script>
{% endblock %} {% endblock %}
{% block info_boxes %} {% block info_boxes %}
@ -28,16 +28,11 @@
<h5>{% trans %}Customer{% endtrans %}</h5> <h5>{% trans %}Customer{% endtrans %}</h5>
{{ user_mini_profile(customer.user) }} {{ user_mini_profile(customer.user) }}
{{ user_subscription(customer.user) }} {{ user_subscription(customer.user) }}
<p>{% trans %}Amount: {% endtrans %}{{ customer.amount }} €</p> <p>{% trans %}Amount: {% endtrans %}<span x-text="customerBalance"></span> €</p>
{% if counter.type == 'BAR' %}
<h5>{% trans %}Student card{% endtrans %}</h3>
{{ student_card_fragment }}
{% endif %}
</div> </div>
<div id="click_form"> <div id="click_form" style="width: 20%;">
<h5>{% trans %}Selling{% endtrans %}</h5> <h5 id="selling-accordion">{% trans %}Selling{% endtrans %}</h5>
<div> <div>
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %} {% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %}
@ -105,17 +100,32 @@
<input type="submit" value="{% trans %}Cancel{% endtrans %}"/> <input type="submit" value="{% trans %}Cancel{% endtrans %}"/>
</form> </form>
</div> </div>
{% if (counter.type == 'BAR' and barmens_can_refill) %} {% if object.type == "BAR" %}
<h5>{% trans %}Refilling{% endtrans %}</h5> <h5>{% trans %}Refilling{% endtrans %}</h5>
<div> {% if refilling_fragment %}
<form method="post" <div
action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}"> @htmx:after-request="onRefillingSuccess"
{% csrf_token %} >
{{ refill_form.as_p() }} {{ refilling_fragment }}
<input type="hidden" name="action" value="refill"> </div>
<input type="submit" value="{% trans %}Go{% endtrans %}"/> {% else %}
</form> <div>
</div> <p class="alert alert-yellow">
{% trans trimmed %}
As a barman, you are not able to refill any account on your own.
An admin should be connected on this counter for that.
The customer can refill by using the eboutic.
{% endtrans %}
</p>
</div>
{% endif %}
{% if student_card_fragment %}
<h5>{% trans %}Student card{% endtrans %}</h3>
<div>
{{ student_card_fragment }}
</div>
{% endif %}
{% endif %} {% endif %}
</div> </div>
@ -155,9 +165,6 @@
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script> <script>
const csrfToken = "{{ csrf_token }}";
const clickApiUrl = "{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}";
const sessionBasket = {{ request.session["basket"]|tojson }};
const products = { const products = {
{%- for p in products -%} {%- for p in products -%}
{{ p.id }}: { {{ p.id }}: {
@ -176,5 +183,14 @@
}, },
{%- endfor %} {%- endfor %}
]; ];
window.addEventListener("DOMContentLoaded", () => {
loadCounter({
csrfToken: "{{ csrf_token }}",
clickApiUrl: "{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}",
sessionBasket: {{ request.session["basket"]|tojson }},
customerBalance: {{ customer.amount }},
customerId: {{ customer.pk }},
});
});
</script> </script>
{% endblock script %} {% endblock script %}

View File

@ -0,0 +1,9 @@
<form
hx-trigger="submit"
hx-post="{{ action }}"
hx-swap="outerHTML"
>
{% csrf_token %}
{{ form.as_p() }}
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>

View File

@ -67,17 +67,22 @@ class TestCounter(TestCase):
{"code": self.richard.customer.account_id, "counter_token": counter_token}, {"code": self.richard.customer.account_id, "counter_token": counter_token},
) )
counter_url = response.get("location") counter_url = response.get("location")
response = self.client.get(response.get("location")) refill_url = reverse(
"counter:refilling_create",
kwargs={"customer_id": self.richard.customer.pk},
)
response = self.client.get(counter_url)
assert ">Richard Batsbak</" in str(response.content) assert ">Richard Batsbak</" in str(response.content)
self.client.post( self.client.post(
counter_url, refill_url,
{ {
"action": "refill",
"amount": "5", "amount": "5",
"payment_method": "CASH", "payment_method": "CASH",
"bank": "OTHER", "bank": "OTHER",
}, },
HTTP_REFERER=counter_url,
) )
self.client.post(counter_url, "action=code&code=BARB", content_type="text/xml") self.client.post(counter_url, "action=code&code=BARB", content_type="text/xml")
self.client.post( self.client.post(
@ -110,15 +115,15 @@ class TestCounter(TestCase):
) )
response = self.client.post( response = self.client.post(
counter_url, refill_url,
{ {
"action": "refill",
"amount": "5", "amount": "5",
"payment_method": "CASH", "payment_method": "CASH",
"bank": "OTHER", "bank": "OTHER",
}, },
HTTP_REFERER=counter_url,
) )
assert response.status_code == 200 assert response.status_code == 302
self.client.post( self.client.post(
reverse("counter:login", kwargs={"counter_id": self.foyer.id}), reverse("counter:login", kwargs={"counter_id": self.foyer.id}),
@ -138,17 +143,23 @@ class TestCounter(TestCase):
{"code": self.richard.customer.account_id, "counter_token": counter_token}, {"code": self.richard.customer.account_id, "counter_token": counter_token},
) )
counter_url = response.get("location") counter_url = response.get("location")
refill_url = reverse(
"counter:refilling_create",
kwargs={
"customer_id": self.richard.customer.pk,
},
)
response = self.client.post( response = self.client.post(
counter_url, refill_url,
{ {
"action": "refill",
"amount": "5", "amount": "5",
"payment_method": "CASH", "payment_method": "CASH",
"bank": "OTHER", "bank": "OTHER",
}, },
HTTP_REFERER=counter_url,
) )
assert response.status_code == 200 assert response.status_code == 403 # Krophil is not board admin
def test_annotate_has_barman_queryset(self): def test_annotate_has_barman_queryset(self):
"""Test if the custom queryset method `annotate_has_barman` works as intended.""" """Test if the custom queryset method `annotate_has_barman` works as intended."""

View File

@ -39,7 +39,7 @@ from counter.views.cash import (
CashSummaryListView, CashSummaryListView,
CounterCashSummaryView, CounterCashSummaryView,
) )
from counter.views.click import CounterClick from counter.views.click import CounterClick, RefillingCreateView
from counter.views.eticket import ( from counter.views.eticket import (
EticketCreateView, EticketCreateView,
EticketEditView, EticketEditView,
@ -60,6 +60,11 @@ from counter.views.student_card import (
urlpatterns = [ urlpatterns = [
path("<int:counter_id>/", CounterMain.as_view(), name="details"), path("<int:counter_id>/", CounterMain.as_view(), name="details"),
path("<int:counter_id>/click/<int:user_id>/", CounterClick.as_view(), name="click"), path("<int:counter_id>/click/<int:user_id>/", CounterClick.as_view(), name="click"),
path(
"refill/<int:customer_id>/",
RefillingCreateView.as_view(),
name="refilling_create",
),
path( path(
"<int:counter_id>/last_ops/", "<int:counter_id>/last_ops/",
CounterLastOperationsView.as_view(), CounterLastOperationsView.as_view(),

View File

@ -24,11 +24,13 @@ from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView from django.views.generic import DetailView, FormView
from core.utils import FormFragmentTemplateData
from core.views import CanViewMixin from core.views import CanViewMixin
from counter.forms import RefillForm from counter.forms import RefillForm
from counter.models import Counter, Customer, Product, Selling from counter.models import Counter, Customer, Product, Selling
from counter.utils import is_logged_in_counter
from counter.views.mixins import CounterTabsMixin from counter.views.mixins import CounterTabsMixin
from counter.views.student_card import StudentCardFormView from counter.views.student_card import StudentCardFormView
@ -100,7 +102,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
request.session["too_young"] = False request.session["too_young"] = False
request.session["not_allowed"] = False request.session["not_allowed"] = False
request.session["no_age"] = False request.session["no_age"] = False
self.refill_form = None
ret = super().get(request, *args, **kwargs) ret = super().get(request, *args, **kwargs)
if (self.object.type != "BAR" and not request.user.is_authenticated) or ( if (self.object.type != "BAR" and not request.user.is_authenticated) or (
self.object.type == "BAR" and len(self.object.barmen_list) == 0 self.object.type == "BAR" and len(self.object.barmen_list) == 0
@ -111,7 +112,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""Handle the many possibilities of the post request.""" """Handle the many possibilities of the post request."""
self.object = self.get_object() self.object = self.get_object()
self.refill_form = None
if (self.object.type != "BAR" and not request.user.is_authenticated) or ( if (self.object.type != "BAR" and not request.user.is_authenticated) or (
self.object.type == "BAR" and len(self.object.barmen_list) < 1 self.object.type == "BAR" and len(self.object.barmen_list) < 1
): # Check that at least one barman is logged in ): # Check that at least one barman is logged in
@ -137,7 +137,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
request.session["no_age"] = False request.session["no_age"] = False
if self.object.type != "BAR": if self.object.type != "BAR":
self.operator = request.user self.operator = request.user
elif self.customer_is_barman(): elif self.object.customer_is_barman(self.customer):
self.operator = self.customer.user self.operator = self.customer.user
else: else:
self.operator = self.object.get_random_barman() self.operator = self.object.get_random_barman()
@ -148,8 +148,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
self.add_product(request) self.add_product(request)
elif action == "del_product": elif action == "del_product":
self.del_product(request) self.del_product(request)
elif action == "refill":
self.refill(request)
elif action == "code": elif action == "code":
return self.parse_code(request) return self.parse_code(request)
elif action == "cancel": elif action == "cancel":
@ -159,16 +157,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
context = self.get_context_data(object=self.object) context = self.get_context_data(object=self.object)
return self.render_to_response(context) return self.render_to_response(context)
def customer_is_barman(self) -> bool:
barmen = self.object.barmen_list
return self.object.type == "BAR" and self.customer.user in barmen
def get_product(self, pid): def get_product(self, pid):
return Product.objects.filter(pk=int(pid)).first() return Product.objects.filter(pk=int(pid)).first()
def get_price(self, pid): def get_price(self, pid):
p = self.get_product(pid) p = self.get_product(pid)
if self.customer_is_barman(): if self.object.customer_is_barman(self.customer):
price = p.special_selling_price price = p.special_selling_price
else: else:
price = p.selling_price price = p.selling_price
@ -333,7 +327,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
for pid, infos in request.session["basket"].items(): for pid, infos in request.session["basket"].items():
# This duplicates code for DB optimization (prevent to load many times the same object) # This duplicates code for DB optimization (prevent to load many times the same object)
p = Product.objects.filter(pk=pid).first() p = Product.objects.filter(pk=pid).first()
if self.customer_is_barman(): if self.object.customer_is_barman(self.customer):
uprice = p.special_selling_price uprice = p.special_selling_price
else: else:
uprice = p.selling_price uprice = p.selling_price
@ -383,24 +377,11 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
reverse_lazy("counter:details", args=self.args, kwargs=kwargs) reverse_lazy("counter:details", args=self.args, kwargs=kwargs)
) )
def refill(self, request):
"""Refill the customer's account."""
if not self.object.can_refill():
raise PermissionDenied
form = RefillForm(request.POST)
if form.is_valid():
form.instance.counter = self.object
form.instance.operator = self.operator
form.instance.customer = self.customer
form.instance.save()
else:
self.refill_form = form
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add customer to the context.""" """Add customer to the context."""
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
products = self.object.products.select_related("product_type") products = self.object.products.select_related("product_type")
if self.customer_is_barman(): if self.object.customer_is_barman(self.customer):
products = products.annotate(price=F("special_selling_price")) products = products.annotate(price=F("special_selling_price"))
else: else:
products = products.annotate(price=F("selling_price")) products = products.annotate(price=F("selling_price"))
@ -413,9 +394,75 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
) )
kwargs["customer"] = self.customer kwargs["customer"] = self.customer
kwargs["basket_total"] = self.sum_basket(self.request) kwargs["basket_total"] = self.sum_basket(self.request)
kwargs["refill_form"] = self.refill_form or RefillForm()
kwargs["barmens_can_refill"] = self.object.can_refill() if self.object.type == "BAR":
kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( kwargs["student_card_fragment"] = StudentCardFormView.get_template_data(
self.customer self.customer
).render(self.request) ).render(self.request)
if self.object.can_refill():
kwargs["refilling_fragment"] = RefillingCreateView.get_template_data(
self.customer
).render(self.request)
return kwargs return kwargs
class RefillingCreateView(FormView):
"""This is a fragment only view which integrates with counter_click.jinja"""
form_class = RefillForm
template_name = "counter/fragments/create_refill.jinja"
@classmethod
def get_template_data(
cls, customer: Customer, *, form_instance: form_class | None = None
) -> FormFragmentTemplateData[form_class]:
return FormFragmentTemplateData(
form=form_instance if form_instance else cls.form_class(),
template=cls.template_name,
context={
"action": reverse_lazy(
"counter:refilling_create", kwargs={"customer_id": customer.pk}
),
},
)
def dispatch(self, request, *args, **kwargs):
self.customer: Customer = get_object_or_404(Customer, pk=kwargs["customer_id"])
if not self.customer.can_buy:
raise Http404
if not is_logged_in_counter(request):
raise PermissionDenied
self.counter: Counter = get_object_or_404(
Counter, token=request.session["counter_token"]
)
if not self.counter.can_refill():
raise PermissionDenied
if self.counter.customer_is_barman(self.customer):
self.operator = self.customer.user
else:
self.operator = self.counter.get_random_barman()
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
res = super().form_valid(form)
form.clean()
form.instance.counter = self.counter
form.instance.operator = self.operator
form.instance.customer = self.customer
form.instance.save()
return res
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
data = self.get_template_data(self.customer, form_instance=context["form"])
context.update(data.context)
return context
def get_success_url(self, **kwargs):
return self.request.path

View File

@ -70,11 +70,11 @@ class StudentCardFormView(FormView):
@classmethod @classmethod
def get_template_data( def get_template_data(
cls, customer: Customer cls, customer: Customer, *, form_instance: form_class | None = None
) -> FormFragmentTemplateData[StudentCardForm]: ) -> FormFragmentTemplateData[form_class]:
"""Get necessary data to pre-render the fragment""" """Get necessary data to pre-render the fragment"""
return FormFragmentTemplateData( return FormFragmentTemplateData(
form=cls.form_class(), form=form_instance if form_instance else cls.form_class(),
template=cls.template_name, template=cls.template_name,
context={ context={
"action": reverse( "action": reverse(
@ -105,7 +105,7 @@ class StudentCardFormView(FormView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
data = self.get_template_data(self.customer) data = self.get_template_data(self.customer, form_instance=context["form"])
context.update(data.context) context.update(data.context)
return context return context

File diff suppressed because it is too large Load Diff

View File

@ -432,14 +432,6 @@ SITH_SUBSCRIPTION_LOCATIONS = [
SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")]
SITH_COUNTER_OFFICES = {2: "PdF", 1: "AE"}
SITH_COUNTER_PAYMENT_METHOD = [
("CHECK", _("Check")),
("CASH", _("Cash")),
("CARD", _("Credit card")),
]
SITH_COUNTER_BANK = [ SITH_COUNTER_BANK = [
("OTHER", "Autre"), ("OTHER", "Autre"),
("SOCIETE-GENERALE", "Société générale"), ("SOCIETE-GENERALE", "Société générale"),

View File

@ -21,6 +21,7 @@ from django.utils.timezone import localdate
from django.views.generic import CreateView, DetailView, TemplateView from django.views.generic import CreateView, DetailView, TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from counter.apps import PAYMENT_METHOD
from subscription.forms import ( from subscription.forms import (
SelectionDateForm, SelectionDateForm,
SubscriptionExistingUserForm, SubscriptionExistingUserForm,
@ -108,6 +109,6 @@ class SubscriptionsStatsView(FormView):
subscription_end__gte=self.end_date, subscription_start__lte=self.start_date subscription_end__gte=self.end_date, subscription_start__lte=self.start_date
) )
kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS
kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD kwargs["payment_types"] = PAYMENT_METHOD
kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS
return kwargs return kwargs