diff --git a/src/pretix/base/models/tax.py b/src/pretix/base/models/tax.py index 716f867643..4eedfb5aef 100644 --- a/src/pretix/base/models/tax.py +++ b/src/pretix/base/models/tax.py @@ -174,7 +174,7 @@ class TaxRule(LoggedModel): return Decimal(self.rate) def tax(self, base_price, base_price_is='auto', currency=None, override_tax_rate=None, invoice_address=None, - subtract_from_gross=Decimal('0.00')): + subtract_from_gross=Decimal('0.00'), gross_price_is_tax_rate: Decimal = None): from .event import Event try: currency = currency or self.event.currency @@ -186,7 +186,9 @@ class TaxRule(LoggedModel): rate = override_tax_rate elif invoice_address: adjust_rate = self.tax_rate_for(invoice_address) - if adjust_rate != rate: + if adjust_rate == gross_price_is_tax_rate and base_price_is == 'gross': + rate = adjust_rate + elif adjust_rate != rate: normal_price = self.tax(base_price, base_price_is, currency, subtract_from_gross=subtract_from_gross) base_price = normal_price.net base_price_is = 'net' diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 21261a3940..0237742e69 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -618,8 +618,10 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio except ItemBundle.MultipleObjectsReturned: raise OrderError("Invalid product configuration (duplicate bundle)") price = get_price(cp.item, cp.variation, cp.voucher, bprice, cp.subevent, custom_price_is_net=False, + custom_price_is_tax_rate=cp.override_tax_rate, invoice_address=address, force_custom_price=True, max_discount=max_discount) pbv = get_price(cp.item, cp.variation, None, bprice, cp.subevent, custom_price_is_net=False, + custom_price_is_tax_rate=cp.override_tax_rate, invoice_address=address, force_custom_price=True, max_discount=max_discount) changed_prices[cp.pk] = bprice else: @@ -631,10 +633,10 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio price = get_price(cp.item, cp.variation, cp.voucher, cp.price, cp.subevent, custom_price_is_net=False, addon_to=cp.addon_to, invoice_address=address, bundled_sum=bundled_sum, - max_discount=max_discount) + max_discount=max_discount, custom_price_is_tax_rate=cp.override_tax_rate) pbv = get_price(cp.item, cp.variation, None, cp.price, cp.subevent, custom_price_is_net=False, addon_to=cp.addon_to, invoice_address=address, bundled_sum=bundled_sum, - max_discount=max_discount) + max_discount=max_discount, custom_price_is_tax_rate=cp.override_tax_rate) if max_discount is not None: v_budget[cp.voucher] = v_budget[cp.voucher] + current_discount - (pbv.gross - price.gross) diff --git a/src/pretix/base/services/pricing.py b/src/pretix/base/services/pricing.py index 06433f3cf0..e2082513be 100644 --- a/src/pretix/base/services/pricing.py +++ b/src/pretix/base/services/pricing.py @@ -11,6 +11,7 @@ from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule def get_price(item: Item, variation: ItemVariation = None, voucher: Voucher = None, custom_price: Decimal = None, subevent: SubEvent = None, custom_price_is_net: bool = False, + custom_price_is_tax_rate: Decimal=None, addon_to: AbstractPosition = None, invoice_address: InvoiceAddress = None, force_custom_price: bool = False, bundled_sum: Decimal = Decimal('0.00'), max_discount: Decimal = None, tax_rule=None) -> TaxedPrice: @@ -66,7 +67,7 @@ def get_price(item: Item, variation: ItemVariation = None, price = tax_rule.tax(max(custom_price, price.net), base_price_is='net', invoice_address=invoice_address, subtract_from_gross=bundled_sum) else: - price = tax_rule.tax(max(custom_price, price.gross), base_price_is='gross', + price = tax_rule.tax(max(custom_price, price.gross), base_price_is='gross', gross_price_is_tax_rate=custom_price_is_tax_rate, invoice_address=invoice_address, subtract_from_gross=bundled_sum) else: price = tax_rule.tax(price, invoice_address=invoice_address, subtract_from_gross=bundled_sum) diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py index 8f0919f06d..228a8a29b2 100644 --- a/src/tests/presale/test_checkout.py +++ b/src/tests/presale/test_checkout.py @@ -402,6 +402,44 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase): cr1.refresh_from_db() assert cr1.price == Decimal('23.20') + def test_country_taxing_free_price_and_voucher(self): + self._enable_country_specific_taxing() + + self.ticket.free_price = True + self.ticket.save() + + with scopes_disabled(): + cr1 = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=now() + timedelta(minutes=10), + voucher=self.event.vouchers.create() + ) + + with mock.patch('vat_moss.id.validate') as mock_validate: + mock_validate.return_value = ('AT', 'AT123456', 'Foo') + self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), { + 'is_business': 'individual', + 'name': 'Bar', + 'street': 'Baz', + 'zipcode': '12345', + 'city': 'Here', + 'country': 'AT', + 'email': 'admin@localhost' + }, follow=True) + + cr1.refresh_from_db() + assert cr1.price == Decimal('23.20') + + self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), { + 'payment': 'banktransfer' + }, follow=True) + + self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True) + with scopes_disabled(): + assert not CartPosition.objects.filter(pk=cr1.pk).exists() + o = Order.objects.last() + assert o.positions.get().price == Decimal('23.20') + def test_country_taxing_switch(self): self.test_country_taxing()