Compare commits

..
Author SHA1 Message Date
Raphael Michel 6b04e07f83 Drop importlib_metadata dependency
No longer needed as a polyfill and starts to break things
2026-10-30 09:21:27 +01:00
9 changed files with 25 additions and 410 deletions

No files matched your search

-4
View File
@@ -1029,7 +1029,6 @@ Creating orders
charge will be created), this is just informative in case you *handled the payment already*.
* ``payment_date`` (optional) – Date and time of the completion of the payment.
* ``tax_rounding_mode`` (optional)
* ``total`` (optional, will be computed from backend and operation will fail if the computation has a different result)
* ``comment`` (optional)
* ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional)
@@ -1060,9 +1059,6 @@ Creating orders
* ``item``
* ``variation`` (optional)
* ``price`` (optional, if set to ``null`` or missing the price will be computed from the given product)
* ``tax_rule`` (optional, if set to ``null`` or missing the tax rule will be selected from the given product)
* ``tax_rate`` (optional, only works together with a tax rule, if set to ``null`` or missing the tax rate will be computed from the given tax rule)
* ``tax_code`` (optional, only works together with a tax rule, if set to ``null`` or missing the tax code will be computed from the given tax rule)
* ``seat`` (The ``seat_guid`` attribute of a seat. Required when the specified ``item`` requires a seat, otherwise must be ``null``.)
* ``attendee_name`` **or** ``attendee_name_parts`` (optional)
* ``voucher`` (optional, the ``code`` attribute of a valid voucher)
-1
View File
@@ -61,7 +61,6 @@ dependencies = [
"dnspython==2.8.*",
"drf_ujson2==1.7.*",
"geoip2==5.*",
"importlib_metadata==9.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek",
"jsonschema",
"kombu==5.6.*",
+15 -153
View File
@@ -607,6 +607,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'print_logs', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'print_logs', 'downloads', 'answers', 'tax_rule', 'tax_code', 'pseudonymization_id', 'pdf_data', 'seat',
'canceled', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use', 'plugin_data')
read_only_fields = (
@@ -1026,56 +1027,18 @@ class OrderFeeCreateSerializer(I18nAwareModelSerializer):
_treat_value_as_percentage = serializers.BooleanField(default=False, required=False)
_split_taxes_like_products = serializers.BooleanField(default=False, required=False)
tax_rate = serializers.DecimalField(required=False, decimal_places=4, max_digits=7)
tax_value = serializers.DecimalField(required=False, decimal_places=2, max_digits=13)
value_includes_rounding_correction = serializers.DecimalField(required=False, decimal_places=2, max_digits=13)
tax_value_includes_rounding_correction = serializers.DecimalField(required=False, decimal_places=2, max_digits=13)
class Meta:
model = OrderFee
fields = (
'fee_type', 'value', 'description', 'internal_type', 'tax_rule',
'_treat_value_as_percentage', '_split_taxes_like_products',
# The following fields should usually not be set by the client but computed by the backend. However, it makes
# sense to set them when the decision on the tax rate is made irrevertably on the client side, such as with
# pretixPOS.
'tax_rate', 'tax_code',
# The following fields are undocumented and only used by pretixPOS. They do *NOT* override the calculations
# by the backends but are checked against them and must match exactly, otherwise the entire operation is
# aborted to avoid diverging tax values between POS reports and backend reports.
'tax_value', 'value_includes_rounding_correction', 'tax_value_includes_rounding_correction',
)
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rule',
'_treat_value_as_percentage', '_split_taxes_like_products')
def validate_tax_rule(self, tr):
if tr and tr.event != self.context['event']:
raise ValidationError(
'The specified tax rule does not belong to this event.'
'The specified tax rate does not belong to this event.'
)
return tr
def validate(self, attrs):
if attrs.get('_treat_value_as_percentage'):
for k in ('tax_value', 'value_includes_rounding_correction', 'tax_value_includes_rounding_correction'):
if k in attrs:
raise ValidationError({
k: ['You cannot ask for the value to be treated as a percentage if you already computed taxes.']
})
if attrs.get('_split_taxes_like_products'):
for k in ('tax_rate', 'tax_value', 'value_includes_rounding_correction', 'tax_value_includes_rounding_correction'):
if k in attrs:
raise ValidationError({
k: ['You cannot ask for the value to be distributed over tax rates if you already set the tax rate or computed taxes.']
})
if 'tax_rate' in attrs and 'tax_rule' not in attrs:
raise ValidationError({
'tax_rate': ['Tax rate cannot be stored without tax rule.']
})
if 'value_includes_rounding_correction' in attrs:
# Make all computations like discounts on the price before rounding, later compare against rounding again
attrs['value'] = attrs['value'] - attrs['value_includes_rounding_correction']
return attrs
class OrderPositionCreateSerializer(I18nAwareModelSerializer):
answers = AnswerCreateSerializer(many=True, required=False)
@@ -1091,27 +1054,13 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
requested_valid_from = serializers.DateTimeField(required=False, allow_null=True)
use_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(),
required=False, allow_null=True)
tax_rate = serializers.DecimalField(required=False, decimal_places=4, max_digits=7)
tax_value = serializers.DecimalField(required=False, decimal_places=2, max_digits=13)
price_includes_rounding_correction = serializers.DecimalField(required=False, decimal_places=2, max_digits=13)
tax_value_includes_rounding_correction = serializers.DecimalField(required=False, decimal_places=2, max_digits=13)
class Meta:
model = OrderPosition
fields = (
'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
'requested_valid_from', 'use_reusable_medium', 'discount',
# The following fields should usually not be set by the client but computed by the backend. However, it makes
# sense to set them when the decision on the tax rate is made irrevertably on the client side, such as with
# pretixPOS.
'tax_rule', 'tax_rate', 'tax_code',
# The following fields are undocumented and only used by pretixPOS. They do *NOT* override the calculations
# by the backends but are checked against them and must match exactly, otherwise the entire operation is
# aborted to avoid diverging tax values between POS reports and backend reports.
'tax_value', 'price_includes_rounding_correction', 'tax_value_includes_rounding_correction',
)
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
'requested_valid_from', 'use_reusable_medium', 'discount')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1149,13 +1098,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
)
return item
def validate_tax_rule(self, tax_rule):
if tax_rule.event != self.context['event']:
raise ValidationError(
'The specified tax rule does not belong to this event.'
)
return tax_rule
def validate_subevent(self, subevent):
if self.context['event'].has_subevents:
if not subevent:
@@ -1219,15 +1161,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
{'discount': ['You can only specify a discount if you do the price computation, but price is not set.']}
)
if 'tax_rate' in data and 'tax_rule' not in data and not data['item'].tax_rule_id:
raise ValidationError({
'tax_rate': ['Tax rate cannot be stored without tax rule.']
})
if 'price_includes_rounding_correction' in data:
# Make all computations like discounts on the price before rounding, later compare against rounding again
data['price'] = data['price'] - data['price_includes_rounding_correction']
return data
@@ -1285,7 +1218,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
tax_rounding_mode = serializers.ChoiceField(choices=ROUNDING_MODES, allow_null=True, required=False,)
locale = serializers.ChoiceField(choices=[], required=False, allow_null=True)
use_gift_cards = serializers.ListField(child=serializers.CharField(required=False), required=False)
total = serializers.DecimalField(required=False, decimal_places=2, max_digits=13)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -1300,7 +1232,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
model = Order
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date',
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', 'total',
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at',
'require_approval', 'valid_if_pending', 'expires', 'api_meta', 'tax_rounding_mode', 'use_gift_cards')
def validate_payment_provider(self, pp):
@@ -1640,7 +1572,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
if validated_data.get('locale', None) is None:
validated_data['locale'] = self.context['event'].settings.locale
expected_total = validated_data.pop('total', None)
order = Order(event=self.context['event'], **validated_data)
if not validated_data.get('expires'):
order.set_expires(subevents=[p.get('subevent') for p in positions_data])
@@ -1672,14 +1603,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos_data['attendee_name_parts'] = {
'_legacy': attendee_name
}
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in (
'answers',
'_quotas',
'use_reusable_medium',
'tax_value',
'price_includes_rounding_correction',
'tax_value_includes_rounding_correction',
)})
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium')})
if simulate:
pos.order = order._wrapped
else:
@@ -1722,12 +1646,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
if not pos.item.tax_rule or pos.item.tax_rule.price_includes_tax:
price_after_voucher = max(pos.price, pos.voucher.calculate_price(listed_price))
else:
pos._calculate_tax(
invoice_address=ia,
tax_rule=pos_data.get('tax_rule'),
override_tax_rate=pos_data.get('tax_rate'),
override_tax_code=pos_data.get('tax_code'),
)
pos._calculate_tax(invoice_address=ia)
price_after_voucher = max(pos.price - pos.tax_value, pos.voucher.calculate_price(listed_price))
else:
price_after_voucher = listed_price
@@ -1759,12 +1678,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
answers_data = pos_data.pop('answers', [])
use_reusable_medium = pos_data.pop('use_reusable_medium', None)
pos = pos_data['__instance']
pos._calculate_tax(
invoice_address=ia,
tax_rule=pos_data.get('tax_rule', None),
override_tax_rate=pos_data.get('tax_rate'),
override_tax_code=pos_data.get('tax_code'),
)
pos._calculate_tax(invoice_address=ia)
if simulate:
pos = WrappedModel(pos)
@@ -1863,11 +1777,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
for tr, val in fee_values:
fee_data['tax_rule'] = tr
fee_data['value'] = val
f = OrderFee(**{k: v for k, v in fee_data.items() if k not in (
'tax_value',
'value_includes_rounding_correction',
'tax_value_includes_rounding_correction'
)})
f = OrderFee(**fee_data)
f.order = order._wrapped if simulate else order
f._calculate_tax()
fees.append(f)
@@ -1876,17 +1786,9 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
else:
f.save()
else:
f = OrderFee(**{k: v for k, v in fee_data.items() if k not in (
'tax_value',
'value_includes_rounding_correction',
'tax_value_includes_rounding_correction'
)})
f = OrderFee(**fee_data)
f.order = order._wrapped if simulate else order
f._calculate_tax(
override_tax_rate=fee_data.get('tax_rate'),
override_tax_code=fee_data.get('tax_code'),
)
fee_data['__instance'] = f
f._calculate_tax()
fees.append(f)
if simulate:
f.id = 0
@@ -1918,47 +1820,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
"value", "value_includes_rounding_correction", "tax_value", "tax_value_includes_rounding_correction"
])
for i, pos_data in enumerate(positions_data):
pos = pos_data['__instance']
if 'tax_value' in pos_data and pos_data['tax_value'] != pos.tax_value:
raise ValidationError(f'Tax value {pos_data["tax_value"]} in position {pos.positionid} does not match backend computation of {pos.tax_value}.')
if 'price_includes_rounding_correction' in pos_data and pos_data['price_includes_rounding_correction'] != pos.price_includes_rounding_correction:
raise ValidationError(
f'Price rounding {pos_data["price_includes_rounding_correction"]} in position {pos.positionid} does not '
f'match backend computation of {pos.price_includes_rounding_correction}.'
)
if ('tax_value_includes_rounding_correction' in pos_data and
pos_data['tax_value_includes_rounding_correction'] != pos.tax_value_includes_rounding_correction):
raise ValidationError(
f'Tax rounding {pos_data["tax_value_includes_rounding_correction"]} in position {pos.positionid} does not '
f'match backend computation of {pos.tax_value_includes_rounding_correction}.'
)
for i, fee_data in enumerate(fees_data):
if '__instance' not in fee_data: # split fee not supported for this, validated in OrderFeeCreateSerializer
continue
fee = fee_data['__instance']
if 'tax_value' in fee_data and fee_data['tax_value'] != fee.tax_value:
raise ValidationError(f'Tax value {fee_data["tax_value"]} in fee does not match backend computation of {fee.tax_value}.')
if 'value_includes_rounding_correction' in fee_data and fee_data['value_includes_rounding_correction'] != fee.value_includes_rounding_correction:
raise ValidationError(
f'Price rounding {fee_data["value_includes_rounding_correction"]} in fee does not '
f'match backend computation of {fee.value_includes_rounding_correction}.'
)
if ('tax_value_includes_rounding_correction' in fee_data and
fee_data['tax_value_includes_rounding_correction'] != fee.tax_value_includes_rounding_correction):
raise ValidationError(
f'Tax rounding {fee_data["tax_value_includes_rounding_correction"]} in fee does not '
f'match backend computation of {fee.tax_value_includes_rounding_correction}.'
)
order.total = sum([c.price for c in pos_map.values()]) + sum([f.value for f in fees])
if expected_total is not None and expected_total != order.total:
raise ValidationError({
'total': [f'Order total of {expected_total} does not match backend computation of {order.total}.']
})
if simulate:
order.fees = fees
order.positions = pos_map.values()
+4 -6
View File
@@ -2450,7 +2450,7 @@ class OrderFee(RoundingCorrectionMixin, models.Model):
self.fee_type, self.value
)
def _calculate_tax(self, tax_rule=None, invoice_address=None, event=None, override_tax_rate=None, override_tax_code=None):
def _calculate_tax(self, tax_rule=None, invoice_address=None, event=None):
if tax_rule:
self.tax_rule = tax_rule
@@ -2469,8 +2469,7 @@ class OrderFee(RoundingCorrectionMixin, models.Model):
self.tax_rule = event.cached_default_tax_rule
if self.tax_rule:
tax = self.tax_rule.tax(self.value, base_price_is='gross', invoice_address=ia, force_fixed_gross_price=True,
override_tax_rate=override_tax_rate, override_tax_code=override_tax_code)
tax = self.tax_rule.tax(self.value, base_price_is='gross', invoice_address=ia, force_fixed_gross_price=True)
self.tax_rate = tax.rate
self.tax_code = tax.code
self.tax_value = tax.tax
@@ -2833,15 +2832,14 @@ class OrderPosition(AbstractPosition):
self.item.id, self.variation.id if self.variation else 0, self.order_id
)
def _calculate_tax(self, tax_rule=None, invoice_address=None, override_tax_rate=None, override_tax_code=None):
def _calculate_tax(self, tax_rule=None, invoice_address=None):
self.tax_rule = tax_rule or self.item.tax_rule
try:
ia = invoice_address or self.order.invoice_address
except InvoiceAddress.DoesNotExist:
ia = None
if self.tax_rule:
tax = self.tax_rule.tax(self.price, invoice_address=ia, base_price_is='gross', force_fixed_gross_price=True,
override_tax_rate=override_tax_rate, override_tax_code=override_tax_code)
tax = self.tax_rule.tax(self.price, invoice_address=ia, base_price_is='gross', force_fixed_gross_price=True)
self.tax_rate = tax.rate
self.tax_code = tax.code
self.tax_value = tax.tax
+1 -1
View File
@@ -22,9 +22,9 @@
import os
import sys
from enum import Enum
from importlib import metadata
from typing import List
import importlib_metadata as metadata
from django.apps import AppConfig, apps
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
+1 -1
View File
@@ -23,10 +23,10 @@ import json
import logging
import pathlib
import re
from importlib import metadata
from urllib.parse import urljoin
from urllib.request import urlopen
import importlib_metadata as metadata
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
+2 -1
View File
@@ -31,7 +31,8 @@
# 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.
import importlib_metadata as metadata
from importlib import metadata as metadata
from django.conf import settings
from django.contrib import messages
from django.db import transaction
+1 -1
View File
@@ -41,7 +41,7 @@ from json import loads
from pathlib import Path
from urllib.parse import urlparse
import importlib_metadata as metadata
from importlib import metadata
from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import get_random_string
from kombu import Queue
+1 -242
View File
@@ -1012,58 +1012,6 @@ def test_order_create_tax_rules(token_client, organizer, event, item, quota, que
assert pos.tax_rule == taxrule
@pytest.mark.django_db
def test_order_create_tax_rule_explicit(token_client, organizer, event, item, quota, question, taxrule):
item.tax_rule = None
item.save()
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['fees'][0]['tax_rule'] = taxrule.pk
res['positions'][0]['item'] = item.pk
res['positions'][0]['tax_rule'] = taxrule.pk
res['positions'][0]['answers'][0]['question'] = question.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
o = Order.objects.get(code=resp.data['code'])
pos = o.positions.first()
assert pos.item == item
assert pos.tax_rate == Decimal('19.00')
assert pos.tax_value == Decimal('3.67')
assert pos.tax_rule == taxrule
assert pos.tax_code == "S/standard"
@pytest.mark.django_db
def test_order_create_tax_rule_custom_rate_and_code(token_client, organizer, event, item, quota, question, taxrule):
item.tax_rule = None
item.save()
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['fees'][0]['tax_rule'] = taxrule.pk
res['positions'][0]['item'] = item.pk
res['positions'][0]['tax_rule'] = taxrule.pk
res['positions'][0]['tax_code'] = 'S/reduced'
res['positions'][0]['tax_rate'] = "7.00"
res['positions'][0]['answers'][0]['question'] = question.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
o = Order.objects.get(code=resp.data['code'])
pos = o.positions.first()
assert pos.item == item
assert pos.tax_rate == Decimal('7.00')
assert pos.tax_value == Decimal('1.50')
assert pos.tax_rule == taxrule
assert pos.tax_code == "S/reduced"
@pytest.mark.django_db
def test_order_create_fee_type_validation(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
@@ -1159,75 +1107,6 @@ def test_order_create_fee_with_auto_tax(token_client, organizer, event, item, qu
assert o.total == Decimal('25.30')
@pytest.mark.django_db
def test_order_create_fee_with_explicit_tax_and_percentage(token_client, organizer, event, item, quota, question, taxrule):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['fees'][0]['tax_rate'] = '7.00'
res['fees'][0]['tax_rule'] = taxrule.pk
res['fees'][0]['_split_taxes_like_products'] = False
res['fees'][0]['_treat_value_as_percentage'] = True
res['fees'][0]['value'] = '10.00'
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
item.tax_rule = taxrule
item.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
o = Order.objects.get(code=resp.data['code'])
fee = o.fees.first()
assert fee.value == Decimal('2.30')
assert fee.tax_rate == Decimal('7.00')
assert fee.tax_value == Decimal('0.15')
assert o.total == Decimal('25.30')
@pytest.mark.django_db
def test_order_create_fee_with_explicit_tax_and_split_requested(token_client, organizer, event, item, quota, question, taxrule):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['fees'][0]['tax_rate'] = '7.00'
res['fees'][0]['tax_rule'] = taxrule.pk
res['fees'][0]['_split_taxes_like_products'] = True
res['fees'][0]['_treat_value_as_percentage'] = True
res['fees'][0]['value'] = '10.00'
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
item.tax_rule = taxrule
item.save()
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 == {
'fees': [{'tax_rate': ['You cannot ask for the value to be distributed over tax rates if you already set the tax rate or computed taxes.']}],
}
res['fees'][0]['tax_rate'] = '7.00'
res['fees'][0]['tax_value'] = '1.50'
res['fees'][0]['tax_rule'] = taxrule.pk
res['fees'][0]['_treat_value_as_percentage'] = True
res['fees'][0]['value'] = '10.00'
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
item.tax_rule = taxrule
item.save()
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 == {
'fees': [{'tax_value': ['You cannot ask for the value to be treated as a percentage if you already computed taxes.']}],
}
@pytest.mark.django_db
def test_order_create_negative_fee_with_auto_tax(token_client, organizer, event, item, quota, question, taxrule):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
@@ -1257,7 +1136,6 @@ def test_order_create_tax_rule_wrong_event(token_client, organizer, event, item,
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['fees'][0]['tax_rule'] = taxrule2.pk
res['positions'][0]['item'] = item.pk
res['positions'][0]['tax_rule'] = taxrule2.pk
res['positions'][0]['answers'][0]['question'] = question.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
@@ -1265,10 +1143,7 @@ def test_order_create_tax_rule_wrong_event(token_client, organizer, event, item,
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {
'positions': [{'tax_rule': ['The specified tax rule does not belong to this event.']}],
'fees': [{'tax_rule': ['The specified tax rule does not belong to this event.']}],
}
assert resp.data == {'fees': [{'tax_rule': ['The specified tax rate does not belong to this event.']}]}
@pytest.mark.django_db
@@ -3818,119 +3693,3 @@ def test_order_create_use_gift_card_invalid_secret(token_client, organizer, even
assert o.payments.count() == 2
assert o.payments.all()[0].state == OrderPayment.PAYMENT_STATE_FAILED
assert o.payments.all()[1].state == OrderPayment.PAYMENT_STATE_CONFIRMED
@pytest.mark.django_db
@pytest.mark.parametrize("rounding_mode,data,error", [
("line", {"tax_value": "2.40"}, "Tax value 2.40 in position 1 does not match backend computation of 2.39."),
("line", {"tax_value": "2.39"}, None),
("line", {"price_includes_rounding_correction": "0.01"}, "Price rounding 0.01 in position 1 does not match backend computation of 0.00."),
("line", {"tax_value_includes_rounding_correction": "0.01"}, "Tax rounding 0.01 in position 1 does not match backend computation of 0.00."),
("sum_by_net",
{"tax_value": "2.39"},
"Tax value 2.39 in position 1 does not match backend computation of 2.40."),
("sum_by_net",
{"tax_value": "2.40", "price": "15.01", "price_includes_rounding_correction": "0.01"},
None),
("sum_by_net",
{"tax_value": "2.40", "price": "15.00", "price_includes_rounding_correction": "0.00"},
"Price rounding 0.00 in position 1 does not match backend computation of 0.01."),
("sum_by_net",
{"tax_value": "2.40", "price": "15.01", "price_includes_rounding_correction": "0.01", "tax_value_includes_rounding_correction": "0.00"},
"Tax rounding 0.00 in position 1 does not match backend computation of 0.01."),
])
def test_order_tax_match_validation(token_client, organizer, event, item, quota, question, taxrule,
rounding_mode, data, error):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
del res['fees']
res['tax_rounding_mode'] = rounding_mode
res['positions'][0]['item'] = item.pk
res['positions'][0]['price'] = "15.00"
res['positions'][0]['tax_rule'] = taxrule.pk
res['positions'][0]['answers'][0]['question'] = question.pk
res['positions'][0].update(data)
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
if error:
assert resp.status_code == 400
assert resp.data == [error]
else:
assert resp.status_code == 201
@pytest.mark.django_db
@pytest.mark.parametrize("rounding_mode,data,error", [
("line", {"tax_value": "1.61"}, "Tax value 1.61 in fee does not match backend computation of 1.60."),
("line", {"tax_value": "1.60"}, None),
("line", {"value_includes_rounding_correction": "0.01"},
"Price rounding 0.01 in fee does not match backend computation of 0.00."),
("line", {"tax_value_includes_rounding_correction": "0.01"},
"Tax rounding 0.01 in fee does not match backend computation of 0.00."),
("sum_by_net",
{"tax_value": "1.60"},
"Tax value 1.60 in fee does not match backend computation of 1.59."),
("sum_by_net",
{"tax_value": "1.59", "value": "9.99", "value_includes_rounding_correction": "-0.01"},
None),
("sum_by_net",
{"tax_value": "1.59", "value": "10.00", "value_includes_rounding_correction": "0.00"},
"Price rounding 0.00 in fee does not match backend computation of -0.01."),
("sum_by_net",
{"tax_value": "1.59", "value": "9.99", "value_includes_rounding_correction": "-0.01",
"tax_value_includes_rounding_correction": "0.00"},
"Tax rounding 0.00 in fee does not match backend computation of -0.01."),
])
def test_order_tax_match_validation_of_fee(token_client, organizer, event, item, quota, question, taxrule,
rounding_mode, data, error):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['tax_rounding_mode'] = rounding_mode
res['positions'][0]['item'] = item.pk
res['positions'][0]['price'] = "5.00"
res['positions'][0]['tax_rule'] = taxrule.pk
res['positions'][0]['answers'][0]['question'] = question.pk
res['fees'][0]['value'] = "10.00"
res['fees'][0]['tax_rule'] = taxrule.pk
res['fees'][0].update(data)
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
if error:
assert resp.status_code == 400
assert resp.data == [error]
else:
assert resp.status_code == 201
@pytest.mark.django_db
def test_order_expect_total(token_client, organizer, event, item, quota, question, taxrule):
# Force a rounding difference to make sure it's the last step of total computation that is compared
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
del res['fees']
res['tax_rounding_mode'] = "sum_by_net"
res['positions'][0]['item'] = item.pk
res['positions'][0]['price'] = "15.00"
res['positions'][0]['tax_rule'] = taxrule.pk
res['positions'][0]['answers'][0]['question'] = question.pk
res['total'] = "15.00"
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 == {
'total': ['Order total of 15.00 does not match backend computation of 15.01.']
}
res['total'] = "15.01"
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201