From 991e4127f60678a65d27fddb12c368704011ac2a Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sat, 23 Mar 2019 13:51:25 +0100 Subject: [PATCH] Refs #654 -- Allow to update invoice addresses --- doc/api/resources/orders.rst | 2 + src/pretix/api/serializers/order.py | 28 +++++++++++- src/pretix/api/views/order.py | 10 +++++ src/tests/api/test_orders.py | 68 +++++++++++++++++++++++++++-- 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 76cee34dc2..f365b9435e 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -578,6 +578,8 @@ Updating order fields * ``comment`` + * ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address) + **Example request**: .. sourcecode:: http diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 3d90086448..3904952942 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -220,7 +220,7 @@ class OrderRefundSerializer(I18nAwareModelSerializer): class OrderSerializer(I18nAwareModelSerializer): - invoice_address = InvoiceAddressSerializer(read_only=True) + invoice_address = InvoiceAddressSerializer(allow_null=True) positions = OrderPositionSerializer(many=True, read_only=True) fees = OrderFeeSerializer(many=True, read_only=True) downloads = OrderDownloadsField(source='*', read_only=True) @@ -238,7 +238,7 @@ class OrderSerializer(I18nAwareModelSerializer): ) read_only_fields = ( 'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date', - 'payment_provider', 'fees', 'total', 'invoice_address', 'positions', 'downloads', + 'payment_provider', 'fees', 'total', 'positions', 'downloads', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel' ) @@ -256,10 +256,34 @@ class OrderSerializer(I18nAwareModelSerializer): # Even though all fields that shouldn't be edited are marked as read_only in the serializer # (hopefully), we'll be extra careful here and be explicit about the model fields we update. update_fields = ['comment', 'checkin_attention', 'email', 'locale'] + print(validated_data) + + if 'invoice_address' in validated_data: + iadata = validated_data.pop('invoice_address') + + if not iadata: + try: + instance.invoice_address.delete() + except InvoiceAddress.DoesNotExist: + pass + else: + name = iadata.pop('name', '') + if name and not iadata.get('name_parts'): + iadata['name_parts'] = { + '_legacy': name + } + try: + ia = instance.invoice_address + if iadata.get('vat_id') != ia.vat_id: + ia.vat_id_validated = False + self.fields['invoice_address'].update(ia, iadata) + except InvoiceAddress.DoesNotExist: + InvoiceAddress.objects.create(order=instance, **iadata) for attr, value in validated_data.items(): if attr in update_fields: setattr(instance, attr, value) + instance.save(update_fields=update_fields) return instance diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 12d6c481ff..9b9e9134ff 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -494,6 +494,16 @@ class OrderViewSet(viewsets.ModelViewSet): } ) + if 'invoice_address' in self.request.data: + serializer.instance.log_action( + 'pretix.event.order.modified', + user=self.request.user, + auth=self.request.auth, + data={ + 'invoice_data': self.request.data.get('invoice_address'), + } + ) + serializer.save() def perform_create(self, serializer): diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 41b3fb70b3..a4fd746ff2 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -94,7 +94,8 @@ def order(event, item, taxrule, question): ) o.fees.create(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=Decimal('0.25'), tax_rate=Decimal('19.00'), tax_value=Decimal('0.05'), tax_rule=taxrule) - InvoiceAddress.objects.create(order=o, company="Sample company", country=Country('NZ')) + InvoiceAddress.objects.create(order=o, company="Sample company", country=Country('NZ'), + vat_id="DE123", vat_id_validated=True) op = OrderPosition.objects.create( order=o, item=item, @@ -214,8 +215,8 @@ TEST_ORDER_RES = { "city": "", "country": "NZ", "internal_reference": "", - "vat_id": "", - "vat_id_validated": False + "vat_id": "DE123", + "vat_id_validated": True }, "require_approval": False, "positions": [TEST_ORDERPOSITION_RES], @@ -2586,7 +2587,19 @@ def test_order_update_allowed_fields(token_client, organizer, event, order): 'comment': 'Here is a comment', 'checkin_attention': True, 'email': 'foo@bar.com', - 'locale': 'de' + 'locale': 'de', + 'invoice_address': { + "is_business": False, + "company": "This is my company name", + "name": "John Doe", + "name_parts": {}, + "street": "", + "zipcode": "", + "city": "Paris", + "country": "Fr", + "internal_reference": "", + "vat_id": "", + } } ) assert resp.status_code == 200 @@ -2595,10 +2608,57 @@ def test_order_update_allowed_fields(token_client, organizer, event, order): assert order.checkin_attention assert order.email == 'foo@bar.com' assert order.locale == 'de' + assert order.invoice_address.company == "This is my company name" + assert order.invoice_address.name_cached == "John Doe" + assert order.invoice_address.name_parts == {'_legacy': 'John Doe'} + assert str(order.invoice_address.country) == "FR" + assert not order.invoice_address.vat_id_validated + assert order.invoice_address.city == "Paris" assert order.all_logentries().get(action_type='pretix.event.order.comment') assert order.all_logentries().get(action_type='pretix.event.order.checkin_attention') assert order.all_logentries().get(action_type='pretix.event.order.contact.changed') assert order.all_logentries().get(action_type='pretix.event.order.locale.changed') + assert order.all_logentries().get(action_type='pretix.event.order.modified') + + +@pytest.mark.django_db +def test_order_update_invoiceaddress_delete_create(token_client, organizer, event, order): + event.settings.locales = ['de', 'en'] + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/orders/{}/'.format( + organizer.slug, event.slug, order.code + ), format='json', data={ + 'invoice_address': None, + } + ) + assert resp.status_code == 200 + order.refresh_from_db() + with pytest.raises(InvoiceAddress.DoesNotExist): + order.invoice_address + + resp = token_client.patch( + '/api/v1/organizers/{}/events/{}/orders/{}/'.format( + organizer.slug, event.slug, order.code + ), format='json', data={ + 'invoice_address': { + "is_business": False, + "company": "This is my company name", + "name": "", + "name_parts": {}, + "street": "", + "zipcode": "", + "city": "Paris", + "country": "Fr", + "internal_reference": "", + "vat_id": "", + } + } + ) + assert resp.status_code == 200 + order.refresh_from_db() + assert order.invoice_address.company == "This is my company name" + assert str(order.invoice_address.country) == "FR" + assert order.invoice_address.city == "Paris" @pytest.mark.django_db