CartManager: Fix TransactionManagementError

Bug occured when extending a product and deleting it at the same time
This commit is contained in:
Raphael Michel
2023-08-22 13:42:56 +02:00
parent e8ea6e0f5c
commit 10a83935d9
2 changed files with 39 additions and 8 deletions

View File

@@ -1078,6 +1078,7 @@ class CartManager:
quotas_ok = _get_quota_availability(self._quota_diff, self.now_dt) quotas_ok = _get_quota_availability(self._quota_diff, self.now_dt)
err = None err = None
new_cart_positions = [] new_cart_positions = []
deleted_positions = set()
err = err or self._check_min_max_per_product() err = err or self._check_min_max_per_product()
@@ -1089,7 +1090,10 @@ class CartManager:
if op.position.expires > self.now_dt: if op.position.expires > self.now_dt:
for q in op.position.quotas: for q in op.position.quotas:
quotas_ok[q] += 1 quotas_ok[q] += 1
op.position.addons.all().delete() addons = op.position.addons.all()
deleted_positions |= {a.pk for a in addons}
addons.delete()
deleted_positions.add(op.position.pk)
op.position.delete() op.position.delete()
elif isinstance(op, (self.AddOperation, self.ExtendOperation)): elif isinstance(op, (self.AddOperation, self.ExtendOperation)):
@@ -1239,20 +1243,28 @@ class CartManager:
if op.seat and not op.seat.is_available(ignore_cart=op.position, sales_channel=self._sales_channel, if op.seat and not op.seat.is_available(ignore_cart=op.position, sales_channel=self._sales_channel,
ignore_voucher_id=op.position.voucher_id): ignore_voucher_id=op.position.voucher_id):
err = err or error_messages['seat_unavailable'] err = err or error_messages['seat_unavailable']
op.position.addons.all().delete()
addons = op.position.addons.all()
deleted_positions |= {a.pk for a in addons}
deleted_positions.add(op.position.pk)
addons.delete()
op.position.delete() op.position.delete()
elif available_count == 1: elif available_count == 1:
op.position.expires = self._expiry op.position.expires = self._expiry
op.position.listed_price = op.listed_price op.position.listed_price = op.listed_price
op.position.price_after_voucher = op.price_after_voucher op.position.price_after_voucher = op.price_after_voucher
# op.position.price will be updated by recompute_final_prices_and_taxes() # op.position.price will be updated by recompute_final_prices_and_taxes()
try: if op.position.pk not in deleted_positions:
op.position.save(force_update=True, update_fields=['expires', 'listed_price', 'price_after_voucher']) try:
except DatabaseError: op.position.save(force_update=True, update_fields=['expires', 'listed_price', 'price_after_voucher'])
# Best effort... The position might have been deleted in the meantime! except DatabaseError:
pass # Best effort... The position might have been deleted in the meantime!
pass
elif available_count == 0: elif available_count == 0:
op.position.addons.all().delete() addons = op.position.addons.all()
deleted_positions |= {a.pk for a in addons}
deleted_positions.add(op.position.pk)
addons.delete()
op.position.delete() op.position.delete()
else: else:
raise AssertionError("ExtendOperation cannot affect more than one item") raise AssertionError("ExtendOperation cannot affect more than one item")

View File

@@ -2414,6 +2414,25 @@ class CartAddonTest(CartTestMixin, TestCase):
assert cp2.item == self.workshop1 assert cp2.item == self.workshop1
assert cp2.price == 0 assert cp2.price == 0
@classscope(attr='orga')
def test_extend_included_addon_no_longer_available(self):
self.addon1.price_included = True
self.addon1.save()
self.quota_tickets.size = 0
self.quota_tickets.save()
cp1 = CartPosition.objects.create(
expires=now() - timedelta(minutes=10), item=self.ticket, price=Decimal('23.00'),
event=self.event, cart_id=self.session_key
)
CartPosition.objects.create(
expires=now() - timedelta(minutes=10), item=self.workshop1, price=Decimal('0.00'),
event=self.event, cart_id=self.session_key, addon_to=cp1
)
self.cm.extend_expired_positions()
with self.assertRaises(CartError):
self.cm.commit()
assert CartPosition.objects.count() == 0
@classscope(attr='orga') @classscope(attr='orga')
def test_cart_addon_remove_parent(self): def test_cart_addon_remove_parent(self):
self.addon1.price_included = True self.addon1.price_included = True