Add support for custom taxation rules

This commit is contained in:
Raphael Michel
2018-02-28 23:03:25 +01:00
parent d8d00a7e26
commit 578c1ecfaf
8 changed files with 644 additions and 4 deletions

View File

@@ -0,0 +1,387 @@
import json
from decimal import Decimal
import pytest
from django.utils.timezone import now
from django_countries.fields import Country
from pretix.base.models import Event, InvoiceAddress, Organizer, TaxRule
@pytest.fixture
def event():
o = Organizer.objects.create(name='Dummy', slug='dummy')
event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy',
date_from=now()
)
return event
@pytest.mark.django_db
def test_from_gross_price(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=True
)
tp = tr.tax(Decimal('100.00'))
assert tp.gross == Decimal('100')
assert tp.net == Decimal('90.91')
assert tp.tax == Decimal('100.00') - Decimal('90.91')
assert tp.rate == Decimal('10.00')
@pytest.mark.django_db
def test_from_net_price(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False
)
tp = tr.tax(Decimal('100.00'))
assert tp.gross == Decimal('110.00')
assert tp.net == Decimal('100.00')
assert tp.tax == Decimal('10.00')
assert tp.rate == Decimal('10.00')
@pytest.mark.django_db
def test_reverse_charge_no_address(event):
tr = TaxRule(
event=event, eu_reverse_charge=True,
rate=Decimal('10.00'), price_includes_tax=False
)
assert not tr.is_reverse_charge(None)
assert tr.tax_applicable(None)
@pytest.mark.django_db
def test_reverse_charge_no_country(event):
tr = TaxRule(
event=event, eu_reverse_charge=True,
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_individual_same_country(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=False,
country=Country('DE')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_individual_eu(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=False,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_individual_3rdc(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=False,
country=Country('US')
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_business_same_country(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=True,
country=Country('DE')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_business_eu(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_business_3rdc(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=True,
country=Country('US')
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_valid_vat_id_business_same_country(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=True,
country=Country('DE'),
vat_id='DE123456',
vat_id_validated=True
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_valid_vat_id_business_eu(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=True,
vat_id='AT12346',
vat_id_validated=True,
country=Country('AT')
)
assert tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_valid_vat_id_business_3rdc(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=True,
country=Country('US'),
vat_id='US12346',
vat_id_validated=True
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
@pytest.mark.django_db
def test_reverse_charge_disabled(event):
tr = TaxRule(
event=event, eu_reverse_charge=False, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False
)
ia = InvoiceAddress(
is_business=True,
vat_id='AT12346',
vat_id_validated=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_override(event):
tr = TaxRule(
event=event, eu_reverse_charge=True, home_country=Country('DE'),
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'ZZ', 'address_type': '', 'action': 'vat'}
])
)
ia = InvoiceAddress(
is_business=True,
vat_id='AT12346',
vat_id_validated=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_in_order(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'ZZ', 'address_type': '', 'action': 'vat'},
{'country': 'ZZ', 'address_type': '', 'action': 'reverse'}
])
)
ia = InvoiceAddress(
is_business=True,
vat_id='AT12346',
vat_id_validated=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_any_country(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'ZZ', 'address_type': '', 'action': 'no'},
])
)
ia = InvoiceAddress(
is_business=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_eu_country(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'EU', 'address_type': '', 'action': 'no'},
])
)
ia = InvoiceAddress(
is_business=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
ia = InvoiceAddress(
is_business=True,
country=Country('US')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_specific_country(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'AT', 'address_type': '', 'action': 'no'},
])
)
ia = InvoiceAddress(
is_business=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
ia = InvoiceAddress(
is_business=True,
country=Country('DE')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_individual(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'ZZ', 'address_type': 'individual', 'action': 'no'},
])
)
ia = InvoiceAddress(
is_business=False,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
ia = InvoiceAddress(
is_business=True,
country=Country('DE')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_business(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'ZZ', 'address_type': 'business', 'action': 'no'},
])
)
ia = InvoiceAddress(
is_business=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)
ia = InvoiceAddress(
is_business=False,
country=Country('DE')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
@pytest.mark.django_db
def test_custom_rules_vat_id(event):
tr = TaxRule(
event=event,
rate=Decimal('10.00'), price_includes_tax=False,
custom_rules=json.dumps([
{'country': 'EU', 'address_type': 'business_vat_id', 'action': 'reverse'},
])
)
ia = InvoiceAddress(
is_business=True,
country=Country('AT')
)
assert not tr.is_reverse_charge(ia)
assert tr.tax_applicable(ia)
ia = InvoiceAddress(
is_business=True,
country=Country('DE'),
vat_id='DE1234',
vat_id_validated=True
)
assert tr.is_reverse_charge(ia)
assert not tr.tax_applicable(ia)

View File

@@ -1,4 +1,5 @@
import datetime
import json
import os
from datetime import timedelta
from decimal import Decimal
@@ -415,6 +416,51 @@ class CheckoutTestCase(TestCase):
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert not ia.vat_id_validated
def test_custom_tax_rules(self):
self.tr19.custom_rules = json.dumps([
{'country': 'AT', 'address_type': '', 'action': 'vat'},
{'country': 'ZZ', 'address_type': '', 'action': 'reverse'},
])
self.tr19.save()
self.event.settings.invoice_address_vatid = True
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
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': 'business',
'company': 'Foo',
'name': 'Bar',
'street': 'Baz',
'zipcode': '12345',
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
}, follow=True)
cr1.refresh_from_db()
assert cr1.price == Decimal('23.00')
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': 'Foo',
'name': 'Bar',
'street': 'Baz',
'zipcode': '12345',
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
}, follow=True)
cr1.refresh_from_db()
assert cr1.price == Decimal('19.33')
def test_question_file_upload(self):
q1 = Question.objects.create(
event=self.event, question='Student ID', type=Question.TYPE_FILE,