# # This file is part of pretix (Community Edition). # # Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors # # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # Public License as published by the Free Software Foundation in version 3 of the License. # # ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are # applicable granting you additional permissions and placing additional restrictions on your usage of this software. # Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive # this file, see . # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # import datetime import json from decimal import Decimal from unittest import mock import pytest from django.core import mail as djmail from django.core.files.base import ContentFile from django.utils.timezone import now from django_countries.fields import Country from django_scopes import scopes_disabled from tests.const import SAMPLE_PNG from pretix.base.models import ( InvoiceAddress, Order, OrderPosition, Question, SeatingPlan, ) from pretix.base.models.orders import OrderFee from pretix.base.services.invoices import generate_invoice @pytest.fixture def item(event): return event.items.create(name="Budget Ticket", default_price=23) @pytest.fixture def item2(event2): return event2.items.create(name="Budget Ticket", default_price=23) @pytest.fixture def taxrule(event): return event.tax_rules.create(rate=Decimal('19.00')) @pytest.fixture def question(event, item): q = event.questions.create(question="T-Shirt size", type="S", identifier="ABC") q.items.add(item) q.options.create(answer="XL", identifier="LVETRWVU") return q @pytest.fixture def question2(event2, item2): q = event2.questions.create(question="T-Shirt size", type="S", identifier="ABC") q.items.add(item2) return q @pytest.fixture def quota(event, item): q = event.quotas.create(name="Budget Quota", size=200) q.items.add(item) return q @pytest.fixture def seat(event, organizer, item): SeatingPlan.objects.create( name="Plan", organizer=organizer, layout="{}" ) event.seat_category_mappings.create( layout_category='Stalls', product=item ) return event.seats.create(seat_number="A1", product=item, seat_guid="A1") @pytest.fixture def order(event, item, taxrule, question): testtime = datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=datetime.timezone.utc) event.plugins += ",pretix.plugins.stripe" event.save() with mock.patch('django.utils.timezone.now') as mock_now: mock_now.return_value = testtime o = Order.objects.create( code='FOO', event=event, email='dummy@dummy.test', status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1", datetime=datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=datetime.timezone.utc), expires=datetime.datetime(2017, 12, 10, 10, 0, 0, tzinfo=datetime.timezone.utc), total=23, locale='en', sales_channel=event.organizer.sales_channels.get(identifier="web"), ) p1 = o.payments.create( provider='stripe', state='refunded', amount=Decimal('23.00'), payment_date=testtime, ) o.refunds.create( provider='stripe', state='done', source='admin', amount=Decimal('23.00'), execution_date=testtime, payment=p1, ) o.payments.create( provider='banktransfer', state='pending', amount=Decimal('23.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=taxrule) 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=taxrule, canceled=True) InvoiceAddress.objects.create(order=o, company="Sample company", country=Country('NZ'), vat_id="DE123", vat_id_validated=True) op = OrderPosition.objects.create( order=o, item=item, variation=None, price=Decimal("23"), attendee_name_parts={"full_name": "Peter", "_scheme": "full"}, secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w", pseudonymization_id="ABCDEFGHKL", positionid=1, ) OrderPosition.objects.create( order=o, item=item, variation=None, price=Decimal("23"), attendee_name_parts={"full_name": "Peter", "_scheme": "full"}, secret="YBiYJrmF5ufiTLdV1iDf", pseudonymization_id="JKLM", canceled=True, positionid=2, ) op.answers.create(question=question, answer='S') return o @pytest.mark.django_db def test_order_update_ignore_fields(token_client, organizer, event, order): resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'status': 'c' } ) assert resp.status_code == 200 order.refresh_from_db() assert order.status == 'n' @pytest.mark.django_db def test_order_update_only_partial(token_client, organizer, event, order): resp = token_client.put( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'status': 'c' } ) assert resp.status_code == 405 @pytest.mark.django_db def test_order_update_state_validation(token_client, organizer, event, order): resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'invoice_address': { "is_business": False, "company": "This is my company name", "name": "John Doe", "name_parts": {}, "street": "", "state": "", "zipcode": "", "city": "Paris", "country": "NONEXISTANT", "internal_reference": "", "vat_id": "", } } ) assert resp.status_code == 400 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'invoice_address': { "is_business": False, "company": "This is my company name", "name": "John Doe", "name_parts": {}, "street": "", "state": "NONEXISTANT", "zipcode": "", "city": "Test", "country": "AU", "internal_reference": "", "vat_id": "", } } ) assert resp.status_code == 400 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'invoice_address': { "is_business": False, "company": "This is my company name", "name": "John Doe", "name_parts": {}, "street": "", "state": "QLD", "zipcode": "", "city": "Test", "country": "AU", "internal_reference": "", "vat_id": "", } } ) assert resp.status_code == 200 order.invoice_address.refresh_from_db() assert order.invoice_address.state == "QLD" assert order.invoice_address.country == "AU" @pytest.mark.django_db def test_order_update_allowed_fields(token_client, organizer, event, order): event.settings.locales = ['de', 'en'] resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'comment': 'Here is a comment', 'api_meta': { 'test': 1 }, 'valid_if_pending': True, 'custom_followup_at': '2021-06-12', 'checkin_attention': True, 'checkin_text': 'foobar', 'email': 'foo@bar.com', 'phone': '+4962219999', 'locale': 'de', 'invoice_address': { "is_business": False, "company": "This is my company name", "name": "John Doe", "name_parts": {}, "street": "", "state": "", "zipcode": "", "city": "Paris", "country": "FR", "internal_reference": "", "vat_id": "", } } ) assert resp.status_code == 200 order.refresh_from_db() assert order.comment == 'Here is a comment' assert order.api_meta == { 'test': 1 } assert order.custom_followup_at.isoformat() == '2021-06-12' assert order.checkin_attention assert order.checkin_text == 'foobar' assert order.email == 'foo@bar.com' assert order.phone == '+4962219999' assert order.locale == 'de' assert order.valid_if_pending assert order.invoice_address.company == "This is my company name" assert order.invoice_address.name_cached == "John Doe" assert order.invoice_address.name_parts == {'_legacy': 'John Doe'} assert str(order.invoice_address.country) == "FR" assert not order.invoice_address.vat_id_validated assert order.invoice_address.city == "Paris" with scopes_disabled(): assert order.all_logentries().get(action_type='pretix.event.order.comment') assert order.all_logentries().get(action_type='pretix.event.order.custom_followup_at') assert order.all_logentries().get(action_type='pretix.event.order.checkin_attention') assert order.all_logentries().get(action_type='pretix.event.order.checkin_text') assert order.all_logentries().get(action_type='pretix.event.order.contact.changed') assert order.all_logentries().get(action_type='pretix.event.order.phone.changed') assert order.all_logentries().get(action_type='pretix.event.order.locale.changed') assert order.all_logentries().get(action_type='pretix.event.order.modified') @pytest.mark.django_db def test_order_update_validated_vat_id(token_client, organizer, event, order): resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'invoice_address': { "is_business": False, "company": "This is my company name", "name": "John Doe", "name_parts": {}, "street": "", "state": "", "zipcode": "", "city": "Paris", "country": "FR", "internal_reference": "", "vat_id": "FR123", "vat_id_validated": True } } ) assert resp.status_code == 200 order.refresh_from_db() assert order.invoice_address.vat_id == "FR123" assert order.invoice_address.vat_id_validated @pytest.mark.django_db def test_order_update_invoiceaddress_delete_create(token_client, organizer, event, order): event.settings.locales = ['de', 'en'] resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'invoice_address': None, } ) assert resp.status_code == 200 order.refresh_from_db() with pytest.raises(InvoiceAddress.DoesNotExist): order.invoice_address resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'invoice_address': { "is_business": False, "company": "This is my company name", "name": "", "name_parts": {}, "street": "", "state": "", "zipcode": "", "city": "Paris", "country": "Fr", "internal_reference": "", "vat_id": "", } } ) assert resp.status_code == 200 order.refresh_from_db() assert order.invoice_address.company == "This is my company name" assert str(order.invoice_address.country) == "FR" assert order.invoice_address.city == "Paris" @pytest.mark.django_db def test_order_update_email_to_none(token_client, organizer, event, order): resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'email': None, } ) assert resp.status_code == 200 order.refresh_from_db() assert order.email is None @pytest.mark.django_db def test_order_update_locale_to_invalid(token_client, organizer, event, order): resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orders/{}/'.format( organizer.slug, event.slug, order.code ), format='json', data={ 'locale': 'de', } ) assert resp.status_code == 400 assert resp.data == {'locale': ['"de" is not a supported locale for this event.']} @pytest.mark.django_db def test_order_create_invoice(token_client, organizer, event, order): event.settings.invoice_generate = 'True' event.settings.invoice_generate_sales_channels = [] resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/create_invoice/'.format( organizer.slug, event.slug, order.code ), format='json', data={} ) assert resp.status_code == 400 event.settings.invoice_generate_sales_channels = ['web'] resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/create_invoice/'.format( organizer.slug, event.slug, order.code ), format='json', data={} ) assert resp.status_code == 201 with scopes_disabled(): pos = order.positions.first() assert json.loads(json.dumps(resp.data)) == { 'order': 'FOO', 'event': 'dummy', 'number': 'DUMMY-00001', 'is_cancellation': False, "invoice_from_name": "", "invoice_from": "", "invoice_from_zipcode": "", "invoice_from_city": "", "invoice_from_country": None, "invoice_from_tax_id": "", "invoice_from_vat_id": "", "invoice_to": "Sample company\nNew Zealand\nVAT-ID: DE123", "invoice_to_company": "Sample company", "invoice_to_name": "", "invoice_to_street": "", "invoice_to_zipcode": "", "invoice_to_city": "", "invoice_to_state": "", "invoice_to_country": "NZ", "invoice_to_vat_id": "DE123", "invoice_to_beneficiary": "", "custom_field": None, 'date': now().astimezone(event.timezone).date().isoformat(), 'refers': None, 'locale': 'en', 'introductory_text': '', 'additional_text': '', 'payment_provider_text': '', 'payment_provider_stamp': None, 'footer_text': '', 'lines': [ { 'position': 1, 'description': 'Budget Ticket
Attendee: Peter', 'subevent': None, 'event_date_from': '2017-12-27T10:00:00Z', 'event_date_to': None, 'event_location': None, 'fee_type': None, 'fee_internal_type': None, 'attendee_name': 'Peter', 'item': pos.item_id, 'variation': None, 'gross_value': '23.00', 'tax_value': '0.00', 'tax_rate': '0.00', 'tax_code': None, 'tax_name': '' }, { 'position': 2, 'description': 'Payment fee', 'subevent': None, 'event_date_from': '2017-12-27T10:00:00Z', 'event_date_to': None, 'event_location': None, 'fee_type': "payment", 'fee_internal_type': None, 'attendee_name': None, 'item': None, 'variation': None, 'gross_value': '0.25', 'tax_value': '0.05', 'tax_rate': '19.00', 'tax_code': None, 'tax_name': '' } ], 'foreign_currency_display': None, 'foreign_currency_rate': None, 'foreign_currency_rate_date': None, 'internal_reference': '' } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/create_invoice/'.format( organizer.slug, event.slug, order.code ), format='json', data={} ) assert resp.data == {'detail': 'An invoice for this order already exists.'} assert resp.status_code == 400 event.settings.invoice_generate = 'False' resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/create_invoice/'.format( organizer.slug, event.slug, order.code ), format='json', data={} ) assert resp.status_code == 400 assert resp.data == {'detail': 'You cannot generate an invoice for this order.'} @pytest.mark.django_db def test_order_regenerate_secrets(token_client, organizer, event, order): s = order.secret with scopes_disabled(): ps = order.positions.first().secret psw = order.positions.first().web_secret resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/regenerate_secrets/'.format( organizer.slug, event.slug, order.code ), format='json', data={} ) assert resp.status_code == 200 order.refresh_from_db() assert s != order.secret with scopes_disabled(): assert ps != order.positions.first().secret assert psw != order.positions.first().web_secret @pytest.mark.django_db def test_position_regenerate_secrets(token_client, organizer, event, order): with scopes_disabled(): p = order.positions.first() ps = p.secret psw = p.web_secret resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/regenerate_secrets/'.format( organizer.slug, event.slug, p.pk, ), format='json', data={} ) assert resp.status_code == 200 p.refresh_from_db() with scopes_disabled(): assert ps != p.secret assert psw != p.web_secret @pytest.mark.django_db def test_position_manage_blocks(token_client, organizer, event, order): with scopes_disabled(): p = order.positions.first() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/add_block/'.format( organizer.slug, event.slug, p.pk, ), format='json', data={ 'name': 'invalid' } ) assert resp.status_code == 400 resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/add_block/'.format( organizer.slug, event.slug, p.pk, ), format='json', data={ 'name': 'admin' } ) assert resp.status_code == 200 p.refresh_from_db() assert p.blocked == ['admin'] resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/add_block/'.format( organizer.slug, event.slug, p.pk, ), format='json', data={ 'name': 'api:custom' } ) assert resp.status_code == 200 p.refresh_from_db() assert p.blocked == ['admin', 'api:custom'] resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/remove_block/'.format( organizer.slug, event.slug, p.pk, ), format='json', data={ 'name': 'api:custom' } ) assert resp.status_code == 200 p.refresh_from_db() assert p.blocked == ['admin'] resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/remove_block/'.format( organizer.slug, event.slug, p.pk, ), format='json', data={ 'name': 'admin' } ) assert resp.status_code == 200 p.refresh_from_db() assert p.blocked is None @pytest.mark.django_db def test_order_resend_link(token_client, organizer, event, order): djmail.outbox = [] resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/resend_link/'.format( organizer.slug, event.slug, order.code ), format='json', data={} ) assert resp.status_code == 204 assert len(djmail.outbox) == 1 order.email = None order.save() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/resend_link/'.format( organizer.slug, event.slug, order.code ), format='json', data={} ) assert resp.status_code == 400 @pytest.mark.django_db def test_orderposition_price_calculation(token_client, organizer, event, order, item): with scopes_disabled(): op = order.positions.first() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('23.00'), 'gross_formatted': '23.00', 'name': '', 'net': Decimal('23.00'), 'rate': Decimal('0.00'), 'tax_rule': None, 'tax': Decimal('0.00') } @pytest.mark.django_db def test_orderposition_price_calculation_item_with_tax(token_client, organizer, event, order, item, taxrule): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23, tax_rule=taxrule) op = order.positions.first() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('23.00'), 'gross_formatted': '23.00', 'name': '', 'net': Decimal('19.33'), 'rate': Decimal('19.00'), 'tax_rule': taxrule.pk, 'tax': Decimal('3.67') } @pytest.mark.django_db def test_orderposition_price_calculation_item_with_variation(token_client, organizer, event, order): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) var = item2.variations.create(default_price=12, value="XS") op = order.positions.first() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk, 'variation': var.pk } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('12.00'), 'gross_formatted': '12.00', 'name': '', 'net': Decimal('12.00'), 'rate': Decimal('0.00'), 'tax_rule': None, 'tax': Decimal('0.00') } @pytest.mark.django_db def test_orderposition_price_calculation_subevent(token_client, organizer, event, order, subevent): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) op = order.positions.first() op.subevent = subevent op.save() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk, 'subevent': subevent.pk } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('23.00'), 'gross_formatted': '23.00', 'name': '', 'net': Decimal('23.00'), 'rate': Decimal('0.00'), 'tax_rule': None, 'tax': Decimal('0.00') } @pytest.mark.django_db def test_orderposition_price_calculation_subevent_with_override(token_client, organizer, event, order, subevent): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) se2 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=datetime.timezone.utc)) se2.subeventitem_set.create(item=item2, price=12) op = order.positions.first() op.subevent = subevent op.save() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk, 'subevent': se2.pk } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('12.00'), 'gross_formatted': '12.00', 'name': '', 'net': Decimal('12.00'), 'rate': Decimal('0.00'), 'tax_rule': None, 'tax': Decimal('0.00') } @pytest.mark.django_db def test_orderposition_price_calculation_voucher_matching(token_client, organizer, event, order, subevent, item): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) q = event.quotas.create(name="Quota") q.items.add(item) q.items.add(item2) voucher = event.vouchers.create(price_mode="set", value=15, quota=q) op = order.positions.first() op.voucher = voucher op.save() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk, } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('15.00'), 'gross_formatted': '15.00', 'name': '', 'net': Decimal('15.00'), 'rate': Decimal('0.00'), 'tax_rule': None, 'tax': Decimal('0.00') } @pytest.mark.django_db def test_orderposition_price_calculation_voucher_not_matching(token_client, organizer, event, order, subevent, item): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) q = event.quotas.create(name="Quota") q.items.add(item) voucher = event.vouchers.create(price_mode="set", value=15, quota=q) op = order.positions.first() op.voucher = voucher op.save() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk, } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('23.00'), 'gross_formatted': '23.00', 'name': '', 'net': Decimal('23.00'), 'rate': Decimal('0.00'), 'tax_rule': None, 'tax': Decimal('0.00') } @pytest.mark.django_db def test_orderposition_price_calculation_net_price(token_client, organizer, event, order, subevent, item, taxrule): taxrule.price_includes_tax = False taxrule.save() with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=10, tax_rule=taxrule) op = order.positions.first() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk, } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('11.90'), 'gross_formatted': '11.90', 'name': '', 'net': Decimal('10.00'), 'rate': Decimal('19.00'), 'tax_rule': taxrule.pk, 'tax': Decimal('1.90') } @pytest.mark.django_db def test_orderposition_price_calculation_reverse_charge(token_client, organizer, event, order, subevent, item, taxrule): taxrule.price_includes_tax = False taxrule.eu_reverse_charge = True taxrule.home_country = Country('DE') taxrule.save() order.invoice_address.is_business = True order.invoice_address.vat_id = 'ATU1234567' order.invoice_address.vat_id_validated = True order.invoice_address.country = Country('AT') order.invoice_address.save() with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=10, tax_rule=taxrule) op = order.positions.first() resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk), data={ 'item': item2.pk, } ) assert resp.status_code == 200 assert resp.data == { 'gross': Decimal('10.00'), 'gross_formatted': '10.00', 'name': '', 'net': Decimal('10.00'), 'rate': Decimal('0.00'), 'tax_rule': taxrule.pk, 'tax': Decimal('0.00') } @pytest.mark.django_db def test_position_update_ignore_fields(token_client, organizer, event, order): with scopes_disabled(): op = order.positions.first() resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data={ 'tax_rate': '99.99' } ) assert resp.status_code == 200 op.refresh_from_db() assert op.tax_rate == Decimal('0.00') @pytest.mark.django_db def test_position_update_only_partial(token_client, organizer, event, order): with scopes_disabled(): op = order.positions.first() resp = token_client.put( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data={ 'price': '99.99' } ) assert resp.status_code == 405 @pytest.mark.django_db def test_position_update_info(token_client, organizer, event, order, question): with scopes_disabled(): op = order.positions.first() question.type = Question.TYPE_CHOICE_MULTIPLE question.save() opt = question.options.create(answer="L") payload = { 'company': 'VILE', 'attendee_name_parts': { 'full_name': 'Max Mustermann' }, 'street': 'Sesame Street 21', 'zipcode': '99999', 'city': 'Springfield', 'country': 'US', 'state': 'CA', 'attendee_email': 'foo@example.org', 'answers': [ { 'question': question.pk, 'answer': 'ignored', 'options': [opt.pk] } ] } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 assert resp.data['answers'] == [ { 'question': question.pk, 'question_identifier': question.identifier, 'answer': 'L', 'options': [opt.pk], 'option_identifiers': [opt.identifier], } ] op.refresh_from_db() assert op.company == 'VILE' assert op.attendee_name_cached == 'Max Mustermann' assert op.attendee_name_parts == { '_scheme': 'full', 'full_name': 'Max Mustermann' } with scopes_disabled(): assert op.answers.get().answer == 'L' assert op.street == 'Sesame Street 21' assert op.zipcode == '99999' assert op.city == 'Springfield' assert str(op.country) == 'US' assert op.state == 'CA' assert op.attendee_email == 'foo@example.org' le = order.all_logentries().last() assert le.action_type == 'pretix.event.order.modified' assert le.parsed_data == { 'data': [ { 'position': op.pk, 'company': 'VILE', 'attendee_name_parts': { '_scheme': 'full', 'full_name': 'Max Mustermann' }, 'street': 'Sesame Street 21', 'zipcode': '99999', 'city': 'Springfield', 'country': 'US', 'state': 'CA', 'attendee_email': 'foo@example.org', f'question_{question.pk}': 'L' } ] } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): assert order.all_logentries().last().pk == le.pk @pytest.mark.django_db def test_position_update_legacy_name(token_client, organizer, event, order): with scopes_disabled(): op = order.positions.first() payload = { 'attendee_name': 'Max Mustermann', 'attendee_name_parts': { '_legacy': 'maria' }, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 payload = { 'attendee_name': 'Max Mustermann', } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.attendee_name_cached == 'Max Mustermann' assert op.attendee_name_parts == { '_legacy': 'Max Mustermann' } with scopes_disabled(): assert op.answers.count() == 1 # answer does not get deleted @pytest.mark.django_db def test_position_update_state_validation(token_client, organizer, event, order): with scopes_disabled(): op = order.positions.first() payload = { 'country': 'DE', 'state': 'BW' } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 @pytest.mark.django_db def test_position_update_question_handling(token_client, organizer, event, order, question): with scopes_disabled(): op = order.positions.first() payload = { 'answers': [ { 'question': question.pk, 'answer': 'FOOBAR', }, { 'question': question.pk, 'answer': 'FOOBAR', }, ] } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 payload = { 'answers': [ { 'question': question.pk, 'answer': 'FOOBAR', }, ] } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): assert op.answers.count() == 1 payload = { 'answers': [ ] } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): assert op.answers.count() == 0 r = token_client.post( '/api/v1/upload', data={ 'media_type': 'image/png', 'file': ContentFile(SAMPLE_PNG) }, format='upload', HTTP_CONTENT_DISPOSITION='attachment; filename="file.png"', ) assert r.status_code == 201 file_id_png = r.data['id'] payload = { 'answers': [ { "question": question.id, "answer": file_id_png } ] } question.type = Question.TYPE_FILE question.save() resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): answ = op.answers.get() assert answ.file assert answ.answer.startswith("file://") payload = { 'answers': [ { "question": question.id, "answer": "file:keep" } ] } question.type = Question.TYPE_FILE question.save() resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): answ = op.answers.get() assert answ.file assert answ.answer.startswith("file://") @pytest.mark.django_db def test_position_update_change_item(token_client, organizer, event, order, quota): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) quota.items.add(item2) op = order.positions.first() payload = { 'item': item2.pk, } assert op.item != item2 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.item == item2 @pytest.mark.django_db def test_position_update_change_item_wrong_event(token_client, organizer, event, event2, order, quota): with scopes_disabled(): item2 = event2.items.create(name="Budget Ticket", default_price=23) quota.items.add(item2) op = order.positions.first() payload = { 'item': item2.pk, } assert op.item != item2 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 assert 'object does not exist.' in str(resp.data) @pytest.mark.django_db def test_position_update_change_item_no_quota(token_client, organizer, event, order): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) op = order.positions.first() payload = { 'item': item2.pk, } assert op.item != item2 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 assert 'quota' in str(resp.data) @pytest.mark.django_db def test_position_update_change_item_variation(token_client, organizer, event, order, quota): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) v = item2.variations.create(value="foo") quota.items.add(item2) quota.variations.add(v) op = order.positions.first() payload = { 'item': item2.pk, 'variation': v.pk, } assert op.item != item2 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.item == item2 assert op.variation == v @pytest.mark.django_db def test_position_update_change_item_variation_required(token_client, organizer, event, order, quota): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) v = item2.variations.create(value="foo") quota.items.add(item2) quota.variations.add(v) op = order.positions.first() payload = { 'item': item2.pk, } assert op.item != item2 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 assert 'variation' in str(resp.data) @pytest.mark.django_db def test_position_update_change_item_variation_mismatch(token_client, organizer, event, order, quota): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) v = item2.variations.create(value="foo") item3 = event.items.create(name="Budget Ticket", default_price=23) v3 = item3.variations.create(value="foo") quota.items.add(item2) quota.items.add(item3) quota.variations.add(v) quota.variations.add(v3) op = order.positions.first() payload = { 'item': item2.pk, 'variation': v3.pk, } assert op.item != item2 resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 assert 'variation' in str(resp.data) @pytest.mark.django_db def test_position_update_change_subevent(token_client, organizer, event, order, quota, item, subevent): with scopes_disabled(): se2 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=datetime.timezone.utc)) q2 = se2.quotas.create(name="foo", size=1, event=event) q2.items.add(item) op = order.positions.first() op.subevent = subevent op.save() payload = { 'subevent': se2.pk, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.subevent == se2 @pytest.mark.django_db def test_position_update_change_subevent_quota_empty(token_client, organizer, event, order, quota, item, subevent): with scopes_disabled(): se2 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=datetime.timezone.utc)) q2 = se2.quotas.create(name="foo", size=0, event=event) q2.items.add(item) op = order.positions.first() op.subevent = subevent op.save() payload = { 'subevent': se2.pk, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 assert 'quota' in str(resp.data) @pytest.mark.django_db def test_position_update_change_seat(token_client, organizer, event, order, quota, item, seat): with scopes_disabled(): seat2 = event.seats.create(seat_number="A2", product=item, seat_guid="A2") op = order.positions.first() op.seat = seat op.save() payload = { 'seat': seat2.seat_guid, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.seat == seat2 @pytest.mark.django_db def test_position_update_unset_seat(token_client, organizer, event, order, quota, item, seat): with scopes_disabled(): op = order.positions.first() op.seat = seat op.save() payload = { 'seat': None, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.seat is None @pytest.mark.django_db def test_position_update_change_seat_taken(token_client, organizer, event, order, quota, item, seat): with scopes_disabled(): seat2 = event.seats.create(seat_number="A2", product=item, seat_guid="A2", blocked=True) op = order.positions.first() op.seat = seat op.save() payload = { 'seat': seat2.seat_guid, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 assert 'seat' in str(resp.data) @pytest.mark.django_db def test_position_update_change_subevent_keep_seat(token_client, organizer, event, order, quota, item, subevent, seat): with scopes_disabled(): seat.subevent = subevent seat.save() se2 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=datetime.timezone.utc)) seat2 = event.seats.create(seat_number="A1", product=item, seat_guid="A1", subevent=se2) q2 = se2.quotas.create(name="foo", size=1, event=event) q2.items.add(item) op = order.positions.first() op.subevent = subevent op.seat = seat op.save() payload = { 'subevent': se2.pk, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.subevent == se2 assert op.seat == seat2 @pytest.mark.django_db def test_position_update_change_subevent_missing_seat(token_client, organizer, event, order, quota, item, subevent, seat): with scopes_disabled(): seat.subevent = subevent seat.save() se2 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=datetime.timezone.utc)) q2 = se2.quotas.create(name="foo", size=1, event=event) q2.items.add(item) op = order.positions.first() op.subevent = subevent op.seat = seat op.save() payload = { 'subevent': se2.pk, } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 400 assert 'seat' in str(resp.data) @pytest.mark.django_db def test_position_update_change_price(token_client, organizer, event, order, quota): with scopes_disabled(): op = order.positions.first() payload = { 'price': Decimal('119.00') } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.price == Decimal('119.00') assert op.tax_rate == Decimal('0.00') assert op.tax_value == Decimal('0.00') @pytest.mark.django_db def test_position_update_change_price_and_tax_rule(token_client, organizer, event, order, quota): with scopes_disabled(): op = order.positions.first() tr = event.tax_rules.create(rate=19) payload = { 'price': Decimal('119.00'), 'tax_rule': tr.pk } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.price == Decimal('119.00') assert op.tax_rate == Decimal('19.00') assert op.tax_value == Decimal('19.00') assert op.tax_rule == tr @pytest.mark.django_db def test_position_add_simple(token_client, organizer, event, order, quota, item): with scopes_disabled(): assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item.pk, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 201 with scopes_disabled(): assert order.positions.count() == 2 op = order.positions.last() assert op.item == item assert op.price == item.default_price assert op.positionid == 3 @pytest.mark.django_db def test_position_add_price(token_client, organizer, event, order, quota, item): with scopes_disabled(): assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item.pk, 'price': '99.99' } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 201 with scopes_disabled(): assert order.positions.count() == 2 op = order.positions.last() assert op.item == item assert op.price == Decimal('99.99') assert op.positionid == 3 @pytest.mark.django_db def test_position_add_subevent(token_client, organizer, event, order, quota, item, subevent): with scopes_disabled(): assert order.positions.count() == 1 quota.subevent = subevent quota.save() payload = { 'order': order.code, 'item': item.pk, 'subevent': subevent.pk, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 201 with scopes_disabled(): assert order.positions.count() == 2 op = order.positions.last() assert op.item == item assert op.price == item.default_price assert op.positionid == 3 assert op.subevent == subevent @pytest.mark.django_db def test_position_add_subevent_required(token_client, organizer, event, order, quota, item, subevent): with scopes_disabled(): assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item.pk, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 400 assert 'subevent' in str(resp.data) @pytest.mark.django_db def test_position_add_quota_empty(token_client, organizer, event, order, quota, item): with scopes_disabled(): assert order.positions.count() == 1 quota.size = 1 quota.save() payload = { 'order': order.code, 'item': item.pk, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 400 assert 'quota' in str(resp.data) @pytest.mark.django_db def test_position_add_seat(token_client, organizer, event, order, quota, item, seat): with scopes_disabled(): assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item.pk, 'seat': seat.seat_guid, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 201 with scopes_disabled(): assert order.positions.count() == 2 op = order.positions.last() assert op.item == item assert op.price == item.default_price assert op.positionid == 3 assert op.seat == seat @pytest.mark.django_db def test_position_add_seat_required(token_client, organizer, event, order, quota, item, seat): with scopes_disabled(): assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item.pk, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 400 assert 'seat' in str(resp.data) @pytest.mark.django_db def test_position_add_addon_to(token_client, organizer, event, order, quota, item): with scopes_disabled(): cat = event.categories.create(name="Workshops") item2 = event.items.create(name="WS1", default_price=23, category=cat) quota.items.add(item2) item.addons.create(addon_category=cat) assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item2.pk, 'addon_to': 1, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 201 with scopes_disabled(): assert order.positions.count() == 2 op = order.positions.last() assert op.positionid == 3 assert op.addon_to.positionid == 1 @pytest.mark.django_db def test_position_add_addon_to_canceled_position(token_client, organizer, event, order, quota, item): with scopes_disabled(): cat = event.categories.create(name="Workshops") item2 = event.items.create(name="WS1", default_price=23, category=cat) quota.items.add(item2) item.addons.create(addon_category=cat) assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item2.pk, 'addon_to': 2, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 400 assert 'unknown position' in str(resp.data) @pytest.mark.django_db def test_position_add_addon_to_wrong_product(token_client, organizer, event, order, quota, item): with scopes_disabled(): assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item.pk, 'addon_to': 1, } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 400 assert 'selected base position does not allow you to add this product as an add-on' in str(resp.data) @pytest.mark.django_db def test_position_add_and_set_info(token_client, organizer, event, order, question, quota, item): with scopes_disabled(): assert order.positions.count() == 1 payload = { 'order': order.code, 'item': item.pk, 'attendee_name': 'John Doe', 'valid_from': '2022-12-12T12:12:12+00:00', 'valid_until': '2022-12-12T13:12:12+00:00', 'answers': [ { 'question': question.pk, 'answer': 'FOOBAR', }, ] } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orderpositions/'.format( organizer.slug, event.slug, ), format='json', data=payload ) assert resp.status_code == 201 with scopes_disabled(): assert order.positions.count() == 2 op = order.positions.last() assert op.item == item assert op.price == item.default_price assert op.positionid == 3 assert op.attendee_name == 'John Doe' assert op.answers.count() == 1 assert op.valid_from.isoformat() == '2022-12-12T12:12:12+00:00' assert op.valid_until.isoformat() == '2022-12-12T13:12:12+00:00' @pytest.mark.django_db def test_position_update_validity(token_client, organizer, event, order, quota, item, subevent): with scopes_disabled(): op = order.positions.get() payload = { 'valid_from': '2022-12-12T12:12:12+00:00', 'valid_until': '2022-12-12T13:12:12+00:00', } resp = token_client.patch( '/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format( organizer.slug, event.slug, op.pk ), format='json', data=payload ) assert resp.status_code == 200 op.refresh_from_db() assert op.valid_from.isoformat() == '2022-12-12T12:12:12+00:00' assert op.valid_until.isoformat() == '2022-12-12T13:12:12+00:00' @pytest.mark.django_db def test_order_change_patch(token_client, organizer, event, order, quota): with scopes_disabled(): item2 = event.items.create(name="Budget Ticket", default_price=23) quota.items.add(item2) p = order.positions.first() f = order.fees.first() payload = { 'patch_positions': [ { 'position': p.pk, 'body': { 'item': item2.pk, 'price': '99.44', }, }, ], 'patch_fees': [ { 'fee': f.pk, 'body': { 'value': '10.00', } } ] } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): p.refresh_from_db() assert p.price == Decimal('99.44') assert p.item == item2 f.refresh_from_db() assert f.value == Decimal('10.00') order.refresh_from_db() assert order.total == Decimal('109.44') @pytest.mark.django_db def test_order_change_cancel_and_create(token_client, organizer, event, order, quota, item): with scopes_disabled(): p = order.positions.first() f = order.fees.first() quota.size = 0 quota.save() payload = { 'cancel_positions': [ { 'position': p.pk, }, ], 'create_positions': [ { 'item': item.pk, 'price': '99.99' }, ], 'cancel_fees': [ { 'fee': f.pk, } ] } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): p.refresh_from_db() assert p.canceled p_new = order.positions.last() assert p_new != p assert p_new.item == item assert p_new.price == Decimal('99.99') f.refresh_from_db() assert f.canceled @pytest.mark.django_db def test_order_change_send_email_reissue_invoice(token_client, organizer, event, order, quota, item): djmail.outbox = [] with scopes_disabled(): f = order.fees.first() generate_invoice(order) payload = { 'send_email': False, 'reissue_invoice': True, 'create_positions': [ { 'item': item.pk, 'price': '99.99' }, ], } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 assert len(djmail.outbox) == 0 with scopes_disabled(): assert order.invoices.count() == 3 payload = { 'send_email': True, 'reissue_invoice': False, 'cancel_fees': [ { 'fee': f.pk, } ] } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 assert len(djmail.outbox) == 1 with scopes_disabled(): assert order.invoices.count() == 3 @pytest.mark.django_db def test_order_change_recalculate_taxes(token_client, organizer, event, order, quota, item): djmail.outbox = [] with scopes_disabled(): tax_rule = event.tax_rules.create(rate=7) p = order.positions.first() p.tax_rule = tax_rule p.save() assert p.tax_rate == 0 payload = { 'recalculate_taxes': 'keep_gross', } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): p.refresh_from_db() assert p.tax_rule == tax_rule assert p.tax_rate == Decimal('7.00') assert p.price == Decimal('23.00') assert p.tax_value == Decimal('1.50') tax_rule.rate = 10 tax_rule.save() payload = { 'recalculate_taxes': 'keep_net', } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): p.refresh_from_db() assert p.tax_rule == tax_rule assert p.tax_rate == Decimal('10.00') assert p.price == Decimal('23.65') assert p.tax_value == Decimal('2.15') @pytest.mark.django_db def test_order_change_split(token_client, organizer, event, order): djmail.outbox = [] with scopes_disabled(): p_canceled = order.all_positions.filter(canceled=True).first() p_canceled.canceled = False p_canceled.save() assert event.orders.count() == 1 payload = { 'split_positions': [ {'position': p_canceled.pk} ] } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): assert event.orders.count() == 2 @pytest.mark.django_db def test_order_change_invalid_input(token_client, organizer, event, order, quota, item, item2): djmail.outbox = [] with scopes_disabled(): tax_rule = event.tax_rules.create(rate=7) p = order.positions.first() p_canceled = order.all_positions.filter(canceled=True).first() f_canceled = order.all_fees.filter(canceled=True).first() p.tax_rule = tax_rule p.save() assert p.tax_rate == 0 payload = { 'cancel_fees': [ {'fee': f_canceled.pk} ] } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert 'does not exist' in str(resp.data) assert resp.status_code == 400 payload = { 'patch_positions': [ {'position': p_canceled.pk, 'body': {'price': '99.00'}} ], } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert 'does not exist' in str(resp.data) assert resp.status_code == 400 payload = { 'patch_positions': [ {'position': p.pk, 'body': {'item': item2.pk}} ], } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert 'does not exist' in str(resp.data) assert resp.status_code == 400 payload = { 'cancel_positions': [ {'position': p.pk} ], } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert 'empty' in str(resp.data) assert resp.status_code == 400 payload = { 'split_positions': [ {'position': p.pk} ], } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert 'empty' in str(resp.data) assert resp.status_code == 400 payload = { 'patch_positions': [ {'position': p.pk, 'body': {}}, {'position': p.pk, 'body': {}}, ], } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert 'twice' in str(resp.data) assert resp.status_code == 400 @pytest.mark.django_db def test_order_change_create_addon(token_client, organizer, event, order, quota, item): with scopes_disabled(): cat = event.categories.create(name="Workshops") item2 = event.items.create(name="WS1", default_price=23, category=cat) quota.items.add(item2) item.addons.create(addon_category=cat) assert order.positions.count() == 1 payload = { 'create_positions': [ { 'item': item2.pk, 'addon_to': 1, }, ], } resp = token_client.post( '/api/v1/organizers/{}/events/{}/orders/{}/change/'.format( organizer.slug, event.slug, order.code, ), format='json', data=payload ) assert resp.status_code == 200 with scopes_disabled(): assert order.positions.count() == 2 op = order.positions.last() assert op.positionid == 3 assert op.addon_to.positionid == 1