Pluggable invoice transmission methods (#5020)

* Flexible invoice transmission

* UI work

* Add peppol and output

* API support

* Profile integration

* Simplify form for individuals

* Remove sent_to_customer usage

* more steps

* Revert "Bank transfer: Allow to send the invoice direclty to the accounting department (#2975)"

This reverts commit cea6c340be.

* minor fixes

* Fixes after rebase

* update stati

* Backend view

* Transmit and show status

* status, retransmission

* API retransmission

* More fields

* API docs

* Plugin docs

* Update migration

* Add missing license headers

* Remove dead code, fix current tests

* Run isort

* Update regex

* Rebase migration

* Fix migration

* Add tests, fix bugs

* Rebase migration

* Apply suggestion from @luelista

Co-authored-by: luelista <weller@rami.io>

* Apply suggestion from @luelista

Co-authored-by: luelista <weller@rami.io>

* Apply suggestion from @luelista

Co-authored-by: luelista <weller@rami.io>

* Apply suggestion from @luelista

Co-authored-by: luelista <weller@rami.io>

* Apply suggestion from @luelista

Co-authored-by: luelista <weller@rami.io>

* Make migration reversible

* Add TransmissionType.enforce_transmission

* Fix registries API usage after rebase

* Remove code I forgot to delete

* Update transmission status display depending on type

* Add testmode_supported

* Update src/pretix/static/pretixbase/js/addressform.js

Co-authored-by: luelista <weller@rami.io>

* Update src/pretix/static/pretixbase/js/addressform.js

Co-authored-by: luelista <weller@rami.io>

* Update src/pretix/static/pretixbase/js/addressform.js

Co-authored-by: luelista <weller@rami.io>

* New mechanism for non-required invoice forms

* Update src/pretix/base/invoicing/transmission.py

Co-authored-by: luelista <weller@rami.io>

* Declare testmode_supported for email

* Make transmission_email_other an implementation detail

* Fix failing tests and add new ones

* Update src/pretix/base/services/invoices.py

Co-authored-by: luelista <weller@rami.io>

* Add emails to email history

* Fix comma error

* More generic default email text

* Cleanup

* Remove "email invoices" button and refine logic

* Rebase migration

* Fix edge case

---------

Co-authored-by: luelista <weller@rami.io>
This commit is contained in:
Raphael Michel
2025-08-19 17:59:45 +02:00
committed by GitHub
parent 37910f6037
commit 05c74b7ad6
65 changed files with 4514 additions and 1825 deletions

View File

@@ -67,7 +67,7 @@ def event(organizer, meta_prop):
e = Event.objects.create(
organizer=organizer, name='Dummy', slug='dummy',
date_from=datetime(2017, 12, 27, 10, 0, 0, tzinfo=timezone.utc),
plugins='pretix.plugins.banktransfer,pretix.plugins.ticketoutputpdf',
plugins='pretix.plugins.banktransfer,pretix.plugins.ticketoutputpdf,tests.testdummy',
is_public=True
)
e.meta_values.create(property=meta_prop, value="Conference")

View File

@@ -28,7 +28,7 @@ import pytest
from django_countries.fields import Country
from django_scopes import scopes_disabled
from pretix.base.models import InvoiceAddress, Order, OrderPosition
from pretix.base.models import Invoice, InvoiceAddress, Order, OrderPosition
from pretix.base.models.orders import OrderFee
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice,
@@ -201,6 +201,7 @@ TEST_INVOICE_RES = {
"invoice_from_tax_id": "",
"invoice_from_vat_id": "",
"invoice_to": "Sample company\nNew Zealand\nVAT-ID: DE123",
"invoice_to_is_business": False,
"invoice_to_company": "Sample company",
"invoice_to_name": "",
"invoice_to_street": "",
@@ -210,6 +211,7 @@ TEST_INVOICE_RES = {
"invoice_to_country": "NZ",
"invoice_to_vat_id": "DE123",
"invoice_to_beneficiary": "",
"invoice_to_transmission_info": {},
"custom_field": None,
"date": "2017-12-10",
"refers": None,
@@ -260,7 +262,11 @@ TEST_INVOICE_RES = {
"tax_name": "",
"tax_rate": "19.00"
}
]
],
"transmission_type": "email",
"transmission_provider": None,
"transmission_status": "pending",
"transmission_date": None
}
@@ -366,6 +372,26 @@ def test_invoice_detail(token_client, organizer, event, item, invoice):
assert res == resp.data
@pytest.mark.django_db
def test_invoice_retransmit(token_client, organizer, event, invoice):
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_INFLIGHT
invoice.save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/invoices/{}/retransmit/'.format(
organizer.slug, event.slug, invoice.number
))
assert resp.status_code == 409
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
invoice.save()
resp = token_client.post('/api/v1/organizers/{}/events/{}/invoices/{}/retransmit/'.format(
organizer.slug, event.slug, invoice.number
))
assert resp.status_code == 204
invoice.refresh_from_db()
assert invoice.transmission_status == Invoice.TRANSMISSION_STATUS_PENDING
@pytest.mark.django_db
def test_invoice_regenerate(token_client, organizer, event, invoice):
organizer.settings.invoice_regenerate_allowed = True

View File

