from datetime import timedelta from decimal import Decimal from unittest import mock import pytest from bs4 import BeautifulSoup from django.core import mail from django.utils.timezone import now from django_countries.fields import Country from django_scopes import scopes_disabled from tests.base import SoupTest from tests.plugins.stripe.test_provider import MockedCharge from pretix.base.models import ( Event, GiftCard, InvoiceAddress, Item, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, Organizer, Question, QuestionAnswer, Quota, Team, User, ) from pretix.base.payment import PaymentException from pretix.base.services.invoices import ( generate_cancellation, generate_invoice, ) @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,pretix.plugins.stripe,tests.testdummy' ) event.settings.set('ticketoutput_testdummy__enabled', True) user = User.objects.create_user('dummy@dummy.dummy', 'dummy') t = Team.objects.create(organizer=o, can_view_orders=True, can_change_orders=True) t.members.add(user) t.limit_events.add(event) o = Order.objects.create( code='FOO', event=event, email='dummy@dummy.test', status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10), total=14, locale='en' ) o.payments.create( amount=o.total, provider='banktransfer', state=OrderPayment.PAYMENT_STATE_PENDING ) ticket = Item.objects.create(event=event, name='Early-bird ticket', category=None, default_price=23, admission=True) event.settings.set('attendee_names_asked', True) event.settings.set('locales', ['en', 'de']) OrderPosition.objects.create( order=o, item=ticket, variation=None, price=Decimal("14"), attendee_name_parts={'full_name': "Peter", "_scheme": "full"} ) OrderPosition.objects.create( order=o, item=ticket, variation=None, price=Decimal("14"), canceled=True, attendee_name_parts={'full_name': "Lukas Gelöscht", "_scheme": "full"} ) return event, user, o, ticket @pytest.mark.django_db def test_order_list(client, env): with scopes_disabled(): otherticket = Item.objects.create(event=env[0], name='Early-bird ticket', category=None, default_price=23, admission=True) client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/') assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?query=peter') assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?query=hans') assert 'FOO' not in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?query=dummy') assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?status=p') assert 'FOO' not in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?status=n') assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?status=ne') assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?item=%s' % otherticket.id) assert 'FOO' not in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?item=%s' % env[3].id) assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?provider=free') assert 'FOO' not in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?provider=banktransfer') assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?status=o') assert 'FOO' not in response.content.decode() env[2].expires = now() - timedelta(days=10) env[2].save() response = client.get('/control/event/dummy/dummy/orders/?status=o') assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?status=pa') assert 'FOO' not in response.content.decode() env[2].require_approval = True env[2].save() response = client.get('/control/event/dummy/dummy/orders/?status=pa') assert 'FOO' in response.content.decode() with scopes_disabled(): q = Question.objects.create(event=env[0], question="Q", type="N", required=True) q.items.add(env[3]) op = env[2].positions.first() qa = QuestionAnswer.objects.create(question=q, orderposition=op, answer="12") response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=12' % q.pk) assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=13' % q.pk) assert 'FOO' not in response.content.decode() q.type = "C" q.save() with scopes_disabled(): qo1 = q.options.create(answer="Foo") qo2 = q.options.create(answer="Bar") qa.options.add(qo1) response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=%d' % (q.pk, qo1.pk)) assert 'FOO' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=%d' % (q.pk, qo2.pk)) assert 'FOO' not in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/?status=testmode') assert 'FOO' not in response.content.decode() assert 'TEST MODE' not in response.content.decode() env[2].testmode = True env[2].save() response = client.get('/control/event/dummy/dummy/orders/?status=testmode') assert 'FOO' in response.content.decode() assert 'TEST MODE' in response.content.decode() @pytest.mark.django_db def test_order_detail(client, env): client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/') assert 'Early-bird' in response.content.decode() assert 'Peter' in response.content.decode() assert 'Lukas Gelöscht' in response.content.decode() assert 'TEST MODE' not in response.content.decode() @pytest.mark.django_db def test_order_detail_show_test_mode(client, env): env[2].testmode = True env[2].save() client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/') assert 'TEST MODE' in response.content.decode() @pytest.mark.django_db def test_order_set_contact(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/contact', { 'email': 'admin@rami.io' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.email == 'admin@rami.io' @pytest.mark.django_db def test_order_set_locale(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/locale', { 'locale': 'de' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.locale == 'de' @pytest.mark.django_db def test_order_set_locale_with_invalid_locale_value(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/locale', { 'locale': 'fr' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.locale == 'en' @pytest.mark.django_db def test_order_set_comment(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/comment', { 'comment': 'Foo' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.comment == 'Foo' @pytest.mark.django_db def test_order_transition_to_expired_success(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'e' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_EXPIRED @pytest.mark.django_db def test_order_transition_to_paid_in_time_success(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'amount': str(env[2].pending_sum), 'payment_date': now().date().isoformat(), 'status': 'p' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PAID @pytest.mark.django_db def test_order_transition_to_paid_expired_quota_left(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_EXPIRED o.save() q = Quota.objects.create(event=env[0], size=10) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') res = client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'p', 'payment_date': now().date().isoformat(), 'amount': str(o.pending_sum), }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert res.status_code < 400 assert o.status == Order.STATUS_PAID @pytest.mark.django_db def test_order_approve(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_PENDING o.require_approval = True o.save() q = Quota.objects.create(event=env[0], size=10) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') res = client.post('/control/event/dummy/dummy/orders/FOO/approve', { }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert res.status_code < 400 assert o.status == Order.STATUS_PENDING assert not o.require_approval @pytest.mark.django_db def test_order_deny(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_PENDING o.require_approval = True o.save() q = Quota.objects.create(event=env[0], size=10) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') res = client.post('/control/event/dummy/dummy/orders/FOO/deny', { }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert res.status_code < 400 assert o.status == Order.STATUS_CANCELED assert o.require_approval @pytest.mark.django_db def test_order_delete_require_testmode(client, env): client.login(email='dummy@dummy.dummy', password='dummy') res = client.get('/control/event/dummy/dummy/orders/FOO/delete', {}, follow=True) assert 'alert-danger' in res.content.decode() assert 'Only orders created in test mode can be deleted' in res.content.decode() client.post('/control/event/dummy/dummy/orders/FOO/delete', {}, follow=True) with scopes_disabled(): assert Order.objects.get(id=env[2].id) @pytest.mark.django_db def test_order_delete(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.testmode = True o.save() client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/delete', {}, follow=True) with scopes_disabled(): assert not Order.objects.filter(id=env[2].id).exists() @pytest.mark.django_db @pytest.mark.parametrize("process", [ # (Old status, new status, success expected) (Order.STATUS_CANCELED, Order.STATUS_PAID, False), (Order.STATUS_CANCELED, Order.STATUS_PENDING, False), (Order.STATUS_CANCELED, Order.STATUS_EXPIRED, False), (Order.STATUS_PAID, Order.STATUS_PENDING, False), (Order.STATUS_PAID, Order.STATUS_CANCELED, True), (Order.STATUS_PAID, Order.STATUS_EXPIRED, False), (Order.STATUS_PENDING, Order.STATUS_CANCELED, True), (Order.STATUS_PENDING, Order.STATUS_PAID, True), (Order.STATUS_PENDING, Order.STATUS_EXPIRED, True), ]) def test_order_transition(client, env, process): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = process[0] o.save() client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/transition?status=' + process[1]) client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'amount': str(o.pending_sum), 'payment_date': now().date().isoformat(), 'status': process[1] }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) if process[2]: assert o.status == process[1] else: assert o.status == process[0] @pytest.mark.django_db def test_order_cancel_free(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_PAID o.total = Decimal('0.00') o.save() client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c') client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'c' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_CANCELED @pytest.mark.django_db def test_order_cancel_paid_keep_fee(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=o.total) o.status = Order.STATUS_PAID o.save() tr7 = o.event.tax_rules.create(rate=Decimal('7.00')) o.event.settings.tax_rate_default = tr7 client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c') client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'c', 'cancellation_fee': '6.00' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert not o.positions.exists() assert o.all_positions.exists() f = o.fees.get() assert f.fee_type == OrderFee.FEE_TYPE_CANCELLATION assert f.value == Decimal('6.00') assert f.tax_value == Decimal('0.39') assert f.tax_rate == Decimal('7') assert f.tax_rule == tr7 assert o.status == Order.STATUS_PAID assert o.total == Decimal('6.00') assert o.pending_sum == Decimal('-8.00') @pytest.mark.django_db def test_order_cancel_pending_keep_fee(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=Decimal('8.00')) o.status = Order.STATUS_PENDING o.save() client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c') client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'c', 'cancellation_fee': '6.00' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert not o.positions.exists() assert o.all_positions.exists() f = o.fees.get() assert f.fee_type == OrderFee.FEE_TYPE_CANCELLATION assert f.value == Decimal('6.00') assert o.status == Order.STATUS_PAID assert o.total == Decimal('6.00') assert o.pending_sum == Decimal('-2.00') @pytest.mark.django_db def test_order_cancel_pending_fee_too_high(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=Decimal('4.00')) o.status = Order.STATUS_PENDING o.save() client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c') client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'c', 'cancellation_fee': '6.00' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.positions.exists() assert not o.fees.exists() assert o.status == Order.STATUS_PENDING assert o.total == Decimal('14.00') @pytest.mark.django_db def test_order_cancel_unpaid_no_fees_allowed(client, env): client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c') client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'c', 'cancellation_fee': '6.00' }) with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.positions.exists() assert not o.fees.exists() assert o.status == Order.STATUS_CANCELED assert o.total == Decimal('14.00') @pytest.mark.django_db def test_order_invoice_create_forbidden(client, env): client.login(email='dummy@dummy.dummy', password='dummy') env[0].settings.set('invoice_generate', 'no') response = client.post('/control/event/dummy/dummy/orders/FOO/invoice', {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_invoice_create_duplicate(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): generate_invoice(env[2]) env[0].settings.set('invoice_generate', 'admin') response = client.post('/control/event/dummy/dummy/orders/FOO/invoice', {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_invoice_create_ok(client, env): client.login(email='dummy@dummy.dummy', password='dummy') env[0].settings.set('invoice_generate', 'admin') response = client.post('/control/event/dummy/dummy/orders/FOO/invoice', {}, follow=True) assert 'alert-success' in response.content.decode() with scopes_disabled(): assert env[2].invoices.exists() @pytest.mark.django_db def test_order_invoice_regenerate(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): i = generate_invoice(env[2]) InvoiceAddress.objects.create(name_parts={'full_name': 'Foo', "_scheme": "full"}, order=env[2]) env[0].settings.set('invoice_generate', 'admin') response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/regenerate' % i.pk, {}, follow=True) assert 'alert-success' in response.content.decode() i.refresh_from_db() assert 'Foo' in i.invoice_to with scopes_disabled(): assert env[2].invoices.exists() @pytest.mark.django_db def test_order_invoice_regenerate_canceled(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): i = generate_invoice(env[2]) generate_cancellation(i) response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/regenerate' % i.pk, {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_invoice_regenerate_unknown(client, env): client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/regenerate' % 3, {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_invoice_reissue(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): i = generate_invoice(env[2]) InvoiceAddress.objects.create(name_parts={'full_name': 'Foo', "_scheme": "full"}, order=env[2]) env[0].settings.set('invoice_generate', 'admin') response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/reissue' % i.pk, {}, follow=True) assert 'alert-success' in response.content.decode() i.refresh_from_db() with scopes_disabled(): assert env[2].invoices.count() == 3 assert 'Foo' not in env[2].invoices.all()[0].invoice_to assert 'Foo' not in env[2].invoices.all()[1].invoice_to assert 'Foo' in env[2].invoices.all()[2].invoice_to @pytest.mark.django_db def test_order_invoice_reissue_canceled(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): i = generate_invoice(env[2]) generate_cancellation(i) response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/reissue' % i.pk, {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_invoice_reissue_unknown(client, env): client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/reissue' % 3, {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_resend_link(client, env): mail.outbox = [] client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/resend', {}, follow=True) assert 'alert-success' in response.content.decode() assert 'FOO' in mail.outbox[0].body @pytest.mark.django_db def test_order_reactivate_not_canceled(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_PAID o.save() client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/reactivate', follow=True) assert 'alert-danger' in response.content.decode() response = client.post('/control/event/dummy/dummy/orders/FOO/reactivate', follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_reactivate(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=3) q.items.add(env[3]) o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_CANCELED o.save() client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/reactivate', { }, follow=True) print(response.content.decode()) assert 'alert-success' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PENDING @pytest.mark.django_db def test_order_extend_not_pending(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_PAID o.save() client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/extend', follow=True) assert 'alert-danger' in response.content.decode() response = client.post('/control/event/dummy/dummy/orders/FOO/extend', follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_order_extend_not_expired(client, env): with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) o = Order.objects.get(id=env[2].id) generate_invoice(o) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert 'alert-success' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == newdate[:10] + " 23:59:59" assert o.invoices.count() == 1 @pytest.mark.django_db def test_order_extend_overdue_quota_empty(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.save() q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert 'alert-success' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == newdate[:10] + " 23:59:59" @pytest.mark.django_db def test_order_extend_overdue_quota_blocked_by_waiting_list(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_EXPIRED o.expires = now() - timedelta(days=5) o.save() q = Quota.objects.create(event=env[0], size=1) q.items.add(env[3]) env[0].waitinglistentries.create(item=env[3], email='foo@bar.com') generate_cancellation(generate_invoice(o)) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert 'alert-success' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == newdate[:10] + " 23:59:59" assert o.status == Order.STATUS_PENDING assert o.invoices.count() == 3 @pytest.mark.django_db def test_order_extend_expired_quota_left(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED o.save() generate_cancellation(generate_invoice(o)) q = Quota.objects.create(event=env[0], size=3) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): assert o.invoices.count() == 2 response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-success' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == newdate[:10] + " 23:59:59" assert o.status == Order.STATUS_PENDING assert o.invoices.count() == 3 @pytest.mark.django_db def test_order_extend_expired_quota_empty(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED olddate = o.expires o.save() with scopes_disabled(): q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-danger' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == olddate.strftime("%Y-%m-%d %H:%M:%S") assert o.status == Order.STATUS_EXPIRED @pytest.mark.django_db def test_order_extend_expired_quota_empty_ignore(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED o.save() q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate, 'quota_ignore': 'on' }, follow=True) assert b'alert-success' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PENDING @pytest.mark.django_db def test_order_extend_expired_seat_free(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED o.save() generate_cancellation(generate_invoice(o)) seat_a1 = env[0].seats.create(seat_number="A1", product=env[3], seat_guid="A1") p = o.positions.first() p.seat = seat_a1 p.save() q = Quota.objects.create(event=env[0], size=3) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') assert o.invoices.count() == 2 response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-success' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == newdate[:10] + " 23:59:59" assert o.status == Order.STATUS_PENDING assert o.invoices.count() == 3 @pytest.mark.django_db def test_order_extend_expired_seat_blocked(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED olddate = o.expires o.save() seat_a1 = env[0].seats.create(seat_number="A1", product=env[3], seat_guid="A1", blocked=True) p = o.positions.first() p.seat = seat_a1 p.save() q = Quota.objects.create(event=env[0], size=100) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-danger' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == olddate.strftime("%Y-%m-%d %H:%M:%S") assert o.status == Order.STATUS_EXPIRED @pytest.mark.django_db def test_order_extend_expired_seat_taken(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED olddate = o.expires o.save() seat_a1 = env[0].seats.create(seat_number="A1", product=env[3], seat_guid="A1") p = o.positions.first() p.seat = seat_a1 p.save() o = Order.objects.create( code='BAR', event=env[0], email='dummy@dummy.test', status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10), total=14, locale='en' ) OrderPosition.objects.create( order=o, item=env[3], variation=None, price=Decimal("14"), attendee_name_parts={'full_name': "Peter", "_scheme": "full"}, seat=seat_a1 ) q = Quota.objects.create(event=env[0], size=100) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-danger' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == olddate.strftime("%Y-%m-%d %H:%M:%S") assert o.status == Order.STATUS_EXPIRED @pytest.mark.django_db def test_order_extend_expired_quota_partial(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) OrderPosition.objects.create( order=o, item=env[3], variation=None, price=Decimal("14"), attendee_name_parts={'full_name': "Peter", "_scheme": "full"} ) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED olddate = o.expires o.save() q = Quota.objects.create(event=env[0], size=1) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-danger' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == olddate.strftime("%Y-%m-%d %H:%M:%S") assert o.status == Order.STATUS_EXPIRED @pytest.mark.django_db def test_order_extend_expired_voucher_budget_ok(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED o.save() v = env[0].vouchers.create( code="foo", price_mode='subtract', value=Decimal('1.50'), budget=Decimal('1.50') ) p = o.positions.first() p.voucher = v p.price_before_voucher = p.price p.price -= Decimal('1.50') p.save() q = Quota.objects.create(event=env[0], size=100) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-success' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PENDING assert v.budget_used() == Decimal('1.50') @pytest.mark.django_db def test_order_extend_expired_voucher_budget_fail(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED olddate = o.expires o.save() v = env[0].vouchers.create( code="foo", price_mode='subtract', value=Decimal('1.50'), budget=Decimal('0.00') ) p = o.positions.first() p.voucher = v p.price_before_voucher = p.price p.price -= Decimal('1.50') p.save() q = Quota.objects.create(event=env[0], size=100) q.items.add(env[3]) newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d") client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/extend', { 'expires': newdate }, follow=True) assert b'alert-danger' in response.content assert b'The voucher "FOO" no longer has sufficient budget.' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == olddate.strftime("%Y-%m-%d %H:%M:%S") assert o.status == Order.STATUS_EXPIRED assert v.budget_used() == Decimal('0.00') @pytest.mark.django_db def test_order_mark_paid_overdue_quota_blocked_by_waiting_list(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_EXPIRED o.expires = now() - timedelta(days=5) o.save() q = Quota.objects.create(event=env[0], size=1) q.items.add(env[3]) env[0].waitinglistentries.create(item=env[3], email='foo@bar.com') client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'p', 'payment_date': now().date().isoformat(), 'amount': str(o.pending_sum), }, follow=True) assert 'alert-success' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PAID @pytest.mark.django_db def test_order_mark_paid_blocked(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_EXPIRED o.expires = now() - timedelta(days=5) o.save() q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'amount': str(o.pending_sum), 'payment_date': now().date().isoformat(), 'status': 'p' }, follow=True) assert 'alert-danger' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_EXPIRED @pytest.mark.django_db def test_order_mark_paid_overpaid_expired(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_EXPIRED o.expires = now() - timedelta(days=5) o.save() o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=o.total * 2) assert o.pending_sum == -1 * o.total q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'p', 'payment_date': now().date().isoformat(), 'amount': '0.00', 'force': 'on' }, follow=True) assert 'alert-success' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PAID assert o.payments.last().amount == 0 assert o.pending_sum == -1 * o.total @pytest.mark.django_db def test_order_mark_paid_forced(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.status = Order.STATUS_EXPIRED o.expires = now() - timedelta(days=5) o.save() q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'p', 'payment_date': now().date().isoformat(), 'amount': str(o.pending_sum), 'force': 'on' }, follow=True) print(response.content.decode()) assert 'alert-success' in response.content.decode() with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.status == Order.STATUS_PAID @pytest.mark.django_db def test_order_mark_paid_expired_seat_taken(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.expires = now() - timedelta(days=5) o.status = Order.STATUS_EXPIRED olddate = o.expires o.save() seat_a1 = env[0].seats.create(seat_number="A1", product=env[3], seat_guid="A1") p = o.positions.first() p.seat = seat_a1 p.save() o = Order.objects.create( code='BAR', event=env[0], email='dummy@dummy.test', status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10), total=14, locale='en' ) OrderPosition.objects.create( order=o, item=env[3], variation=None, price=Decimal("14"), attendee_name_parts={'full_name': "Peter", "_scheme": "full"}, seat=seat_a1 ) q = Quota.objects.create(event=env[0], size=100) q.items.add(env[3]) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/transition', { 'status': 'p', 'payment_date': now().date().isoformat(), 'amount': str(o.pending_sum), 'force': 'on' }, follow=True) assert b'alert-danger' in response.content with scopes_disabled(): o = Order.objects.get(id=env[2].id) assert o.expires.strftime("%Y-%m-%d %H:%M:%S") == olddate.strftime("%Y-%m-%d %H:%M:%S") assert o.status == Order.STATUS_EXPIRED @pytest.mark.django_db def test_order_go_lowercase(client, env): client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/go?code=DuMmyfoO') assert response['Location'].endswith('/control/event/dummy/dummy/orders/FOO/') @pytest.mark.django_db def test_order_go_with_slug(client, env): client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/go?code=DUMMYFOO') assert response['Location'].endswith('/control/event/dummy/dummy/orders/FOO/') @pytest.mark.django_db def test_order_go_found(client, env): client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/go?code=FOO') assert response['Location'].endswith('/control/event/dummy/dummy/orders/FOO/') @pytest.mark.django_db def test_order_go_not_found(client, env): client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/go?code=BAR') assert response['Location'].endswith('/control/event/dummy/dummy/orders/') @pytest.fixture def order_url(env): event = env[0] order = env[2] url = '/control/event/{orga}/{event}/orders/{code}'.format( event=event.slug, orga=event.organizer.slug, code=order.code ) return url @pytest.mark.django_db def test_order_sendmail_view(client, order_url): client.login(email='dummy@dummy.dummy', password='dummy') sendmail_url = order_url + '/sendmail' response = client.get(sendmail_url) assert response.status_code == 200 @pytest.mark.django_db def test_order_sendmail_simple_case(client, order_url, env): order = env[2] client.login(email='dummy@dummy.dummy', password='dummy') sendmail_url = order_url + '/sendmail' mail.outbox = [] response = client.post( sendmail_url, { 'sendto': order.email, 'subject': 'Test subject', 'message': 'This is a test file for sending mails.' }, follow=True) assert response.status_code == 200 assert 'alert-success' in response.content.decode() assert len(mail.outbox) == 1 assert mail.outbox[0].to == [order.email] assert mail.outbox[0].subject == 'Test subject' assert 'This is a test file for sending mails.' in mail.outbox[0].body mail_history_url = order_url + '/mail_history' response = client.get(mail_history_url) assert response.status_code == 200 assert 'Test subject' in response.content.decode() @pytest.mark.django_db def test_order_sendmail_preview(client, order_url, env): order = env[2] client.login(email='dummy@dummy.dummy', password='dummy') sendmail_url = order_url + '/sendmail' mail.outbox = [] response = client.post( sendmail_url, { 'sendto': order.email, 'subject': 'Test subject', 'message': 'This is a test file for sending mails.', 'action': 'preview' }, follow=True) assert response.status_code == 200 assert 'E-mail preview' in response.content.decode() assert len(mail.outbox) == 0 @pytest.mark.django_db def test_order_sendmail_invalid_data(client, order_url, env): order = env[2] client.login(email='dummy@dummy.dummy', password='dummy') sendmail_url = order_url + '/sendmail' mail.outbox = [] response = client.post( sendmail_url, { 'sendto': order.email, 'subject': 'Test invalid mail', }, follow=True) assert 'has-error' in response.content.decode() assert len(mail.outbox) == 0 mail_history_url = order_url + '/mail_history' response = client.get(mail_history_url) assert response.status_code == 200 assert 'Test invalid mail' not in response.content.decode() class OrderChangeTests(SoupTest): @scopes_disabled() def setUp(self): super().setUp() o = Organizer.objects.create(name='Dummy', slug='dummy') self.event = Event.objects.create(organizer=o, name='Dummy', slug='dummy', date_from=now(), plugins='pretix.plugins.banktransfer') self.order = Order.objects.create( code='FOO', event=self.event, email='dummy@dummy.test', status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10), total=Decimal('46.00'), ) self.tr7 = self.event.tax_rules.create(rate=Decimal('7.00')) self.tr19 = self.event.tax_rules.create(rate=Decimal('19.00')) self.ticket = Item.objects.create(event=self.event, name='Early-bird ticket', tax_rule=self.tr7, default_price=Decimal('23.00'), admission=True) self.shirt = Item.objects.create(event=self.event, name='T-Shirt', tax_rule=self.tr19, default_price=Decimal('12.00')) self.op1 = OrderPosition.objects.create( order=self.order, item=self.ticket, variation=None, price=Decimal("23.00"), attendee_name_parts={'full_name': "Peter", "_scheme": "full"} ) self.op2 = OrderPosition.objects.create( order=self.order, item=self.ticket, variation=None, price=Decimal("23.00"), attendee_name_parts={'full_name': "Dieter", "_scheme": "full"} ) self.op3 = OrderPosition.objects.create( order=self.order, item=self.ticket, variation=None, price=Decimal("23.00"), attendee_name_parts={'full_name': "Lukas", "_scheme": "full"}, canceled=True ) self.quota = self.event.quotas.create(name="All", size=100) self.quota.items.add(self.ticket) self.quota.items.add(self.shirt) user = User.objects.create_user('dummy@dummy.dummy', 'dummy') t = Team.objects.create(organizer=o, can_view_orders=True, can_change_orders=True) t.members.add(user) t.limit_events.add(self.event) self.client.login(email='dummy@dummy.dummy', password='dummy') def test_do_not_show_canceled(self): r = self.client.get('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code )) assert self.op1.secret[:5] in r.content.decode() assert self.op2.secret[:5] in r.content.decode() assert self.op3.secret[:5] not in r.content.decode() def test_change_item_success(self): self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'op-{}-itemvar'.format(self.op1.pk): str(self.shirt.pk), 'op-{}-price'.format(self.op1.pk): str('12.00'), }) self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.shirt assert self.op1.price == self.shirt.default_price assert self.op1.tax_rate == self.shirt.tax_rule.rate assert self.order.total == self.op1.price + self.op2.price def test_change_subevent_success(self): self.event.has_subevents = True self.event.save() with scopes_disabled(): se1 = self.event.subevents.create(name='Foo', date_from=now()) se2 = self.event.subevents.create(name='Bar', date_from=now()) self.op1.subevent = se1 self.op1.save() self.op2.subevent = se1 self.op2.save() self.quota.subevent = se1 self.quota.save() q2 = self.event.quotas.create(name='Q2', size=100, subevent=se2) q2.items.add(self.ticket) q2.items.add(self.shirt) self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'op-{}-subevent'.format(self.op1.pk): str(se2.pk), }) self.op1.refresh_from_db() self.op2.refresh_from_db() self.order.refresh_from_db() assert self.op1.subevent == se2 assert self.op2.subevent == se1 def test_change_price_success(self): self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'op-{}-operation'.format(self.op1.pk): 'price', 'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk), 'op-{}-price'.format(self.op1.pk): '24.00', 'op-{}-operation'.format(self.op2.pk): '', 'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk), }) self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.ticket assert self.op1.price == Decimal('24.00') assert self.order.total == self.op1.price + self.op2.price def test_cancel_success(self): self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'op-{}-operation_cancel'.format(self.op1.pk): 'on', }) self.order.refresh_from_db() with scopes_disabled(): assert self.order.positions.count() == 1 assert self.order.total == self.op2.price def test_add_item_success(self): self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '1', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'add-0-itemvar': str(self.shirt.pk), 'add-0-do': 'on', 'add-0-price': '14.00', }) with scopes_disabled(): assert self.order.positions.count() == 3 assert self.order.positions.last().item == self.shirt assert self.order.positions.last().price == 14 def test_recalculate_reverse_charge(self): self.tr7.eu_reverse_charge = True self.tr7.home_country = Country('DE') self.tr7.save() self.tr19.eu_reverse_charge = True self.tr19.home_country = Country('DE') self.tr19.save() with scopes_disabled(): InvoiceAddress.objects.create( order=self.order, is_business=True, vat_id='ATU1234567', vat_id_validated=True, country=Country('AT') ) self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'other-recalculate_taxes': 'net', 'op-{}-operation'.format(self.op1.pk): '', 'op-{}-operation'.format(self.op2.pk): '', 'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk), 'op-{}-price'.format(self.op2.pk): str(self.op2.price), 'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk), 'op-{}-price'.format(self.op1.pk): str(self.op1.price), }) with scopes_disabled(): ops = list(self.order.positions.all()) for op in ops: assert op.price == Decimal('21.50') assert op.tax_value == Decimal('0.00') assert op.tax_rate == Decimal('0.00') def test_recalculate_reverse_charge_keep_gross(self): self.tr7.eu_reverse_charge = True self.tr7.home_country = Country('DE') self.tr7.save() self.tr19.eu_reverse_charge = True self.tr19.home_country = Country('DE') self.tr19.save() with scopes_disabled(): InvoiceAddress.objects.create( order=self.order, is_business=True, vat_id='ATU1234567', vat_id_validated=True, country=Country('AT') ) self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'other-recalculate_taxes': 'gross', 'op-{}-operation'.format(self.op1.pk): '', 'op-{}-operation'.format(self.op2.pk): '', 'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk), 'op-{}-price'.format(self.op2.pk): str(self.op2.price), 'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk), 'op-{}-price'.format(self.op1.pk): str(self.op1.price), }) with scopes_disabled(): ops = list(self.order.positions.all()) for op in ops: assert op.price == Decimal('23.00') assert op.tax_value == Decimal('0.00') assert op.tax_rate == Decimal('0.00') def test_change_fee_value_success(self): with scopes_disabled(): fee = self.order.fees.create(fee_type="shipping", value=Decimal('5.00'), tax_rule=self.tr19) self.order.total += Decimal('5.00') self.order.save() self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'op-{}-price'.format(self.op1.pk): '24.00', 'op-{}-operation'.format(self.op2.pk): '', 'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk), 'of-{}-value'.format(fee.pk): '3.50', }) self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.ticket assert self.op1.price == Decimal('24.00') fee.refresh_from_db() self.op1.refresh_from_db() self.op2.refresh_from_db() assert self.order.total == self.op1.price + self.op2.price + Decimal('3.50') assert fee.value == Decimal('3.50') def test_cancel_fee_success(self): with scopes_disabled(): fee = self.order.fees.create(fee_type="shipping", value=Decimal('5.00'), tax_rule=self.tr19) self.order.total += Decimal('5.00') self.order.save() self.client.post('/control/event/{}/{}/orders/{}/change'.format( self.event.organizer.slug, self.event.slug, self.order.code ), { 'add-TOTAL_FORMS': '0', 'add-INITIAL_FORMS': '0', 'add-MIN_NUM_FORMS': '0', 'add-MAX_NUM_FORMS': '100', 'op-{}-operation'.format(self.op1.pk): 'price', 'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk), 'op-{}-price'.format(self.op1.pk): '24.00', 'op-{}-operation'.format(self.op2.pk): '', 'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk), 'of-{}-value'.format(fee.pk): '5.00', 'of-{}-operation_cancel'.format(fee.pk): 'on', }) self.order.refresh_from_db() fee.refresh_from_db() assert fee.canceled self.op1.refresh_from_db() self.op2.refresh_from_db() assert self.order.total == self.op1.price + self.op2.price @pytest.mark.django_db def test_check_vatid(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT')) with mock.patch('vat_moss.id.validate') as mock_validate: mock_validate.return_value = ('AT', 'AT123456', 'Foo') response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-success' in response.content.decode() ia.refresh_from_db() assert ia.vat_id_validated @pytest.mark.django_db def test_check_vatid_no_entered(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): ia = InvoiceAddress.objects.create(order=env[2], is_business=True, country=Country('AT')) with mock.patch('vat_moss.id.validate') as mock_validate: mock_validate.return_value = ('AT', 'AT123456', 'Foo') response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-danger' in response.content.decode() ia.refresh_from_db() assert not ia.vat_id_validated @pytest.mark.django_db def test_check_vatid_invalid_country(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('FR')) with mock.patch('vat_moss.id.validate') as mock_validate: mock_validate.return_value = ('AT', 'AT123456', 'Foo') response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-danger' in response.content.decode() ia.refresh_from_db() assert not ia.vat_id_validated @pytest.mark.django_db def test_check_vatid_noneu_country(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='CHU1234567', country=Country('CH')) with mock.patch('vat_moss.id.validate') as mock_validate: mock_validate.return_value = ('AT', 'AT123456', 'Foo') response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-danger' in response.content.decode() ia.refresh_from_db() assert not ia.vat_id_validated @pytest.mark.django_db def test_check_vatid_no_country(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567') with mock.patch('vat_moss.id.validate') as mock_validate: mock_validate.return_value = ('AT', 'AT123456', 'Foo') response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-danger' in response.content.decode() ia.refresh_from_db() assert not ia.vat_id_validated @pytest.mark.django_db def test_check_vatid_no_invoiceaddress(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with mock.patch('vat_moss.id.validate') as mock_validate: mock_validate.return_value = ('AT', 'AT123456', 'Foo') response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_check_vatid_invalid(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT')) with mock.patch('vat_moss.id.validate') as mock_validate: def raiser(*args, **kwargs): import vat_moss.errors raise vat_moss.errors.InvalidError('Fail') mock_validate.side_effect = raiser response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-danger' in response.content.decode() ia.refresh_from_db() assert not ia.vat_id_validated @pytest.mark.django_db def test_check_vatid_unavailable(client, env): client.login(email='dummy@dummy.dummy', password='dummy') with scopes_disabled(): ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT')) with mock.patch('vat_moss.id.validate') as mock_validate: def raiser(*args, **kwargs): import vat_moss.errors raise vat_moss.errors.WebServiceUnavailableError('Fail') mock_validate.side_effect = raiser response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True) assert 'alert-danger' in response.content.decode() ia.refresh_from_db() assert not ia.vat_id_validated @pytest.mark.django_db def test_cancel_payment(client, env): with scopes_disabled(): p = env[2].payments.last() client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/cancel'.format(p.pk), {}, follow=True) assert 'alert-success' in response.content.decode() p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CANCELED response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/cancel'.format(p.pk), {}, follow=True) assert 'alert-danger' in response.content.decode() @pytest.mark.django_db def test_cancel_refund(client, env): with scopes_disabled(): r = env[2].refunds.create( provider='stripe', state='transit', source='admin', amount=Decimal('23.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/cancel'.format(r.pk), {}, follow=True) assert 'alert-success' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_CANCELED r.state = OrderRefund.REFUND_STATE_DONE r.save() response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/cancel'.format(r.pk), {}, follow=True) assert 'alert-danger' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_DONE @pytest.mark.django_db def test_process_refund(client, env): with scopes_disabled(): r = env[2].refunds.create( provider='stripe', state='external', source='external', amount=Decimal('23.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True) assert 'alert-success' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_DONE env[2].refresh_from_db() assert env[2].status == Order.STATUS_PENDING @pytest.mark.django_db def test_process_refund_overpaid_externally(client, env): with scopes_disabled(): env[2].payments.first().confirm() env[2].payments.create( state='confirmed', provider='stripe', amount=Decimal('14.00'), payment_date=now() ) assert env[2].pending_sum == -14 r = env[2].refunds.create( provider='stripe', state='external', source='external', amount=Decimal('14.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True) assert 'alert-success' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_DONE env[2].refresh_from_db() assert env[2].status == Order.STATUS_PAID assert env[2].pending_sum == 0 @pytest.mark.django_db def test_process_refund_invalid_state(client, env): with scopes_disabled(): r = env[2].refunds.create( provider='stripe', state='canceled', source='external', amount=Decimal('23.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True) assert 'alert-danger' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_CANCELED @pytest.mark.django_db def test_process_refund_mark_refunded(client, env): with scopes_disabled(): r = env[2].refunds.create( provider='stripe', state='external', source='external', amount=Decimal('23.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {'action': 'r'}, follow=True) assert 'alert-success' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_DONE env[2].refresh_from_db() assert env[2].status == Order.STATUS_CANCELED @pytest.mark.django_db def test_done_refund(client, env): with scopes_disabled(): r = env[2].refunds.create( provider='stripe', state='transit', source='admin', amount=Decimal('23.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/done'.format(r.pk), {}, follow=True) assert 'alert-success' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_DONE @pytest.mark.django_db def test_done_refund_invalid_state(client, env): with scopes_disabled(): r = env[2].refunds.create( provider='stripe', state='external', source='external', amount=Decimal('23.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/done'.format(r.pk), {}, follow=True) assert 'alert-danger' in response.content.decode() r.refresh_from_db() assert r.state == OrderRefund.REFUND_STATE_EXTERNAL @pytest.mark.django_db def test_confirm_payment(client, env): with scopes_disabled(): p = env[2].payments.last() client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {}, follow=True) assert 'alert-success' in response.content.decode() p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() assert env[2].status == Order.STATUS_PAID @pytest.mark.django_db def test_confirm_payment_invalid_state(client, env): with scopes_disabled(): p = env[2].payments.last() p.state = OrderPayment.PAYMENT_STATE_FAILED p.save() client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {}, follow=True) assert 'alert-danger' in response.content.decode() p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_FAILED env[2].refresh_from_db() assert env[2].status == Order.STATUS_PENDING @pytest.mark.django_db def test_confirm_payment_partal_amount(client, env): with scopes_disabled(): p = env[2].payments.last() p.amount -= Decimal(5.00) p.save() client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {}, follow=True) assert 'alert-success' in response.content.decode() p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() assert env[2].status == Order.STATUS_PENDING @pytest.mark.django_db def test_refund_paid_order_fully_mark_as_refunded(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/refund') doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00" client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '14.00', 'start-mode': 'full', 'start-action': 'mark_refunded' }, follow=True) client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '14.00', 'start-mode': 'full', 'start-action': 'mark_refunded', 'refund-manual': '14.00', 'manual_state': 'done', 'perform': 'on' }, follow=True) p.refresh_from_db() with scopes_disabled(): assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() r = env[2].refunds.last() assert r.provider == "manual" assert r.state == OrderRefund.REFUND_STATE_DONE assert r.amount == Decimal('14.00') assert env[2].status == Order.STATUS_CANCELED @pytest.mark.django_db def test_refund_paid_order_fully_mark_as_pending(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/refund') doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00" client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '14.00', 'start-mode': 'full', 'start-action': 'mark_pending', 'refund-manual': '14.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() with scopes_disabled(): r = env[2].refunds.last() assert r.provider == "manual" assert r.state == OrderRefund.REFUND_STATE_CREATED assert r.amount == Decimal('14.00') assert env[2].status == Order.STATUS_PENDING @pytest.mark.django_db def test_refund_paid_order_partially_mark_as_pending(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/refund') doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00" client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending' }, follow=True) client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-manual': '7.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() with scopes_disabled(): r = env[2].refunds.last() assert r.provider == "manual" assert r.state == OrderRefund.REFUND_STATE_CREATED assert r.amount == Decimal('7.00') assert env[2].status == Order.STATUS_PENDING @pytest.mark.django_db def test_refund_propose_lower_payment(client, env): with scopes_disabled(): p = env[2].payments.last() p.amount = Decimal('8.00') p.confirm() p2 = env[2].payments.create( amount=Decimal('6.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED ) client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/refund') response = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending' }, follow=True) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '6.00' assert doc.select("input[name=refund-manual]".format(p2.pk))[0]['value'] == '1.00' @pytest.mark.django_db def test_refund_propose_equal_payment(client, env): with scopes_disabled(): p = env[2].payments.last() p.amount = Decimal('7.00') p.confirm() p2 = env[2].payments.create( amount=Decimal('7.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED ) client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/refund') response = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending' }, follow=True) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '7.00' assert not doc.select("input[name=refund-manual]".format(p2.pk))[0].get('value') @pytest.mark.django_db def test_refund_propose_higher_payment(client, env): with scopes_disabled(): p = env[2].payments.last() p.amount = Decimal('6.00') p.confirm() p2 = env[2].payments.create( amount=Decimal('8.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED ) client.login(email='dummy@dummy.dummy', password='dummy') client.get('/control/event/dummy/dummy/orders/FOO/refund') response = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending' }, follow=True) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select("input[name=refund-{}]".format(p2.pk))[0]['value'] == '7.00' assert not doc.select("input[name=refund-manual]".format(p2.pk))[0].get('value') @pytest.mark.django_db def test_refund_amount_does_not_match_or_invalid(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-manual': '4.00', 'refund-{}'.format(p.pk): '4.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) assert b'alert-danger' in resp.content assert b'do not match the' in resp.content resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '15.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-manual': '0.00', 'refund-{}'.format(p.pk): '15.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) assert b'alert-danger' in resp.content assert b'The refund amount needs to be positive' in resp.content resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-manual': '-3.00', 'refund-{}'.format(p.pk): '10.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) assert b'alert-danger' in resp.content assert b'do not match the' in resp.content resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-manual': 'AA', 'refund-{}'.format(p.pk): '10.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) assert b'alert-danger' in resp.content assert b'invalid number' in resp.content @pytest.mark.django_db def test_refund_paid_order_automatically_failed(client, env, monkeypatch): with scopes_disabled(): p = env[2].payments.last() p.provider = 'stripe' p.info_data = { 'id': 'foo' } p.save() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') def charge_retr(*args, **kwargs): def refund_create(amount): raise PaymentException('This failed.') c = MockedCharge() c.refunds.create = refund_create return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) r = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-{}'.format(p.pk): '7.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) assert b'This failed.' in r.content p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() with scopes_disabled(): r = env[2].refunds.last() assert r.provider == "stripe" assert r.state == OrderRefund.REFUND_STATE_FAILED assert r.amount == Decimal('7.00') assert env[2].status == Order.STATUS_PAID @pytest.mark.django_db def test_refund_paid_order_automatically(client, env, monkeypatch): with scopes_disabled(): p = env[2].payments.last() p.provider = 'stripe' p.info_data = { 'id': 'foo' } p.save() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') def charge_retr(*args, **kwargs): def refund_create(amount): r = MockedCharge() r.id = 'foo' r.status = 'succeeded' return r c = MockedCharge() c.refunds.create = refund_create return c monkeypatch.setattr("stripe.Charge.retrieve", charge_retr) client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '7.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-{}'.format(p.pk): '7.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() with scopes_disabled(): r = env[2].refunds.last() assert r.provider == "stripe" assert r.state == OrderRefund.REFUND_STATE_DONE assert r.amount == Decimal('7.00') assert env[2].status == Order.STATUS_PENDING @pytest.mark.django_db def test_refund_paid_order_offsetting_to_unknown(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') r = client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '5.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-offsetting': '5.00', 'order-offsetting': 'BAZ', 'manual_state': 'pending', 'perform': 'on' }, follow=True) assert b'alert-danger' in r.content @pytest.mark.django_db def test_refund_paid_order_offsetting_to_expired(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') o = Order.objects.create( code='BAZ', event=env[0], email='dummy@dummy.test', status=Order.STATUS_EXPIRED, datetime=now(), expires=now() + timedelta(days=10), total=5, locale='en' ) o.positions.create(price=5, item=env[3]) q = Quota.objects.create(event=env[0], size=0) q.items.add(env[3]) client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '5.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-offsetting': '5.00', 'order-offsetting': 'BAZ', 'manual_state': 'pending', 'perform': 'on' }, follow=True) p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() with scopes_disabled(): r = env[2].refunds.last() assert r.provider == "offsetting" assert r.state == OrderRefund.REFUND_STATE_DONE assert r.amount == Decimal('5.00') assert env[2].status == Order.STATUS_PENDING o.refresh_from_db() assert o.status == Order.STATUS_EXPIRED p2 = o.payments.first() assert p2.provider == "offsetting" assert p2.amount == Decimal('5.00') assert p2.state == OrderPayment.PAYMENT_STATE_CONFIRMED @pytest.mark.django_db def test_refund_paid_order_offsetting(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') o = Order.objects.create( code='BAZ', event=env[0], email='dummy@dummy.test', status=Order.STATUS_PENDING, datetime=now(), expires=now() + timedelta(days=10), total=5, locale='en' ) client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '5.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-offsetting': '5.00', 'order-offsetting': 'BAZ', 'manual_state': 'pending', 'perform': 'on' }, follow=True) p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() with scopes_disabled(): r = env[2].refunds.last() assert r.provider == "offsetting" assert r.state == OrderRefund.REFUND_STATE_DONE assert r.amount == Decimal('5.00') assert env[2].status == Order.STATUS_PENDING o.refresh_from_db() assert o.status == Order.STATUS_PAID p2 = o.payments.first() assert p2.provider == "offsetting" assert p2.amount == Decimal('5.00') assert p2.state == OrderPayment.PAYMENT_STATE_CONFIRMED @pytest.mark.django_db def test_refund_paid_order_giftcard(client, env): with scopes_disabled(): p = env[2].payments.last() p.confirm() client.login(email='dummy@dummy.dummy', password='dummy') client.post('/control/event/dummy/dummy/orders/FOO/refund', { 'start-partial_amount': '5.00', 'start-mode': 'partial', 'start-action': 'mark_pending', 'refund-new-giftcard': '5.00', 'manual_state': 'pending', 'perform': 'on' }, follow=True) p.refresh_from_db() assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED env[2].refresh_from_db() with scopes_disabled(): r = env[2].refunds.last() assert r.provider == "giftcard" assert r.state == OrderRefund.REFUND_STATE_DONE assert r.amount == Decimal('5.00') assert env[2].status == Order.STATUS_PENDING gk = GiftCard.objects.get(pk=r.info_data['gift_card']) assert gk.value == Decimal('5.00') @pytest.mark.django_db def test_refund_list(client, env): with scopes_disabled(): env[2].refunds.create( provider='banktransfer', state='done', source='admin', amount=Decimal('23.00'), execution_date=now(), ) env[2].refunds.create( provider='manual', state='created', source='admin', amount=Decimal('23.00'), execution_date=now(), ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/refunds/') assert 'R-1' not in response.content.decode() assert 'R-2' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/refunds/?status=all') assert 'R-1' in response.content.decode() assert 'R-2' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/refunds/?status=created') assert 'R-1' not in response.content.decode() assert 'R-2' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/refunds/?status=done') assert 'R-1' in response.content.decode() assert 'R-2' not in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/refunds/?status=all&provider=manual') assert 'R-1' not in response.content.decode() assert 'R-2' in response.content.decode() response = client.get('/control/event/dummy/dummy/orders/refunds/?status=all&provider=banktransfer') assert 'R-1' in response.content.decode() assert 'R-2' not in response.content.decode() @pytest.mark.django_db def test_delete_cancellation_request(client, env): with scopes_disabled(): r = env[2].cancellation_requests.create( cancellation_fee=Decimal('4.00'), refund_as_giftcard=True ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.post('/control/event/dummy/dummy/orders/FOO/cancellationrequests/{}/delete'.format(r.pk), {}, follow=True) assert 'alert-success' in response.content.decode() assert not env[2].cancellation_requests.exists() @pytest.mark.django_db def test_approve_cancellation_request(client, env): with scopes_disabled(): o = Order.objects.get(id=env[2].id) o.payments.create(state=OrderPayment.PAYMENT_STATE_CONFIRMED, amount=o.total) o.status = Order.STATUS_PAID o.save() r = env[2].cancellation_requests.create( cancellation_fee=Decimal('4.00'), refund_as_giftcard=True ) client.login(email='dummy@dummy.dummy', password='dummy') response = client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c&req={}'.format(r.pk), {}) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select('input[name=cancellation_fee]')[0]['value'] == '4.00' response = client.post('/control/event/dummy/dummy/orders/FOO/transition?req={}'.format(r.pk), { 'status': 'c', 'cancellation_fee': '4.00' }, follow=True) doc = BeautifulSoup(response.content.decode(), "lxml") assert doc.select('input[name=refund-new-giftcard]')[0]['value'] == '10.00' assert not env[2].cancellation_requests.exists()