This commit is contained in:
Mira Weller
2024-06-10 15:11:57 +02:00
parent f56d67ec9c
commit a18adb8a88
3 changed files with 70 additions and 13 deletions

View File

@@ -34,6 +34,7 @@
# License for the specific language governing permissions and limitations under the License.
import calendar
import functools
import os
import sys
import uuid
@@ -41,6 +42,7 @@ import warnings
from collections import Counter, OrderedDict, defaultdict
from datetime import date, datetime, time, timedelta
from decimal import Decimal, DecimalException
from itertools import groupby
from typing import Optional, Tuple
from zoneinfo import ZoneInfo
@@ -142,14 +144,22 @@ class ItemCategory(LoggedModel):
verbose_name_plural = _("Product categories")
ordering = ('position', 'id')
def cross_sell_visible(self, cart, event, sales_channel):
def cross_sell_visible(self, cart, sales_channel):
"""
If this category should be visible in the cross-selling step for a given cart and sales_channel, this method
returns a dict describing the items that should be displayed.
:returns: dict {item: (max_count, discount_rule)}
max_count is None if the item should not be limited
discount_rule is None if the item will not be discounted
"""
if self.cross_selling_mode is None:
return []
if self.cross_selling_condition == 'always':
return self.items.all()
return {item: (None, None) for item in self.items.all()}
if self.cross_selling_condition == 'products':
match = set(match.pk for match in self.cross_selling_match_products.only('pk')) # TODO prefetch this
return self.items.all() if any(pos.item.pk in match for pos in cart) else []
return {item: (None, None) for item in self.items.all()} if any(pos.item.pk in match for pos in cart) else []
if self.cross_selling_condition == 'discounts':
# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarrrrgggghhhhhh
@@ -157,7 +167,7 @@ class ItemCategory(LoggedModel):
potential_discounts_dict = defaultdict(list)
discount_results = apply_discounts(
event,
self.event,
sales_channel,
[
(cp.item_id, cp.subevent_id, cp.line_price_gross, bool(cp.addon_to), cp.is_bundled,
@@ -167,10 +177,38 @@ class ItemCategory(LoggedModel):
collect_potential_discounts=potential_discounts_dict
)
print("potential_discounts_dict", potential_discounts_dict)
potential_discounts = {info for lst in potential_discounts_dict.values() for info in lst}
# TODO sum up the max_counts and pass them on (also pass on the discount_rules so we can calculate actual discounted prices later)
potential_discount_items = {item.pk for (discount_rule, max_count, i) in potential_discounts for item in discount_rule.benefit_limit_products.all()}
return self.items.filter(pk__in=potential_discount_items)
potential_discount_infos = dict.fromkeys(info for lst in potential_discounts_dict.values() for info in lst)
# sum up the max_counts and pass them on (also pass on the discount_rules so we can calculate actual discounted prices later):
# group by benefit product
# - max_count for product: sum up max_counts
# - discount_rule for product: take first discount_rule
grouped_by_item = [
(item, list(infos_for_item)) for item, infos_for_item in
groupby(
sorted(
(
(item, discount_rule, max_count, i)
for (discount_rule, max_count, i) in potential_discount_infos.keys()
for item in discount_rule.benefit_limit_products.all()
),
key=lambda tup: tup[0].pk
),
lambda tup: tup[0])
]
def sum_or_none(iter):
return functools.reduce(lambda x,y: None if x is None or y is None else x + y, iter, 0)
my_item_pks = self.items.values_list('pk', flat=True)
print("grouped:",grouped_by_item)
potential_discount_items = {item: (sum_or_none(max_count for (item, discount_rule, max_count, i) in infos_for_item), next(discount_rule for (item, discount_rule, max_count, i) in infos_for_item))
for item, infos_for_item in grouped_by_item
if item.pk in my_item_pks}
#potential_discount_items = {item.pk for (discount_rule, max_count, i) in potential_discount_infos.keys() for item in discount_rule.benefit_limit_products.all()}
return potential_discount_items
def __str__(self):
name = self.internal_name or self.name

View File

@@ -186,6 +186,8 @@ def apply_discounts(event: Event, sales_channel: str,
}, collect_potential_discounts)
for k in result.keys():
result[k] = (result[k], discount)
if collect_potential_discounts is not None:
print(" ->",collect_potential_discounts)
new_prices.update(result)
return [new_prices.get(idx, (p[2], None)) for idx, p in enumerate(positions)]

View File

@@ -55,6 +55,7 @@ from django.utils.translation import (
from django.views.generic.base import TemplateResponseMixin
from django_scopes import scopes_disabled
from pretix.base.decimal import round_decimal
from pretix.base.models import Customer, Membership, Order
from pretix.base.models.items import Question
from pretix.base.models.orders import (
@@ -500,7 +501,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
return [
(c, products) for (c, products) in
(
(c, c.cross_sell_visible(cart, self.request.event, self.request.sales_channel.identifier))
(c, c.cross_sell_visible(cart, self.request.sales_channel.identifier))
for c in self.request.event.categories.filter(cross_selling_mode__isnull=False)
)
if len(products) > 0
@@ -639,13 +640,13 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
for (category, items) in self.cross_selling_applicable_categories
]
def _items_for_cross_selling(self, subevent, items):
def _items_for_cross_selling(self, subevent, cross_sell_item_info):
items, _btn = get_grouped_items(
self.request.event,
subevent=subevent,
voucher=None,
channel=self.request.sales_channel.identifier,
base_qs=items,
base_qs=cross_sell_item_info.keys(),
allow_addons=True,
allow_cross_sell=True,
memberships=(
@@ -656,8 +657,24 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
if self.request.customer else None
),
)
# TODO calculate discounted price
# TODO set item.order_max for benefit_only_apply_to_cheapest_n_matches discounted items
for item in items:
(max_count, discount_rule) = cross_sell_item_info[item]
# set item.order_max for benefit_only_apply_to_cheapest_n_matches discounted items
if max_count:
item.order_max = min(item.order_max, max_count)
# calculate discounted price
if discount_rule:
item.original_price = item.original_price or item.display_price
previous_price = item.display_price
new_price = round_decimal(
previous_price * (Decimal('100.00') - discount_rule.benefit_discount_matching_percent) / Decimal('100.00'),
self.event.currency,
)
item.display_price = new_price
return items
def get_context_data(self, **kwargs):