@@ -247,6 +247,147 @@ def test_order_update_state_validation(token_client, organizer, event, order):
assert order.invoice_address.country == "AU"
@pytest.mark.django_db
def test_order_update_transmission_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": "FR",
"internal_reference": "",
"vat_id": "",
"transmission_type": "invalid",
"transmission_info": {},
}
}
)
assert resp.status_code == 400
assert resp.data == {"invoice_address": {"transmission_type": ["Unknown transmission type."]}}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/orders/{}/'.format(
organizer.slug, event.slug, order.code
), format='json', data={
'invoice_address': {
"is_business": True,
"company": "This is my company name",
"name": "John Doe",
"name_parts": {},
"street": "",
"zipcode": "",
"city": "Test",
"country": "FR",
"internal_reference": "",
"vat_id": "",
"transmission_type": "it_sdi",
"transmission_info": {
"transmission_it_sdi_pec": "foobar",
},
}
}
)
assert resp.status_code == 400
assert resp.data == {"invoice_address": {
"transmission_type": ["The selected transmission type is not available for this country or address type."]
}}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/orders/{}/'.format(
organizer.slug, event.slug, order.code
), format='json', data={
'invoice_address': {
"is_business": True,
"company": "This is my company name",
"name": "John Doe",
"name_parts": {},
"street": "",
"zipcode": "",
"city": "Test",
"country": "IT",
"internal_reference": "",
"vat_id": "",
"transmission_type": "it_sdi",
"transmission_info": {
"transmission_it_sdi_pec": "foobar",
},
}
}
)
assert resp.status_code == 400
assert resp.data == {"invoice_address": {"transmission_info": {
"transmission_it_sdi_pec": ["Enter a valid email address.", "Enter a valid email address."],
"transmission_it_sdi_recipient_code": ["This field is required."]
}}}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/orders/{}/'.format(
organizer.slug, event.slug, order.code
), format='json', data={
'invoice_address': {
"is_business": True,
"company": "This is my company name",
"name": "John Doe",
"name_parts": {},
"street": "Via Da Vinci 1",
"zipcode": "12345",
"city": "Test",
"country": "IT",
"state": "MI",
"internal_reference": "",
"vat_id": "",
"transmission_type": "it_sdi",
"transmission_info": {
"transmission_it_sdi_pec": "foobar@pec.it",
"transmission_it_sdi_recipient_code": "1234567",
},
}
}
)
assert resp.status_code == 400
assert resp.data == {
"invoice_address": {"vat_id": ["This field is required for the selected type of invoice transmission."]}
}
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/orders/{}/'.format(
organizer.slug, event.slug, order.code
), format='json', data={
'invoice_address': {
"is_business": True,
"company": "This is my company name",
"name": "John Doe",
"name_parts": {},
"street": "Via Una 1",
"zipcode": "12345",
"city": "Test",
"country": "FR",
"internal_reference": "",
"vat_id": "",
"transmission_type": "peppol",
"transmission_info": {
"transmission_peppol_participant_id": "9930:DE811569869",
"ignored": "parameter",
},
}
}
)
assert resp.status_code == 200
order.invoice_address.refresh_from_db()
assert order.invoice_address.transmission_type == "peppol"
assert order.invoice_address.transmission_info == {
"transmission_peppol_participant_id": "9930:DE811569869",
}
@pytest.mark.django_db
def test_order_update_allowed_fields(token_client, organizer, event, order):
event.settings.locales = ['de', 'en']
@@ -442,6 +583,7 @@ def test_order_create_invoice(token_client, organizer, event, order):
"invoice_from_vat_id": "",
"invoice_to": "Sample company\nNew Zealand\nVAT-ID: DE123",
"invoice_to_company": "Sample company",
"invoice_to_is_business": False,
"invoice_to_name": "",
"invoice_to_street": "",
"invoice_to_zipcode": "",
@@ -450,6 +592,7 @@ def test_order_create_invoice(token_client, organizer, event, order):
"invoice_to_country": "NZ",
"invoice_to_vat_id": "DE123",
"invoice_to_beneficiary": "",
"invoice_to_transmission_info": {},
"custom_field": None,
'date': now().astimezone(event.timezone).date().isoformat(),
'refers': None,
@@ -500,7 +643,11 @@ def test_order_create_invoice(token_client, organizer, event, order):
'foreign_currency_display': None,
'foreign_currency_rate': None,
'foreign_currency_rate_date': None,
'internal_reference': ''
'internal_reference': '',
'transmission_date': None,
'transmission_provider': None,
'transmission_status': 'pending',
'transmission_type': 'email',
}
resp = token_client.post(

View File

@@ -436,7 +436,9 @@ def test_order_create_simulate(token_client, organizer, event, item, quota, ques
'vat_id': '',
'vat_id_validated': False,
'internal_reference': '',
'custom_field': None
'custom_field': None,
'transmission_type': 'email',
'transmission_info': None,
},
'positions': [
{
@@ -590,6 +592,39 @@ def test_order_create_invoice_address_optional(token_client, organizer, event, i
o.invoice_address
@pytest.mark.django_db
def test_order_create_invoice_address_transmission_type_validation(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
res['invoice_address'] = {
"is_business": True,
"company": "This is my company name",
"name": "John Doe",
"name_parts": {},
"street": "",
"zipcode": "",
"city": "Test",
"country": "FR",
"internal_reference": "",
"vat_id": "",
"transmission_type": "it_sdi",
"transmission_info": {
"transmission_it_sdi_pec": "foobar@pec.it",
"transmission_it_sdi_recipient_code": "1234567",
},
}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {"invoice_address": {
"transmission_type": ["The selected transmission type is not available for this country or address type."]
}}
@pytest.mark.django_db
def test_order_create_sales_channel_optional(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)

View File

@@ -325,7 +325,9 @@ TEST_ORDER_RES = {
"internal_reference": "",
"custom_field": "Custom info",
"vat_id": "DE123",
"vat_id_validated": True
"vat_id_validated": True,
"transmission_type": "email",
"transmission_info": None,
},
"require_approval": False,
"valid_if_pending": False,

View File

@@ -75,6 +75,7 @@ event_permission_sub_urls = [
('get', 'can_view_orders', 'invoices/1/', 404),
('post', 'can_change_orders', 'invoices/1/regenerate/', 404),
('post', 'can_change_orders', 'invoices/1/reissue/', 404),
('post', 'can_change_orders', 'invoices/1/retransmit/', 404),
('get', 'can_view_orders', 'waitinglistentries/', 200),
('get', 'can_view_orders', 'waitinglistentries/1/', 404),
('post', 'can_change_orders', 'waitinglistentries/', 400),

View File

@@ -0,0 +1,234 @@
#
# 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 <https://pretix.eu/about/en/license>.
#
# 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
# <https://www.gnu.org/licenses/>.
#
import pytest
from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.base.models import Event, Organizer
@pytest.mark.django_db
def test_no_invoice_address(client):
response = client.get('/js_helpers/address_form/?country=DE')
assert response.json() == {
'city': {'required': 'if_any'},
'data': [],
'state': {'label': 'State', 'required': False, 'visible': False},
'street': {'required': 'if_any'},
'vat_id': {'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
response = client.get('/js_helpers/address_form/?country=CR')
assert response.json() == {
'city': {'required': False},
'data': [],
'state': {'label': 'State', 'required': False, 'visible': False},
'street': {'required': 'if_any'},
'vat_id': {'required': False, 'visible': False},
'zipcode': {'required': False}
}
response = client.get('/js_helpers/address_form/?country=US')
d = response.json()
assert d['state'] == {'label': 'State', 'required': 'if_any', 'visible': True}
assert d['data'][0] == {'code': 'AL', 'name': 'Alabama'}
response = client.get('/js_helpers/address_form/?country=IT')
d = response.json()
assert d['state'] == {'label': 'Province', 'required': 'if_any', 'visible': True}
assert d['data'][0] == {'code': 'AG', 'name': 'Agrigento'}
@pytest.fixture
@scopes_disabled()
def event():
o = Organizer.objects.create(name='Dummy', slug='org')
event = Event.objects.create(
organizer=o, name='Dummy', slug='ev',
date_from=now(), plugins='tests.testdummy'
)
return event
@pytest.mark.django_db
def test_invalid_event(client):
response = client.get(
'/js_helpers/address_form/?country=DE&invoice=true&organizer=test&event=test'
)
assert response.status_code == 404
@pytest.mark.django_db
def test_provider_only_email_available(client, event):
response = client.get(
'/js_helpers/address_form/?country=DE&invoice=true&organizer=org&event=ev&transmission_type_required=true'
)
assert response.status_code == 200
d = response.json()
assert d == {
'city': {'required': 'if_any'},
'data': [],
'state': {'label': 'State', 'required': False, 'visible': False},
'street': {'required': 'if_any'},
'transmission_email_address': {'required': False, 'visible': False},
'transmission_email_other': {'required': False, 'visible': False},
'transmission_it_sdi_codice_fiscale': {'required': False, 'visible': False},
'transmission_it_sdi_pec': {'required': False, 'visible': False},
'transmission_it_sdi_recipient_code': {'required': False, 'visible': False},
'transmission_peppol_participant_id': {'required': False, 'visible': False},
'transmission_type': {'visible': False},
'transmission_types': [{'code': 'email', 'name': 'Email'}],
'vat_id': {'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
@pytest.mark.django_db
def test_provider_italy_sdi_not_enforced_when_optional(client, event):
response = client.get(
'/js_helpers/address_form/?country=IT&invoice=true&organizer=org&event=ev&transmission_type_required=false'
)
assert response.status_code == 200
d = response.json()
del d['data']
assert d == {
'city': {'required': 'if_any'},
'state': {'label': 'Province', 'required': 'if_any', 'visible': True},
'street': {'required': 'if_any'},
'transmission_email_address': {'required': False, 'visible': False},
'transmission_email_other': {'required': False, 'visible': False},
'transmission_it_sdi_codice_fiscale': {'required': False, 'visible': False},
'transmission_it_sdi_pec': {'required': False, 'visible': False},
'transmission_it_sdi_recipient_code': {'required': False, 'visible': False},
'transmission_peppol_participant_id': {'required': False, 'visible': False},
'transmission_type': {'visible': True},
'transmission_types': [{'code': 'it_sdi', 'name': 'Exchange System (SdI)'}],
'vat_id': {'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
@pytest.mark.django_db
def test_provider_italy_sdi_enforced_individual(client, event):
response = client.get(
'/js_helpers/address_form/?country=IT&invoice=true&organizer=org&event=ev&transmission_type_required=true'
)
assert response.status_code == 200
d = response.json()
del d['data']
assert d == {
'city': {'required': True},
'state': {'label': 'Province', 'required': True, 'visible': True},
'street': {'required': True},
'transmission_email_address': {'required': False, 'visible': False},
'transmission_email_other': {'required': False, 'visible': False},
'transmission_it_sdi_codice_fiscale': {'required': True, 'visible': True},
'transmission_it_sdi_pec': {'required': False, 'visible': True},
'transmission_it_sdi_recipient_code': {'required': False, 'visible': False},
'transmission_peppol_participant_id': {'required': False, 'visible': False},
'transmission_type': {'visible': True},
'transmission_types': [{'code': 'it_sdi', 'name': 'Exchange System (SdI)'}],
'vat_id': {'required': False, 'visible': True},
'zipcode': {'required': True}
}
@pytest.mark.django_db
def test_provider_italy_sdi_enforced_business(client, event):
response = client.get(
'/js_helpers/address_form/?country=IT&invoice=true&organizer=org&event=ev&transmission_type_required=true'
'&is_business=business'
)
assert response.status_code == 200
d = response.json()
del d['data']
assert d == {
'city': {'required': True},
'state': {'label': 'Province', 'required': True, 'visible': True},
'street': {'required': True},
'transmission_email_address': {'required': False, 'visible': False},
'transmission_email_other': {'required': False, 'visible': False},
'transmission_it_sdi_codice_fiscale': {'required': False, 'visible': True},
'transmission_it_sdi_pec': {'required': True, 'visible': True},
'transmission_it_sdi_recipient_code': {'required': True, 'visible': True},
'transmission_peppol_participant_id': {'required': False, 'visible': False},
'transmission_type': {'visible': True},
'transmission_types': [{'code': 'it_sdi', 'name': 'Exchange System (SdI)'}],
'vat_id': {'required': True, 'visible': True},
'zipcode': {'required': True}
}
@pytest.mark.django_db
def test_email_peppol_choice(client, event):
response = client.get(
'/js_helpers/address_form/?country=DE&invoice=true&organizer=org&event=ev'
'&is_business=business&transmission_type_required=true'
)
assert response.status_code == 200
d = response.json()
assert d == {
'city': {'required': 'if_any'},
'data': [],
'state': {'label': 'State', 'required': False, 'visible': False},
'street': {'required': 'if_any'},
'transmission_email_address': {'required': False, 'visible': True},
'transmission_email_other': {'required': False, 'visible': True},
'transmission_it_sdi_codice_fiscale': {'required': False, 'visible': False},
'transmission_it_sdi_pec': {'required': False, 'visible': False},
'transmission_it_sdi_recipient_code': {'required': False, 'visible': False},
'transmission_peppol_participant_id': {'required': False, 'visible': False},
'transmission_type': {'visible': True},
'transmission_types': [
{'code': 'email', 'name': 'Email'},
{'code': 'peppol', 'name': 'PEPPOL'},
],
'vat_id': {'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
response = client.get(
'/js_helpers/address_form/?country=DE&invoice=true&organizer=org&event=ev'
'&is_business=business&transmission_type=peppol'
)
assert response.status_code == 200
d = response.json()
assert d == {
'city': {'required': True},
'data': [],
'state': {'label': 'State', 'required': False, 'visible': False},
'street': {'required': True},
'transmission_email_address': {'required': False, 'visible': False},
'transmission_email_other': {'required': False, 'visible': False},
'transmission_it_sdi_codice_fiscale': {'required': False, 'visible': False},
'transmission_it_sdi_pec': {'required': False, 'visible': False},
'transmission_it_sdi_recipient_code': {'required': False, 'visible': False},
'transmission_peppol_participant_id': {'required': True, 'visible': True},
'transmission_type': {'visible': True},
'transmission_types': [
{'code': 'email', 'name': 'Email'},
{'code': 'peppol', 'name': 'PEPPOL'},
],
'vat_id': {'required': False, 'visible': True},
'zipcode': {'required': True}
}

View File

@@ -34,12 +34,13 @@ from django.utils.timezone import make_aware, now
from django_countries.fields import Country
from django_scopes import scope
from freezegun import freeze_time
from i18nfield.strings import LazyI18nString
from tests.testdummy.signals import FoobazSalesChannel
from pretix.base.decimal import round_decimal
from pretix.base.models import (
CartPosition, Event, GiftCard, InvoiceAddress, Item, Order, OrderPosition,
Organizer, SeatingPlan,
CartPosition, Event, GiftCard, Invoice, InvoiceAddress, Item, Order,
OrderPosition, Organizer, SeatingPlan,
)
from pretix.base.models.items import SubEventItem
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
@@ -48,7 +49,9 @@ from pretix.base.payment import (
)
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.base.secrets import assign_ticket_secret
from pretix.base.services.invoices import generate_invoice
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice,
)
from pretix.base.services.orders import (
OrderChangeManager, OrderError, _create_order, approve_order, cancel_order,
deny_order, expire_orders, reactivate_order, send_download_reminders,
@@ -573,6 +576,83 @@ def test_approve_send_to_attendees(event):
assert 'awaiting payment' not in djmail.outbox[1].subject
@pytest.mark.django_db
def test_approve_mail_invoice_attached(event):
djmail.outbox = []
event.settings.invoice_address_asked = True
event.settings.invoice_address_required = True
event.settings.invoice_generate = "True"
event.settings.invoice_email_attachment = True
o1 = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=now(), expires=now() - timedelta(days=10),
total=10, require_approval=True, locale='en',
sales_channel=event.organizer.sales_channels.get(identifier="web"),
)
ticket = Item.objects.create(event=event, name='Early-bird ticket',
default_price=Decimal('23.00'), admission=True)
OrderPosition.objects.create(
order=o1, item=ticket, variation=None, price=Decimal("23.00"),
attendee_name_parts={'full_name': "Peter"},
positionid=1
)
InvoiceAddress.objects.create(
order=o1,
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={}
)
o1.create_transactions()
assert o1.transactions.count() == 0
approve_order(o1)
o1.refresh_from_db()
assert len(djmail.outbox) == 1
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
@pytest.mark.django_db(transaction=True)
def test_approve_mail_invoice_sent_somewhere_else(event):
djmail.outbox = []
event.settings.invoice_address_asked = True
event.settings.invoice_address_required = True
event.settings.invoice_generate = "True"
event.settings.invoice_email_attachment = True
o1 = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=now(), expires=now() - timedelta(days=10),
total=10, require_approval=True, locale='en',
sales_channel=event.organizer.sales_channels.get(identifier="web"),
)
ticket = Item.objects.create(event=event, name='Early-bird ticket',
default_price=Decimal('23.00'), admission=True)
OrderPosition.objects.create(
order=o1, item=ticket, variation=None, price=Decimal("23.00"),
attendee_name_parts={'full_name': "Peter"},
positionid=1
)
InvoiceAddress.objects.create(
order=o1,
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_address": "invoice@example.org",
}
)
o1.create_transactions()
assert o1.transactions.count() == 0
approve_order(o1)
o1.refresh_from_db()
assert len(djmail.outbox) == 2
assert ["invoice@example.org"] == djmail.outbox[0].to
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
assert ["dummy@dummy.test"] == djmail.outbox[1].to
assert not any(["Invoice_" in a[0] for a in djmail.outbox[1].attachments])
@pytest.mark.django_db
def test_approve_free(event):
djmail.outbox = []
@@ -670,6 +750,67 @@ def test_deny(event):
assert 'denied' in djmail.outbox[0].subject
@pytest.mark.django_db(transaction=True)
def test_mark_invoices_as_sent(event):
djmail.outbox = []
event.settings.invoice_address_asked = True
event.settings.invoice_address_required = True
event.settings.invoice_generate = "True"
event.settings.invoice_email_attachment = True
o1 = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=now(), expires=now() - timedelta(days=10),
total=10, locale='en',
sales_channel=event.organizer.sales_channels.get(identifier="web"),
)
ticket = Item.objects.create(event=event, name='Early-bird ticket',
default_price=Decimal('23.00'), admission=True)
OrderPosition.objects.create(
order=o1, item=ticket, variation=None, price=Decimal("23.00"),
attendee_name_parts={'full_name': "Peter"},
positionid=1
)
ia = InvoiceAddress.objects.create(
order=o1,
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_address": "invoice@example.org",
}
)
o1.create_transactions()
i = generate_invoice(o1)
assert i.transmission_type == "email"
assert i.transmission_status == Invoice.TRANSMISSION_STATUS_PENDING
assert not i.transmission_provider
# Not marked as sent because it is not the right address
o1.send_mail(
subject=LazyI18nString({"en": "Hey"}),
template=LazyI18nString({"en": "Just wanted to send this invoice"}),
context={},
invoices=[i]
)
i.refresh_from_db()
assert i.transmission_type == "email"
assert i.transmission_status == Invoice.TRANSMISSION_STATUS_PENDING
# If no other address is there, order address will be accepted
ia.transmission_info = {}
ia.save()
o1.send_mail(
subject=LazyI18nString({"en": "Hey"}),
template=LazyI18nString({"en": "Just wanted to send this invoice"}),
context={},
invoices=[i]
)
i.refresh_from_db()
assert i.transmission_status == Invoice.TRANSMISSION_STATUS_COMPLETED
assert i.transmission_provider == "email_pdf"
class PaymentReminderTests(TestCase):
def setUp(self):
super().setUp()
@@ -1033,6 +1174,12 @@ class DownloadReminderTests(TestCase):
assert len(djmail.outbox) == 0
@pytest.fixture
def class_monkeypatch(request, monkeypatch):
request.cls.monkeypatch = monkeypatch
@pytest.mark.usefixtures("class_monkeypatch")
class OrderCancelTests(TestCase):
def setUp(self):
super().setUp()
@@ -1060,6 +1207,7 @@ class OrderCancelTests(TestCase):
self.order.create_transactions()
generate_invoice(self.order)
djmail.outbox = []
self.monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
@classscope(attr='o')
def test_cancel_canceled(self):
@@ -1111,6 +1259,56 @@ class OrderCancelTests(TestCase):
assert self.order.transactions.count() == 4
assert self.order.transactions.aggregate(s=Sum(F('price') * F('count')))['s'] == Decimal('0.00')
@classscope(attr='o')
def test_cancel_mail_invoice_attached(self):
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
InvoiceAddress.objects.update_or_create(
order=self.order,
defaults=dict(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={}
)
)
self.order.status = Order.STATUS_PAID
self.order.save()
djmail.outbox = []
cancel_order(self.order.pk, send_mail=True)
assert len(djmail.outbox) == 1
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
@classscope(attr='o')
def test_cancel_mail_invoice_sent_somewhere_else(self):
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
InvoiceAddress.objects.update_or_create(
order=self.order,
defaults=dict(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_address": "invoice@example.org",
}
)
)
# Recreate invoice because otherwise it will be sent where the original was sent
generate_cancellation(self.order.invoices.get())
generate_invoice(self.order)
self.order.status = Order.STATUS_PAID
self.order.save()
djmail.outbox = []
cancel_order(self.order.pk, send_mail=True)
print([s.subject for s in djmail.outbox])
print([s.to for s in djmail.outbox])
assert len(djmail.outbox) == 2
assert ["invoice@example.org"] == djmail.outbox[0].to
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
assert ["dummy@dummy.test"] == djmail.outbox[1].to
assert not any(["Invoice_" in a[0] for a in djmail.outbox[1].attachments])
@classscope(attr='o')
def test_cancel_paid_with_too_high_fee(self):
self.order.status = Order.STATUS_PAID
@@ -1239,6 +1437,7 @@ class OrderCancelTests(TestCase):
assert self.order.all_logentries().filter(action_type='pretix.event.order.refund.requested').exists()
@pytest.mark.usefixtures("class_monkeypatch")
class OrderChangeManagerTests(TestCase):
def setUp(self):
super().setUp()
@@ -1247,6 +1446,7 @@ class OrderChangeManagerTests(TestCase):
self.event = Event.objects.create(organizer=self.o, name='Dummy', slug='dummy', date_from=now(),
plugins='pretix.plugins.banktransfer')
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
self.order = Order.objects.create(
code='FOO', event=self.event, email='dummy@dummy.test',
status=Order.STATUS_PENDING, locale='en',
@@ -1301,6 +1501,7 @@ class OrderChangeManagerTests(TestCase):
self.seat_a1 = self.event.seats.create(seat_number="A1", product=self.stalls, seat_guid="A1")
self.seat_a2 = self.event.seats.create(seat_number="A2", product=self.stalls, seat_guid="A2")
self.seat_a3 = self.event.seats.create(seat_number="A3", product=self.stalls, seat_guid="A3")
self.monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
def _enable_reverse_charge(self):
self.tr7.eu_reverse_charge = True
@@ -3525,6 +3726,79 @@ class OrderChangeManagerTests(TestCase):
assert self.op1.valid_from is None
assert self.op1.valid_until is None
@classscope(attr='o')
def test_new_invoice_attached(self):
generate_invoice(self.order)
assert self.order.invoices.count() == 1
djmail.outbox = []
self.ocm.add_position(self.ticket, None, Decimal('0.00'))
self.ocm.commit()
assert self.order.invoices.count() == 3
assert len(djmail.outbox) == 1
assert len(["Invoice_" in a[0] for a in djmail.outbox[0].attachments]) == 2
@classscope(attr='o')
def test_new_invoice_send_somewhere_else(self):
generate_invoice(self.order)
assert self.order.invoices.count() == 1
InvoiceAddress.objects.update_or_create(
order=self.order,
defaults=dict(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_address": "invoice@example.org",
}
)
)
djmail.outbox = []
self.ocm.add_position(self.ticket, None, Decimal('0.00'))
self.ocm.commit()
assert self.order.invoices.count() == 3
# Cancellation is still sent to old method!
assert len(djmail.outbox) == 2
assert ["dummy@dummy.test"] == djmail.outbox[0].to
assert len(["Invoice_" in a[0] for a in djmail.outbox[0].attachments]) == 1
assert ["invoice@example.org"] == djmail.outbox[1].to
assert len(["Invoice_" in a[0] for a in djmail.outbox[1].attachments]) == 1
@classscope(attr='o')
def test_new_invoice_split_send_somewhere_else(self):
generate_invoice(self.order)
assert self.order.invoices.count() == 1
InvoiceAddress.objects.update_or_create(
order=self.order,
defaults=dict(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_address": "invoice@example.org",
}
)
)
djmail.outbox = []
self.ocm.split(self.op2)
self.ocm.commit()
assert self.order.invoices.count() == 3
# Cancellation is still sent to old method!
assert len(djmail.outbox) == 4
assert ["dummy@dummy.test"] == djmail.outbox[0].to
assert len(["Invoice_" in a[0] for a in djmail.outbox[0].attachments]) == 1
assert ["dummy@dummy.test"] == djmail.outbox[1].to
assert len(["Invoice_" in a[0] for a in djmail.outbox[1].attachments]) == 0
assert ["invoice@example.org"] == djmail.outbox[2].to
assert len(["Invoice_" in a[0] for a in djmail.outbox[2].attachments]) == 1
assert ["invoice@example.org"] == djmail.outbox[3].to
assert len(["Invoice_" in a[0] for a in djmail.outbox[3].attachments]) == 1
@pytest.mark.django_db
def test_saleschannel_testmode_restriction(event):

View File

@@ -208,14 +208,20 @@ def test_invoice_address_shredder(event, order):
'pretix.event.order.modified',
data={
"data": [{"attendee_name": "Hans", "question_1": "Test"}],
"invoice_data": {"name": "Peter", "country": "DE", "is_business": False, "internal_reference": "",
"state": "",
"company": "ACME", "street": "Sesam Street", "city": "Sample City", "zipcode": "12345"}
"invoice_data": {
"name": "Peter", "country": "DE", "is_business": False, "internal_reference": "",
"state": "", "company": "ACME", "street": "Sesam Street", "city": "Sample City", "zipcode": "12345",
"transmission_type": "email", "transmission_info": {"transmission_email_address": "other@example.org"}
}
}
)
ia = InvoiceAddress.objects.create(
company='Acme Company', street='221B Baker Street',
zipcode='12345', city='London', country='UK',
order=order, transmission_type="email", transmission_info={
"transmission_email_address": "other@example.org"
}
)
ia = InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street',
zipcode='12345', city='London', country='UK',
order=order)
s = InvoiceAddressShredder(event)
f = list(s.generate_files())
assert json.loads(f[0][2]) == {
@@ -233,7 +239,9 @@ def test_invoice_address_shredder(event, order):
'street': '221B Baker Street',
'vat_id': '',
'vat_id_validated': False,
'zipcode': '12345'
'zipcode': '12345',
"transmission_type": "email",
"transmission_info": {"transmission_email_address": "other@example.org"}
}
}
s.shred_data()
@@ -243,7 +251,9 @@ def test_invoice_address_shredder(event, order):
assert l1.parsed_data == {
"data": [{"attendee_name": "Hans", "question_1": "Test"}],
"invoice_data": {"name": "", "country": "", "is_business": False, "internal_reference": "", "company": "",
"street": "", "city": "", "zipcode": "", "state": ""}
"street": "", "city": "", "zipcode": "", "state": "",
"transmission_type": "email",
"transmission_info": {"_shredded": True}}
}

View File

@@ -47,9 +47,9 @@ from tests.plugins.stripe.test_checkout import apple_domain_create
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,
Event, GiftCard, Invoice, 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 (
@@ -625,6 +625,19 @@ def test_order_invoice_create_ok(client, env):
assert env[2].invoices.exists()
@pytest.mark.django_db
def test_order_invoice_retransmit(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
with scopes_disabled():
i = generate_invoice(env[2])
i.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
i.save()
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/retransmit' % i.pk, {}, follow=True)
assert 'alert-success' in response.content.decode()
i.refresh_from_db()
assert i.transmission_status == Invoice.TRANSMISSION_STATUS_PENDING
@pytest.mark.django_db
def test_order_invoice_regenerate(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')

View File

@@ -1,90 +0,0 @@
#
# 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 <https://pretix.eu/about/en/license>.
#
# 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
# <https://www.gnu.org/licenses/>.
#
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
#
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
#
# This file contains Apache-licensed contributions copyrighted by: Flavia Bastos
#
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
from django.core import mail as djmail
from django_countries.fields import Country
from django_scopes import scopes_disabled
from tests.presale.test_orders import BaseOrdersTest
from pretix.base.models import InvoiceAddress, OrderPayment
from pretix.base.services.invoices import generate_invoice
class BanktransferOrdersTest(BaseOrdersTest):
def test_unknown_order(self):
response = self.client.post(
'/%s/%s/banktransfer/ABCDE/123/mail-invoice/' % (self.orga.slug, self.event.slug)
)
assert response.status_code == 404
response = self.client.post(
'/%s/%s/banktransfer/%s/123/mail-invoice/' % (self.orga.slug, self.event.slug, self.not_my_order.code)
)
assert response.status_code == 404
def test_order_with_no_invoice(self):
djmail.outbox = []
response = self.client.post(
'/%s/%s/banktransfer/%s/%s/mail-invoice/' % (
self.orga.slug, self.event.slug, self.order.code, self.order.secret),
{'email': 'test@example.org'}
)
assert response.status_code == 302
from django.contrib.messages import get_messages
messages = list(get_messages(response.wsgi_request))
assert len(messages) == 1
assert str(messages[0]) == 'No pending bank transfer payment found. Maybe the order has been paid already?'
assert len(djmail.outbox) == 0
def test_valid_order(self):
with scopes_disabled():
self.event.settings.set('payment_banktransfer_invoice_email', True)
self.order.payments.create(provider='banktransfer', state=OrderPayment.PAYMENT_STATE_CREATED,
amount=self.order.total)
InvoiceAddress.objects.create(order=self.order, company="Sample company", country=Country('NZ'))
generate_invoice(self.order)
djmail.outbox = []
response = self.client.post(
'/%s/%s/banktransfer/%s/%s/mail-invoice/' % (
self.orga.slug, self.event.slug, self.order.code, self.order.secret),
{'email': 'test@example.org'}
)
assert response.status_code == 302
from django.contrib.messages import get_messages
messages = list(get_messages(response.wsgi_request))
assert len(messages) == 1
assert str(messages[0]) == 'Sending the latest invoice via email to test@example.org.'
assert len(djmail.outbox) == 1

View File

@@ -60,6 +60,12 @@ from pretix.testutils.sessions import get_cart_session_key
from .test_timemachine import TimemachineTestMixin
@pytest.fixture
def class_monkeypatch(request, monkeypatch):
request.cls.monkeypatch = monkeypatch
@pytest.mark.usefixtures("class_monkeypatch")
class BaseCheckoutTestCase:
@scopes_disabled()
def setUp(self):
@@ -98,6 +104,7 @@ class BaseCheckoutTestCase:
self.workshopquota.items.add(self.workshop2)
self.workshopquota.variations.add(self.workshop2a)
self.workshopquota.variations.add(self.workshop2b)
self.monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
def _set_session(self, key, value):
session = self.client.session
@@ -169,7 +176,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '1234',
'city': 'Here',
'country': 'AT',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
assert b'TRACKING SCRIPT' not in payment_r.content
@@ -201,7 +209,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -224,7 +233,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
with scopes_disabled():
@@ -260,7 +270,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
assert 'alert-danger' in resp.content.decode()
@@ -291,7 +302,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'country': 'AU',
'state': 'QLD',
'vat_id': 'AU123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -324,7 +336,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -355,7 +368,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'FR',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
assert 'alert-danger' in resp.content.decode()
@@ -388,7 +402,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -422,7 +437,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -459,7 +475,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -476,7 +493,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -508,7 +526,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '12345',
'city': 'Here',
'country': 'DE',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
@@ -522,7 +541,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
@@ -556,7 +576,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '12345',
'city': 'Here',
'country': 'DE',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(r.content.decode(), "lxml")
assert doc.select(".alert-danger")
@@ -575,7 +596,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(r.content.decode(), "lxml")
assert not doc.select(".alert-danger")
@@ -605,7 +627,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '12345',
'city': 'Here',
'country': 'DE',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
@@ -633,7 +656,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '1234',
'city': 'Here',
'country': 'AT',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -685,7 +709,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '1234',
'city': 'Here',
'country': 'AT',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -714,7 +739,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'US',
'state': 'CA',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
with scopes_disabled():
@@ -767,7 +793,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -831,7 +858,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
@@ -868,7 +896,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
f = SimpleUploadedFile("testfile.txt", b"file_content")
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-question_%s' % (cr1.id, q1.id): f,
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -883,7 +912,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Delete
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-question_%s-clear' % (cr1.id, q1.id): 'on',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
with scopes_disabled():
assert not cr1.answers.exists()
@@ -904,6 +934,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Not all required fields filled out, expect failure
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -913,6 +944,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'email': 'admin@localhost',
'phone_0': '+49',
'phone_1': '0622199999', # yeah the 0 is wrong but users don't know that so it should work fine
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -942,7 +974,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Not all required fields filled out, expect failure
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-attendee_email' % cr1.id: '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -950,7 +983,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Corrected request
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-attendee_email' % cr1.id: 'foo@localhost',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -974,7 +1008,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Not all required fields filled out, expect failure
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-company' % cr1.id: '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -982,7 +1017,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Corrected request
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-company' % cr1.id: 'foobar',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1022,7 +1058,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'%s-city' % cr1.id: 'Musterstadt',
'%s-country' % cr1.id: 'DE',
'%s-state' % cr1.id: '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1049,7 +1086,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Not all required fields filled out, expect failure
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-attendee_name_parts_0' % cr1.id: '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -1057,7 +1095,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Corrected request
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-attendee_name_parts_0' % cr1.id: 'Peter',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1082,7 +1121,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Accepted request
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1112,7 +1152,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'%s-attendee_name_parts_1' % cr1.id: '',
'%s-attendee_name_parts_2' % cr1.id: 'John',
'%s-attendee_name_parts_3' % cr1.id: 'Doe',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
})
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1143,7 +1184,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Not all fields filled out, expect success
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-attendee_name_parts_0' % cr1.id: '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1173,7 +1215,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -1191,7 +1234,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1233,7 +1277,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '',
'city': '',
'country': 'BI',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -1250,7 +1295,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'zipcode': '',
'city': 'Bujumbura',
'country': 'BI',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1293,7 +1339,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -1311,11 +1358,168 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
def test_invoice_transmission_providers_email_special_case(self):
with scopes_disabled():
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertEqual(len(doc.select('input[name="transmission_email_other"]')), 1)
self.event.settings.invoice_generate = "False"
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertEqual(len(doc.select('input[name="transmission_email_other"]')), 0)
def test_invoice_transmission_providers_all_fields_loaded(self):
with scopes_disabled():
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertEqual(len(doc.select('input[name="transmission_email_address"]')), 1)
self.assertEqual(len(doc.select('input[name="transmission_email_other"]')), 1)
self.assertEqual(len(doc.select('input[name="transmission_it_sdi_codice_fiscale"]')), 1)
self.assertEqual(len(doc.select('input[name="transmission_it_sdi_pec"]')), 1)
self.assertEqual(len(doc.select('input[name="transmission_it_sdi_recipient_code"]')), 1)
self.assertEqual(len(doc.select('input[name="transmission_peppol_participant_id"]')), 1)
self.assertEqual(len(doc.select('select[name="transmission_type"]')), 1)
self.assertEqual(len(doc.select('select[name="transmission_type"] option')), 3)
def test_invoice_transmission_invalid_submission(self):
with scopes_disabled():
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
self.event.settings.invoice_address_vatid = True
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': 'Foo',
'name_parts_0': 'Mr',
'name_parts_1': 'John',
'name_parts_2': '',
'name_parts_3': 'Kennedy',
'street': 'Baz',
'zipcode': '12345',
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost',
'transmission_type': 'it_sdi',
}, follow=True)
assert "The selected transmission type is not available" in response.content.decode()
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': 'Foo',
'name_parts_0': 'Mr',
'name_parts_1': 'John',
'name_parts_2': '',
'name_parts_3': 'Kennedy',
'street': 'Baz',
'zipcode': '12345',
'city': 'Here',
'country': 'IT',
'state': 'MI',
'email': 'admin@localhost',
'transmission_type': 'it_sdi',
'vat_id': '',
}, follow=True)
assert "This field is required for the selected type" in response.content.decode()
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': 'Foo',
'name_parts_0': 'Mr',
'name_parts_1': 'John',
'name_parts_2': '',
'name_parts_3': 'Kennedy',
'street': 'Baz',
'zipcode': '12345',
'city': 'Here',
'country': 'IT',
'state': 'MI',
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
assert "must be used for this country" in response.content.decode()
def test_invoice_optional_transmission(self):
with scopes_disabled():
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = False
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
self.event.settings.invoice_address_vatid = True
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': 'Foo',
'name_parts_0': 'Mr',
'name_parts_1': 'John',
'name_parts_2': '',
'name_parts_3': 'Kennedy',
'street': 'Baz',
'zipcode': '12345',
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost',
'transmission_type': '-',
}, follow=True)
assert "If you enter an invoice address, you also need to select an invoice" in response.content.decode()
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': '',
'name_parts_0': '',
'name_parts_1': '',
'name_parts_2': '',
'name_parts_3': '',
'street': '',
'zipcode': '',
'city': '',
'country': 'IT',
'state': '',
'email': 'admin@localhost',
'transmission_type': '-',
'vat_id': '',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
with scopes_disabled():
ia = InvoiceAddress.objects.get()
assert ia.transmission_type == "email"
def test_invoice_address_hidden_for_free(self):
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
@@ -1332,7 +1536,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
self.assertEqual(len(doc.select('input[name="city"]')), 0)
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1365,7 +1570,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'city': 'Here',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1406,7 +1612,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'is_business': 'business',
'country': 'DE',
'city': 'Musterstadt',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -1416,7 +1623,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'is_business': 'business',
'country': 'DE',
'vat_id': 'DE123456',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -1437,7 +1645,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Not all required fields filled out, expect failure
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -1445,7 +1654,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Corrected request
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'name_parts_0': 'Raphael',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -2603,7 +2813,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Date too far in the future, expected to fail
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-requested_valid_from' % cr1.id: '2024-01-20',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -2611,7 +2822,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Corrected request
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-requested_valid_from' % cr1.id: '2023-01-20',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -2649,7 +2861,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-requested_valid_from_0' % cr1.id: '2024-01-20',
'%s-requested_valid_from_1' % cr1.id: '11:00:00',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -2658,7 +2871,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-requested_valid_from_0' % cr1.id: '2023-01-20',
'%s-requested_valid_from_1' % cr1.id: '11:00:00',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -2701,7 +2915,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Date too far in the future, expected to fail
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-requested_valid_from' % cr1.id: '2024-01-17',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -2709,7 +2924,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
# Corrected request
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-requested_valid_from' % cr1.id: '2023-01-10',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -2748,7 +2964,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
)
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -3797,6 +4014,120 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
o.payments.first().confirm()
assert len(djmail.outbox) == 0
def test_order_confirmation_mail_invoice_attached(self):
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
ia = InvoiceAddress.objects.create(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_other": False,
"transmission_email_address": None,
}
)
with scopes_disabled():
cp1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
djmail.outbox = []
_perform_order(self.event, self._manual_payment(), [cp1.pk], 'admin@example.org', 'en', ia.pk, {}, 'web')
assert len(djmail.outbox) == 1
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
def test_order_confirmation_mail_invoice_sent_somewhere_else(self):
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
self.event.settings.invoice_generate = "True"
self.event.settings.invoice_email_attachment = True
ia = InvoiceAddress.objects.create(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_other": True,
"transmission_email_address": "invoice@example.org",
}
)
with scopes_disabled():
cp1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
djmail.outbox = []
_perform_order(self.event, self._manual_payment(), [cp1.pk], 'admin@example.org', 'en', ia.pk, {}, 'web')
assert len(djmail.outbox) == 2
assert ["invoice@example.org"] == djmail.outbox[0].to
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
assert ["admin@example.org"] == djmail.outbox[1].to
assert not any(["Invoice_" in a[0] for a in djmail.outbox[1].attachments])
def test_order_paid_confirmation_mail_invoice_attached(self):
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
self.event.settings.invoice_generate = "paid"
self.event.settings.invoice_email_attachment = True
ia = InvoiceAddress.objects.create(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_other": False,
"transmission_email_address": None,
}
)
with scopes_disabled():
cp1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
djmail.outbox = []
oid = _perform_order(self.event, self._manual_payment(), [cp1.pk], 'admin@example.org', 'en', ia.pk, {}, 'web')
assert len(djmail.outbox) == 1
assert not any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
o = Order.objects.get(pk=oid['order_id'])
o.payments.first().confirm()
assert len(djmail.outbox) == 2
assert any(["Invoice_" in a[0] for a in djmail.outbox[1].attachments])
def test_order_paid_confirmation_mail_invoice_sent_somewhere_else(self):
self.event.settings.invoice_address_asked = True
self.event.settings.invoice_address_required = True
self.event.settings.invoice_generate = "paid"
self.event.settings.invoice_email_attachment = True
ia = InvoiceAddress.objects.create(
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_other": True,
"transmission_email_address": "invoice@example.org",
}
)
with scopes_disabled():
cp1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
djmail.outbox = []
oid = _perform_order(self.event, self._manual_payment(), [cp1.pk], 'admin@example.org', 'en', ia.pk, {}, 'web')
assert len(djmail.outbox) == 1
assert ["admin@example.org"] == djmail.outbox[0].to
assert not any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
o = Order.objects.get(pk=oid['order_id'])
o.payments.first().confirm()
assert len(djmail.outbox) == 3
assert ["invoice@example.org"] == djmail.outbox[2].to
assert any(["Invoice_" in a[0] for a in djmail.outbox[2].attachments])
def test_locale_region_not_saved(self):
self.event.settings.origin = 'US'
self.event.settings.locales = ['de']
@@ -3882,6 +4213,7 @@ class QuestionsTestCase(BaseCheckoutTestCase, TestCase):
'%s-question_%s_0' % (cr.id, q3.id): '2018-01-01',
'%s-question_%s_1' % (cr.id, q3.id): '5:23',
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), target_status_code=200)
self.event.settings.set('timezone', 'US/Central')
@@ -3966,7 +4298,8 @@ class QuestionsTestCase(BaseCheckoutTestCase, TestCase):
'%s-question_%s' % (cr2.id, q1.id): '',
'%s-question_%s' % (cr1.id, q2.id): 'Internet',
'%s-question_%s' % (cr2.id, q2.id): '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -3977,7 +4310,8 @@ class QuestionsTestCase(BaseCheckoutTestCase, TestCase):
'%s-question_%s' % (cr2.id, q1.id): '0',
'%s-question_%s' % (cr1.id, q2.id): 'Internet',
'%s-question_%s' % (cr2.id, q2.id): '',
'email': 'admin@localhost'
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -4000,6 +4334,7 @@ class QuestionsTestCase(BaseCheckoutTestCase, TestCase):
('%s-question_%s' % (cr1.id, k.id)): v for k, v in data.items() if v != 'False'
}
pl['email'] = 'admin@localhost'
pl['transmission_type'] = 'email'
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), pl, follow=True)
if should_fail:
doc = BeautifulSoup(response.content.decode(), "lxml")
@@ -4919,7 +5254,8 @@ class CustomerCheckoutTestCase(BaseCheckoutTestCase, TestCase):
self.assertRedirects(response, '/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug),
target_status_code=200)
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'will-be-ignored'
'email': 'will-be-ignored',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -5003,7 +5339,8 @@ class CustomerCheckoutTestCase(BaseCheckoutTestCase, TestCase):
target_status_code=200)
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'will-be-ignored'
'email': 'will-be-ignored',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
target_status_code=200)
@@ -5140,6 +5477,7 @@ class CustomerCheckoutTestCase(BaseCheckoutTestCase, TestCase):
assert b'Mark Fisher' not in response.content
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'email': 'will-be-ignored',
'transmission_type': 'email',
f'{cp.pk}-attendee_name_parts_0': 'will-be-ignored'
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),

