Compare commits

..
Author SHA1 Message Date
Kara Engelhardt 0423de48bc Fix linting errors in plugins.js 2026-09-10 12:44:47 +02:00
Kara Engelhardt 6df638a3ac Plugin List: Apply searchfilter if prefilled by browser
Browsers often prefill the form fields, e.g. with their old content when reloading or going back from the previous page. However these filters were not applied until you changed one of the form fields
2026-09-10 12:44:47 +02:00
Yasunobu YesNo Kawaguchi c05dee7af5 Translations: Update Japanese
Currently translated at 100.0% (6419 of 6419 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2026-09-10 12:36:49 +02:00
Albizuri e399a49c8b Translations: Update Basque
Currently translated at 48.0% (3086 of 6419 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/eu/

powered by weblate
2026-09-10 12:36:49 +02:00
Linnea Thelander b9b951e56b Translations: Update Swedish
Currently translated at 98.0% (255 of 260 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/sv/

powered by weblate
2026-09-10 12:36:49 +02:00
Linnea Thelander 88bb1f4008 Translations: Update Swedish
Currently translated at 85.8% (5509 of 6419 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/sv/

powered by weblate
2026-09-10 12:36:49 +02:00
Renne Rocha 24e7db7ddd Translations: Update Portuguese (Brazil)
Currently translated at 92.4% (5937 of 6419 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2026-09-10 12:36:49 +02:00
Hijiri Umemoto 96ed6fe1a6 Translations: Update Japanese
Currently translated at 100.0% (6419 of 6419 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2026-09-10 12:36:49 +02:00
13 changed files with 152 additions and 543 deletions
-1
View File
@@ -49,7 +49,6 @@ const ignores = globalIgnores([
'src/pretix/static/pretixcontrol/js/ui/editor.js',
'src/pretix/static/pretixcontrol/js/ui/geo.js',
'src/pretix/static/pretixcontrol/js/ui/main.js',
'src/pretix/static/pretixcontrol/js/ui/plugins.js',
'src/pretix/static/pretixcontrol/js/ui/subevent.js',
'src/pretix/static/pretixcontrol/js/ui/variations.js',
'src/pretix/static/pretixcontrol/js/ui/webauthn.js',
+1 -1
View File
@@ -343,7 +343,7 @@ class Discount(LoggedModel):
if collect_potential_discounts is not None:
if n_groups * self.benefit_only_apply_to_cheapest_n_matches > len(benefit_idx_group):
# partially used discount ("for each 1 ticket you buy, get 50% on 2 t-shirts", cart content: 1 ticket
# but only 1 t-shirt) -> 1 shirt definitively potential discount
# but only 1 t-shirt) -> 1 shirt definitiv potential discount
for idx in consume_idx:
collect_potential_discounts[idx] = [
(self, n_groups * self.benefit_only_apply_to_cheapest_n_matches - len(benefit_idx_group), -1, subevent_id)
+1 -3
View File
@@ -510,9 +510,7 @@ class Item(LoggedModel):
verbose_name=_("Free price input"),
help_text=_("If this option is active, your users can choose the price themselves. The price configured above "
"is then interpreted as the minimum price a user has to enter. You could use this e.g. to collect "
"additional donations for your event. We recommend against combining this feature with automatic "
"discounts since discounts are applied as the last step of price computation, which means that the "
"price entered by the customer will be modified again.")
"additional donations for your event.")
)
free_price_suggestion = models.DecimalField(
verbose_name=_("Suggested price"),
@@ -51,7 +51,6 @@ class CrossSellingService:
self.sales_channel = sales_channel
self.cartpositions = cartpositions
self.customer = customer
self._discount_cache = {}
def get_data(self):
if self.event.has_subevents:
@@ -178,7 +177,6 @@ class CrossSellingService:
)
if self.customer else None
),
_discount_cache=self._discount_cache,
)
new_items = list()
for item in items:
+7 -7
View File
@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
"PO-Revision-Date: 2026-08-24 14:30+0000\n"
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
"Last-Translator: Albizuri <oier@puntu.eus>\n"
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/eu/"
">\n"
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix/"
"eu/>\n"
"Language: eu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.8.1\n"
"X-Generator: Weblate 2026.9\n"
#: pretix/_base_settings.py
msgid "English"
@@ -891,7 +891,7 @@ msgstr "Produktuaren data"
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/presale/templates/pretixpresale/event/order.html
msgid "Order details"
msgstr ""
msgstr "Eskaeraren xehetasunak"
#: pretix/base/datasync/sourcefields.py pretix/base/modelimport_orders.py
#: pretix/control/forms/filter.py
@@ -17296,7 +17296,7 @@ msgstr ""
#: pretix/control/logdisplay.py
msgid "The order details have been changed."
msgstr ""
msgstr "Eskaeraren xehetasunak aldatu dira."
#: pretix/control/logdisplay.py
msgid "The order has been marked as unpaid."
@@ -23106,7 +23106,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
#, python-format
msgid "Order details: %(code)s"
msgstr ""
msgstr "Eskaeraren xehetasunak: %(code)s"
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/index.html
+51 -70
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
"PO-Revision-Date: 2026-08-16 22:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"PO-Revision-Date: 2026-09-07 18:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
"Language: ja\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 2026.8.1\n"
"X-Generator: Weblate 2026.9\n"
#: pretix/_base_settings.py
msgid "English"
@@ -93,7 +93,7 @@ msgstr "ヘブライ語"
#: pretix/_base_settings.py
msgid "Hungarian"
msgstr ""
msgstr "ハンガリー語"
#: pretix/_base_settings.py
msgid "Indonesian"
@@ -220,7 +220,7 @@ msgstr "ターゲットURL"
#: pretix/api/models.py pretix/base/models/devices.py
#: pretix/base/models/organizer.py
msgid "All events (including newly created ones)"
msgstr "すべてのイベント(最近作成されたイベントを含む)"
msgstr "すべてのイベント(今後作成されるものを含む)"
#: pretix/api/models.py pretix/base/models/devices.py
#: pretix/base/models/organizer.py
@@ -428,10 +428,8 @@ msgid "You cannot exchange a medium for a medium."
msgstr "メディアを別のメディアに変更することはできません。"
#: pretix/api/views/checkin.py
#, fuzzy
#| msgid "Product does not support medium exchange."
msgid "You cannot simulate a medium exchange."
msgstr "本製品は中程度の交換に対応していません。"
msgstr "メディアの交換をシミュレートすることはできません。"
#: pretix/api/views/oauth.py pretix/control/logdisplay.py
#, python-brace-format
@@ -5691,7 +5689,7 @@ msgstr "国コード(ISO 3166-1 alpha-2"
#: pretix/base/models/items.py
msgid "Asked on"
msgstr ""
msgstr "質問日"
#: pretix/base/models/items.py pretix/base/models/organizer.py
msgid ""
@@ -7340,12 +7338,9 @@ msgid "The payment for this invoice has already been received."
msgstr "この請求書の支払いはすでに受領済みです。"
#: pretix/base/payment.py
#, fuzzy
#| msgid ""
#| "This payment is already being processed and can not be canceled any more."
msgid ""
"This payment is already being processed and cannot be canceled any more."
msgstr "この支払いは処理中のため、キャンセルできません。"
msgstr "この支払いはすでに処理中のため、キャンセルできません。"
#: pretix/base/payment.py
msgid "Automatic refunds are not supported by this payment provider."
@@ -8105,16 +8100,12 @@ msgid "Presale end"
msgstr "前売り終了"
#: pretix/base/reldate.py
#, fuzzy
#| msgid "Order email"
msgid "Order creation"
msgstr "注文者メール"
msgstr "注文の作成"
#: pretix/base/reldate.py
#, fuzzy
#| msgid "Order expired"
msgid "Order expiry"
msgstr "注文の有効期限が切れました"
msgstr "注文の失効"
#: pretix/base/reldate.py
msgid "before"
@@ -8143,22 +8134,22 @@ msgstr "未設定"
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative date cannot be expressed as \"before\" for \"{}\""
msgstr ""
msgstr "相対的な日付は、「{}」に対して「before」で表すことはできません"
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative date cannot be expressed as \"after\" for \"{}\""
msgstr ""
msgstr "相対的な日付は「{}」に対して「after」として表すことはできません"
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative time cannot be expressed as \"before\" for \"{}\""
msgstr ""
msgstr "相対時間は「{}」に対して「before」で表すことはできません"
#: pretix/base/reldate.py
#, python-brace-format
msgid "A relative time cannot be expressed as \"after\" for \"{}\""
msgstr ""
msgstr "相対時間は「{}」に対して「after」で表すことはできません"
#: pretix/base/secrets.py
msgid "Random (default, works with all pretix apps)"
@@ -10067,12 +10058,12 @@ msgstr ""
#: pretix/base/settings.py
msgid "No dates match your criteria."
msgstr ""
msgstr "条件に合致する日付はありません。"
#: pretix/base/settings.py
msgctxt "subevents"
msgid "Text for empty date results"
msgstr ""
msgstr "空の日付結果のテキスト"
#: pretix/base/settings.py
msgctxt "subevents"
@@ -10083,6 +10074,10 @@ msgid ""
"touch with you to arrange further dates. We do not recommend more than one "
"or two sentences."
msgstr ""
"このテキストは、カレンダーまたは日付リストが空の場合に表示されます。たとえば"
"、月に日付が含まれていない場合や、ユーザーが選択したフィルターに結果が見つか"
"らない場合です。これをご利用いただくことで、今後の日程調整のために連絡する方"
"法を宣伝できます。1文または2文以上は推奨いたしません。"
#: pretix/base/settings.py
msgid "Guidance text"
@@ -13902,26 +13897,20 @@ msgid ""
msgstr "ギフトカードの有効期限を発行年を含めた{}年に設定しました。"
#: pretix/control/forms/event.py
#, fuzzy
#| msgid "Payment date"
msgid "Payment term"
msgstr "支払い日"
msgstr "支払条件"
#: pretix/control/forms/event.py
msgid "same as above"
msgstr ""
msgstr "上記と同じ"
#: pretix/control/forms/event.py
#, fuzzy
#| msgid "Payment term in days"
msgid "different payment term in days"
msgstr "支払条件(日数)"
msgstr "日数で表す異なる支払条件"
#: pretix/control/forms/event.py
#, fuzzy
#| msgid "Payment term in minutes"
msgid "different payment term in minutes"
msgstr "支払い期限(分単位)"
msgstr "分で表す異なる支払条件"
#: pretix/control/forms/event.py
msgid "Prices including tax"
@@ -22533,28 +22522,24 @@ msgstr ""
"食事を提供する場合、ユーザーに食事制限について尋ねることができる一例です。"
#: pretix/control/templates/pretixcontrol/items/questions.html
#, fuzzy
#| msgid "Create a new question"
msgid "Create a new per-ticket question"
msgstr "新しい質問を作成"
msgstr "チケットごとに新しい質問を作成"
#: pretix/control/templates/pretixcontrol/items/questions.html
#, fuzzy
#| msgid "Create a new question"
msgid "Create a new order-level question"
msgstr "新しい質問を作成"
msgstr "注文ごとに新しい質問を作成"
#: pretix/control/templates/pretixcontrol/items/questions.html
#, fuzzy
#| msgid "Delete question"
msgid "Per-ticket questions"
msgstr "質問を削除"
msgstr "チケットごとの質問"
#: pretix/control/templates/pretixcontrol/items/questions.html
msgid ""
"These questions are asked for every ticket, so possibly multiple times in "
"the same order."
msgstr ""
"これらの質問はすべてのチケットに対して尋ねられるため、同じ順序で複数回になる"
"可能性があります。"
#: pretix/control/templates/pretixcontrol/items/questions.html
msgid "Create a new question"
@@ -22573,28 +22558,24 @@ msgid "All personalized products"
msgstr "すべてのパーソナライズされた製品"
#: pretix/control/templates/pretixcontrol/items/questions.html
#, fuzzy
#| msgid "Include questions"
msgid "Per-order questions"
msgstr "質問を含む"
msgstr "注文ごとの質問"
#: pretix/control/templates/pretixcontrol/items/questions.html
msgid ""
"This functionality is in active development and expected to change "
"significantly over the coming months."
msgstr ""
msgstr "この機能は現在開発が活発で、今後数か月で大幅に変化すると予想されています。"
#: pretix/control/templates/pretixcontrol/items/questions.html
msgid ""
"Per-order questions are currently not supported and will not be displayed in "
"pretixPOS."
msgstr ""
msgstr "注文ごとの質問は現在サポートされておらず、pretixPOSでは表示されません。"
#: pretix/control/templates/pretixcontrol/items/questions.html
#, fuzzy
#| msgid "The question has been reordered."
msgid "These questions are asked once per order."
msgstr "質問の並び順が変更されました。"
msgstr "これらの質問は、注文ごとに1回ずつ尋ねられます。"
#: pretix/control/templates/pretixcontrol/items/quota.html
#: pretix/control/templates/pretixcontrol/items/quota_edit.html
@@ -23186,10 +23167,8 @@ msgstr "(任意)"
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html
#: pretix/presale/templates/pretixpresale/event/order_modify.html
#, fuzzy
#| msgid "Additional information"
msgid "Additional order information"
msgstr "追加情報"
msgstr "追加の注文情報"
#: pretix/control/templates/pretixcontrol/order/delete.html
msgid "Delete order"
@@ -24856,14 +24835,13 @@ msgid "Hardware model"
msgstr "ハードウェアの機種"
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#, fuzzy, python-format
#| msgid "Begin: %(time)s"
#, python-format
msgid "Last seen: %(time)s"
msgstr "開始: %(time)s"
msgstr "最後の閲覧: %(time)s"
#: pretix/control/templates/pretixcontrol/organizers/devices.html
msgid "No recent contact"
msgstr ""
msgstr "最近の連絡なし"
#: pretix/control/templates/pretixcontrol/organizers/devices.html
msgid "Not yet initialized"
@@ -28220,19 +28198,14 @@ msgstr ""
"に高いことを意味します。ドメインのDNS設定を更新すべきです。"
#: pretix/control/views/mailsetup.py
#, fuzzy
#| msgid ""
#| "We did not find DMARC record for your domain. This means that there is a "
#| "very high chance most of the emails will be rejected or marked as spam. "
#| "You should update the DNS settings of your domain."
msgid ""
"We did not find a DMARC record for your domain. This means that there is a "
"very high chance most of the emails will be rejected or marked as spam. You "
"should update the DNS settings of your domain."
msgstr ""
"お客様のドメインのDMARCレコードが見つかりませんでした。これは、ほとんどのメー"
"ルが拒否されたスパムとしてマークされたりする可能性が非常に高いことを意味し"
"す。ドメインのDNS設定を更新すべきです。"
"あなたのドメインのDMARCレコードが見つかりませんでした。これは、ほとんどの"
"メールが拒否されたスパムとしてマークされた可能性が非常に高いことを意味し"
"す。ドメインのDNS設定を更新すべきです。"
#: pretix/control/views/mailsetup.py
msgid "The verification code was incorrect, please try again."
@@ -30906,7 +30879,7 @@ msgstr ""
#: pretix/plugins/paypal2/payment.py
msgid "Allow further payments during compliance hold"
msgstr ""
msgstr "コンプライアンス保留の間、追加の支払いを許可する"
#: pretix/plugins/paypal2/payment.py
msgid ""
@@ -30916,16 +30889,20 @@ msgid ""
"attempts during that window. This might result in them being charged twice "
"if the original payment is approved."
msgstr ""
"PayPalの不正防止は、個々の支払いの処理をかなりの期間ブロックする可能性があり"
"ます。この期間中、支払いは「保留中」とマークされています。顧客がその期間中に"
"別の支払い試行を開始できるように許可することができます。元の支払いが承認され"
"た場合、二重に請求される可能性があります。"
#: pretix/plugins/paypal2/payment.py
msgid "Timeout further payment attempts"
msgstr ""
msgstr "追加の支払い試行がタイムアウト"
#: pretix/plugins/paypal2/payment.py
msgid ""
"Time duration in minutes after which another payment attempt is possible, "
"while the last payment is still under investigation."
msgstr ""
msgstr "最後の支払いがまだ調査中である間、別の支払い試行が可能となる時間(分)。"
#: pretix/plugins/paypal2/payment.py
msgid "-- Automatic --"
@@ -31190,6 +31167,10 @@ msgid ""
"twice in case PayPal allows your initial payment attempt. Please contact us "
"to resolve this case."
msgstr ""
"支払いはPayPalで処理されています。通常より時間がかかります。PayPalが支払いを"
"確認するまでお待ちいただくか、こちらまたは別の支払い方法で再度お支払いをお試"
"しください。PayPalが最初の支払い試行を許可した場合、二重に請求される可能性が"
"あります。この件を解決するために、弊社までご連絡ください。"
#: pretix/plugins/paypal2/views.py
msgid ""
+11 -6
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
"PO-Revision-Date: 2026-03-30 21:00+0000\n"
"PO-Revision-Date: 2026-09-02 08:35+0000\n"
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix/pt_BR/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 2026.8.1\n"
#: pretix/_base_settings.py
msgid "English"
@@ -93,7 +93,7 @@ msgstr "Hebraico"
#: pretix/_base_settings.py
msgid "Hungarian"
msgstr ""
msgstr "Húngaro"
#: pretix/_base_settings.py
msgid "Indonesian"
@@ -153,7 +153,7 @@ msgstr "Espanhol (América latina)"
#: pretix/_base_settings.py
msgid "Thai"
msgstr ""
msgstr "Tailandês"
#: pretix/_base_settings.py
msgid "Turkish"
@@ -3022,11 +3022,12 @@ msgid ""
"The field \"%(label)s\" may not contain special characters such as "
"\"%(chars)s\"."
msgstr ""
"O campo \"%(label)s\" não pode conter caracteres especiais como %(chars)s\"."
#: pretix/base/forms/questions.py
#, python-format
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
msgstr ""
msgstr "O campo \"%(label)s\" não pode conter uma URL (%(url)s)."
#: pretix/base/forms/questions.py
msgctxt "phonenumber"
@@ -8173,7 +8174,7 @@ msgstr "Permitida"
#: pretix/base/permissions.py
msgctxt "permission_level"
msgid "Access existing events"
msgstr ""
msgstr "Acessar eventos existentes"
#: pretix/base/permissions.py
msgctxt "permission_level"
@@ -13607,11 +13608,15 @@ msgstr "Por favor, continue em uma nova aba"
#: pretix/base/templates/pretixbase/framebreak.html
msgid "For security reasons, the following step is only possible in a new tab."
msgstr ""
"Por razões de segurança, o passo seguinte só é possível de ser feito em uma "
"nova aba."
#: pretix/base/templates/pretixbase/framebreak.html
msgid ""
"If the new tab did not open automatically, please click the following button:"
msgstr ""
"Se uma nova aba não abrir automaticamente, por favor, clique no botão a "
"seguir:"
#: pretix/base/templates/pretixbase/framebreak.html
#: pretix/presale/templates/pretixpresale/event/cookies.html
+5 -4
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-24 15:35+0000\n"
"PO-Revision-Date: 2026-08-09 06:00+0000\n"
"Last-Translator: Julien <julien@circusiloveyou.com>\n"
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
"sv/>\n"
"Language: sv\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.8.1\n"
"X-Generator: Weblate 2026.9\n"
#: pretix/_base_settings.py
msgid "English"
@@ -19170,8 +19170,9 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/order_pay.html
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html
#: pretix/presale/templates/pretixpresale/event/position_change.html
#, fuzzy
msgid "Continue"
msgstr "Fortsätta"
msgstr "Fortsätt"
#: pretix/control/templates/pretixcontrol/auth/oauth_authorization.html
msgid "Authorize an application"
+5 -4
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
"PO-Revision-Date: 2026-08-09 06:00+0000\n"
"Last-Translator: Julien <julien@circusiloveyou.com>\n"
"PO-Revision-Date: 2026-09-04 14:29+0000\n"
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/sv/>\n"
"Language: sv\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.8.1\n"
"X-Generator: Weblate 2026.9\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
msgid "Marked as paid"
@@ -133,8 +133,9 @@ msgstr "Mercado Pago"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
#: pretix/static/pretixpresale/js/ui/cart.js
#, fuzzy
msgid "Continue"
msgstr "Fortsätta"
msgstr "Fortsätt"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js
+32 -124
View File
@@ -21,8 +21,7 @@
#
import sys
from datetime import datetime
from decimal import Decimal
from typing import List, Optional, Union
from typing import Optional
from django.conf import settings
from django.db.models import (
@@ -30,13 +29,11 @@ from django.db.models import (
)
from django.db.models.lookups import Exact
from pretix.base.decimal import round_decimal
from pretix.base.models import (
Discount, Event, Item, ItemVariation, Quota, SalesChannel,
SeatCategoryMapping, SubEvent, Voucher,
ItemVariation, Quota, SalesChannel, SeatCategoryMapping,
)
from pretix.base.models.items import (
ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
Item, ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
)
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.timemachine import time_machine_now
@@ -57,39 +54,10 @@ def item_group_by_category(items):
)
def _single_item_discounts(event: Event, sales_channel: Union[str, SalesChannel],
subevent: SubEvent=None, voucher: Voucher=None, is_addons=False) -> List[Discount]:
discount_qs = event.discounts.filter(
Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()),
Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()),
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=sales_channel),
active=True,
# Only discounts that can be applied before we know the full cart
benefit_same_products=True,
benefit_only_apply_to_cheapest_n_matches__isnull=True,
condition_min_value=Decimal("0.00"),
condition_min_count=1,
).prefetch_related('condition_limit_products').order_by('position', 'pk')
if subevent:
discount_qs = discount_qs.filter(
Q(subevent_date_from__isnull=True) | Q(subevent_date_from__lte=subevent.date_from),
Q(subevent_date_until__isnull=True) | Q(subevent_date_until__gte=subevent.date_from),
)
if is_addons:
discount_qs = discount_qs.filter(condition_apply_to_addons=True, benefit_apply_to_addons=True)
if voucher and voucher.price_mode != "none":
discount_qs = discount_qs.filter(condition_ignore_voucher_discounted=False, benefit_ignore_voucher_discounted=False)
return list(discount_qs)
def prepare_item_list_for_shop(event, *, channel: SalesChannel, subevent=None, voucher=None, require_seat=0, base_qs=None,
allow_addons=False, allow_cross_sell=False,
quota_cache=None, filter_items=None, filter_categories=None, memberships=None,
ignore_hide_sold_out_for_item_ids=None, _discount_cache=None):
ignore_hide_sold_out_for_item_ids=None):
base_qs_set = base_qs is not None
base_qs = base_qs if base_qs is not None else event.items
@@ -222,23 +190,6 @@ def prepare_item_list_for_shop(event, *, channel: SalesChannel, subevent=None, v
if filter_categories:
items = items.filter(category_id__in=[a for a in filter_categories if a.isdigit()])
# We pre-computate discounts that do not rely on specific combinations in the cart.
# This is not the same order of operations that is applied in the cart, so there could be some differences
# when it comes to tax rate handling, but we don't have that information in the product list anyway, so
# that is acceptable.
cache_key = (event, channel, voucher, subevent, allow_addons)
if _discount_cache is None:
_discount_cache = {}
if cache_key not in _discount_cache:
_discount_cache[cache_key] = list(_single_item_discounts(
event=event,
sales_channel=channel,
voucher=voucher,
subevent=subevent,
is_addons=allow_addons,
))
discounts = _discount_cache[cache_key]
display_add_to_cart = False
quota_cache_key = f'item_quota_cache:{subevent.id if subevent else 0}:{channel.identifier}:{bool(require_seat)}'
quota_cache = quota_cache or event.cache.get(quota_cache_key) or {}
@@ -321,10 +272,6 @@ def prepare_item_list_for_shop(event, *, channel: SalesChannel, subevent=None, v
if resp:
item.description += ("<br/>" if item.description else "") + resp
matching_discounts = [
d for d in discounts if d.condition_all_products or item in d.condition_limit_products.all()
]
if not item.has_variations:
item._remove = False
if not bool(item._subevent_quotas):
@@ -352,51 +299,30 @@ def prepare_item_list_for_shop(event, *, channel: SalesChannel, subevent=None, v
max_per_order
)
configured_price = item_price_override.get(item.pk, item.default_price)
original_price = item_price_override.get(item.pk, item.default_price)
voucher_reduced = False
if voucher:
price = voucher.calculate_price(configured_price)
voucher_reduced = price < configured_price
price = voucher.calculate_price(original_price)
voucher_reduced = price < original_price
include_bundled = not voucher.all_bundles_included
else:
price = configured_price
price = original_price
include_bundled = True
if matching_discounts and not item.free_price:
# Discounts and free prices are a non-recommended combination that behaves unintuitively, so we can
# accept it not being handled here.
discount = matching_discounts[0] # First matching discount rule always wins
# First handle taxes because discount is always computed on gross
taxed_price = item.tax(price, currency=event.currency, include_bundled=include_bundled)
price_without_bundles = taxed_price.gross - sum(b.count * b.designated_price for b in item.bundles.all())
discounted_price = round_decimal(
taxed_price.gross - price_without_bundles * discount.benefit_discount_matching_percent / Decimal('100.00'),
event.currency,
)
item.display_price = item.tax(discounted_price, currency=event.currency, include_bundled=include_bundled,
base_price_is='gross')
else:
discount = None
item.display_price = item.tax(price, currency=event.currency, include_bundled=include_bundled)
item.display_price = item.tax(price, currency=event.currency, include_bundled=include_bundled)
if item.free_price and item.free_price_suggestion is not None and not voucher_reduced:
item.suggested_price = item.tax(max(price, item.free_price_suggestion), currency=event.currency, include_bundled=include_bundled)
else:
item.suggested_price = item.display_price
if voucher_reduced or (discount and not item.original_price):
# If a voucher is used, we use the non-voucher price as the "original price", even if a different
# original price is set, to highlight the voucher's impact. We also use the configured price as
# original price if a discount is applied and no explicit original price is set
item.original_price = item.tax(configured_price, currency=event.currency, include_bundled=True)
elif item.original_price:
item.original_price = item.tax(
item.original_price, currency=event.currency, include_bundled=True,
base_price_is='net' if event.settings.display_net_prices else 'gross' # backwards-compat
)
if price != original_price:
item.original_price = item.tax(original_price, currency=event.currency, include_bundled=True)
else:
item.original_price = None
item.original_price = (
item.tax(item.original_price, currency=event.currency, include_bundled=True,
base_price_is='net' if event.settings.display_net_prices else 'gross') # backwards-compat
if item.original_price else None
)
if not display_add_to_cart:
display_add_to_cart = not item.requires_seat and item.order_max > 0
else:
@@ -426,53 +352,35 @@ def prepare_item_list_for_shop(event, *, channel: SalesChannel, subevent=None, v
max_per_order
)
configured_price = var_price_override.get(var.pk, var.price)
original_price = var_price_override.get(var.pk, var.price)
voucher_reduced = False
if voucher:
price = voucher.calculate_price(configured_price)
voucher_reduced = price < configured_price
price = voucher.calculate_price(original_price)
voucher_reduced = price < original_price
include_bundled = not voucher.all_bundles_included
else:
price = configured_price
price = original_price
include_bundled = True
if matching_discounts and not item.free_price:
# Discounts and free prices are a non-recommended combination that behaves unintuitively, so we can
# accept it not being handled here.
discount = matching_discounts[0] # First matching discount rule always wins
# First handle taxes because discount is always computed on gross
taxed_price = var.tax(price, currency=event.currency, include_bundled=include_bundled)
price_without_bundles = taxed_price.gross - sum(b.count * b.designated_price for b in item.bundles.all())
discounted_price = round_decimal(
taxed_price.gross - price_without_bundles * discount.benefit_discount_matching_percent / Decimal('100.00'),
event.currency,
)
var.display_price = var.tax(discounted_price, currency=event.currency,
include_bundled=include_bundled,
base_price_is='gross')
else:
discount = None
var.display_price = var.tax(price, currency=event.currency, include_bundled=include_bundled)
var.display_price = var.tax(price, currency=event.currency, include_bundled=include_bundled)
if item.free_price and var.free_price_suggestion is not None and not voucher_reduced:
var.suggested_price = var.tax(max(price, var.free_price_suggestion), currency=event.currency,
include_bundled=include_bundled)
var.suggested_price = item.tax(max(price, var.free_price_suggestion), currency=event.currency,
include_bundled=include_bundled)
elif item.free_price and item.free_price_suggestion is not None and not voucher_reduced:
var.suggested_price = var.tax(max(price, item.free_price_suggestion), currency=event.currency,
include_bundled=include_bundled)
var.suggested_price = item.tax(max(price, item.free_price_suggestion), currency=event.currency,
include_bundled=include_bundled)
else:
var.suggested_price = var.display_price
if voucher_reduced or (discount and not item.original_price and not var.original_price):
var.original_price = var.tax(configured_price, currency=event.currency, include_bundled=True)
elif item.original_price or var.original_price:
var.original_price = var.tax(
var.original_price or item.original_price, currency=event.currency,
include_bundled=True,
base_price_is='net' if event.settings.display_net_prices else 'gross' # backwards-compat
)
if price != original_price:
var.original_price = var.tax(original_price, currency=event.currency, include_bundled=True)
else:
var.original_price = None
var.original_price = (
var.tax(var.original_price or item.original_price, currency=event.currency,
include_bundled=True,
base_price_is='net' if event.settings.display_net_prices else 'gross') # backwards-compat
) if var.original_price or item.original_price else None
var.current_unavailability_reason = _get_variant_unavailability_reason(var, has_voucher=voucher, subevent=subevent)
@@ -67,13 +67,14 @@ $(function () {
continue
matches.push([rank, plugin])
}
matches.sort(function (a, b) { return (b[0] - a[0]) || strcmp(a[1].sortName, b[1].sortName) })
$results.append(matches.map(function (res) { return $(res[1].html).prepend('<span class="pull-right">' + res[1].categoryLabel + '</span>') }))
matches.sort((a, b) => ((b[0] - a[0]) || strcmp(a[1].sortName, b[1].sortName)))
$results.append(matches.map((res) => $(res[1].html).prepend('<span class="pull-right">' + res[1].categoryLabel + '</span>')))
$results.find('.panel-body, .panel, .featured-plugin, .btn-lg').removeClass('panel-body panel featured-plugin btn-lg')
if (matches.length === 0) {
$results.append(gettext('No results'))
}
}
search() // if pre-filled by browser (e.g. after reload)
$('#plugin_search_input').on('input', search)
$('input[name=plugin_state_filter]').on('change', search)
$results_box.find('button.close').on('click', function () {
@@ -81,9 +82,9 @@ $(function () {
$('#plugin_search_input').val('').trigger('input')
})
if (location.search) {
var search = new URLSearchParams(location.search)
if (search.has('q')) {
$('#plugin_search_input').val(search.get('q')).trigger('input')
const search_params = new URLSearchParams(location.search)
if (search_params.has('q')) {
$('#plugin_search_input').val(search_params.get('q')).trigger('input')
}
}
})
+33 -33
View File
@@ -749,22 +749,22 @@ def test_query_count_many_items(event, itemcount):
''',
recommendations=''' Price Discounted Price Max Count Prefix
''',
expect_num_queries=8,
)
check_cart_behaviour(
event,
cart_contents=''' Price Discounted Subev
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
''',
recommendations=''' Price Discounted Price Max Count Prefix
Tickets Ticket 2 42.00 0.00 1 -
''',
expect_num_queries=9,
)
check_cart_behaviour(
event,
cart_contents=''' Price Discounted Subev
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
Ticket 1 42.00 42.00 0
''',
recommendations=''' Price Discounted Price Max Count Prefix
Tickets Ticket 2 42.00 0.00 1 -
''',
expect_num_queries=10,
)
check_cart_behaviour(
event,
cart_contents=''' Price Discounted Subev
@@ -779,7 +779,7 @@ def test_query_count_many_items(event, itemcount):
recommendations=''' Price Discounted Price Max Count Prefix
Tickets Ticket 2 42.00 0.00 1 -
''',
expect_num_queries=10,
expect_num_queries=9,
)
@@ -803,22 +803,22 @@ def test_query_count_many_categories_and_discounts(event, catcount):
''',
recommendations=''' Price Discounted Price Max Count Prefix
''',
expect_num_queries=8,
)
check_cart_behaviour(
event,
cart_contents=''' Price Discounted Subev
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
''',
recommendations=''' Price Discounted Price Max Count Prefix
Category 1 Ticket 1-B 42.00 0.00 1 -
''',
expect_num_queries=9,
)
check_cart_behaviour(
event,
cart_contents=''' Price Discounted Subev
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
Ticket 1-A 42.00 42.00 0
''',
recommendations=''' Price Discounted Price Max Count Prefix
Category 1 Ticket 1-B 42.00 0.00 1 -
''',
expect_num_queries=10,
)
check_cart_behaviour(
event,
cart_contents=''' Price Discounted Subev
@@ -833,7 +833,7 @@ def test_query_count_many_categories_and_discounts(event, catcount):
recommendations=''' Price Discounted Price Max Count Prefix
Category 1 Ticket 1-B 42.00 0.00 1 -
''',
expect_num_queries=10,
expect_num_queries=9,
)
@@ -857,7 +857,7 @@ def test_query_count_many_cartpos(event, catcount):
''',
recommendations=''' Price Discounted Price Max Count Prefix
''',
expect_num_queries=9,
expect_num_queries=8,
)
check_cart_behaviour(
event,
@@ -871,7 +871,7 @@ def test_query_count_many_cartpos(event, catcount):
recommendations=''' Price Discounted Price Max Count Prefix
Category 1 Ticket 1-B 42.00 0.00 1 -
''',
expect_num_queries=10,
expect_num_queries=9,
)
check_cart_behaviour(
event,
@@ -893,5 +893,5 @@ def test_query_count_many_cartpos(event, catcount):
Category 1 Ticket 1-B 42.00 0.00 1 -
Category 2 Ticket 2-B 42.00 0.00 1 -
''',
expect_num_queries=14,
expect_num_queries=13,
)
-283
View File
@@ -1,283 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix 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/>.
#
from datetime import UTC, datetime, timedelta
from decimal import Decimal
import pytest
from django.utils.timezone import now
from django_scopes import scope
from pretix.base.models import Event, Organizer
from pretix.presale.productlist import prepare_item_list_for_shop
# Tests for prepare_item_list_for_shop are really incomplete since historically, most features are
# tested on the test_event or test_widget layer. We'll slowly add new tests here to test closer to the
# source.
@pytest.fixture
def event():
o = Organizer.objects.create(name='MRMCD', slug='mrmcd')
e = Event.objects.create(
organizer=o, name='MRMCD2015', slug='2015',
date_from=now(), live=True
)
with scope(organizer=o):
yield e
@pytest.fixture
def quota(event):
return event.quotas.create(name="Tickets", size=500)
@pytest.fixture
def item(event, quota):
i = event.items.create(name="Ticket", default_price=Decimal("42.00"))
quota.items.add(i)
return i
@pytest.fixture
def variation(event, quota):
i = event.items.create(name="Ticket with variants", default_price=Decimal("99.00"))
v = i.variations.create(value="Default", default_price=Decimal("42.00"))
quota.items.add(i)
quota.variations.add(v)
return v
@pytest.fixture
def discount(event):
return event.discounts.create(
internal_name="Early-Bird-Discount",
all_sales_channels=True,
available_from=now() - timedelta(days=2),
available_until=now() + timedelta(days=2),
condition_all_products=True,
condition_min_count=1,
benefit_discount_matching_percent=Decimal("10.00"),
)
@pytest.fixture
def channel(event):
return event.organizer.sales_channels.get(identifier="web")
@pytest.mark.django_db
def test_default_price(event, item, variation, channel):
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("42")
assert items[1].available_variations[0].display_price.gross == Decimal("42")
def _test_no_discount(event, channel):
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("42")
assert items[1].available_variations[0].display_price.gross == Decimal("42")
@pytest.mark.django_db
def test_discount_applied(event, item, variation, channel, discount):
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("37.80")
assert items[0].original_price.gross == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("37.80")
assert items[1].available_variations[0].original_price.gross == Decimal("42.00")
@pytest.mark.django_db
def test_discount_for_groups_ignored(event, item, variation, channel, discount):
discount.condition_min_count = 2
discount.save()
_test_no_discount(event, channel)
@pytest.mark.django_db
def test_discount_original_price_kept(event, item, variation, channel, discount):
item.original_price = Decimal("46.00")
item.save()
variation.original_price = Decimal("46.00")
variation.save()
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("37.80")
assert items[0].original_price.gross == Decimal("46.00")
assert items[1].available_variations[0].display_price.gross == Decimal("37.80")
assert items[1].available_variations[0].original_price.gross == Decimal("46.00")
@pytest.mark.django_db
def test_discount_out_of_timeframe(event, item, variation, channel, discount):
discount.available_from = now() + timedelta(days=2)
discount.save()
_test_no_discount(event, channel)
@pytest.mark.django_db
def test_discount_wrong_channel(event, item, variation, channel, discount):
discount.all_sales_channels = False
discount.save()
_test_no_discount(event, channel)
@pytest.mark.django_db
def test_discount_benefits_other_products_ignored(event, item, variation, channel, discount):
discount.benefit_same_products = False
discount.save()
_test_no_discount(event, channel)
@pytest.mark.django_db
def test_discounts_for_addons(event, item, variation, channel, discount):
discount.condition_apply_to_addons = False
discount.save()
items, _ = prepare_item_list_for_shop(event, channel=channel, allow_addons=True)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("42.00")
discount.condition_apply_to_addons = True
discount.save()
items, _ = prepare_item_list_for_shop(event, channel=channel, allow_addons=True)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("37.80")
assert items[1].available_variations[0].display_price.gross == Decimal("37.80")
@pytest.mark.django_db
def test_discounts_with_voucher(event, item, variation, channel, discount):
voucher = event.vouchers.create(code="FOO", price_mode="subtract", value=Decimal("10.00"))
voucher2 = event.vouchers.create(code="BAR")
discount.condition_ignore_voucher_discounted = True
discount.save()
item.original_price = Decimal("46.00")
item.save()
variation.item.original_price = Decimal("46.00")
variation.item.save()
items, _ = prepare_item_list_for_shop(event, channel=channel, voucher=voucher)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("32.00")
assert items[0].original_price.gross == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("32.00")
assert items[1].available_variations[0].original_price.gross == Decimal("42.00")
items, _ = prepare_item_list_for_shop(event, channel=channel, voucher=voucher2)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("37.80")
assert items[0].original_price.gross == Decimal("46.00")
assert items[1].available_variations[0].display_price.gross == Decimal("37.80")
assert items[1].available_variations[0].original_price.gross == Decimal("46.00")
discount.condition_ignore_voucher_discounted = False
discount.save()
items, _ = prepare_item_list_for_shop(event, channel=channel, voucher=voucher)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("28.80")
assert items[0].original_price.gross == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("28.80")
assert items[1].available_variations[0].original_price.gross == Decimal("42.00")
@pytest.mark.django_db
def test_discounts_for_subevent_timeframe(event, quota, item, variation, channel, discount):
event.has_subevents = True
event.save()
se = event.subevents.create(
name="Foobar", date_from=datetime(2028, 12, 27, 10, 0, 0, tzinfo=UTC)
)
quota.subevent = se
quota.save()
discount.subevent_date_from = se.date_from + timedelta(days=1)
discount.save()
items, _ = prepare_item_list_for_shop(event, channel=channel, subevent=se)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("42.00")
discount.subevent_date_from = se.date_from - timedelta(days=1)
discount.save()
items, _ = prepare_item_list_for_shop(event, channel=channel, subevent=se)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("37.80")
assert items[1].available_variations[0].display_price.gross == Decimal("37.80")
@pytest.mark.django_db
def test_discounts_for_products(event, quota, item, variation, channel, discount):
discount.condition_all_products = False
discount.save()
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("42.00")
discount.condition_limit_products.add(item)
discount.condition_limit_products.add(variation.item)
discount.save()
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("37.80")
assert items[1].available_variations[0].display_price.gross == Decimal("37.80")
@pytest.mark.django_db
def test_discounts_for_products_tax_additive(event, quota, item, variation, channel, discount):
tr = event.tax_rules.create(rate=Decimal("19.00"), price_includes_tax=False)
item.tax_rule = tr
item.save()
variation.item.tax_rule = tr
variation.item.save()
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("44.98")
assert items[0].original_price.gross == Decimal("49.98")
assert items[0].original_price.net == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("44.98")
assert items[1].available_variations[0].original_price.gross == Decimal("49.98")
assert items[1].available_variations[0].original_price.net == Decimal("42.00")
@pytest.mark.django_db
def test_discounts_for_products_tax_additive_bundle_included(event, quota, item, variation, channel, discount):
tr = event.tax_rules.create(rate=Decimal("19.00"), price_includes_tax=False)
b = event.items.create(name="Bundled product", default_price=Decimal("10.00"), tax_rule=tr, require_bundling=True)
quota.items.add(b)
item.tax_rule = tr
item.save()
variation.item.tax_rule = tr
variation.item.save()
item.bundles.create(bundled_item=b, count=2, designated_price=Decimal("5.00"))
variation.item.bundles.create(bundled_item=b, count=2, designated_price=Decimal("5.00"))
items, _ = prepare_item_list_for_shop(event, channel=channel)
assert len(items) == 2
assert items[0].display_price.gross == Decimal("45.98")
assert items[0].original_price.gross == Decimal("49.98")
assert items[0].original_price.net == Decimal("42.00")
assert items[1].available_variations[0].display_price.gross == Decimal("45.98")
assert items[1].available_variations[0].original_price.gross == Decimal("49.98")
assert items[1].available_variations[0].original_price.net == Decimal("42.00")