Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make counter click client side first #959

Merged
merged 32 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
60f1866
Make counter click client side first
klmp200 Dec 20, 2024
a383f3e
Don't use codes as a primary key in counter click
klmp200 Dec 20, 2024
a548f47
Fix counter main
klmp200 Dec 20, 2024
8ebea00
Fix crash during validation
klmp200 Dec 20, 2024
f9d7dc7
Restore form when form submit fails due to error
klmp200 Dec 20, 2024
5f0b4d2
Properly display form errors in counter
klmp200 Dec 20, 2024
ffe6fc8
Redirect when cancelling instead of submitting a form
klmp200 Dec 20, 2024
ccf5767
Fix customerBalance not init and submit/cancel buttons visuals
klmp200 Dec 21, 2024
c37288c
Display nice product cards on counter click interface
klmp200 Dec 21, 2024
eea237b
Pre-filter allowed products in backend for counter click
klmp200 Dec 22, 2024
7071553
Optimize product id validation on counter click
klmp200 Dec 22, 2024
372470b
Improve empty basket and tray price management
klmp200 Dec 22, 2024
eed434a
Improve age management for getting products and make get_product a pa…
klmp200 Dec 22, 2024
b8430ad
Split counter-click-index.ts
klmp200 Dec 22, 2024
5079938
Fix get_operator on non bar counters and better display of counter wi…
klmp200 Dec 22, 2024
3464d5d
Add proper tests for refilling view
klmp200 Dec 22, 2024
38f491c
Properly test annotations in counter click
klmp200 Dec 22, 2024
f6693e1
Basic counter click tests
klmp200 Dec 22, 2024
b8d43a6
Increase selling label size and add more counter click tests
klmp200 Dec 22, 2024
472800e
Add nice snackbar message on counter interface and fix not enough mon…
klmp200 Dec 22, 2024
9c93c00
Add more counter click tests
klmp200 Dec 23, 2024
2e5e217
Disable eboutic in counter click/main
klmp200 Dec 23, 2024
022c19c
Fix counter permissions issues
klmp200 Dec 23, 2024
ccf5118
Add invalid form tests
klmp200 Dec 23, 2024
7f6fd7d
Fix wrong tests/permissions
klmp200 Dec 23, 2024
6f003ff
Add translations
klmp200 Dec 23, 2024
139221d
Apply review comments
klmp200 Dec 23, 2024
c80fe09
Remove useless form elements in counters and improve alignment
klmp200 Dec 23, 2024
138e166
Add popup css class and display basket error messages with it on coun…
klmp200 Dec 23, 2024
280d273
Put error popup inside the basket
klmp200 Dec 25, 2024
43768f1
Refactor counter-click css
klmp200 Dec 26, 2024
43f47e2
Improve product card display on counter click
klmp200 Dec 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,14 +578,6 @@ def get_display_name(self) -> str:
return "%s (%s)" % (self.get_full_name(), self.nick_name)
return self.get_full_name()

def get_age(self):
"""Returns the age."""
today = timezone.now()
born = self.date_of_birth
return (
today.year - born.year - ((today.month, today.day) < (born.month, born.day))
)

def get_family(
self,
godfathers_depth: NonNegativeInt = 4,
Expand Down
43 changes: 8 additions & 35 deletions core/static/core/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ body {
a.btn {
display: inline-block;
}

.btn {
font-size: 15px;
font-weight: normal;
Expand Down Expand Up @@ -336,7 +337,8 @@ body {
margin-left: -125px;
box-sizing: border-box;
position: fixed;
z-index: 1;
z-index: 10;
/* to get on top of tomselect */
left: 50%;
top: 60px;
text-align: center;
Expand Down Expand Up @@ -431,12 +433,17 @@ body {
flex-wrap: wrap;

$col-gap: 1rem;
$row-gap: 0.5rem;

&.gap {
column-gap: var($col-gap);
row-gap: var($row-gap);
}

@for $i from 2 through 5 {
&.gap-#{$i}x {
column-gap: $i * $col-gap;
row-gap: $i * $row-gap;
}
}
}
Expand Down Expand Up @@ -1242,40 +1249,6 @@ u,
text-decoration: underline;
}

#bar-ui {
padding: 0.4em;
display: flex;
flex-wrap: wrap;
flex-direction: row-reverse;

#products {
flex-basis: 100%;
margin: 0.2em;
overflow: auto;
}

#click_form {
flex: auto;
margin: 0.2em;
}

#user_info {
flex: auto;
padding: 0.5em;
margin: 0.2em;
height: 100%;
background: $secondary-neutral-light-color;

img {
max-width: 70%;
}

input {
background: white;
}
}
}

/*-----------------------------USER PROFILE----------------------------*/

