diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 6ac57f2b6c..0f2bda20e5 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -953,7 +953,7 @@ Order state operations .. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/mark_expired/ - Marks a unpaid order as expired. + Marks an unpaid order as expired. **Example request**: @@ -1115,6 +1115,47 @@ Order state operations :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 404: The requested order does not exist. +Invoice generation +------------------ + +.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/create_invoice/ + + Creates an invoice for an order which currently does not have an invoice. Returns the + invoice object. + + **Example request**: + + .. sourcecode:: http + + POST /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/create_invoice/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "order": "FOO", + "number": "DUMMY-00001", + "is_cancellation": false, + ... + } + + :param organizer: The ``slug`` field of the organizer to modify + :param event: The ``slug`` field of the event to modify + :param code: The ``code`` field of the order to create an invoice for + :statuscode 200: no error + :statuscode 400: The invoice can not be created (invoicing disabled, the order already has an invoice, …) + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. + :statuscode 404: The requested order does not exist. + List of all order positions --------------------------- diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index a5b70ddeb1..3b930ad8dc 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -9,6 +9,7 @@ from django.db.models.functions import Coalesce, Concat from django.http import FileResponse from django.shortcuts import get_object_or_404 from django.utils.timezone import make_aware, now +from django.utils.translation import ugettext as _ from django_filters.rest_framework import DjangoFilterBackend, FilterSet from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import detail_route @@ -308,6 +309,38 @@ class OrderViewSet(viewsets.ModelViewSet): ) return self.retrieve(request, [], **kwargs) + @detail_route(methods=['POST']) + def create_invoice(self, request, **kwargs): + order = self.get_object() + has_inv = order.invoices.exists() and not ( + order.status in (Order.STATUS_PAID, Order.STATUS_PENDING) + and order.invoices.filter(is_cancellation=True).count() >= order.invoices.filter(is_cancellation=False).count() + ) + if self.request.event.settings.get('invoice_generate') not in ('admin', 'user', 'paid', 'True') or not invoice_qualified(order): + return Response( + {'detail': _('You cannot generate an invoice for this order.')}, + status=status.HTTP_400_BAD_REQUEST + ) + elif has_inv: + return Response( + {'detail': _('An invoice for this order already exists.')}, + status=status.HTTP_400_BAD_REQUEST + ) + + inv = generate_invoice(order) + order.log_action( + 'pretix.event.order.invoice.generated', + user=self.request.user, + auth=self.request.auth, + data={ + 'invoice': inv.pk + } + ) + return Response( + InvoiceSerializer(inv).data, + status=status.HTTP_201_CREATED + ) + @detail_route(methods=['POST']) def extend(self, request, **kwargs): new_date = request.data.get('expires', None) diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 528d75f043..cc6ceaa095 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -2626,3 +2626,65 @@ def test_order_update_locale_to_invalid(token_client, organizer, event, order): ) assert resp.status_code == 400 assert resp.data == {'locale': ['"de" is not a supported locale for this event.']} + + +@pytest.mark.django_db +def test_order_create_invoice(token_client, organizer, event, order): + event.settings.invoice_generate = 'True' + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/{}/create_invoice/'.format( + organizer.slug, event.slug, order.code + ), format='json', data={} + ) + assert resp.status_code == 201 + assert resp.data == { + 'order': 'FOO', + 'number': 'DUMMY-00001', + 'is_cancellation': False, + 'invoice_from': '', + 'invoice_to': 'Sample company\n\n\n \nNew Zealand', + 'date': '2019-03-23', + 'refers': None, + 'locale': 'en', + 'introductory_text': '', + 'additional_text': '', + 'payment_provider_text': '', + 'footer_text': '', + 'lines': [ + { + 'description': 'Budget Ticket
Attendee: Peter', + 'gross_value': '23.00', + 'tax_value': '0.00', + 'tax_rate': '0.00', + 'tax_name': '' + }, + { + 'description': 'Payment fee', + 'gross_value': '0.25', + 'tax_value': '0.05', + 'tax_rate': '19.00', + 'tax_name': '' + } + ], + 'foreign_currency_display': None, + 'foreign_currency_rate': None, + 'foreign_currency_rate_date': None, + 'internal_reference': '' + } + + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/{}/create_invoice/'.format( + organizer.slug, event.slug, order.code + ), format='json', data={} + ) + assert resp.data == {'detail': 'An invoice for this order already exists.'} + assert resp.status_code == 400 + + event.settings.invoice_generate = 'False' + resp = token_client.post( + '/api/v1/organizers/{}/events/{}/orders/{}/create_invoice/'.format( + organizer.slug, event.slug, order.code + ), format='json', data={} + ) + assert resp.status_code == 400 + assert resp.data == {'detail': 'You cannot generate an invoice for this order.'}