forked from CGM_Public/pretix_original
wip
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
# License for the specific language governing permissions and limitations under the License.
|
# License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
@@ -41,6 +42,7 @@ import warnings
|
|||||||
from collections import Counter, OrderedDict, defaultdict
|
from collections import Counter, OrderedDict, defaultdict
|
||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
from decimal import Decimal, DecimalException
|
from decimal import Decimal, DecimalException
|
||||||
|
from itertools import groupby
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
@@ -142,14 +144,22 @@ class ItemCategory(LoggedModel):
|
|||||||
verbose_name_plural = _("Product categories")
|
verbose_name_plural = _("Product categories")
|
||||||
ordering = ('position', 'id')
|
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:
|
if self.cross_selling_mode is None:
|
||||||
return []
|
return []
|
||||||
if self.cross_selling_condition == 'always':
|
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':
|
if self.cross_selling_condition == 'products':
|
||||||
match = set(match.pk for match in self.cross_selling_match_products.only('pk')) # TODO prefetch this
|
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':
|
if self.cross_selling_condition == 'discounts':
|
||||||
# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarrrrgggghhhhhh
|
# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarrrrgggghhhhhh
|
||||||
|
|
||||||
@@ -157,7 +167,7 @@ class ItemCategory(LoggedModel):
|
|||||||
|
|
||||||
potential_discounts_dict = defaultdict(list)
|
potential_discounts_dict = defaultdict(list)
|
||||||
discount_results = apply_discounts(
|
discount_results = apply_discounts(
|
||||||
event,
|
self.event,
|
||||||
sales_channel,
|
sales_channel,
|
||||||
[
|
[
|
||||||
(cp.item_id, cp.subevent_id, cp.line_price_gross, bool(cp.addon_to), cp.is_bundled,
|
(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
|
collect_potential_discounts=potential_discounts_dict
|
||||||
)
|
)
|
||||||
print("potential_discounts_dict", potential_discounts_dict)
|
print("potential_discounts_dict", potential_discounts_dict)
|
||||||
potential_discounts = {info for lst in potential_discounts_dict.values() for info in lst}
|
potential_discount_infos = dict.fromkeys(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()}
|
# sum up the max_counts and pass them on (also pass on the discount_rules so we can calculate actual discounted prices later):
|
||||||
return self.items.filter(pk__in=potential_discount_items)
|
# 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):
|
def __str__(self):
|
||||||
name = self.internal_name or self.name
|
name = self.internal_name or self.name
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ def apply_discounts(event: Event, sales_channel: str,
|
|||||||
}, collect_potential_discounts)
|
}, collect_potential_discounts)
|
||||||
for k in result.keys():
|
for k in result.keys():
|
||||||
result[k] = (result[k], discount)
|
result[k] = (result[k], discount)
|
||||||
|
if collect_potential_discounts is not None:
|
||||||
|
print(" ->",collect_potential_discounts)
|
||||||
new_prices.update(result)
|
new_prices.update(result)
|
||||||
|
|
||||||
return [new_prices.get(idx, (p[2], None)) for idx, p in enumerate(positions)]
|
return [new_prices.get(idx, (p[2], None)) for idx, p in enumerate(positions)]
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ from django.utils.translation import (
|
|||||||
from django.views.generic.base import TemplateResponseMixin
|
from django.views.generic.base import TemplateResponseMixin
|
||||||
from django_scopes import scopes_disabled
|
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 import Customer, Membership, Order
|
||||||
from pretix.base.models.items import Question
|
from pretix.base.models.items import Question
|
||||||
from pretix.base.models.orders import (
|
from pretix.base.models.orders import (
|
||||||
@@ -500,7 +501,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
return [
|
return [
|
||||||
(c, products) for (c, products) in
|
(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)
|
for c in self.request.event.categories.filter(cross_selling_mode__isnull=False)
|
||||||
)
|
)
|
||||||
if len(products) > 0
|
if len(products) > 0
|
||||||
@@ -639,13 +640,13 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
for (category, items) in self.cross_selling_applicable_categories
|
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(
|
items, _btn = get_grouped_items(
|
||||||
self.request.event,
|
self.request.event,
|
||||||
subevent=subevent,
|
subevent=subevent,
|
||||||
voucher=None,
|
voucher=None,
|
||||||
channel=self.request.sales_channel.identifier,
|
channel=self.request.sales_channel.identifier,
|
||||||
base_qs=items,
|
base_qs=cross_sell_item_info.keys(),
|
||||||
allow_addons=True,
|
allow_addons=True,
|
||||||
allow_cross_sell=True,
|
allow_cross_sell=True,
|
||||||
memberships=(
|
memberships=(
|
||||||
@@ -656,8 +657,24 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
if self.request.customer else None
|
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
|
return items
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|||||||
Reference in New Issue
Block a user