mirror of
https://github.com/pretix/pretix.git
synced 2026-01-03 18:52:26 +00:00
- [x] attendee names - [x] Invoice address names - [x] Data migration - [x] API serializers - [x] orderposition - [x] cartposition - [x] invoiceaddress - [x] checkinlistposition - [x] position API search - [x] invoice API search - [x] business/individual required toggle - [x] Split columns in CSV exports - [x] ticket editor - [x] shredder - [x] ticket/invoice sample data - [x] order search - [x] Handle changed naming scheme - [x] tests - [x] make use in: - [x] Boabee - [x] Certificate download order - [x] Badge download order - [x] Ticket download order - [x] Document new MySQL requirement - [x] Plugins
372 lines
13 KiB
Python
372 lines
13 KiB
Python
import json
|
|
from datetime import date, timedelta
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
from django.db import DatabaseError, transaction
|
|
from django.utils.timezone import now
|
|
from django_countries.fields import Country
|
|
|
|
from pretix.base.models import (
|
|
Event, Invoice, InvoiceAddress, Item, ItemVariation, Order, OrderPosition,
|
|
Organizer,
|
|
)
|
|
from pretix.base.models.orders import OrderFee
|
|
from pretix.base.services.invoices import (
|
|
build_preview_invoice_pdf, generate_cancellation, generate_invoice,
|
|
invoice_pdf_task, regenerate_invoice,
|
|
)
|
|
from pretix.base.services.orders import OrderChangeManager
|
|
from pretix.base.settings import GlobalSettingsObject
|
|
|
|
|
|
@pytest.fixture
|
|
def env():
|
|
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
|
event = Event.objects.create(
|
|
organizer=o, name='Dummy', slug='dummy',
|
|
date_from=now(), plugins='pretix.plugins.banktransfer'
|
|
)
|
|
o = Order.objects.create(
|
|
code='FOO', event=event, email='dummy@dummy.test',
|
|
status=Order.STATUS_PENDING,
|
|
datetime=now(), expires=now() + timedelta(days=10),
|
|
total=0, locale='en'
|
|
)
|
|
tr = event.tax_rules.create(rate=Decimal('19.00'))
|
|
o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'),
|
|
tax_value=Decimal('0.05'), tax_rule=tr)
|
|
ticket = Item.objects.create(event=event, name='Early-bird ticket',
|
|
category=None, default_price=23, tax_rule=tr,
|
|
admission=True)
|
|
t_shirt = Item.objects.create(event=event, name='T-Shirt',
|
|
category=None, default_price=42, tax_rule=tr,
|
|
admission=True)
|
|
variation = ItemVariation.objects.create(value='M', item=t_shirt)
|
|
OrderPosition.objects.create(
|
|
order=o,
|
|
item=ticket,
|
|
variation=None,
|
|
price=Decimal("23.00"),
|
|
positionid=1,
|
|
)
|
|
OrderPosition.objects.create(
|
|
order=o,
|
|
item=t_shirt,
|
|
variation=variation,
|
|
price=Decimal("42.00"),
|
|
positionid=2,
|
|
)
|
|
gs = GlobalSettingsObject()
|
|
gs.settings.ecb_rates_date = date.today()
|
|
gs.settings.ecb_rates_dict = json.dumps({
|
|
"USD": "1.1648",
|
|
"RON": "4.5638",
|
|
"CZK": "26.024",
|
|
"BGN": "1.9558",
|
|
"HRK": "7.4098",
|
|
"EUR": "1.0000",
|
|
"NOK": "9.3525",
|
|
"HUF": "305.15",
|
|
"DKK": "7.4361",
|
|
"PLN": "4.2408",
|
|
"GBP": "0.89350",
|
|
"SEK": "9.5883"
|
|
}, cls=DjangoJSONEncoder)
|
|
return event, o
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_locale_setting(env):
|
|
event, order = env
|
|
event.settings.set('invoice_language', 'de')
|
|
inv = generate_invoice(order)
|
|
assert inv.locale == 'de'
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_locale_user(env):
|
|
event, order = env
|
|
order.locale = 'en'
|
|
event.settings.set('invoice_language', '__user__')
|
|
inv = generate_invoice(order)
|
|
assert inv.locale == order.locale
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_address_old_country(env):
|
|
event, order = env
|
|
event.settings.set('invoice_language', 'en')
|
|
InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street',
|
|
zipcode='12345', city='London', country_old='England', country='',
|
|
order=order)
|
|
inv = generate_invoice(order)
|
|
assert inv.invoice_to == "Acme Company\n\n221B Baker Street\n12345 London\nEngland"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_address(env):
|
|
event, order = env
|
|
event.settings.set('invoice_language', 'en')
|
|
InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street',
|
|
zipcode='12345', city='London', country=Country('GB'),
|
|
order=order)
|
|
inv = generate_invoice(order)
|
|
assert inv.invoice_to == "Acme Company\n\n221B Baker Street\n12345 London\nUnited Kingdom"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_address_vat_id(env):
|
|
event, order = env
|
|
event.settings.set('invoice_language', 'en')
|
|
InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street',
|
|
name_parts={'full_name': 'Sherlock Holmes', '_scheme': 'full'},
|
|
zipcode='12345',
|
|
city='London',
|
|
country_old='UK',
|
|
country='', vat_id='UK1234567', order=order)
|
|
inv = generate_invoice(order)
|
|
assert inv.invoice_to == "Acme Company\nSherlock Holmes\n221B Baker Street\n12345 London\nUK\nVAT-ID: UK1234567"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_positions_skip_free(env):
|
|
event, order = env
|
|
event.settings.invoice_include_free = False
|
|
op1 = order.positions.first()
|
|
op1.price = Decimal('0.00')
|
|
op1.save()
|
|
inv = generate_invoice(order)
|
|
assert inv.lines.count() == 2
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_reverse_charge_note(env):
|
|
event, order = env
|
|
|
|
tr = event.tax_rules.first()
|
|
tr.eu_reverse_charge = True
|
|
tr.home_country = Country('DE')
|
|
tr.save()
|
|
|
|
event.settings.set('invoice_language', 'en')
|
|
InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street', zipcode='12345', city='Warsaw',
|
|
country=Country('PL'), vat_id='PL123456780', vat_id_validated=True, order=order,
|
|
is_business=True)
|
|
|
|
ocm = OrderChangeManager(order, None)
|
|
ocm.recalculate_taxes()
|
|
ocm.commit()
|
|
assert not order.positions.filter(tax_value__gt=0).exists()
|
|
|
|
inv = generate_invoice(order)
|
|
assert "reverse charge" in inv.additional_text.lower()
|
|
assert inv.foreign_currency_display == "PLN"
|
|
assert inv.foreign_currency_rate == Decimal("4.2408")
|
|
assert inv.foreign_currency_rate_date == date.today()
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_reverse_charge_foreign_currency_data_too_old(env):
|
|
event, order = env
|
|
gs = GlobalSettingsObject()
|
|
gs.settings.ecb_rates_date = date.today() - timedelta(days=14)
|
|
|
|
tr = event.tax_rules.first()
|
|
tr.eu_reverse_charge = True
|
|
tr.home_country = Country('DE')
|
|
tr.save()
|
|
|
|
event.settings.set('invoice_language', 'en')
|
|
InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street', zipcode='12345', city='Warsaw',
|
|
country=Country('PL'), vat_id='PL123456780', vat_id_validated=True, order=order,
|
|
is_business=True)
|
|
|
|
ocm = OrderChangeManager(order, None)
|
|
ocm.recalculate_taxes()
|
|
ocm.commit()
|
|
assert not order.positions.filter(tax_value__gt=0).exists()
|
|
|
|
inv = generate_invoice(order)
|
|
assert "reverse charge" in inv.additional_text.lower()
|
|
assert inv.foreign_currency_rate is None
|
|
assert inv.foreign_currency_rate_date is None
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_positions(env):
|
|
event, order = env
|
|
inv = generate_invoice(order)
|
|
assert inv.lines.count() == 3
|
|
first = inv.lines.first()
|
|
assert 'Early-bird' in first.description
|
|
assert first.gross_value == Decimal('23.00')
|
|
|
|
second = inv.lines.all()[1]
|
|
assert 'T-Shirt' in second.description
|
|
assert 'M' in second.description
|
|
assert second.gross_value == Decimal('42.00')
|
|
|
|
last = inv.lines.last()
|
|
assert 'Payment' in last.description
|
|
fee = order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT)
|
|
assert last.gross_value == fee.value
|
|
assert last.tax_rate == fee.tax_rate
|
|
assert last.tax_value == fee.tax_value
|
|
assert inv.invoice_to == ""
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_rebuilding(env):
|
|
event, order = env
|
|
inv = generate_invoice(order)
|
|
inv2 = regenerate_invoice(inv)
|
|
assert inv.order == inv2.order
|
|
|
|
inv3 = generate_cancellation(inv)
|
|
inv4 = regenerate_invoice(inv3)
|
|
assert inv3.order == inv4.order
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_cannot_delete_invoice(env):
|
|
event, order = env
|
|
inv = generate_invoice(order)
|
|
with pytest.raises(Exception):
|
|
inv.delete()
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_cannot_write_invoice_without_order(env):
|
|
event, _ = env
|
|
with pytest.raises(Exception):
|
|
i = Invoice(order=None, event=event)
|
|
i.save()
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_pdf_generation(env):
|
|
event, order = env
|
|
inv = generate_invoice(order)
|
|
cancellation = generate_cancellation(inv)
|
|
assert invoice_pdf_task(inv.pk)
|
|
assert invoice_pdf_task(cancellation.pk)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_pdf_generation_custom_text(env):
|
|
event, order = env
|
|
event.settings.set('invoice_introductory_text', 'introductory invoice text')
|
|
# set a really long additional text, to make the invoice span two pages
|
|
event.settings.set('invoice_additional_text', 'additional invoice text\n' * 100)
|
|
event.settings.set('show_date_to', False)
|
|
inv = generate_invoice(order)
|
|
assert invoice_pdf_task(inv.pk)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_pdf_preview_generation(env):
|
|
event, order = env
|
|
assert build_preview_invoice_pdf(event)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_invoice_numbers(env):
|
|
event, order = env
|
|
order2 = Order.objects.create(
|
|
code='BAR', event=event, email='dummy2@dummy.test',
|
|
status=Order.STATUS_PENDING,
|
|
datetime=now(), expires=now() + timedelta(days=10),
|
|
total=0,
|
|
locale='en'
|
|
)
|
|
order2.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('0.00'),
|
|
tax_value=Decimal('0.00'))
|
|
inv1 = generate_invoice(order)
|
|
inv2 = generate_invoice(order)
|
|
|
|
event.settings.set('invoice_numbers_consecutive', False)
|
|
inv3 = generate_invoice(order)
|
|
inv4 = generate_invoice(order)
|
|
inv21 = generate_invoice(order2)
|
|
inv22 = generate_invoice(order2)
|
|
|
|
event.settings.set('invoice_numbers_consecutive', True)
|
|
inv5 = generate_invoice(order)
|
|
inv23 = generate_invoice(order2)
|
|
|
|
# expected behaviour for switching between numbering formats
|
|
assert inv1.invoice_no == '00001'
|
|
assert inv2.invoice_no == '00002'
|
|
assert inv3.invoice_no == '{}-3'.format(order.code)
|
|
assert inv4.invoice_no == '{}-4'.format(order.code)
|
|
assert inv5.invoice_no == '00003'
|
|
|
|
# test that separate orders are counted separately in this mode
|
|
assert inv21.invoice_no == '{}-1'.format(order2.code)
|
|
assert inv22.invoice_no == '{}-2'.format(order2.code)
|
|
# but consecutively in this mode
|
|
assert inv23.invoice_no == '00004'
|
|
|
|
# test Invoice.number, too
|
|
assert inv1.number == '{}-00001'.format(event.slug.upper())
|
|
assert inv3.number == '{}-{}-3'.format(event.slug.upper(), order.code)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_invoice_number_prefixes(env):
|
|
event, order = env
|
|
event2 = Event.objects.create(
|
|
organizer=event.organizer, name='Dummy', slug='dummy2',
|
|
date_from=now(), plugins='pretix.plugins.banktransfer'
|
|
)
|
|
order2 = Order.objects.create(
|
|
event=event2, email='dummy2@dummy.test',
|
|
status=Order.STATUS_PENDING,
|
|
datetime=now(), expires=now() + timedelta(days=10),
|
|
total=0,
|
|
locale='en'
|
|
)
|
|
order2.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('0.00'),
|
|
tax_value=Decimal('0.00'))
|
|
event.settings.set('invoice_numbers_consecutive', False)
|
|
event2.settings.set('invoice_numbers_consecutive', False)
|
|
assert generate_invoice(order).number == 'DUMMY-{}-1'.format(order.code)
|
|
assert generate_invoice(order2).number == 'DUMMY2-{}-1'.format(order2.code)
|
|
|
|
event.settings.set('invoice_numbers_consecutive', True)
|
|
event2.settings.set('invoice_numbers_consecutive', True)
|
|
event.settings.set('invoice_numbers_prefix', '')
|
|
event2.settings.set('invoice_numbers_prefix', '')
|
|
|
|
assert generate_invoice(order).number == 'DUMMY-00001'
|
|
assert generate_invoice(order).number == 'DUMMY-00002'
|
|
assert generate_invoice(order2).number == 'DUMMY2-00001'
|
|
assert generate_invoice(order2).number == 'DUMMY2-00002'
|
|
|
|
event.settings.set('invoice_numbers_prefix', 'shared_')
|
|
event2.settings.set('invoice_numbers_prefix', 'shared_')
|
|
|
|
assert generate_invoice(order).number == 'shared_00001'
|
|
assert generate_invoice(order2).number == 'shared_00002'
|
|
assert generate_invoice(order).number == 'shared_00003'
|
|
assert generate_invoice(order2).number == 'shared_00004'
|
|
|
|
event.settings.set('invoice_numbers_consecutive', False)
|
|
event2.settings.set('invoice_numbers_consecutive', False)
|
|
assert generate_invoice(order).number == 'shared_{}-6'.format(order.code)
|
|
assert generate_invoice(order2).number == 'shared_{}-6'.format(order2.code)
|
|
|
|
# Test database uniqueness check
|
|
with pytest.raises(DatabaseError):
|
|
with transaction.atomic():
|
|
Invoice.objects.create(
|
|
order=order,
|
|
event=order.event,
|
|
organizer=order.event.organizer,
|
|
date=now().date(),
|
|
locale='en',
|
|
invoice_no='00001',
|
|
)
|