.user_mini_profile {
Expand Down
2 changes: 1 addition & 1 deletion core/templates/core/macros.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
{% endif %}
{% if user.date_of_birth %}
<div class="user_mini_profile_dob">
{{ user.date_of_birth|date("d/m/Y") }} ({{ user.get_age() }})
{{ user.date_of_birth|date("d/m/Y") }} ({{ user.age }})
</div>
{% endif %}
</div>
Expand Down
19 changes: 10 additions & 9 deletions counter/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def as_p(self):

def clean(self):
cleaned_data = super().clean()
cus = None
customer = None
if cleaned_data["code"] != "":
if len(cleaned_data["code"]) == StudentCard.UID_SIZE:
card = (
Expand All @@ -96,17 +96,18 @@ def clean(self):
.first()
)
if card is not None:
cus = card.customer
if cus is None:
cus = Customer.objects.filter(
customer = card.customer
if customer is None:
customer = Customer.objects.filter(
account_id__iexact=cleaned_data["code"]
).first()
elif cleaned_data["id"] is not None:
cus = Customer.objects.filter(user=cleaned_data["id"]).first()
if cus is None or not cus.can_buy:
elif cleaned_data["id"]:
customer = Customer.objects.filter(user=cleaned_data["id"]).first()

if customer is None or not customer.can_buy:
raise forms.ValidationError(_("User not found"))
cleaned_data["user_id"] = cus.user.id
cleaned_data["user"] = cus.user
cleaned_data["user_id"] = customer.user.id
cleaned_data["user"] = customer.user
return cleaned_data


Expand Down
17 changes: 17 additions & 0 deletions counter/migrations/0029_alter_selling_label.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.17 on 2024-12-22 22:59

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("counter", "0028_alter_producttype_comment_and_more"),
]

operations = [
migrations.AlterField(
model_name="selling",
name="label",
field=models.CharField(max_length=128, verbose_name="label"),
),
]
39 changes: 35 additions & 4 deletions counter/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from datetime import date, datetime, timedelta
from datetime import timezone as tz
from decimal import Decimal
from typing import Self, Tuple
from typing import Self

from dict2xml import dict2xml
from django.conf import settings
Expand Down Expand Up @@ -138,7 +138,7 @@ def can_buy(self) -> bool:
return (date.today() - subscription.subscription_end) < timedelta(days=90)

@classmethod
def get_or_create(cls, user: User) -> Tuple[Customer, bool]:
def get_or_create(cls, user: User) -> tuple[Customer, bool]:
"""Work in pretty much the same way as the usual get_or_create method,
but with the default field replaced by some under the hood.

Expand Down Expand Up @@ -327,6 +327,8 @@ def is_owned_by(self, user):
class Product(models.Model):
"""A product, with all its related information."""

QUANTITY_FOR_TRAY_PRICE = 6
klmp200 marked this conversation as resolved.
Show resolved Hide resolved

name = models.CharField(_("name"), max_length=64)
description = models.TextField(_("description"), default="")
product_type = models.ForeignKey(
Expand Down Expand Up @@ -525,7 +527,7 @@ def is_owned_by(self, user: User) -> bool:
if user.is_anonymous:
return False
mem = self.club.get_membership_for(user)
if mem and mem.role >= 7:
if mem and mem.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True
return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)

Expand Down Expand Up @@ -657,6 +659,34 @@ def customer_is_barman(self, customer: Customer | User) -> bool:
# but they share the same primary key
return self.type == "BAR" and any(b.pk == customer.pk for b in self.barmen_list)

def get_products_for(self, customer: Customer) -> list[Product]:
"""
Get all allowed products for the provided customer on this counter
Prices will be annotated
"""

products = self.products.select_related("product_type").prefetch_related(
"buying_groups"
)

# Only include age appropriate products
age = customer.user.age
if customer.user.is_banned_alcohol:
age = min(age, 17)
products = products.filter(limit_age__lte=age)

# Compute special price for customer if he is a barmen on that bar
if self.customer_is_barman(customer):
products = products.annotate(price=F("special_selling_price"))
else:
products = products.annotate(price=F("selling_price"))

return [
product
for product in products.all()
if product.can_be_sold_to(customer.user)
]


class RefillingQuerySet(models.QuerySet):
def annotate_total(self) -> Self:
Expand Down Expand Up @@ -761,7 +791,8 @@ def annotate_total(self) -> Self:
class Selling(models.Model):
"""Handle the sellings."""

label = models.CharField(_("label"), max_length=64)
# We make sure that sellings have a way begger label than any product name is allowed to
label = models.CharField(_("label"), max_length=128)
klmp200 marked this conversation as resolved.
Show resolved Hide resolved
product = models.ForeignKey(
Product,
related_name="sellings",
Expand Down
25 changes: 25 additions & 0 deletions counter/static/bundled/counter/basket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Product } from "#counter:counter/types";

export class BasketItem {
quantity: number;
product: Product;
quantityForTrayPrice: number;
errors: string[];

constructor(product: Product, quantity: number) {
this.quantity = quantity;
this.product = product;
this.errors = [];
}

getBonusQuantity(): number {
if (!this.product.hasTrayPrice) {
return 0;
}
return Math.floor(this.quantity / this.product.quantityForTrayPrice);
}

sum(): number {
return (this.quantity - this.getBonusQuantity()) * this.product.price;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
return ["FIN", "ANN"];
}

public getSelectedProduct(): [number, string] {
return parseProduct(this.widget.getValue() as string);
}

protected attachBehaviors(): void {
this.allowMultipleProducts();
}
Expand Down
Loading
Loading