View File

@@ -273,6 +273,7 @@ class OrdersTest(BaseOrdersTest):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-attendee_name_parts_0' % self.ticket_pos.id: '',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
@@ -304,6 +305,7 @@ class OrdersTest(BaseOrdersTest):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-attendee_name_parts_0' % self.ticket_pos.id: 'Peter',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response, '/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.order.secret),
@@ -326,6 +328,7 @@ class OrdersTest(BaseOrdersTest):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): '',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
@@ -350,6 +353,7 @@ class OrdersTest(BaseOrdersTest):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): '',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -357,6 +361,7 @@ class OrdersTest(BaseOrdersTest):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
@@ -374,6 +379,7 @@ class OrdersTest(BaseOrdersTest):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
@@ -390,6 +396,7 @@ class OrdersTest(BaseOrdersTest):
'street': 'Main Street',
'city': 'Heidelberg',
'country': 'DE',
'transmission_type': 'email',
}, follow=True)
doc = BeautifulSoup(response.content.decode(), "lxml")
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
@@ -403,6 +410,7 @@ class OrdersTest(BaseOrdersTest):
response = self.client.post(
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
@@ -419,6 +427,7 @@ class OrdersTest(BaseOrdersTest):
'street': 'Main Street',
'city': 'Heidelberg',
'country': 'DE',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
@@ -436,6 +445,7 @@ class OrdersTest(BaseOrdersTest):
'street': 'Main Street',
'city': 'Heidelberg',
'country': 'DE',
'transmission_type': 'email',
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
@@ -477,8 +487,12 @@ class OrdersTest(BaseOrdersTest):
# Not all fields filled out, expect success
response = self.client.post(
'/%s/%s/ticket/%s/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code,
self.ticket_pos.positionid, self.ticket_pos.web_secret)
self.ticket_pos.positionid, self.ticket_pos.web_secret),
{
'transmission_type': 'email',
}
)
print(response.content.decode())
self.assertRedirects(response,
'/%s/%s/ticket/%s/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.ticket_pos.positionid, self.ticket_pos.web_secret),

View File

@@ -22,6 +22,10 @@
from django.dispatch import receiver
from pretix.base.channels import SalesChannelType
from pretix.base.invoicing.transmission import (
TransmissionProvider, transmission_providers,
)
from pretix.base.models import Invoice
from pretix.base.signals import (
register_payment_providers, register_sales_channel_types,
register_ticket_outputs,
@@ -70,3 +74,37 @@ def html_head_presale(sender, request=None, **kwargs):
# No tracking scripts on PCI DSS relevant payment pages
return ""
return "<script>alert('BAD TRACKING SCRIPT')</script>"
@transmission_providers.new()
class TestSdiTransmissionProvider(TransmissionProvider):
identifier = "fatturapa_test"
type = "it_sdi"
verbose_name = "FatturaPA Test"
def is_ready(self, event) -> bool:
return True
def is_available(self, event, country, is_business: bool) -> bool:
return str(country) == "IT"
def transmit(self, invoice):
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
invoice.save()
@transmission_providers.new()
class TestPeppolTransmissionProvider(TransmissionProvider):
identifier = "peppol_test"
type = "peppol"
verbose_name = "PEPPOL Test"
def is_ready(self, event) -> bool:
return True
def is_available(self, event, country, is_business: bool) -> bool:
return is_business
def transmit(self, invoice):
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
invoice.save()