mirror of
https://github.com/pretix/pretix.git
synced 2026-05-12 16:24:00 +00:00
cleanup
# Conflicts: # src/pretix/base/services/orders.py
This commit is contained in:
@@ -1,30 +1,20 @@
|
|||||||
import dataclasses
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from functools import reduce
|
from typing import Callable, Dict, List, Literal, Set
|
||||||
from typing import Callable, Dict, List, Literal, Optional, Set, Union
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.dispatch import receiver
|
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_scopes import ScopedManager
|
|
||||||
|
|
||||||
from pretix.base.decimal import round_decimal
|
from pretix.base.decimal import round_decimal
|
||||||
from pretix.base.models import Checkin
|
from pretix.base.models import Event, Item, ItemVariation, Order, OrderPosition
|
||||||
from pretix.base.models import Event, Order, OrderPosition
|
|
||||||
from pretix.base.models import Item
|
|
||||||
from pretix.base.models import ItemVariation
|
|
||||||
from pretix.base.reldate import ModelRelativeDateTimeField
|
from pretix.base.reldate import ModelRelativeDateTimeField
|
||||||
from pretix.base.services.orders import CancellationCheck
|
|
||||||
from pretix.base.signals import self_service_cancellation_checks
|
|
||||||
from pretix.base.timemachine import time_machine_now
|
from pretix.base.timemachine import time_machine_now
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class OrderDiff:
|
class OrderDiff:
|
||||||
order: Order
|
order: Order
|
||||||
@@ -37,17 +27,22 @@ class OrderDiff:
|
|||||||
def cancel_all(order: Order) -> "OrderDiff":
|
def cancel_all(order: Order) -> "OrderDiff":
|
||||||
return OrderDiff(order=order, prev=set(order.positions.all()), next=set())
|
return OrderDiff(order=order, prev=set(order.positions.all()), next=set())
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CancellationCheckResult:
|
class CancellationCheckResult:
|
||||||
cancellation_possible: bool
|
cancellation_possible: bool
|
||||||
reason: str
|
reason: str
|
||||||
|
|
||||||
|
|
||||||
# Maps Check identifier → cancellation check result
|
# Maps Check identifier → cancellation check result
|
||||||
CancellationCheckResultsById = Dict[str, CancellationCheckResult]
|
CancellationCheckResultsById = Dict[str, CancellationCheckResult]
|
||||||
CheckFn=Callable[[Order, Set[OrderPosition], OrderPosition], CancellationCheckResultsById]
|
|
||||||
|
|
||||||
|
CheckFn = Callable[[Order, Set[OrderPosition], OrderPosition], CancellationCheckResultsById]
|
||||||
|
|
||||||
FeeType = Literal['position_fee', 'process_fee']
|
FeeType = Literal['position_fee', 'process_fee']
|
||||||
|
|
||||||
|
|
||||||
class Ruling:
|
class Ruling:
|
||||||
"""
|
"""
|
||||||
A Ruling is the result of applying a CancellationRule onto an Order or OrderPosition.
|
A Ruling is the result of applying a CancellationRule onto an Order or OrderPosition.
|
||||||
@@ -69,9 +64,7 @@ class Ruling:
|
|||||||
self.results = results
|
self.results = results
|
||||||
self.fee_type = fee_type
|
self.fee_type = fee_type
|
||||||
self.fee = fee
|
self.fee = fee
|
||||||
self.cancellation_possible = all(ruling.cancellation_possible
|
self.cancellation_possible = all(ruling.cancellation_possible for ruling in results.values())
|
||||||
for ruling in results.values()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_absolute_fee(
|
def from_absolute_fee(
|
||||||
@@ -103,7 +96,7 @@ class Ruling:
|
|||||||
) -> "Ruling":
|
) -> "Ruling":
|
||||||
"""
|
"""
|
||||||
Constructs a Ruling with an absolute fee.
|
Constructs a Ruling with an absolute fee.
|
||||||
:param rule_id: Id of the rule
|
:param rule_id: ID of the rule
|
||||||
:param results: CheckResult object
|
:param results: CheckResult object
|
||||||
:param fee_type: Must be a position_fee as the fee can only be in reference to a position
|
:param fee_type: Must be a position_fee as the fee can only be in reference to a position
|
||||||
:param reference_price: Price of the position to reference
|
:param reference_price: Price of the position to reference
|
||||||
@@ -114,7 +107,12 @@ class Ruling:
|
|||||||
if fee_type == "process_fee":
|
if fee_type == "process_fee":
|
||||||
raise ValidationError("Process fee cannot be used with relative fees")
|
raise ValidationError("Process fee cannot be used with relative fees")
|
||||||
|
|
||||||
return Ruling(rule_id=rule_id, results=results, fee_type=fee_type, fee=round_decimal(reference_price * (percentage/100), currency))
|
return Ruling(
|
||||||
|
rule_id=rule_id,
|
||||||
|
results=results,
|
||||||
|
fee_type=fee_type,
|
||||||
|
fee=round_decimal(reference_price * (percentage / 100), currency)
|
||||||
|
)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
if not isinstance(other, Ruling):
|
if not isinstance(other, Ruling):
|
||||||
@@ -130,7 +128,7 @@ class Ruling:
|
|||||||
|
|
||||||
|
|
||||||
def validate_status_chars(value):
|
def validate_status_chars(value):
|
||||||
invalid=set(value) - Order.ALLOWED_STATUS_CHARS
|
invalid = set(value) - Order.ALLOWED_STATUS_CHARS
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Invalid characters: {invalid}. Allowed: {Order.ALLOWED_STATUS_CHARS}"
|
f"Invalid characters: {invalid}. Allowed: {Order.ALLOWED_STATUS_CHARS}"
|
||||||
@@ -139,51 +137,48 @@ def validate_status_chars(value):
|
|||||||
raise ValidationError("Duplicate characters are not allowed.")
|
raise ValidationError("Duplicate characters are not allowed.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CancellationRule(models.Model):
|
class CancellationRule(models.Model):
|
||||||
event=models.ForeignKey(
|
event = models.ForeignKey(
|
||||||
Event,
|
Event,
|
||||||
verbose_name=_("Event"),
|
verbose_name=_("Event"),
|
||||||
related_name="orders",
|
related_name="orders",
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
all_products=models.BooleanField(
|
all_products = models.BooleanField(
|
||||||
verbose_name=_("All products and variations"),
|
verbose_name=_("All products and variations"),
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
limit_products=models.ManyToManyField(Item, verbose_name=_("Products"), blank=True)
|
limit_products = models.ManyToManyField(Item, verbose_name=_("Products"), blank=True)
|
||||||
limit_variations=models.ManyToManyField(
|
limit_variations = models.ManyToManyField(
|
||||||
ItemVariation, blank=True, verbose_name=_("Variations")
|
ItemVariation, blank=True, verbose_name=_("Variations")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
allowed_if_in_order_status = models.CharField(
|
||||||
allowed_if_in_order_status=models.CharField(
|
|
||||||
max_length=4,
|
max_length=4,
|
||||||
choices=Order.STATUS_CHOICE,
|
choices=Order.STATUS_CHOICE,
|
||||||
verbose_name=_("Cancellation possible if order is in status"),
|
verbose_name=_("Cancellation possible if order is in status"),
|
||||||
validators=[validate_status_chars],
|
validators=[validate_status_chars],
|
||||||
default="".join(Order.ALLOWED_STATUS_CHARS),
|
default="".join(Order.ALLOWED_STATUS_CHARS),
|
||||||
)
|
)
|
||||||
allowed_until=ModelRelativeDateTimeField(null=True, blank=True)
|
allowed_until = ModelRelativeDateTimeField(null=True, blank=True)
|
||||||
except_after=ModelRelativeDateTimeField(null=True, blank=True)
|
except_after = ModelRelativeDateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
fee_percentage_per_item=models.DecimalField(
|
fee_percentage_per_item = models.DecimalField(
|
||||||
max_digits=5,
|
max_digits=5,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
validators=[MinValueValidator("0.00"), MaxValueValidator("100.00")],
|
validators=[MinValueValidator("0.00"), MaxValueValidator("100.00")],
|
||||||
verbose_name=_("Fee Percentage per OrderPosition"),
|
verbose_name=_("Fee Percentage per OrderPosition"),
|
||||||
default=Decimal("0.00"),
|
default=Decimal("0.00"),
|
||||||
) # wird als sum() kombiniert
|
) # wird als sum() kombiniert
|
||||||
fee_absolute_per_item=models.DecimalField(
|
fee_absolute_per_item = models.DecimalField(
|
||||||
max_digits=13,
|
max_digits=13,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
verbose_name=_("Absolute fee per OrderPosition"),
|
verbose_name=_("Absolute fee per OrderPosition"),
|
||||||
default=Decimal("0.00"),
|
default=Decimal("0.00"),
|
||||||
) # wird als sum() kombiniert
|
) # wird als sum() kombiniert
|
||||||
|
|
||||||
|
fee_cancellation_process = models.DecimalField(
|
||||||
fee_cancellation_process=models.DecimalField(
|
|
||||||
max_digits=13,
|
max_digits=13,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
verbose_name=_("Absolute fee per Cancellation"),
|
verbose_name=_("Absolute fee per Cancellation"),
|
||||||
@@ -200,14 +195,14 @@ class CancellationRule(models.Model):
|
|||||||
if not self.allowed_until and not self.allowed_until:
|
if not self.allowed_until and not self.allowed_until:
|
||||||
return {check_id: CancellationCheckResult(
|
return {check_id: CancellationCheckResult(
|
||||||
cancellation_possible=True,
|
cancellation_possible=True,
|
||||||
reason=f"No time window specified",
|
reason="No time window specified",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
relevant_event=order_position.subevent or order_position.event
|
relevant_event = order_position.subevent or order_position.event
|
||||||
in_allowed_until=time_machine_now() < self.allowed_until.datetime(
|
in_allowed_until = time_machine_now() < self.allowed_until.datetime(
|
||||||
relevant_event) if self.allowed_until else False
|
relevant_event) if self.allowed_until else False
|
||||||
in_exemption=time_machine_now() > self.except_after.datetime(
|
in_exemption = time_machine_now() > self.except_after.datetime(
|
||||||
relevant_event) if self.except_after else False
|
relevant_event) if self.except_after else False
|
||||||
|
|
||||||
if in_allowed_until and not in_exemption:
|
if in_allowed_until and not in_exemption:
|
||||||
except_after_message = f" and not after {self.except_after.datetime(relevant_event)}" if self.except_after else ""
|
except_after_message = f" and not after {self.except_after.datetime(relevant_event)}" if self.except_after else ""
|
||||||
@@ -226,14 +221,13 @@ class CancellationRule(models.Model):
|
|||||||
reason=f"Cancellation after time window ending on {self.allowed_until.datetime(relevant_event)}",
|
reason=f"Cancellation after time window ending on {self.allowed_until.datetime(relevant_event)}",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
def _check_order_status(self, diff: OrderDiff, order_position: OrderPosition) -> CancellationCheckResultsById:
|
def _check_order_status(self, diff: OrderDiff, order_position: OrderPosition) -> CancellationCheckResultsById:
|
||||||
check_id = "ORDER_STATUS"
|
check_id = "ORDER_STATUS"
|
||||||
|
|
||||||
if diff.order.status == "".join(Order.ALLOWED_STATUS_CHARS):
|
if diff.order.status == "".join([]):
|
||||||
return {check_id: CancellationCheckResult(
|
return {check_id: CancellationCheckResult(
|
||||||
cancellation_possible=True,
|
cancellation_possible=True,
|
||||||
reason=f"Orders in every status can be cancelled",
|
reason="Orders in every status can be cancelled",
|
||||||
)}
|
)}
|
||||||
elif diff.order.status in self.allowed_if_in_order_status:
|
elif diff.order.status in self.allowed_if_in_order_status:
|
||||||
return {check_id: CancellationCheckResult(
|
return {check_id: CancellationCheckResult(
|
||||||
@@ -245,3 +239,12 @@ class CancellationRule(models.Model):
|
|||||||
cancellation_possible=False,
|
cancellation_possible=False,
|
||||||
reason=f"Order in status '{diff.order.status}' cannot be canceled",
|
reason=f"Order in status '{diff.order.status}' cannot be canceled",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
class CancellationCheck:
|
||||||
|
id: str
|
||||||
|
prefetches: List[Prefetch] = []
|
||||||
|
related_selects: List[str] = []
|
||||||
|
|
||||||
|
def check(self, order: Order, keep: Set[OrderPosition], order_position: OrderPosition) -> CancellationCheckResult:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ class Order(LockModel, LoggedModel):
|
|||||||
(STATUS_EXPIRED, _("expired")),
|
(STATUS_EXPIRED, _("expired")),
|
||||||
(STATUS_CANCELED, _("canceled")),
|
(STATUS_CANCELED, _("canceled")),
|
||||||
)
|
)
|
||||||
ALLOWED_STATUS_CHARS={char for char, _ in STATUS_CHOICE} # {'n', 'p', 'e', 'c'}
|
|
||||||
|
|
||||||
code = models.CharField(
|
code = models.CharField(
|
||||||
max_length=16,
|
max_length=16,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ from pretix.base.email import get_email_context
|
|||||||
from pretix.base.i18n import get_language_without_region, language
|
from pretix.base.i18n import get_language_without_region, language
|
||||||
from pretix.base.media import MEDIA_TYPES
|
from pretix.base.media import MEDIA_TYPES
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CartPosition, Device, Event, GiftCard, Item, ItemVariation, LogEntry,
|
CartPosition, Checkin, Device, Event, GiftCard, Item, ItemVariation, LogEntry,
|
||||||
Membership, Order, OrderPayment, OrderPosition, Quota, Seat,
|
Membership, Order, OrderPayment, OrderPosition, Quota, Seat,
|
||||||
SeatCategoryMapping, User, Voucher,
|
SeatCategoryMapping, User, Voucher,
|
||||||
)
|
)
|
||||||
@@ -1633,7 +1633,7 @@ class OrderChangeManager:
|
|||||||
MembershipOperation = namedtuple('MembershipOperation', ('position', 'membership'))
|
MembershipOperation = namedtuple('MembershipOperation', ('position', 'membership'))
|
||||||
CancelOperation = namedtuple('CancelOperation', ('position', 'price_diff'))
|
CancelOperation = namedtuple('CancelOperation', ('position', 'price_diff'))
|
||||||
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent', 'seat', 'membership',
|
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent', 'seat', 'membership',
|
||||||
'valid_from', 'valid_until', 'is_bundled', 'result', 'count'))
|
'valid_from', 'valid_until', 'is_bundled', 'result'))
|
||||||
SplitOperation = namedtuple('SplitOperation', ('position',))
|
SplitOperation = namedtuple('SplitOperation', ('position',))
|
||||||
FeeValueOperation = namedtuple('FeeValueOperation', ('fee', 'value', 'price_diff'))
|
FeeValueOperation = namedtuple('FeeValueOperation', ('fee', 'value', 'price_diff'))
|
||||||
AddFeeOperation = namedtuple('AddFeeOperation', ('fee', 'price_diff'))
|
AddFeeOperation = namedtuple('AddFeeOperation', ('fee', 'price_diff'))
|
||||||
@@ -1647,24 +1647,16 @@ class OrderChangeManager:
|
|||||||
ForceRecomputeOperation = namedtuple('ForceRecomputeOperation', tuple())
|
ForceRecomputeOperation = namedtuple('ForceRecomputeOperation', tuple())
|
||||||
|
|
||||||
class AddPositionResult:
|
class AddPositionResult:
|
||||||
_positions: Optional[List[OrderPosition]]
|
_position: Optional[OrderPosition]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._positions = None
|
self._position = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def position(self) -> OrderPosition:
|
def position(self) -> OrderPosition:
|
||||||
if self._positions is None:
|
if self._position is None:
|
||||||
raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.")
|
raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.")
|
||||||
if len(self._positions) != 1:
|
return self._position
|
||||||
raise RuntimeError("More than one position created.")
|
|
||||||
return self._positions[0]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def positions(self) -> List[OrderPosition]:
|
|
||||||
if self._positions is None:
|
|
||||||
raise RuntimeError("Order position has not been created yet. Call commit() first on OrderChangeManager.")
|
|
||||||
return self._positions
|
|
||||||
|
|
||||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False):
|
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True, allow_blocked_seats=False):
|
||||||
self.order = order
|
self.order = order
|
||||||
@@ -1871,12 +1863,8 @@ class OrderChangeManager:
|
|||||||
|
|
||||||
def add_position(self, item: Item, variation: ItemVariation, price: Decimal, addon_to: OrderPosition = None,
|
def add_position(self, item: Item, variation: ItemVariation, price: Decimal, addon_to: OrderPosition = None,
|
||||||
subevent: SubEvent = None, seat: Seat = None, membership: Membership = None,
|
subevent: SubEvent = None, seat: Seat = None, membership: Membership = None,
|
||||||
valid_from: datetime = None, valid_until: datetime = None, count: int = 1) -> 'OrderChangeManager.AddPositionResult':
|
valid_from: datetime = None, valid_until: datetime = None) -> 'OrderChangeManager.AddPositionResult':
|
||||||
if count < 1:
|
|
||||||
raise ValueError("Count must be positive")
|
|
||||||
if isinstance(seat, str):
|
if isinstance(seat, str):
|
||||||
if count > 1:
|
|
||||||
raise ValueError("Cannot combine count > 1 with seat")
|
|
||||||
if not seat:
|
if not seat:
|
||||||
seat = None
|
seat = None
|
||||||
else:
|
else:
|
||||||
@@ -1930,14 +1918,14 @@ class OrderChangeManager:
|
|||||||
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00'):
|
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00'):
|
||||||
self._invoice_dirty = True
|
self._invoice_dirty = True
|
||||||
|
|
||||||
self._totaldiff_guesstimate += price.gross * count
|
self._totaldiff_guesstimate += price.gross
|
||||||
self._quotadiff.update({q: count for q in new_quotas})
|
self._quotadiff.update(new_quotas)
|
||||||
if seat:
|
if seat:
|
||||||
self._seatdiff.update([seat])
|
self._seatdiff.update([seat])
|
||||||
|
|
||||||
result = self.AddPositionResult()
|
result = self.AddPositionResult()
|
||||||
self._operations.append(self.AddOperation(item, variation, price, addon_to, subevent, seat, membership,
|
self._operations.append(self.AddOperation(item, variation, price, addon_to, subevent, seat, membership,
|
||||||
valid_from, valid_until, is_bundled, result, count))
|
valid_from, valid_until, is_bundled, result))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def split(self, position: OrderPosition):
|
def split(self, position: OrderPosition):
|
||||||
@@ -2557,35 +2545,29 @@ class OrderChangeManager:
|
|||||||
secret_dirty.remove(position)
|
secret_dirty.remove(position)
|
||||||
position.save(update_fields=['canceled', 'secret'])
|
position.save(update_fields=['canceled', 'secret'])
|
||||||
elif isinstance(op, self.AddOperation):
|
elif isinstance(op, self.AddOperation):
|
||||||
new_pos = []
|
pos = OrderPosition.objects.create(
|
||||||
new_logs = []
|
item=op.item, variation=op.variation, addon_to=op.addon_to,
|
||||||
for i in range(op.count):
|
price=op.price.gross, order=self.order, tax_rate=op.price.rate, tax_code=op.price.code,
|
||||||
pos = OrderPosition.objects.create(
|
tax_value=op.price.tax, tax_rule=op.item.tax_rule,
|
||||||
item=op.item, variation=op.variation, addon_to=op.addon_to,
|
positionid=nextposid, subevent=op.subevent, seat=op.seat,
|
||||||
price=op.price.gross, order=self.order, tax_rate=op.price.rate, tax_code=op.price.code,
|
used_membership=op.membership, valid_from=op.valid_from, valid_until=op.valid_until,
|
||||||
tax_value=op.price.tax, tax_rule=op.item.tax_rule,
|
is_bundled=op.is_bundled,
|
||||||
positionid=nextposid, subevent=op.subevent, seat=op.seat,
|
)
|
||||||
used_membership=op.membership, valid_from=op.valid_from, valid_until=op.valid_until,
|
nextposid += 1
|
||||||
is_bundled=op.is_bundled,
|
self.order.log_action('pretix.event.order.changed.add', user=self.user, auth=self.auth, data={
|
||||||
)
|
'position': pos.pk,
|
||||||
nextposid += 1
|
'item': op.item.pk,
|
||||||
new_pos.append(pos)
|
'variation': op.variation.pk if op.variation else None,
|
||||||
new_logs.append(self.order.log_action('pretix.event.order.changed.add', user=self.user, auth=self.auth, data={
|
'addon_to': op.addon_to.pk if op.addon_to else None,
|
||||||
'position': pos.pk,
|
'price': op.price.gross,
|
||||||
'item': op.item.pk,
|
'positionid': pos.positionid,
|
||||||
'variation': op.variation.pk if op.variation else None,
|
'membership': pos.used_membership_id,
|
||||||
'addon_to': op.addon_to.pk if op.addon_to else None,
|
'subevent': op.subevent.pk if op.subevent else None,
|
||||||
'price': op.price.gross,
|
'seat': op.seat.pk if op.seat else None,
|
||||||
'positionid': pos.positionid,
|
'valid_from': op.valid_from.isoformat() if op.valid_from else None,
|
||||||
'membership': pos.used_membership_id,
|
'valid_until': op.valid_until.isoformat() if op.valid_until else None,
|
||||||
'subevent': op.subevent.pk if op.subevent else None,
|
})
|
||||||
'seat': op.seat.pk if op.seat else None,
|
op.result._position = pos
|
||||||
'valid_from': op.valid_from.isoformat() if op.valid_from else None,
|
|
||||||
'valid_until': op.valid_until.isoformat() if op.valid_until else None,
|
|
||||||
}, save=False))
|
|
||||||
|
|
||||||
op.result._positions = new_pos
|
|
||||||
LogEntry.bulk_create_and_postprocess(new_logs)
|
|
||||||
elif isinstance(op, self.SplitOperation):
|
elif isinstance(op, self.SplitOperation):
|
||||||
position = position_cache.setdefault(op.position.pk, op.position)
|
position = position_cache.setdefault(op.position.pk, op.position)
|
||||||
split_positions.append(position)
|
split_positions.append(position)
|
||||||
@@ -2910,7 +2892,7 @@ class OrderChangeManager:
|
|||||||
return total
|
return total
|
||||||
|
|
||||||
def _check_order_size(self):
|
def _check_order_size(self):
|
||||||
if (len(self.order.positions.all()) + sum([op.count for op in self._operations if isinstance(op, self.AddOperation)])) > settings.PRETIX_MAX_ORDER_SIZE:
|
if (len(self.order.positions.all()) + len([op for op in self._operations if isinstance(op, self.AddOperation)])) > settings.PRETIX_MAX_ORDER_SIZE:
|
||||||
raise OrderError(
|
raise OrderError(
|
||||||
self.error_messages['max_order_size'] % {
|
self.error_messages['max_order_size'] % {
|
||||||
'max': settings.PRETIX_MAX_ORDER_SIZE,
|
'max': settings.PRETIX_MAX_ORDER_SIZE,
|
||||||
@@ -2971,7 +2953,7 @@ class OrderChangeManager:
|
|||||||
]) + len([
|
]) + len([
|
||||||
o for o in self._operations if isinstance(o, self.SplitOperation)
|
o for o in self._operations if isinstance(o, self.SplitOperation)
|
||||||
])
|
])
|
||||||
adds = sum([o.count for o in self._operations if isinstance(o, self.AddOperation)])
|
adds = len([o for o in self._operations if isinstance(o, self.AddOperation)])
|
||||||
if current > 0 and current - cancels + adds < 1:
|
if current > 0 and current - cancels + adds < 1:
|
||||||
raise OrderError(self.error_messages['complete_cancel'])
|
raise OrderError(self.error_messages['complete_cancel'])
|
||||||
|
|
||||||
@@ -3018,18 +3000,17 @@ class OrderChangeManager:
|
|||||||
elif isinstance(op, self.CancelOperation) and op.position in positions_to_fake_cart:
|
elif isinstance(op, self.CancelOperation) and op.position in positions_to_fake_cart:
|
||||||
fake_cart.remove(positions_to_fake_cart[op.position])
|
fake_cart.remove(positions_to_fake_cart[op.position])
|
||||||
elif isinstance(op, self.AddOperation):
|
elif isinstance(op, self.AddOperation):
|
||||||
for i in range(op.count):
|
cp = CartPosition(
|
||||||
cp = CartPosition(
|
event=self.event,
|
||||||
event=self.event,
|
item=op.item,
|
||||||
item=op.item,
|
variation=op.variation,
|
||||||
variation=op.variation,
|
used_membership=op.membership,
|
||||||
used_membership=op.membership,
|
subevent=op.subevent,
|
||||||
subevent=op.subevent,
|
seat=op.seat,
|
||||||
seat=op.seat,
|
)
|
||||||
)
|
cp.override_valid_from = op.valid_from
|
||||||
cp.override_valid_from = op.valid_from
|
cp.override_valid_until = op.valid_until
|
||||||
cp.override_valid_until = op.valid_until
|
fake_cart.append(cp)
|
||||||
fake_cart.append(cp)
|
|
||||||
try:
|
try:
|
||||||
validate_memberships_in_order(self.order.customer, fake_cart, self.event, lock=True, ignored_order=self.order, testmode=self.order.testmode)
|
validate_memberships_in_order(self.order.customer, fake_cart, self.event, lock=True, ignored_order=self.order, testmode=self.order.testmode)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
|
|||||||
@@ -1212,8 +1212,5 @@ self_service_cancellation_checks = EventPluginSignal()
|
|||||||
This signal is sent out to collect checks to approve or deny a self service cancellation.
|
This signal is sent out to collect checks to approve or deny a self service cancellation.
|
||||||
You are expected to return a class instance that implements CancellationCheck.
|
You are expected to return a class instance that implements CancellationCheck.
|
||||||
It is is expected that that the CheckFn will not issue any further queries.
|
It is is expected that that the CheckFn will not issue any further queries.
|
||||||
|
|
||||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#
|
#
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||||
from django.db import connection, transaction
|
from django.db import connection, transaction
|
||||||
@@ -31,6 +32,7 @@ from django.utils.functional import lazy
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DummyRollbackException(Exception):
|
class DummyRollbackException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ from django_scopes import scope
|
|||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
|
||||||
from pretix.base.models import Event, Item, Order, OrderPosition, Organizer
|
from pretix.base.models import Event, Item, Order, OrderPosition, Organizer
|
||||||
from pretix.base.models.cancellation import CancellationRule, Ruling
|
from pretix.base.models.cancellation import CancellationRule, OrderDiff
|
||||||
from pretix.base.models.cancellation import CheckRes
|
|
||||||
from pretix.base.models.cancellation import OrderDiff
|
|
||||||
|
|
||||||
NOW = now()
|
NOW = now()
|
||||||
DAYS_UNTIL_EVENT=60
|
DAYS_UNTIL_EVENT=60
|
||||||
|
|||||||
Reference in New Issue
Block a user