mirror of
https://github.com/pretix/pretix.git
synced 2026-08-05 09:57:49 +00:00
Add tests
This commit is contained in:
@@ -1619,7 +1619,7 @@ class OrderChangeManager:
|
||||
self.split_order = None
|
||||
self.reissue_invoice = reissue_invoice
|
||||
self._committed = False
|
||||
self._totaldiff = 0
|
||||
self._totaldiff_guesstimate = 0
|
||||
self._quotadiff = Counter()
|
||||
self._seatdiff = Counter()
|
||||
self._operations = []
|
||||
@@ -1736,7 +1736,7 @@ class OrderChangeManager:
|
||||
if position.issued_gift_cards.exists():
|
||||
raise OrderError(self.error_messages['gift_card_change'])
|
||||
|
||||
self._totaldiff += price.gross - position.price
|
||||
self._totaldiff_guesstimate += price.gross - position.price
|
||||
|
||||
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00') or position.price != Decimal('0.00'):
|
||||
self._invoice_dirty = True
|
||||
@@ -1781,29 +1781,29 @@ class OrderChangeManager:
|
||||
else:
|
||||
new_tax = tax_rule.tax(pos.price, base_price_is='gross', currency=self.event.currency,
|
||||
override_tax_rate=new_rate, override_tax_code=new_code)
|
||||
self._totaldiff += new_tax.gross - pos.price
|
||||
self._totaldiff_guesstimate += new_tax.gross - pos.price
|
||||
self._operations.append(self.PriceOperation(pos, new_tax, new_tax.gross - pos.price))
|
||||
self._invoice_dirty = True
|
||||
|
||||
def cancel_fee(self, fee: OrderFee):
|
||||
self._totaldiff -= fee.value
|
||||
self._totaldiff_guesstimate -= fee.value
|
||||
self._operations.append(self.CancelFeeOperation(fee, -fee.value))
|
||||
self._invoice_dirty = True
|
||||
|
||||
def add_fee(self, fee: OrderFee):
|
||||
self._totaldiff += fee.value
|
||||
self._totaldiff_guesstimate += fee.value
|
||||
self._invoice_dirty = True
|
||||
self._operations.append(self.AddFeeOperation(fee, fee.value))
|
||||
|
||||
def change_fee(self, fee: OrderFee, value: Decimal):
|
||||
value = (fee.tax_rule or TaxRule.zero()).tax(value, base_price_is='gross', invoice_address=self._invoice_address,
|
||||
force_fixed_gross_price=True)
|
||||
self._totaldiff += value.gross - fee.value
|
||||
self._totaldiff_guesstimate += value.gross - fee.value
|
||||
self._invoice_dirty = True
|
||||
self._operations.append(self.FeeValueOperation(fee, value, value.gross - fee.value))
|
||||
|
||||
def cancel(self, position: OrderPosition):
|
||||
self._totaldiff -= position.price
|
||||
self._totaldiff_guesstimate -= position.price
|
||||
self._quotadiff.subtract(position.quotas)
|
||||
self._operations.append(self.CancelOperation(position, -position.price))
|
||||
if position.seat:
|
||||
@@ -1869,7 +1869,7 @@ class OrderChangeManager:
|
||||
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00'):
|
||||
self._invoice_dirty = True
|
||||
|
||||
self._totaldiff += price.gross
|
||||
self._totaldiff_guesstimate += price.gross
|
||||
self._quotadiff.update(new_quotas)
|
||||
if seat:
|
||||
self._seatdiff.update([seat])
|
||||
@@ -2165,8 +2165,8 @@ class OrderChangeManager:
|
||||
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < diff):
|
||||
raise OrderError(self.error_messages['quota'].format(name=quota.name))
|
||||
|
||||
def _check_paid_price_change(self):
|
||||
if self.order.status == Order.STATUS_PAID and self._totaldiff > 0:
|
||||
def _check_paid_price_change(self, totaldiff):
|
||||
if self.order.status == Order.STATUS_PAID and totaldiff > 0:
|
||||
if self.order.pending_sum > Decimal('0.00'):
|
||||
self.order.status = Order.STATUS_PENDING
|
||||
self.order.set_expires(
|
||||
@@ -2174,7 +2174,7 @@ class OrderChangeManager:
|
||||
self.order.event.subevents.filter(id__in=self.order.positions.values_list('subevent_id', flat=True))
|
||||
)
|
||||
self.order.save()
|
||||
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff < 0:
|
||||
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and totaldiff < 0:
|
||||
if self.order.pending_sum <= Decimal('0.00') and not self.order.require_approval:
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
@@ -2201,7 +2201,7 @@ class OrderChangeManager:
|
||||
user=self.user,
|
||||
auth=self.auth
|
||||
)
|
||||
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff > 0:
|
||||
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and totaldiff > 0:
|
||||
if self.open_payment:
|
||||
try:
|
||||
self.open_payment.payment_provider.cancel_payment(self.open_payment)
|
||||
@@ -2221,11 +2221,11 @@ class OrderChangeManager:
|
||||
auth=self.auth,
|
||||
)
|
||||
|
||||
def _check_paid_to_free(self):
|
||||
if self.event.currency == 'XXX' and self.order.total + self._totaldiff > Decimal("0.00"):
|
||||
def _check_paid_to_free(self, totaldiff):
|
||||
if self.event.currency == 'XXX' and self.order.total + totaldiff > Decimal("0.00"):
|
||||
raise OrderError(error_messages['currency_XXX'])
|
||||
|
||||
if self.order.total == 0 and (self._totaldiff < 0 or (self.split_order and self.split_order.total > 0)) and not self.order.require_approval:
|
||||
if self.order.total == 0 and (totaldiff < 0 or (self.split_order and self.split_order.total > 0)) and not self.order.require_approval:
|
||||
if not self.order.fees.exists() and not self.order.positions.exists():
|
||||
# The order is completely empty now, so we cancel it.
|
||||
self.order.status = Order.STATUS_CANCELED
|
||||
@@ -2233,7 +2233,7 @@ class OrderChangeManager:
|
||||
order_canceled.send(self.order.event, order=self.order)
|
||||
elif self.order.status != Order.STATUS_CANCELED:
|
||||
# if the order becomes free, mark it paid using the 'free' provider
|
||||
# this could happen if positions have been made cheaper or removed (_totaldiff < 0)
|
||||
# this could happen if positions have been made cheaper or removed (totaldiff < 0)
|
||||
# or positions got split off to a new order (split_order with positive total)
|
||||
p = self.order.payments.create(
|
||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||
@@ -2788,6 +2788,7 @@ class OrderChangeManager:
|
||||
|
||||
self.order.total = total
|
||||
self.order.save()
|
||||
return total
|
||||
|
||||
def _check_order_size(self):
|
||||
if (len(self.order.positions.all()) + len([op for op in self._operations if isinstance(op, self.AddOperation)])) > settings.PRETIX_MAX_ORDER_SIZE:
|
||||
@@ -2797,23 +2798,6 @@ class OrderChangeManager:
|
||||
}
|
||||
)
|
||||
|
||||
def _payment_fee_diff(self):
|
||||
total = self.order.total + self._totaldiff
|
||||
if self.open_payment:
|
||||
current_fee = Decimal('0.00')
|
||||
if self.open_payment and self.open_payment.fee:
|
||||
current_fee = self.open_payment.fee.value
|
||||
total -= current_fee
|
||||
|
||||
# Do not change payment fees of paid orders
|
||||
payment_fee = Decimal('0.00')
|
||||
if self.order.pending_sum - current_fee != 0:
|
||||
prov = self.open_payment.payment_provider
|
||||
if prov:
|
||||
payment_fee = prov.calculate_fee(total - self.completed_payment_sum)
|
||||
|
||||
self._totaldiff += payment_fee - current_fee
|
||||
|
||||
def _reissue_invoice(self):
|
||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if self.reissue_invoice and self._invoice_dirty:
|
||||
@@ -2932,6 +2916,13 @@ class OrderChangeManager:
|
||||
shared_lock_objects=[self.event]
|
||||
)
|
||||
|
||||
def guess_totaldiff(self):
|
||||
"""
|
||||
Return the estimated difference of ``order.total`` based on the currently queued operations. This is only
|
||||
a guess since it does not account for (a) tax rounding or (b) payment fee changes.
|
||||
"""
|
||||
return self._totaldiff_guesstimate
|
||||
|
||||
def commit(self, check_quotas=True):
|
||||
if self._committed:
|
||||
# an order change can only be committed once
|
||||
@@ -2947,8 +2938,6 @@ class OrderChangeManager:
|
||||
# so it's dangerous to keep the cache around.
|
||||
self.order._prefetched_objects_cache = {}
|
||||
|
||||
# finally, incorporate difference in payment fees
|
||||
self._payment_fee_diff()
|
||||
self._check_order_size()
|
||||
|
||||
with transaction.atomic():
|
||||
@@ -2956,6 +2945,7 @@ class OrderChangeManager:
|
||||
if locked_instance.last_modified != self.order.last_modified:
|
||||
raise OrderError(error_messages['race_condition'])
|
||||
|
||||
original_total = self.order.total
|
||||
if self.order.status in (Order.STATUS_PENDING, Order.STATUS_PAID):
|
||||
if check_quotas:
|
||||
self._check_quotas()
|
||||
@@ -2967,9 +2957,10 @@ class OrderChangeManager:
|
||||
self._perform_operations()
|
||||
except TaxRule.SaleNotAllowed:
|
||||
raise OrderError(self.error_messages['tax_rule_country_blocked'])
|
||||
self._recalculate_rounding_total_and_payment_fee()
|
||||
self._check_paid_price_change()
|
||||
self._check_paid_to_free()
|
||||
new_total = self._recalculate_rounding_total_and_payment_fee()
|
||||
totaldiff = new_total - original_total
|
||||
self._check_paid_price_change(totaldiff)
|
||||
self._check_paid_to_free(totaldiff)
|
||||
if self.order.status in (Order.STATUS_PENDING, Order.STATUS_PAID):
|
||||
self._reissue_invoice()
|
||||
self._clear_tickets_cache()
|
||||
|
||||
@@ -1525,6 +1525,7 @@ class OrderChangeMixin:
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
was_paid = self.order.status == Order.STATUS_PAID
|
||||
original_total = self.order.total
|
||||
ocm = OrderChangeManager(
|
||||
self.order,
|
||||
notify=True,
|
||||
@@ -1576,7 +1577,8 @@ class OrderChangeMixin:
|
||||
except OrderError as e:
|
||||
messages.error(self.request, str(e))
|
||||
else:
|
||||
if self.order.pending_sum < Decimal('0.00') and ocm._totaldiff < Decimal('0.00'):
|
||||
totaldiff = self.order.total - original_total
|
||||
if self.order.pending_sum < Decimal('0.00') and totaldiff < Decimal('0.00'):
|
||||
auto_refund = (
|
||||
not self.request.event.settings.cancel_allow_user_paid_require_approval
|
||||
and self.request.event.settings.cancel_allow_user_paid_refund_as_giftcard != "manually"
|
||||
@@ -1604,7 +1606,7 @@ class OrderChangeMixin:
|
||||
messages.info(self.request, _('You did not make any changes.'))
|
||||
return redirect(self.get_self_url())
|
||||
else:
|
||||
new_pending_sum = self.order.pending_sum + ocm._totaldiff
|
||||
new_pending_sum = self.order.pending_sum + ocm.guess_totaldiff()
|
||||
can_auto_refund = False
|
||||
if new_pending_sum < Decimal('0.00'):
|
||||
proposals = self.order.propose_auto_refunds(Decimal('-1.00') * new_pending_sum)
|
||||
@@ -1612,7 +1614,7 @@ class OrderChangeMixin:
|
||||
|
||||
return render(request, self.confirm_template_name, {
|
||||
'operations': ocm._operations,
|
||||
'totaldiff': ocm._totaldiff,
|
||||
'totaldiff': ocm.guess_totaldiff(),
|
||||
'order': self.order,
|
||||
'payment_refund_sum': self.order.payment_refund_sum,
|
||||
'new_pending_sum': new_pending_sum,
|
||||
@@ -1624,16 +1626,17 @@ class OrderChangeMixin:
|
||||
|
||||
def _validate_total_diff(self, ocm):
|
||||
pr = self.get_price_requirement()
|
||||
if ocm._totaldiff < Decimal('0.00') and pr == 'gte':
|
||||
totaldiff = ocm.guess_totaldiff()
|
||||
if totaldiff < Decimal('0.00') and pr == 'gte':
|
||||
raise OrderError(_('You may not change your order in a way that reduces the total price.'))
|
||||
if ocm._totaldiff <= Decimal('0.00') and pr == 'gt':
|
||||
if totaldiff <= Decimal('0.00') and pr == 'gt':
|
||||
raise OrderError(_('You may only change your order in a way that increases the total price.'))
|
||||
if ocm._totaldiff != Decimal('0.00') and pr == 'eq':
|
||||
if totaldiff != Decimal('0.00') and pr == 'eq':
|
||||
raise OrderError(_('You may not change your order in a way that changes the total price.'))
|
||||
if ocm._totaldiff < Decimal('0.00') and self.order.total + ocm._totaldiff < self.order.payment_refund_sum and pr == 'gte_paid':
|
||||
if totaldiff < Decimal('0.00') and self.order.total + totaldiff < self.order.payment_refund_sum and pr == 'gte_paid':
|
||||
raise OrderError(_('You may not change your order in a way that would require a refund.'))
|
||||
|
||||
if ocm._totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PAID:
|
||||
if totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PAID:
|
||||
self.order.set_expires(
|
||||
now(),
|
||||
self.order.event.subevents.filter(id__in=self.order.positions.values_list('subevent_id', flat=True))
|
||||
@@ -1642,7 +1645,7 @@ class OrderChangeMixin:
|
||||
raise OrderError(_('You may not change your order in a way that increases the total price since '
|
||||
'payments are no longer being accepted for this event.'))
|
||||
|
||||
if ocm._totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PENDING:
|
||||
if totaldiff > Decimal('0.00') and self.order.status == Order.STATUS_PENDING:
|
||||
for p in self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_PENDING):
|
||||
if not p.payment_provider.abort_pending_allowed:
|
||||
raise OrderError(_('You may not change your order in a way that requires additional payment while '
|
||||
|
||||
@@ -2156,6 +2156,24 @@ class OrderChangeManagerTests(TestCase):
|
||||
assert nop.price == Decimal('12.00')
|
||||
assert nop.subevent == se1
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_add_item_with_rounding(self):
|
||||
self.order.tax_rounding_mode = "sum_by_net"
|
||||
self.order.save()
|
||||
self.ocm.add_position(self.ticket, None, None, None)
|
||||
self.ocm.commit()
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.positions.count() == 3
|
||||
op1, op2, op3 = self.order.positions.all()
|
||||
assert op1.price == Decimal("23.01")
|
||||
assert op1.price_includes_rounding_correction == Decimal("0.01")
|
||||
assert op2.price == Decimal("23.01")
|
||||
assert op2.price_includes_rounding_correction == Decimal("0.01")
|
||||
assert op3.price == Decimal("23.00")
|
||||
assert op3.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert self.order.total == Decimal("69.02")
|
||||
assert self.order.transactions.count() == 7
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_reissue_invoice(self):
|
||||
generate_invoice(self.order)
|
||||
@@ -2563,6 +2581,61 @@ class OrderChangeManagerTests(TestCase):
|
||||
assert p.amount == Decimal('23.00')
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_split_with_rounding_change(self):
|
||||
# Order starts with 2*100€ tickets, but rounding corrects it to 199€. Then, it gets split, so its now 100 + 100
|
||||
# and 1€ is pending. Nasty, but we didn't choose the EN16931 rounding method…
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.tax_rounding_mode = "sum_by_net"
|
||||
self.order.save()
|
||||
self.op1.price = Decimal("100.00")
|
||||
self.op1._calculate_tax(tax_rule=self.tr19)
|
||||
self.op1.save()
|
||||
self.op2.price = Decimal("100.00")
|
||||
self.op2._calculate_tax(tax_rule=self.tr19)
|
||||
self.op2.save()
|
||||
self.order.refresh_from_db()
|
||||
self.ocm.regenerate_secret(self.op1)
|
||||
self.ocm.commit() # Force re-rounding
|
||||
self.order.refresh_from_db()
|
||||
self.ocm = OrderChangeManager(self.order, None)
|
||||
|
||||
assert self.order.total == Decimal("199.99")
|
||||
self.order.payments.create(
|
||||
provider='manual',
|
||||
state=OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
amount=self.order.total,
|
||||
)
|
||||
|
||||
# Split
|
||||
self.ocm.split(self.op2)
|
||||
self.ocm.commit()
|
||||
self.order.refresh_from_db()
|
||||
self.op2.refresh_from_db()
|
||||
|
||||
# First order
|
||||
assert self.order.total == Decimal('100.00')
|
||||
assert not self.order.fees.exists()
|
||||
assert self.order.pending_sum == Decimal('0.01')
|
||||
assert self.order.status == Order.STATUS_PENDING
|
||||
r = self.order.refunds.last()
|
||||
assert r.provider == 'offsetting'
|
||||
assert r.amount == Decimal('100.00')
|
||||
assert r.state == OrderRefund.REFUND_STATE_DONE
|
||||
|
||||
# New order
|
||||
assert self.op2.order != self.order
|
||||
o2 = self.op2.order
|
||||
assert o2.total == Decimal('100.00')
|
||||
assert o2.status == Order.STATUS_PAID
|
||||
assert o2.positions.count() == 1
|
||||
assert o2.fees.count() == 0
|
||||
assert o2.pending_sum == Decimal('0.00')
|
||||
p = o2.payments.last()
|
||||
assert p.provider == 'offsetting'
|
||||
assert p.amount == Decimal('100.00')
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_split_and_change_higher(self):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
|
||||
@@ -23,7 +23,7 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from pretix.base.models import OrderPosition
|
||||
from pretix.base.models import InvoiceAddress, OrderPosition, TaxRule
|
||||
from pretix.base.services.pricing import apply_rounding
|
||||
|
||||
|
||||
@@ -41,26 +41,26 @@ def sample_lines():
|
||||
def _validate_sample_lines(sample_lines, rounding_mode):
|
||||
apply_rounding(rounding_mode, "EUR", sample_lines)
|
||||
if rounding_mode == "line":
|
||||
for l in sample_lines:
|
||||
assert l.price == Decimal("100.00")
|
||||
assert l.tax_value == Decimal("15.97")
|
||||
assert l.tax_rate == Decimal("19.00")
|
||||
assert sum(l.price for l in sample_lines) == Decimal("500.00")
|
||||
assert sum(l.tax_value for l in sample_lines) == Decimal("79.85")
|
||||
for line in sample_lines:
|
||||
assert line.price == Decimal("100.00")
|
||||
assert line.tax_value == Decimal("15.97")
|
||||
assert line.tax_rate == Decimal("19.00")
|
||||
assert sum(line.price for line in sample_lines) == Decimal("500.00")
|
||||
assert sum(line.tax_value for line in sample_lines) == Decimal("79.85")
|
||||
elif rounding_mode == "sum_by_net":
|
||||
for l in sample_lines:
|
||||
for line in sample_lines:
|
||||
# gross price may vary
|
||||
assert l.price - l.tax_value == Decimal("84.03")
|
||||
assert l.tax_rate == Decimal("19.00")
|
||||
assert sum(l.price for l in sample_lines) == Decimal("499.98")
|
||||
assert sum(l.tax_value for l in sample_lines) == Decimal("79.83")
|
||||
assert line.price - line.tax_value == Decimal("84.03")
|
||||
assert line.tax_rate == Decimal("19.00")
|
||||
assert sum(line.price for line in sample_lines) == Decimal("499.98")
|
||||
assert sum(line.tax_value for line in sample_lines) == Decimal("79.83")
|
||||
elif rounding_mode == "sum_by_gross":
|
||||
for l in sample_lines:
|
||||
assert l.price == Decimal("100.00")
|
||||
for line in sample_lines:
|
||||
assert line.price == Decimal("100.00")
|
||||
# net price may vary
|
||||
assert l.tax_rate == Decimal("19.00")
|
||||
assert sum(l.price for l in sample_lines) == Decimal("500.00")
|
||||
assert sum(l.tax_value for l in sample_lines) == Decimal("79.83")
|
||||
assert line.tax_rate == Decimal("19.00")
|
||||
assert sum(line.price for line in sample_lines) == Decimal("500.00")
|
||||
assert sum(line.tax_value for line in sample_lines) == Decimal("79.83")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -98,7 +98,9 @@ def test_revert_net_rounding_to_single_line(sample_lines):
|
||||
)
|
||||
apply_rounding("sum_by_net", "EUR", [l])
|
||||
assert l.price == Decimal("100.00")
|
||||
assert l.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert l.tax_value == Decimal("15.97")
|
||||
assert l.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
assert l.tax_rate == Decimal("19.00")
|
||||
|
||||
|
||||
@@ -114,5 +116,21 @@ def test_revert_gross_rounding_to_single_line(sample_lines):
|
||||
)
|
||||
apply_rounding("sum_by_gross", "EUR", [l])
|
||||
assert l.price == Decimal("100.00")
|
||||
assert l.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert l.tax_value == Decimal("15.97")
|
||||
assert l.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
assert l.tax_rate == Decimal("19.00")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rounding_of_impossible_price(sample_lines):
|
||||
l = OrderPosition(
|
||||
price=Decimal("23.00"),
|
||||
)
|
||||
l._calculate_tax(tax_rule=TaxRule(rate=Decimal("7.00")), invoice_address=InvoiceAddress())
|
||||
apply_rounding("sum_by_net", "EUR", [l])
|
||||
assert l.price == Decimal("23.01")
|
||||
assert l.price_includes_rounding_correction == Decimal("0.01")
|
||||
assert l.tax_value == Decimal("1.51")
|
||||
assert l.tax_value_includes_rounding_correction == Decimal("0.01")
|
||||
assert l.tax_rate == Decimal("7.00")
|
||||
|
||||
@@ -71,7 +71,7 @@ class BaseCheckoutTestCase:
|
||||
plugins='pretix.plugins.stripe,pretix.plugins.banktransfer,tests.testdummy',
|
||||
live=True
|
||||
)
|
||||
self.tr19 = self.event.tax_rules.create(rate=19)
|
||||
self.tr19 = self.event.tax_rules.create(rate=19, default=True)
|
||||
self.category = ItemCategory.objects.create(event=self.event, name="Everything", position=0)
|
||||
self.quota_tickets = Quota.objects.create(event=self.event, name='Tickets', size=5)
|
||||
self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket',
|
||||
@@ -483,6 +483,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
assert cr1.price == Decimal('23.00')
|
||||
|
||||
def test_custom_tax_rules_blocked_on_fee(self):
|
||||
self.tr19.default = False
|
||||
self.tr19.save()
|
||||
self.tr7 = self.event.tax_rules.create(rate=7, default=True)
|
||||
self.tr7.custom_rules = json.dumps([
|
||||
{'country': 'AT', 'address_type': 'business_vat_id', 'action': 'reverse'},
|
||||
@@ -2091,6 +2093,252 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
self.assertRedirects(response, '/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
def test_rounding_sum_by_net(self):
|
||||
self.event.settings.tax_rounding = "sum_by_net"
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.ticket.default_price = Decimal("100.00")
|
||||
self.ticket.save()
|
||||
with scopes_disabled():
|
||||
cm = CartManager(event=self.event, cart_id=self.session_key, sales_channel=self.orga.sales_channels.get(identifier="web"))
|
||||
cm.add_new_items([{
|
||||
'item': self.ticket.pk,
|
||||
'variation': None,
|
||||
'count': 2
|
||||
}])
|
||||
cm.commit()
|
||||
|
||||
response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
assert b"199.99" in response.content
|
||||
assert b"200.00" not in response.content
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'banktransfer',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
assert response.context_data['cart']['total'] == Decimal('199.99')
|
||||
assert response.context_data['cart']['net_total'] == Decimal('84.03') * 2
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
o = Order.objects.last()
|
||||
p1 = o.payments.get()
|
||||
assert p1.amount == Decimal('199.99')
|
||||
assert o.total == Decimal("199.99")
|
||||
op1, op2 = o.positions.all()
|
||||
assert op1.price == Decimal("99.99")
|
||||
assert op1.price_includes_rounding_correction == Decimal("-0.01")
|
||||
assert op1.tax_value == Decimal("15.96")
|
||||
assert op1.tax_value_includes_rounding_correction == Decimal("-0.01")
|
||||
assert op2.price == Decimal("100.00")
|
||||
assert op2.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert op2.tax_value == Decimal("15.97")
|
||||
assert op2.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
|
||||
def test_rounding_sum_by_gross(self):
|
||||
self.event.settings.tax_rounding = "sum_by_gross"
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.ticket.default_price = Decimal("100.00")
|
||||
self.ticket.save()
|
||||
with scopes_disabled():
|
||||
cm = CartManager(event=self.event, cart_id=self.session_key, sales_channel=self.orga.sales_channels.get(identifier="web"))
|
||||
cm.add_new_items([{
|
||||
'item': self.ticket.pk,
|
||||
'variation': None,
|
||||
'count': 2
|
||||
}])
|
||||
cm.commit()
|
||||
|
||||
response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
assert b"199.99" not in response.content
|
||||
assert b"200.00" in response.content
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'banktransfer',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
assert response.context_data['cart']['total'] == Decimal('200.00')
|
||||
assert response.context_data['cart']['net_total'] == Decimal('84.03') + Decimal('84.04')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
o = Order.objects.last()
|
||||
p1 = o.payments.get()
|
||||
assert p1.amount == Decimal('200.00')
|
||||
assert o.total == Decimal("200.00")
|
||||
op1, op2 = o.positions.all()
|
||||
assert op1.price == Decimal("100.00")
|
||||
assert op1.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert op1.tax_value == Decimal("15.96")
|
||||
assert op1.tax_value_includes_rounding_correction == Decimal("-0.01")
|
||||
assert op2.price == Decimal("100.00")
|
||||
assert op2.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert op2.tax_value == Decimal("15.97")
|
||||
assert op2.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
|
||||
def test_rounding_line(self):
|
||||
self.event.settings.tax_rounding = "line"
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.ticket.default_price = Decimal("100.00")
|
||||
self.ticket.save()
|
||||
with scopes_disabled():
|
||||
cm = CartManager(event=self.event, cart_id=self.session_key, sales_channel=self.orga.sales_channels.get(identifier="web"))
|
||||
cm.add_new_items([{
|
||||
'item': self.ticket.pk,
|
||||
'variation': None,
|
||||
'count': 2
|
||||
}])
|
||||
cm.commit()
|
||||
|
||||
response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
assert b"199.99" not in response.content
|
||||
assert b"200.00" in response.content
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'banktransfer',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
assert response.context_data['cart']['total'] == Decimal('200.00')
|
||||
assert response.context_data['cart']['net_total'] == Decimal('84.03') * 2
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
o = Order.objects.last()
|
||||
p1 = o.payments.get()
|
||||
assert p1.amount == Decimal('200.00')
|
||||
assert o.total == Decimal("200.00")
|
||||
op1, op2 = o.positions.all()
|
||||
assert op1.price == Decimal("100.00")
|
||||
assert op1.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert op1.tax_value == Decimal("15.97")
|
||||
assert op1.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
assert op2.price == Decimal("100.00")
|
||||
assert op2.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert op2.tax_value == Decimal("15.97")
|
||||
assert op2.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
|
||||
def test_rounding_sum_by_net_with_payment_fee(self):
|
||||
self.event.settings.tax_rounding = "sum_by_net"
|
||||
self.event.settings.tax_rule_payment = "default"
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.set('payment_banktransfer__fee_abs', Decimal("100.00"))
|
||||
self.ticket.default_price = Decimal("100.00")
|
||||
self.ticket.save()
|
||||
with scopes_disabled():
|
||||
cm = CartManager(event=self.event, cart_id=self.session_key, sales_channel=self.orga.sales_channels.get(identifier="web"))
|
||||
cm.add_new_items([{
|
||||
'item': self.ticket.pk,
|
||||
'variation': None,
|
||||
'count': 1
|
||||
}])
|
||||
cm.commit()
|
||||
|
||||
response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
assert b"100.00" in response.content
|
||||
assert b"99.99" not in response.content
|
||||
assert b"199.99" not in response.content
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'banktransfer',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
assert response.context_data['cart']['total'] == Decimal('199.99')
|
||||
assert response.context_data['cart']['net_total'] == Decimal('84.03') * 2
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
o = Order.objects.last()
|
||||
p1 = o.payments.get()
|
||||
assert p1.amount == Decimal('199.99')
|
||||
assert o.total == Decimal("199.99")
|
||||
op1 = o.positions.get()
|
||||
of1 = o.fees.get()
|
||||
assert op1.price == Decimal("99.99")
|
||||
assert op1.price_includes_rounding_correction == Decimal("-0.01")
|
||||
assert op1.tax_value == Decimal("15.96")
|
||||
assert op1.tax_value_includes_rounding_correction == Decimal("-0.01")
|
||||
assert of1.price == Decimal("100.00")
|
||||
assert of1.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert of1.tax_value == Decimal("15.97")
|
||||
assert of1.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
|
||||
def test_rounding_sum_by_net_with_payment_fee_that_makes_card_insufficient(self):
|
||||
# Our built-in gift card payment does not actually support setting a payment fee, but we still want to
|
||||
# test the core behavior in case a gift-card plugin does
|
||||
gc = self.orga.issued_gift_cards.create(currency="EUR")
|
||||
gc.transactions.create(value=199.96, acceptor=self.orga)
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.set('payment_giftcard__fee_abs', "99.98")
|
||||
self.event.settings.set('payment_giftcard__fee_reverse_calc', False)
|
||||
self.event.settings.tax_rounding = "sum_by_net"
|
||||
self.event.settings.tax_rule_payment = "default"
|
||||
self.ticket.default_price = Decimal("99.98")
|
||||
self.ticket.save()
|
||||
with scopes_disabled():
|
||||
cm = CartManager(event=self.event, cart_id=self.session_key, sales_channel=self.orga.sales_channels.get(identifier="web"))
|
||||
cm.add_new_items([{
|
||||
'item': self.ticket.pk,
|
||||
'variation': None,
|
||||
'count': 1
|
||||
}])
|
||||
cm.commit()
|
||||
|
||||
response = self.client.get('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
assert b"99.98" in response.content
|
||||
assert b"99.99" not in response.content
|
||||
assert b"199.97" not in response.content
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'giftcard',
|
||||
'payment_giftcard-code': gc.secret
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'banktransfer',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
assert response.context_data['cart']['total'] == Decimal('199.97')
|
||||
assert response.context_data['cart']['net_total'] == Decimal('84.02') * 2
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
o = Order.objects.last()
|
||||
p1, p2 = o.payments.all()
|
||||
assert p1.amount == Decimal('199.96')
|
||||
assert p2.amount == Decimal('0.01')
|
||||
assert o.total == Decimal("199.97")
|
||||
op1 = o.positions.get()
|
||||
of1 = o.fees.get()
|
||||
assert op1.price == Decimal("99.99")
|
||||
assert op1.price_includes_rounding_correction == Decimal("0.01")
|
||||
assert op1.tax_value == Decimal("15.97")
|
||||
assert op1.tax_value_includes_rounding_correction == Decimal("0.01")
|
||||
assert of1.price == Decimal("99.98")
|
||||
assert of1.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert of1.tax_value == Decimal("15.96")
|
||||
assert of1.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
|
||||
def test_subevent(self):
|
||||
self.event.has_subevents = True
|
||||
self.event.save()
|
||||
|
||||
@@ -1280,6 +1280,56 @@ class OrdersTest(BaseOrdersTest):
|
||||
p.refresh_from_db()
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CREATED
|
||||
|
||||
def test_change_paymentmethod_with_rounding_change(self):
|
||||
tr19 = self.event.tax_rules.create(
|
||||
name='VAT',
|
||||
rate=Decimal('19.00'),
|
||||
default=True
|
||||
)
|
||||
self.ticket.tax_rule = tr19
|
||||
self.ticket.save()
|
||||
self.ticket_pos.price = Decimal("100.00")
|
||||
self.ticket_pos.tax_rule = tr19
|
||||
self.ticket_pos._calculate_tax()
|
||||
self.ticket_pos.save()
|
||||
self.order.total = Decimal("100.00")
|
||||
self.order.tax_rounding_mode = "sum_by_net"
|
||||
self.order.save()
|
||||
|
||||
self.event.settings.tax_rounding = "sum_by_net"
|
||||
self.event.settings.set('payment_banktransfer__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__enabled', True)
|
||||
self.event.settings.set('payment_testdummy__fee_reverse_calc', False)
|
||||
self.event.settings.set('payment_testdummy__fee_abs', '100.00')
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
)
|
||||
assert 'Test dummy' in response.content.decode()
|
||||
assert '+ €100.00' in response.content.decode()
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
{
|
||||
'payment': 'testdummy'
|
||||
}, follow=True
|
||||
)
|
||||
assert 'Total: €199.99' in response.content.decode()
|
||||
self.order.refresh_from_db()
|
||||
with scopes_disabled():
|
||||
assert self.order.payments.last().provider == 'testdummy'
|
||||
fee = self.order.fees.filter(fee_type=OrderFee.FEE_TYPE_PAYMENT).last()
|
||||
assert fee.value == Decimal('100.00')
|
||||
assert fee.tax_value == Decimal('15.97')
|
||||
self.ticket_pos.refresh_from_db()
|
||||
assert self.ticket_pos.price == Decimal("99.99")
|
||||
assert self.ticket_pos.price_includes_rounding_correction == Decimal("-0.01")
|
||||
self.order.refresh_from_db()
|
||||
assert self.order.total == Decimal('199.99')
|
||||
p = self.order.payments.last()
|
||||
assert p.provider == 'testdummy'
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CREATED
|
||||
assert p.amount == Decimal('199.99')
|
||||
|
||||
def test_change_paymentmethod_to_same(self):
|
||||
with scopes_disabled():
|
||||
p_old = self.order.payments.create(
|
||||
|
||||
@@ -35,7 +35,7 @@ class DummyPaymentProvider(BasePaymentProvider):
|
||||
abort_pending_allowed = False
|
||||
|
||||
def payment_is_valid_session(self, request: HttpRequest) -> bool:
|
||||
pass
|
||||
return True
|
||||
|
||||
def checkout_confirm_render(self, request) -> str:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user