Compare commits

..

30 Commits

Author SHA1 Message Date
Phin Wolkwitz
efd5ca5e25 Update django to 5.2, initial fixes 2025-12-04 14:45:28 +01:00
luelista
e1f5678d7c Refactor payment QR code generation code and add SPAYD format (#5680)
Move generation of QR code contents out of the HTML template and into Python code, so it can
be reused in plugins and tested with unit tests. Add the SPAYD QR code format which is used in
Czech Republic and Slovakia [1]. Display BezahlCode QR codes only for German IBANs.

[1] https://en.wikipedia.org/wiki/Short_Payment_Descriptor
2025-12-04 14:15:29 +01:00
luelista
609b7c82ee Handle duplicate column names in CSV import (#5681)
- display a warning message to the user
- automatically rename columns by adding "__1", "__2", ... suffixes
2025-12-04 14:03:27 +01:00
Raphael Michel
8d66e1e732 Cart extension: Fix bundled product being removed from cart when sold out (#5690)
Instead, the entire bundle must be removed as it may not be sold
individually.
2025-12-04 11:48:40 +01:00
Richard Schreiber
c925f094f2 Reduce item event queries in waitinglist assign 2025-12-04 11:01:30 +01:00
Richard Schreiber
5caaa8586d Fix accounting report pending payment timezone (#5698) 2025-12-04 10:59:57 +01:00
SJang1
1b1cf1557d Translations: Update Korean
Currently translated at 50.8% (3139 of 6172 strings)

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

powered by weblate
2025-12-04 10:40:16 +01:00
sandra r
35d8a7eec5 Translations: Update Galician
Currently translated at 100.0% (254 of 254 strings)

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

powered by weblate
2025-12-04 10:40:16 +01:00
sandra r
d428c3e1a4 Translations: Update Galician
Currently translated at 14.0% (869 of 6172 strings)

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

powered by weblate
2025-12-04 10:40:16 +01:00
dependabot[bot]
63850f3139 Update sentry-sdk requirement from ==2.46.* to ==2.47.*
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.46.0...2.47.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.47.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-04 10:40:05 +01:00
Felix Rindt
04c8270d43 Update pricing.rst to fix number typo (#5691)
I think you meant to point out the difference to the values in the table above...
2025-12-04 07:27:36 +01:00
dependabot[bot]
74a960e239 Update celery requirement from ==5.5.* to ==5.6.* (#5676) 2025-12-03 17:00:53 +01:00
Raphael Michel
5a1bcae085 Invoice address: Improve VAT ID input (#5647)
* Remove unmaintained depdendency vat_moss

* VAT ID normalization: Auto-add country codes

* VAT ID: County-specific labels

* Invoice address: Allow to set VAT ID as required per country

* Fix failing tests

* Update src/pretix/base/settings.py

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

* Review fixes

---------

Co-authored-by: luelista <weller@rami.io>
2025-12-03 16:48:19 +01:00
SJang1
051eb78312 Translations: Update Korean
Currently translated at 50.8% (3140 of 6172 strings)

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

powered by weblate
2025-12-03 16:29:20 +01:00
Ana Rute Pacheco Vivas
15808e55fd Translations: Update Portuguese (Portugal)
Currently translated at 83.1% (5134 of 6172 strings)

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

powered by weblate
2025-12-03 16:29:20 +01:00
David Ibáñez Cerdeira
c886c0b415 Translations: Update Galician
Currently translated at 9.2% (569 of 6172 strings)

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

powered by weblate
2025-12-03 16:29:20 +01:00
David Ibáñez Cerdeira
47472447eb Translations: Update Galician
Currently translated at 9.1% (567 of 6172 strings)

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

powered by weblate
2025-12-03 16:29:20 +01:00
Richard Schreiber
1a40215e91 Fix N+1 queries in API (#5684)
* Fix N+1 query in API quotas list

* fix membership N+1

* fix vouchers N+1 budget_used

* rename and reuse Voucher.annotate_budget_used_orders to budget_used

* fix flake8
2025-12-03 15:37:40 +01:00
Raphael Michel
d3fde85c39 Fix typo in CSS variable 2025-12-02 17:47:45 +01:00
Richard Schreiber
40bd66cb86 Fix PayPal2 payment patch request (#5678) 2025-12-02 13:14:12 +01:00
Raphael Michel
bdd94b1f8a Add prioritization to webhook/notifications queue (#5513)
* Add prioritization to webhook/notifications queue

* Add missing code

* Missing license header

* Fix argument

* Use redis pipeline

* Update license header
2025-12-02 09:13:01 +01:00
José Manuel Silva
1c907f6a6f Translations: Update Portuguese (Portugal)
Currently translated at 83.1% (5133 of 6172 strings)

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

powered by weblate
2025-12-01 13:49:40 +01:00
José Manuel Silva
39e3ed9c25 Translations: Update Portuguese (Portugal)
Currently translated at 83.2% (5136 of 6172 strings)

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

powered by weblate
2025-12-01 13:49:40 +01:00
Richard Schreiber
4b5711253e Fix display_add_to_cart for variations 2025-12-01 13:48:02 +01:00
Raphael Michel
bd554c7c29 Update remaining icon files 2025-12-01 13:41:06 +01:00
Raphael Michel
2261951b15 Peppol: Live ID validation (#5602)
* Peppol: Live ID validation

* Always check both systems

* Simplify logic
2025-11-27 19:50:53 +01:00
Raphael Michel
0f82e1cae6 Update pretix logo to new version (#5651)
* Update pretix logo to new version

* Make favicon transparent

* Update src/pretix/static/pretixcontrol/scss/main.scss

Co-authored-by: Richard Schreiber <schreiber@rami.io>

* Update src/pretix/static/pretixcontrol/scss/main.scss

Co-authored-by: Richard Schreiber <schreiber@rami.io>

---------

Co-authored-by: Richard Schreiber <schreiber@rami.io>
2025-11-27 16:05:30 +01:00
dependabot[bot]
b0760157ce Update sentry-sdk requirement from ==2.45.* to ==2.46.*
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.45.0...2.46.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.46.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 16:05:18 +01:00
dependabot[bot]
de2dec9089 Update pypdf requirement from ==6.3.* to ==6.4.* (#5659)
Updates the requirements on [pypdf](https://github.com/py-pdf/pypdf) to permit the latest version.
- [Release notes](https://github.com/py-pdf/pypdf/releases)
- [Changelog](https://github.com/py-pdf/pypdf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/py-pdf/pypdf/compare/6.3.0...6.4.0)

---
updated-dependencies:
- dependency-name: pypdf
  dependency-version: 6.4.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 16:05:04 +01:00
Raphael Michel
446c8e622b Bump version to 2025.11.0.dev0 2025-11-27 15:34:32 +01:00
77 changed files with 2012 additions and 1527 deletions

View File

@@ -211,7 +211,7 @@ The line-based computation has a few significant advantages:
The main disadvantage is that the tax looks "wrong" when computed from the sum. Taking the sum of net prices (420.15)
and multiplying it with the tax rate (19%) yields a tax amount of 79.83 (instead of 79.85) and a gross sum of 499.98
(instead of 499.98). This becomes a problem when juristictions, data formats, or external systems expect this calculation
(instead of 500.00). This becomes a problem when juristictions, data formats, or external systems expect this calculation
to work on the level of the entire order. A prominent example is the EN 16931 standard for e-invoicing that
does not allow the computation as created by pretix.

View File

@@ -3,7 +3,7 @@ name = "pretix"
dynamic = ["version"]
description = "Reinventing presales, one ticket at a time"
readme = "README.rst"
requires-python = ">=3.9"
requires-python = ">=3.10"
license = {file = "LICENSE"}
keywords = ["tickets", "web", "shop", "ecommerce"]
authors = [
@@ -22,7 +22,7 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.2",
]
dependencies = [
@@ -30,12 +30,13 @@ dependencies = [
"babel",
"BeautifulSoup4==4.14.*",
"bleach==6.2.*",
"celery==5.5.*",
"celery==5.6.*",
"chardet==5.2.*",
"cryptography>=44.0.0",
"css-inline==0.18.*",
"defusedcsv>=1.1.0",
"Django[argon2]==4.2.*,>=4.2.26",
"dnspython==2.*",
"Django[argon2]==5.2.*",
"django-bootstrap3==25.2",
"django-compressor==4.5.1",
"django-countries==7.6.*",
@@ -61,7 +62,7 @@ dependencies = [
"importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek",
"jsonschema",
"kombu==5.5.*",
"kombu==5.6.*",
"libsass==0.23.*",
"lxml",
"markdown==3.9", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
@@ -81,7 +82,7 @@ dependencies = [
"pycountry",
"pycparser==2.23",
"pycryptodome==3.23.*",
"pypdf==6.3.*",
"pypdf==6.4.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
"pytz",
@@ -91,14 +92,13 @@ dependencies = [
"redis==6.4.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.45.*",
"sentry-sdk==2.47.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2020041600",
"tqdm==4.*",
"ua-parser==1.0.*",
"vat_moss_forked==2020.3.20.0.11.0",
"vobject==0.9.*",
"webauthn==2.7.*",
"zeep==4.3.*"

View File

@@ -19,4 +19,4 @@
# 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/>.
#
__version__ = "2025.10.0"
__version__ = "2025.11.0.dev0"

View File

@@ -795,6 +795,7 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_address_asked',
'invoice_address_required',
'invoice_address_vatid',
'invoice_address_vatid_required_countries',
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',
@@ -943,6 +944,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
'invoice_address_asked',
'invoice_address_required',
'invoice_address_vatid',
'invoice_address_vatid_required_countries',
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',

View File

@@ -567,7 +567,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
write_permission = 'can_change_items'
def get_queryset(self):
return self.request.event.quotas.all()
return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all()
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()).distinct()

View File

@@ -721,7 +721,7 @@ class MembershipViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Membership.objects.filter(
customer__organizer=self.request.organizer
)
).select_related('customer')
def get_serializer_context(self):
ctx = super().get_serializer_context()

View File

@@ -19,6 +19,7 @@
# 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 django.db import transaction
from django.db.models import F, Q
from django.utils.timezone import now
@@ -64,8 +65,13 @@ class VoucherViewSet(viewsets.ModelViewSet):
permission = 'can_view_vouchers'
write_permission = 'can_change_vouchers'
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self):
return self.request.event.vouchers.select_related('seat').all()
return Voucher.annotate_budget_used(
self.request.event.vouchers
).select_related(
'item', 'quota', 'seat', 'variation'
)
@transaction.atomic()
def create(self, request, *args, **kwargs):

View File

@@ -43,6 +43,7 @@ from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import periodic_task
from pretix.celery_app import app
from pretix.helpers import OF_SELF
from pretix.helpers.celery import get_task_priority
logger = logging.getLogger(__name__)
_ALL_EVENTS = None
@@ -474,7 +475,10 @@ def notify_webhooks(logentry_ids: list):
)
for wh in webhooks:
send_webhook.apply_async(args=(logentry.id, notification_type.action_type, wh.pk))
send_webhook.apply_async(
args=(logentry.id, notification_type.action_type, wh.pk),
priority=get_task_priority("notifications", logentry.organizer_id),
)
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=60, acks_late=True, autoretry_for=(DatabaseError,),)

View File

@@ -83,7 +83,7 @@ from pretix.base.invoicing.transmission import (
from pretix.base.models import InvoiceAddress, Item, Question, QuestionOption
from pretix.base.models.tax import ask_for_vat_id
from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
VATIDFinalError, VATIDTemporaryError, normalize_vat_id, validate_vat_id,
)
from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, COUNTRY_STATE_LABEL,
@@ -1165,13 +1165,11 @@ class BaseInvoiceAddressForm(forms.ModelForm):
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but depending on the country you reside in we might need to charge you '
'additional taxes if you do not enter it.')),
str(_('If you are registered in Switzerland, you can enter your UID instead.')),
])
else:
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but it might be required for you to claim tax benefits on your invoice '
'depending on your and the sellers country of residence.')),
str(_('If you are registered in Switzerland, you can enter your UID instead.')),
])
transmission_type_choices = [
@@ -1358,13 +1356,24 @@ class BaseInvoiceAddressForm(forms.ModelForm):
"transmission method.")}
)
vat_id_applicable = (
'vat_id' in self.fields and
data.get('is_business') and
ask_for_vat_id(data.get('country'))
)
vat_id_required = vat_id_applicable and str(data.get('country')) in self.event.settings.invoice_address_vatid_required_countries
if vat_id_required and not data.get('vat_id'):
raise ValidationError({
"vat_id": _("This field is required.")
})
if self.validate_vat_id and self.instance.vat_id_validated and 'vat_id' not in self.changed_data:
pass
elif self.validate_vat_id and data.get('is_business') and ask_for_vat_id(data.get('country')) and data.get('vat_id'):
pass # Skip re-validation if it is validated
elif self.validate_vat_id and vat_id_applicable:
try:
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
self.instance.vat_id_validated = True
self.instance.vat_id = normalized_id
self.instance.vat_id = data['vat_id'] = normalized_id
except VATIDFinalError as e:
if self.all_optional:
self.instance.vat_id_validated = False
@@ -1372,6 +1381,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
else:
raise ValidationError({"vat_id": e.message})
except VATIDTemporaryError as e:
# We couldn't check it online, but we can still normalize it
normalized_id = normalize_vat_id(data.get('vat_id'), str(data.get('country')))
self.instance.vat_id = data['vat_id'] = normalized_id
self.instance.vat_id_validated = False
if self.request and self.vat_warning:
messages.warning(self.request, e.message)

View File

@@ -32,7 +32,6 @@ from itertools import groupby
from typing import Tuple
import bleach
import vat_moss.exchange_rates
from bidi import get_display
from django.contrib.staticfiles import finders
from django.db.models import Sum
@@ -1059,7 +1058,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
def fmt(val):
try:
return vat_moss.exchange_rates.format(val, self.invoice.foreign_currency_display)
return money_filter(val, self.invoice.foreign_currency_display)
except ValueError:
return localize(val) + ' ' + self.invoice.foreign_currency_display

View File

@@ -19,8 +19,11 @@
# 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 base64
import hashlib
import re
import dns.resolver
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _, pgettext
@@ -123,6 +126,9 @@ class PeppolIdValidator:
"9959": ".*",
}
def __init__(self, validate_online=False):
self.validate_online = validate_online
def __call__(self, value):
if ":" not in value:
raise ValidationError(_("A Peppol participant ID always starts with a prefix, followed by a colon (:)."))
@@ -136,6 +142,28 @@ class PeppolIdValidator:
raise ValidationError(_("The Peppol participant ID does not match the validation rules for the prefix "
"%(number)s. Please reach out to us if you are sure this ID is correct."),
params={"number": prefix})
if self.validate_online:
base_hostnames = ['edelivery.tech.ec.europa.eu', 'acc.edelivery.tech.ec.europa.eu']
smp_id = base64.b32encode(hashlib.sha256(value.lower().encode()).digest()).decode().rstrip("=")
for base_hostname in base_hostnames:
smp_domain = f'{smp_id}.iso6523-actorid-upis.{base_hostname}'
resolver = dns.resolver.Resolver()
try:
answers = resolver.resolve(smp_domain, 'NAPTR', lifetime=1.0)
if answers:
return value
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
# ID not registered, do not set found=True
pass
except Exception: # noqa
# Error likely on our end or infrastructure is down, allow user to proceed
return value
raise ValidationError(
_("The Peppol participant ID is not registered on the Peppol network."),
)
return value
@@ -155,7 +183,9 @@ class PeppolTransmissionType(TransmissionType):
"transmission_peppol_participant_id": forms.CharField(
label=_("Peppol participant ID"),
validators=[
PeppolIdValidator(),
PeppolIdValidator(
validate_online=True,
),
]
),
}

View File

@@ -47,6 +47,19 @@ class DataImportError(LazyLocaleException):
super().__init__(msg)
def rename_duplicates(values):
used = set()
had_duplicates = False
for i, value in enumerate(values):
c = 0
while values[i] in used:
c += 1
values[i] = f'{value}__{c}'
had_duplicates = True
used.add(values[i])
return had_duplicates
def parse_csv(file, length=None, mode="strict", charset=None):
file.seek(0)
data = file.read(length)
@@ -70,6 +83,7 @@ def parse_csv(file, length=None, mode="strict", charset=None):
return None
reader = csv.DictReader(io.StringIO(data), dialect=dialect)
reader._had_duplicates = rename_duplicates(reader.fieldnames)
return reader

View File

@@ -31,6 +31,7 @@ from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from pretix.helpers.celery import get_task_priority
from pretix.helpers.json import CustomJSONEncoder
@@ -131,9 +132,15 @@ class LoggingMixin:
logentry.save()
if logentry.notification_type:
notify.apply_async(args=(logentry.pk,))
notify.apply_async(
args=(logentry.pk,),
priority=get_task_priority("notifications", logentry.organizer_id),
)
if logentry.webhook_type:
notify_webhooks.apply_async(args=(logentry.pk,))
notify_webhooks.apply_async(
args=(logentry.pk,),
priority=get_task_priority("notifications", logentry.organizer_id),
)
return logentry

View File

@@ -35,11 +35,14 @@
import json
import logging
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import connections, models
from django.utils.functional import cached_property
from pretix.helpers.celery import get_task_priority
class VisibleOnlyManager(models.Manager):
def get_queryset(self):
@@ -186,7 +189,19 @@ class LogEntry(models.Model):
to_notify = [o.id for o in objects if o.notification_type]
if to_notify:
notify.apply_async(args=(to_notify,))
organizer_ids = set(o.organizer_id for o in objects if o.notification_type)
notify.apply_async(
args=(to_notify,),
priority=settings.PRIORITY_CELERY_HIGHEST_FUNC(
get_task_priority("notifications", oid) for oid in organizer_ids
),
)
to_wh = [o.id for o in objects if o.webhook_type]
if to_wh:
notify_webhooks.apply_async(args=(to_wh,))
organizer_ids = set(o.organizer_id for o in objects if o.webhook_type)
notify_webhooks.apply_async(
args=(to_wh,),
priority=settings.PRIORITY_CELERY_HIGHEST_FUNC(
get_task_priority("notifications", oid) for oid in organizer_ids
),
)

View File

@@ -623,7 +623,7 @@ class Voucher(LoggedModel):
return max(1, self.min_usages - self.redeemed)
@classmethod
def annotate_budget_used_orders(cls, qs):
def annotate_budget_used(cls, qs):
opq = OrderPosition.objects.filter(
voucher_id=OuterRef('pk'),
voucher_budget_use__isnull=False,
@@ -632,7 +632,7 @@ class Voucher(LoggedModel):
Order.STATUS_PENDING
]
).order_by().values('voucher_id').annotate(s=Sum('voucher_budget_use')).values('s')
return qs.annotate(budget_used_orders=Coalesce(Subquery(opq, output_field=models.DecimalField(max_digits=13, decimal_places=2)), Decimal('0.00')))
return qs.annotate(budget_used=Coalesce(Subquery(opq, output_field=models.DecimalField(max_digits=13, decimal_places=2)), Decimal('0.00')))
def budget_used(self):
ops = OrderPosition.objects.filter(

View File

@@ -1361,6 +1361,11 @@ class CartManager:
deleted_positions.add(op.position.pk)
addons.delete()
op.position.delete()
if op.position.is_bundled:
deleted_positions |= {a.pk for a in op.position.addon_to.addons.all()}
deleted_positions.add(op.position.addon_to.pk)
op.position.addon_to.addons.all().delete()
op.position.addon_to.delete()
else:
raise AssertionError("ExtendOperation cannot affect more than one item")
elif isinstance(op, self.VoucherOperation):

View File

@@ -32,6 +32,7 @@ from pretix.base.services.mail import mail_send_task
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import notification
from pretix.celery_app import app
from pretix.helpers.celery import get_task_priority
from pretix.helpers.urls import build_absolute_uri
@@ -88,12 +89,18 @@ def notify(logentry_ids: list):
for um, enabled in notify_specific.items():
user, method = um
if enabled:
send_notification.apply_async(args=(logentry.id, notification_type.action_type, user.pk, method))
send_notification.apply_async(
args=(logentry.id, notification_type.action_type, user.pk, method),
priority=get_task_priority("notifications", logentry.organizer_id),
)
for um, enabled in notify_global.items():
user, method = um
if enabled and um not in notify_specific:
send_notification.apply_async(args=(logentry.id, notification_type.action_type, user.pk, method))
send_notification.apply_async(
args=(logentry.id, notification_type.action_type, user.pk, method),
priority=get_task_priority("notifications", logentry.organizer_id),
)
notification.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)

View File

@@ -27,7 +27,6 @@ from decimal import Decimal
from xml.etree import ElementTree
import requests
import vat_moss.id
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from zeep import Client, Transport
@@ -42,14 +41,142 @@ logger = logging.getLogger(__name__)
error_messages = {
'unavailable': _(
'Your VAT ID could not be checked, as the VAT checking service of '
'your country is currently not available. We will therefore '
'need to charge VAT on your invoice. You can get the tax amount '
'back via the VAT reimbursement process.'
'your country is currently not available. We will therefore need to '
'charge you the same tax rate as if you did not enter a VAT ID.'
),
'invalid': _('This VAT ID is not valid. Please re-check your input.'),
'country_mismatch': _('Your VAT ID does not match the selected country.'),
}
VAT_ID_PATTERNS = {
# Patterns generated by consulting the following URLs:
#
# - http://en.wikipedia.org/wiki/VAT_identification_number
# - http://ec.europa.eu/taxation_customs/vies/faq.html
# - https://euipo.europa.eu/tunnel-web/secure/webdav/guest/document_library/Documents/COSME/VAT%20numbers%20EU.pdf
# - http://www.skatteetaten.no/en/International-pages/Felles-innhold-benyttes-i-flere-malgrupper/Brochure/Guide-to-value-added-tax-in-Norway/?chapter=7159
'AT': { # Austria
'regex': '^U\\d{8}$',
'country_code': 'AT'
},
'BE': { # Belgium
'regex': '^(1|0?)\\d{9}$',
'country_code': 'BE'
},
'BG': { # Bulgaria
'regex': '^\\d{9,10}$',
'country_code': 'BG'
},
'CH': { # Switzerland
'regex': '^\\dE{9}$',
'country_code': 'CH'
},
'CY': { # Cyprus
'regex': '^\\d{8}[A-Z]$',
'country_code': 'CY'
},
'CZ': { # Czech Republic
'regex': '^\\d{8,10}$',
'country_code': 'CZ'
},
'DE': { # Germany
'regex': '^\\d{9}$',
'country_code': 'DE'
},
'DK': { # Denmark
'regex': '^\\d{8}$',
'country_code': 'DK'
},
'EE': { # Estonia
'regex': '^\\d{9}$',
'country_code': 'EE'
},
'EL': { # Greece
'regex': '^\\d{9}$',
'country_code': 'GR'
},
'ES': { # Spain
'regex': '^[A-Z0-9]\\d{7}[A-Z0-9]$',
'country_code': 'ES'
},
'FI': { # Finland
'regex': '^\\d{8}$',
'country_code': 'FI'
},
'FR': { # France
'regex': '^[A-Z0-9]{2}\\d{9}$',
'country_code': 'FR'
},
'GB': { # United Kingdom
'regex': '^(GD\\d{3}|HA\\d{3}|\\d{9}|\\d{12})$',
'country_code': 'GB'
},
'HR': { # Croatia
'regex': '^\\d{11}$',
'country_code': 'HR'
},
'HU': { # Hungary
'regex': '^\\d{8}$',
'country_code': 'HU'
},
'IE': { # Ireland
'regex': '^(\\d{7}[A-Z]{1,2}|\\d[A-Z+*]\\d{5}[A-Z])$',
'country_code': 'IE'
},
'IT': { # Italy
'regex': '^\\d{11}$',
'country_code': 'IT'
},
'LT': { # Lithuania
'regex': '^(\\d{9}|\\d{12})$',
'country_code': 'LT'
},
'LU': { # Luxembourg
'regex': '^\\d{8}$',
'country_code': 'LU'
},
'LV': { # Latvia
'regex': '^\\d{11}$',
'country_code': 'LV'
},
'MT': { # Malta
'regex': '^\\d{8}$',
'country_code': 'MT'
},
'NL': { # Netherlands
'regex': '^\\d{9}B\\d{2}$',
'country_code': 'NL'
},
'NO': { # Norway
'regex': '^\\d{9}MVA$',
'country_code': 'NO'
},
'PL': { # Poland
'regex': '^\\d{10}$',
'country_code': 'PL'
},
'PT': { # Portugal
'regex': '^\\d{9}$',
'country_code': 'PT'
},
'RO': { # Romania
'regex': '^\\d{2,10}$',
'country_code': 'RO'
},
'SE': { # Sweden
'regex': '^\\d{12}$',
'country_code': 'SE'
},
'SI': { # Slovenia
'regex': '^\\d{8}$',
'country_code': 'SI'
},
'SK': { # Slovakia
'regex': '^\\d{10}$',
'country_code': 'SK'
},
}
class VATIDError(Exception):
def __init__(self, message):
@@ -64,13 +191,57 @@ class VATIDTemporaryError(VATIDError):
pass
def normalize_vat_id(vat_id, country_code):
"""
Accepts a VAT ID and normaizes it, getting rid of spaces, periods, dashes
etc and converting it to upper case.
Original function from https://github.com/wbond/vat_moss-python
Copyright (c) 2015 Will Bond <will@wbond.net>
MIT License
"""
if not vat_id:
return None
if not isinstance(vat_id, str):
raise TypeError('VAT ID is not a string')
if len(vat_id) < 3:
raise ValueError('VAT ID must be at least three character long')
# Normalize the ID for simpler regexes
vat_id = re.sub('\\s+', '', vat_id)
vat_id = vat_id.replace('-', '')
vat_id = vat_id.replace('.', '')
vat_id = vat_id.upper()
# Clean the different shapes a number can take in Switzerland depending on purpse
if country_code == "CH":
vat_id = re.sub('[^A-Z0-9]', '', vat_id.replace('HR', '').replace('MWST', ''))
# Fix people using GR prefix for Greece
if vat_id[0:2] == "GR" and country_code == "GR":
vat_id = "EL" + vat_id[2:]
# Check if we already have a valid country prefix. If not, we try to figure out if we can
# add one, since in some countries (e.g. Italy) it's very custom to enter it without the prefix
if vat_id[:2] in VAT_ID_PATTERNS and re.match(VAT_ID_PATTERNS[vat_id[0:2]]['regex'], vat_id[2:]):
# Prefix set and prefix matches pattern, nothing to do
pass
elif re.match(VAT_ID_PATTERNS[cc_to_vat_prefix(country_code)]['regex'], vat_id):
# Prefix not set but adding it fixes pattern
vat_id = cc_to_vat_prefix(country_code) + vat_id
else:
# We have no idea what this is
pass
return vat_id
def _validate_vat_id_NO(vat_id, country_code):
# Inspired by vat_moss library
if not vat_id.startswith("NO"):
# prefix is not usually used in Norway, but expected by vat_moss library
vat_id = "NO" + vat_id
try:
vat_id = vat_moss.id.normalize(vat_id)
vat_id = normalize_vat_id(vat_id, country_code)
except ValueError:
raise VATIDFinalError(error_messages['invalid'])
@@ -104,7 +275,7 @@ def _validate_vat_id_NO(vat_id, country_code):
def _validate_vat_id_EU(vat_id, country_code):
# Inspired by vat_moss library
try:
vat_id = vat_moss.id.normalize(vat_id)
vat_id = normalize_vat_id(vat_id, country_code)
except ValueError:
raise VATIDFinalError(error_messages['invalid'])
@@ -112,11 +283,10 @@ def _validate_vat_id_EU(vat_id, country_code):
raise VATIDFinalError(error_messages['invalid'])
number = vat_id[2:]
if vat_id[:2] != cc_to_vat_prefix(country_code):
raise VATIDFinalError(error_messages['country_mismatch'])
if not re.match(vat_moss.id.ID_PATTERNS[cc_to_vat_prefix(country_code)]['regex'], number):
if not re.match(VAT_ID_PATTERNS[cc_to_vat_prefix(country_code)]['regex'], number):
raise VATIDFinalError(error_messages['invalid'])
# We are relying on the country code of the normalized VAT-ID and not the user/InvoiceAddress-provided
@@ -175,9 +345,12 @@ def _validate_vat_id_EU(vat_id, country_code):
def _validate_vat_id_CH(vat_id, country_code):
if vat_id[:3] != 'CHE':
raise VATIDFinalError(_('Your VAT ID does not match the selected country.'))
raise VATIDFinalError(error_messages['country_mismatch'])
vat_id = re.sub('[^A-Z0-9]', '', vat_id.replace('HR', '').replace('MWST', ''))
try:
vat_id = normalize_vat_id(vat_id, country_code)
except ValueError:
raise VATIDFinalError(error_messages['invalid'])
try:
transport = Transport(
cache=SqliteCache(os.path.join(settings.CACHE_DIR, "validate_vat_id_ch_zeep_cache.db")),

View File

@@ -113,6 +113,11 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
lock_objects(quotas, shared_lock_objects=[event])
for wle in qs:
# add this event to wle.item as it is not yet cached and is needed in check_quotas
wle.item.event = event
if wle.variation:
wle.variation.item = wle.item
if (wle.item_id, wle.variation_id, wle.subevent_id) in gone:
continue
ev = (wle.subevent or event)

View File

@@ -629,13 +629,40 @@ DEFAULTS = {
'form_kwargs': dict(
label=_("Ask for VAT ID"),
help_text=format_lazy(
_("Only works if an invoice address is asked for. VAT ID is never required and only requested from "
"business customers in the following countries: {countries}"),
_("Only works if an invoice address is asked for. VAT ID is only requested from business customers "
"in the following countries: {countries}."),
countries=lazy(lambda *args: ', '.join(sorted(gettext(Country(cc).name) for cc in VAT_ID_COUNTRIES)), str)()
),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}),
)
},
'invoice_address_vatid_required_countries': {
'default': ['IT', 'GR'],
'type': list,
'form_class': forms.MultipleChoiceField,
'serializer_class': serializers.MultipleChoiceField,
'serializer_kwargs': dict(
choices=lazy(
lambda *args: sorted([(cc, gettext(Country(cc).name)) for cc in VAT_ID_COUNTRIES], key=lambda c: c[1]),
list
)(),
),
'form_kwargs': dict(
label=_("Require VAT ID in"),
choices=lazy(
lambda *args: sorted([(cc, gettext(Country(cc).name)) for cc in VAT_ID_COUNTRIES], key=lambda c: c[1]),
list
)(),
help_text=format_lazy(
_("VAT ID is optional by default, because not all businesses are assigned a VAT ID in all countries. "
"VAT ID will be required for all business addresses in the selected countries."),
),
widget=forms.CheckboxSelectMultiple(attrs={
"class": "scrolling-multiple-choice",
'data-display-dependency': '#id_invoice_address_vatid'
}),
)
},
'invoice_address_explanation_text': {
'default': '',
'type': LazyI18nString,

View File

@@ -60,23 +60,25 @@ def _populate_app_cache():
def get_defining_app(o):
# If sentry packed this in a wrapper, unpack that
if "sentry" in o.__module__:
module = getattr(o, "__module__", None)
if module and "sentry" in module:
o = o.__wrapped__
if hasattr(o, "__mocked_app"):
return o.__mocked_app
# Find the Django application this belongs to
searchpath = o.__module__
searchpath = module or getattr(o.__class__, "__module__", None) or ""
# Core modules are always active
if any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
if searchpath and any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
return 'CORE'
if not app_cache:
_populate_app_cache()
while True:
app = None
while searchpath:
app = app_cache.get(searchpath)
if "." not in searchpath or app:
break
@@ -157,7 +159,11 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
if not app_cache:
_populate_app_cache()
for receiver in self._sorted_receivers(sender):
for receiver in self._sorted_receivers(sender)[0]:
if self._is_receiver_active(sender, receiver):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
for receiver in self._sorted_receivers(sender)[1]:
if self._is_receiver_active(sender, receiver):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
@@ -179,7 +185,11 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
if not app_cache:
_populate_app_cache()
for receiver in self._sorted_receivers(sender):
for receiver in self._sorted_receivers(sender)[0]:
if self._is_receiver_active(sender, receiver):
named[chain_kwarg_name] = response
response = receiver(signal=self, sender=sender, **named)
for receiver in self._sorted_receivers(sender)[1]:
if self._is_receiver_active(sender, receiver):
named[chain_kwarg_name] = response
response = receiver(signal=self, sender=sender, **named)
@@ -204,7 +214,15 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
if not app_cache:
_populate_app_cache()
for receiver in self._sorted_receivers(sender):
for receiver in self._sorted_receivers(sender)[0]:
if self._is_receiver_active(sender, receiver):
try:
response = receiver(signal=self, sender=sender, **named)
except Exception as err:
responses.append((receiver, err))
else:
responses.append((receiver, response))
for receiver in self._sorted_receivers(sender)[1]:
if self._is_receiver_active(sender, receiver):
try:
response = receiver(signal=self, sender=sender, **named)
@@ -215,16 +233,33 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
return responses
def _sorted_receivers(self, sender):
orig_list = self._live_receivers(sender)
sorted_list = sorted(
orig_list,
orig_list_sync = self._live_receivers(sender)[0]
# todo: _live_receivers changed return value from [] to [], []
orig_list_async = self._live_receivers(sender)[1]
def _receiver_module(receiver):
return getattr(receiver, "__module__", receiver.__class__.__module__)
def _receiver_name(receiver):
return getattr(receiver, "__name__", receiver.__class__.__name__)
sorted_list_sync = sorted(
orig_list_sync,
key=lambda receiver: (
0 if any(receiver.__module__.startswith(m) for m in settings.CORE_MODULES) else 1,
receiver.__module__,
receiver.__name__,
0 if any(_receiver_module(receiver).startswith(m) for m in settings.CORE_MODULES) else 1,
_receiver_module(receiver),
_receiver_name(receiver),
)
)
return sorted_list
sorted_list_async = sorted(
orig_list_async,
key=lambda receiver: (
0 if any(_receiver_module(receiver).startswith(m) for m in settings.CORE_MODULES) else 1,
_receiver_module(receiver),
_receiver_name(receiver),
)
)
return sorted_list_sync, sorted_list_async
class EventPluginSignal(PluginSignal[Event]):

View File

@@ -22,7 +22,7 @@
import pycountry
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import pgettext
from django.utils.translation import gettext, pgettext, pgettext_lazy
from django_countries.fields import Country
from django_scopes import scope
@@ -36,6 +36,22 @@ from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, COUNTRY_STATE_LABEL,
)
VAT_ID_LABELS = {
# VAT ID is a EU concept and Switzerland has a distinct, but differently-named concept
"CH": pgettext_lazy("tax_id_swiss", "UID"), # Translators: Only translate to French (IDE) and Italien (IDI), otherwise keep the same
# Awareness around VAT IDs differes by EU country. For example, in Germany the VAT ID is assigned
# separately to each company and only used in cross-country transactions. Therefore, it makes sense
# to call it just "VAT ID" on the form, and people will either know their VAT ID or they don't.
# In contrast, in Italy the EU-compatible VAT ID is not separately assigned, but is just "IT" + the national tax
# number (Partita IVA) and also used on domestic transactions. So someone who never purchased something international
# for their company, might still know the value, if we call it the right way and not just "VAT ID".
"IT": pgettext_lazy("tax_id_italy", "VAT ID / P.IVA"), # Translators: Translate to only "P.IVA" in Italian, keep second part as-is in other languages
"GR": pgettext_lazy("tax_id_greece", "VAT ID / TIN"), # Translators: Translate to only "ΑΦΜ" in Greek
"ES": pgettext_lazy("tax_id_spain", "VAT ID / NIF"), # Translators: Translate to only "NIF" in Spanish
"PT": pgettext_lazy("tax_id_portugal", "VAT ID / NIF"), # Translators: Translate to only "NIF" in Portuguese
}
def _info(cc):
info = {
@@ -47,7 +63,12 @@ def _info(cc):
'required': 'if_any' if cc in COUNTRIES_WITH_STATE_IN_ADDRESS else False,
'label': COUNTRY_STATE_LABEL.get(cc, pgettext('address', 'State')),
},
'vat_id': {'visible': cc in VAT_ID_COUNTRIES, 'required': False},
'vat_id': {
'visible': cc in VAT_ID_COUNTRIES,
'required': False,
'label': VAT_ID_LABELS.get(cc, gettext("VAT ID")),
'helptext_visible': True,
},
}
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
return {'data': [], **info}
@@ -124,4 +145,10 @@ def address_form(request):
"required": transmission_type.identifier == selected_transmission_type and k in required
}
if is_business and country in event.settings.invoice_address_vatid_required_countries and info["vat_id"]["visible"]:
info["vat_id"]["required"] = True
if info["vat_id"]["required"]:
# The help text explains that it is optional, so we want to hide that if it is required
info["vat_id"]["helptext_visible"] = False
return JsonResponse(info)

View File

@@ -927,6 +927,7 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
'invoice_address_asked',
'invoice_address_required',
'invoice_address_vatid',
'invoice_address_vatid_required_countries',
'invoice_address_company_required',
'invoice_address_beneficiary',
'invoice_address_custom_field',

View File

@@ -126,7 +126,9 @@
{% endif %}
<a class="navbar-brand" href="{% url "control:index" %}">
<img src="{% static "pretixbase/img/pretix-icon-white-mini.svg" %}" />
{{ settings.PRETIX_INSTANCE_NAME }}
<span>
{{ settings.PRETIX_INSTANCE_NAME }}
</span>
</a>
</div>
<ul class="nav navbar-nav navbar-top-links navbar-left flip hidden-xs">

View File

@@ -43,6 +43,7 @@
{% bootstrap_field form.invoice_name_required layout="control" %}
{% bootstrap_field form.invoice_address_company_required layout="control" %}
{% bootstrap_field form.invoice_address_vatid layout="control" %}
{% bootstrap_field form.invoice_address_vatid_required_countries layout="control" %}
{% bootstrap_field form.invoice_address_beneficiary layout="control" %}
{% bootstrap_field form.invoice_address_not_asked_free layout="control" %}
{% bootstrap_field form.invoice_address_custom_field layout="control" %}

View File

@@ -165,7 +165,7 @@
{% if v.budget|default_if_none:"NONE" != "NONE" %}
<br>
<small class="text-muted">
{{ v.budget_used_orders|money:request.event.currency }} / {{ v.budget|money:request.event.currency }}
{{ v.budget_used|money:request.event.currency }} / {{ v.budget|money:request.event.currency }}
</small>
{% endif %}
</td>

View File

@@ -146,7 +146,7 @@ class BaseProcessView(AsyncAction, FormView):
else:
charset = None
try:
return parse_csv(self.file.file, 1024 * 1024, charset=charset)
reader = parse_csv(self.file.file, 1024 * 1024, charset=charset)
except UnicodeDecodeError:
messages.warning(
self.request,
@@ -155,7 +155,16 @@ class BaseProcessView(AsyncAction, FormView):
"Some characters were replaced with a placeholder."
)
)
return parse_csv(self.file.file, 1024 * 1024, "replace", charset=charset)
reader = parse_csv(self.file.file, 1024 * 1024, "replace", charset=charset)
if reader._had_duplicates:
messages.warning(
self.request,
_(
"Multiple columns of the CSV file have the same name and were renamed automatically. We "
"recommend that you rename these in your source file to avoid problems during import."
)
)
return reader
@cached_property
def parsed_list(self):

View File

@@ -87,7 +87,7 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self):
qs = Voucher.annotate_budget_used_orders(self.request.event.vouchers.exclude(
qs = Voucher.annotate_budget_used(self.request.event.vouchers.exclude(
Exists(WaitingListEntry.objects.filter(voucher_id=OuterRef('pk')))
).select_related(
'item', 'variation', 'seat'

View File

@@ -0,0 +1,62 @@
#
# 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 django.conf import settings
THRESHOLD_DOWNGRADE_TO_MID = 50
THRESHOLD_DOWNGRADE_TO_LOW = 250
def get_task_priority(shard, organizer_id):
"""
This is an attempt to build a simple "fair-use" policy for webhooks and notifications. The problem is that when
one organizer creates e.g. 20,000 orders through the API, that might schedule 20,000 webhooks and every other
organizer will need to wait for these webhooks to go through.
We try to fix that by building three queues: high-prio, mid-prio, and low-prio. Every organizer starts in the
high-prio queue, and all their tasks are routed immediately. Once an organizer submits more than X jobs of a
certain type per minute, they get downgraded to the mid-prio queue, and then if they submit even more to the
low-prio queue. That way, if another organizer has "regular usage", they are prioritized over the organizer with
high load.
"""
from django_redis import get_redis_connection
if not settings.HAS_REDIS:
return settings.PRIORITY_CELERY_HIGH
# We use redis directly instead of the Django cache API since the Django cache API does not support INCR for
# nonexistant keys
rc = get_redis_connection("redis")
cache_key = f"pretix:task_priority:{shard}:{organizer_id}"
# Make sure counters expire after a while when not used
p = rc.pipeline()
p.incr(cache_key)
p.expire(cache_key, 60)
new_counter = p.execute()[0]
if new_counter >= THRESHOLD_DOWNGRADE_TO_LOW:
return settings.PRIORITY_CELERY_LOW
elif new_counter >= THRESHOLD_DOWNGRADE_TO_MID:
return settings.PRIORITY_CELERY_MID
else:
return settings.PRIORITY_CELERY_HIGH

View File

@@ -0,0 +1,210 @@
#
# 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 urllib.parse import quote, urlencode
import text_unidecode
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
def dotdecimal(value):
return str(value).replace(",", ".")
def commadecimal(value):
return str(value).replace(".", ",")
def generate_payment_qr_codes(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban,
):
out = []
for method in [
swiss_qrbill,
czech_spayd,
euro_epc_qr,
euro_bezahlcode,
]:
data = method(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban,
)
if data:
out.append(data)
return out
def euro_epc_qr(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban,
):
if event.currency != 'EUR' or not bank_details_sepa_iban:
return
return {
"id": "girocode",
"label": "EPC-QR",
"qr_data": "\n".join(text_unidecode.unidecode(str(d or '')) for d in [
"BCD", # Service Tag: BCD
"002", # Version: V2
"2", # Character set: ISO 8859-1
"SCT", # Identification code: SCT
bank_details_sepa_bic, # AT-23 BIC of the Beneficiary Bank
bank_details_sepa_name, # AT-21 Name of the Beneficiary
bank_details_sepa_iban, # AT-20 Account number of the Beneficiary
f"{event.currency}{dotdecimal(amount)}", # AT-04 Amount of the Credit Transfer in Euro
"", # AT-44 Purpose of the Credit Transfer
"", # AT-05 Remittance Information (Structured)
code, # AT-05 Remittance Information (Unstructured)
"", # Beneficiary to originator information
"",
]),
}
def euro_bezahlcode(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban,
):
if not bank_details_sepa_iban or bank_details_sepa_iban[:2] != 'DE':
return
if event.currency != 'EUR':
return
qr_data = "bank://singlepaymentsepa?" + urlencode({
"name": str(bank_details_sepa_name),
"iban": str(bank_details_sepa_iban),
"bic": str(bank_details_sepa_bic),
"amount": commadecimal(amount),
"reason": str(code),
"currency": str(event.currency),
}, quote_via=quote)
return {
"id": "bezahlcode",
"label": "BezahlCode",
"qr_data": mark_safe(qr_data),
"link": qr_data,
"link_aria_label": _("Open BezahlCode in your banking app to start the payment process."),
}
def swiss_qrbill(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban,
):
if not bank_details_sepa_iban or not bank_details_sepa_iban[:2] in ('CH', 'LI'):
return
if event.currency not in ('EUR', 'CHF'):
return
if not event.settings.invoice_address_from or not event.settings.invoice_address_from_country:
return
data_fields = [
'SPC',
'0200',
'1',
bank_details_sepa_iban,
'K',
bank_details_sepa_name[:70],
event.settings.invoice_address_from.replace('\n', ', ')[:70],
(event.settings.invoice_address_from_zipcode + ' ' + event.settings.invoice_address_from_city)[:70],
'',
'',
str(event.settings.invoice_address_from_country),
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
str(amount),
event.currency,
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'NON',
'', # structured reference
code,
'EPD',
]
data_fields = [text_unidecode.unidecode(d or '') for d in data_fields]
qr_data = '\r\n'.join(data_fields)
return {
"id": "qrbill",
"label": "QR-bill",
"html_prefix": mark_safe(
'<svg class="banktransfer-swiss-cross" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.8 19.8">'
'<path stroke="#fff" stroke-width="1.436" d="M.7.7h18.4v18.4H.7z"/><path fill="#fff" d="M8.3 4h3.3v11H8.3z"/>'
'<path fill="#fff" d="M4.4 7.9h11v3.3h-11z"/></svg>'
),
"qr_data": qr_data,
"css_class": "banktransfer-swiss-cross-overlay",
}
def czech_spayd(
event,
code,
amount,
bank_details_sepa_bic,
bank_details_sepa_name,
bank_details_sepa_iban,
):
if not bank_details_sepa_iban or not bank_details_sepa_iban[:2] in ('CZ', 'SK'):
return
if event.currency not in ('EUR', 'CZK'):
return
qr_data = f"SPD*1.0*ACC:{bank_details_sepa_iban}*AM:{dotdecimal(amount)}*CC:{event.currency}*MSG:{code}"
return {
"id": "spayd",
"label": "SPAYD",
"qr_data": qr_data,
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-20 10:37+0000\n"
"PO-Revision-Date: 2022-02-22 22:00+0000\n"
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
"js/gl/>\n"
"PO-Revision-Date: 2025-12-03 23:00+0000\n"
"Last-Translator: sandra r <sandrarial@gestiontickets.online>\n"
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/"
"pretix-js/gl/>\n"
"Language: gl\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 4.8\n"
"X-Generator: Weblate 5.14.3\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -31,106 +31,104 @@ msgstr "Comentario:"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
msgid "PayPal"
msgstr ""
msgstr "PayPal"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:35
msgid "Venmo"
msgstr ""
msgstr "Venmo"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:36
#: pretix/static/pretixpresale/js/walletdetection.js:38
msgid "Apple Pay"
msgstr ""
msgstr "Apple Pay"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:37
msgid "Itaú"
msgstr ""
msgstr "Itaú"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:38
msgid "PayPal Credit"
msgstr ""
msgstr "Crédito PayPal"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:39
msgid "Credit Card"
msgstr ""
msgstr "Tarxeta de crédito"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40
msgid "PayPal Pay Later"
msgstr ""
msgstr "PayPal Pagar Máis Tarde"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL"
msgstr ""
msgstr "iDEAL"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"
msgstr ""
msgstr "Débito directo SEPA"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
msgid "Bancontact"
msgstr ""
msgstr "Bancontact"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:44
msgid "giropay"
msgstr ""
msgstr "giropay"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:45
msgid "SOFORT"
msgstr ""
msgstr "SOFORT"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:46
#, fuzzy
#| msgid "Yes"
msgid "eps"
msgstr "Si"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:47
msgid "MyBank"
msgstr ""
msgstr "MyBank"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:48
msgid "Przelewy24"
msgstr ""
msgstr "Przelewy24"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:49
msgid "Verkkopankki"
msgstr ""
msgstr "Verkkopankki"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:50
msgid "PayU"
msgstr ""
msgstr "PayU"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:51
msgid "BLIK"
msgstr ""
msgstr "BLIK"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:52
msgid "Trustly"
msgstr ""
msgstr "De confianza"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:53
msgid "Zimpler"
msgstr ""
msgstr "Zimpler"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:54
msgid "Maxima"
msgstr ""
msgstr "Máxima"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:55
msgid "OXXO"
msgstr ""
msgstr "OXXO"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:56
msgid "Boleto"
msgstr ""
msgstr "Ticket"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:57
msgid "WeChat Pay"
msgstr ""
msgstr "Pagar con WeChat"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:58
msgid "Mercado Pago"
msgstr ""
msgstr "Mercado Pago"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:167
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
@@ -149,7 +147,7 @@ msgstr "Confirmando o pagamento…"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:254
msgid "Payment method unavailable"
msgstr ""
msgstr "O método de pago non está dispoñible"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
@@ -240,11 +238,11 @@ msgstr "Cancelado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
msgid "Confirmed"
msgstr ""
msgstr "Confirmado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
msgid "Approval pending"
msgstr ""
msgstr "Aprobación pendente"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
msgid "Redeemed"
@@ -300,16 +298,12 @@ msgid "Ticket code revoked/changed"
msgstr "Código de tícket revogado/cambiado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
#, fuzzy
#| msgid "Ticket not paid"
msgid "Ticket blocked"
msgstr "Tícket pendente de pago"
msgstr "Ticket bloqueado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
#, fuzzy
#| msgid "Ticket not paid"
msgid "Ticket not valid at this time"
msgstr "cket pendente de pago"
msgstr "O ticket non é válido neste momento"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
msgid "Order canceled"
@@ -317,11 +311,11 @@ msgstr "Pedido cancelado"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list"
msgstr ""
msgstr "O código do ticket é ambiguo na lista"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
msgid "Order not approved"
msgstr ""
msgstr "Orde non aprobada"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
msgid "Checked-in Tickets"
@@ -422,7 +416,7 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:276
msgid "If this takes longer than a few minutes, please contact us."
msgstr ""
msgstr "Se isto leva máis duns minutos, póñase en contacto connosco."
#: pretix/static/pretixbase/js/asynctask.js:331
msgid "Close message"
@@ -452,7 +446,7 @@ msgstr "está despois"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:40
msgid "="
msgstr ""
msgstr "="
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
msgid "Product"
@@ -464,7 +458,7 @@ msgstr "Ver variacións do produto"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
msgid "Gate"
msgstr ""
msgstr "Porta"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:111
msgid "Current date and time"
@@ -472,11 +466,11 @@ msgstr "Data e hora actual"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
msgstr ""
msgstr "Día actual da semana (1 = luns, 7 = domingo)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
msgid "Current entry status"
msgstr ""
msgstr "Estado de entrada actual"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
msgid "Number of previous entries"
@@ -487,40 +481,32 @@ msgid "Number of previous entries since midnight"
msgstr "Número de entradas previas desde a medianoite"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:131
#, fuzzy
#| msgid "Number of previous entries"
msgid "Number of previous entries since"
msgstr "Número de entradas previas"
msgstr "Número de entradas anteriores desde"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:135
#, fuzzy
#| msgid "Number of previous entries"
msgid "Number of previous entries before"
msgstr "Número de entradas previas"
msgstr "Número de entradas anteriores antes de"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:139
msgid "Number of days with a previous entry"
msgstr "Número de días cunha entrada previa"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:143
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry since"
msgstr "Número de días cunha entrada previa"
msgstr "Número de días cunha entrada previa desde"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:147
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry before"
msgstr "Número de días cunha entrada previa"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:151
msgid "Minutes since last entry (-1 on first entry)"
msgstr ""
msgstr "Minutos desde a última entrada (-1 na primeira entrada)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:155
msgid "Minutes since first entry (-1 on first entry)"
msgstr ""
msgstr "Minutos desde a primeira entrada (-1 na primeira entrada)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:182
msgid "All of the conditions below (AND)"
@@ -564,25 +550,25 @@ msgstr "minutos"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
msgid "Duplicate"
msgstr ""
msgstr "Duplicar"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status"
msgid "present"
msgstr ""
msgstr "presente"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
msgctxt "entry_status"
msgid "absent"
msgstr ""
msgstr "ausente"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!"
msgstr ""
msgstr "Erro: Non se atopou o produto!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!"
msgstr ""
msgstr "Erro: Variación non atopada!"
#: pretix/static/pretixcontrol/js/ui/editor.js:171
msgid "Check-in QR"
@@ -597,16 +583,12 @@ msgid "Group of objects"
msgstr "Grupo de obxectos"
#: pretix/static/pretixcontrol/js/ui/editor.js:909
#, fuzzy
#| msgid "Text object"
msgid "Text object (deprecated)"
msgstr "Obxecto de texto"
msgstr "Obxecto de texto (obsoleto)"
#: pretix/static/pretixcontrol/js/ui/editor.js:911
#, fuzzy
#| msgid "Text object"
msgid "Text box"
msgstr "Obxecto de texto"
msgstr "Caixa de texto"
#: pretix/static/pretixcontrol/js/ui/editor.js:913
msgid "Barcode area"
@@ -655,26 +637,26 @@ msgid "Unknown error."
msgstr "Erro descoñecido."
#: pretix/static/pretixcontrol/js/ui/main.js:309
#, fuzzy
#| msgid "Your color has great contrast and is very easy to read!"
msgid "Your color has great contrast and will provide excellent accessibility."
msgstr "A túa cor ten moito contraste e é moi doada de ler!"
msgstr ""
"A túa cor ten un gran contraste e proporcionará unha excelente "
"accesibilidade."
#: pretix/static/pretixcontrol/js/ui/main.js:313
#, fuzzy
#| msgid "Your color has decent contrast and is probably good-enough to read!"
msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility "
"requirements."
msgstr ""
"A túa cor ten un contraste axeitado e probablemente sexa suficientemente "
"lexible!"
"A túa cor ten un contraste decente e é suficiente para os requisitos mínimos "
"de accesibilidade."
#: pretix/static/pretixcontrol/js/ui/main.js:317
msgid ""
"Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted."
msgstr ""
"A túa cor non ten suficiente contraste co branco. A accesibilidade do teu "
"sitio web verase afectada."
#: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463
@@ -695,11 +677,11 @@ msgstr "Soamente seleccionados"
#: pretix/static/pretixcontrol/js/ui/main.js:839
msgid "Enter page number between 1 and %(max)s."
msgstr ""
msgstr "Introduza o número de páxina entre 1 e %(max)s."
#: pretix/static/pretixcontrol/js/ui/main.js:842
msgid "Invalid page number."
msgstr ""
msgstr "Número de páxina non válido."
#: pretix/static/pretixcontrol/js/ui/main.js:1000
msgid "Use a different name internally"
@@ -718,10 +700,8 @@ msgid "Calculating default price…"
msgstr "Calculando o prezo por defecto…"
#: pretix/static/pretixcontrol/js/ui/plugins.js:69
#, fuzzy
#| msgid "Search results"
msgid "No results"
msgstr "Resultados da procura"
msgstr "Sen resultados"
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
@@ -752,7 +732,7 @@ msgstr "O carro da compra caducou"
#: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire."
msgstr ""
msgstr "O teu carriño está a piques de caducar."
#: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute."
@@ -762,16 +742,10 @@ msgstr[1] ""
"Os artigos da túa cesta están reservados para ti durante {num} minutos."
#: pretix/static/pretixpresale/js/ui/cart.js:83
#, fuzzy
#| msgid "Cart expired"
msgid "Your cart has expired."
msgstr "O carro da compra caducou"
msgstr "O carro da compra caducou."
#: pretix/static/pretixpresale/js/ui/cart.js:86
#, fuzzy
#| msgid ""
#| "The items in your cart are no longer reserved for you. You can still "
#| "complete your order as long as theyre available."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as they're available."
@@ -781,11 +755,11 @@ msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?"
msgstr ""
msgstr "Queres renovar o período de reserva?"
#: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation"
msgstr ""
msgstr "Renovar reserva"
#: pretix/static/pretixpresale/js/ui/main.js:194
msgid "The organizer keeps %(currency)s %(amount)s"
@@ -805,71 +779,66 @@ msgstr "A súa hora local:"
#: pretix/static/pretixpresale/js/walletdetection.js:39
msgid "Google Pay"
msgstr ""
msgstr "Google Pay"
#: pretix/static/pretixpresale/js/widget/widget.js:16
msgctxt "widget"
msgid "Quantity"
msgstr ""
msgstr "Cantidade"
#: pretix/static/pretixpresale/js/widget/widget.js:17
msgctxt "widget"
msgid "Decrease quantity"
msgstr ""
msgstr "Diminuír a cantidade"
#: pretix/static/pretixpresale/js/widget/widget.js:18
msgctxt "widget"
msgid "Increase quantity"
msgstr ""
msgstr "Aumentar a cantidade"
#: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget"
msgid "Filter events by"
msgstr ""
msgstr "Filtrar eventos por"
#: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget"
msgid "Filter"
msgstr ""
msgstr "Filtro"
#: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget"
msgid "Price"
msgstr ""
msgstr "Prezo"
#: pretix/static/pretixpresale/js/widget/widget.js:22
#, javascript-format
msgctxt "widget"
msgid "Original price: %s"
msgstr ""
msgstr "Prezo orixinal: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:23
#, javascript-format
msgctxt "widget"
msgid "New price: %s"
msgstr ""
msgstr "Novo prezo: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:24
#, fuzzy
#| msgid "Selected only"
msgctxt "widget"
msgid "Select"
msgstr "Soamente seleccionados"
msgstr "Seleccione"
#: pretix/static/pretixpresale/js/widget/widget.js:25
#, fuzzy, javascript-format
#| msgid "Selected only"
#, javascript-format
msgctxt "widget"
msgid "Select %s"
msgstr "Soamente seleccionados"
msgstr "Seleccione %s"
#: pretix/static/pretixpresale/js/widget/widget.js:26
#, fuzzy, javascript-format
#| msgctxt "widget"
#| msgid "See variations"
#, javascript-format
msgctxt "widget"
msgid "Select variant %s"
msgstr "Ver variacións"
msgstr "Seleccione a variante %s"
#: pretix/static/pretixpresale/js/widget/widget.js:27
msgctxt "widget"
@@ -905,7 +874,7 @@ msgstr "dende %(currency)s %(price)s"
#, javascript-format
msgctxt "widget"
msgid "Image of %s"
msgstr ""
msgstr "Imaxe de %s"
#: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget"
@@ -940,25 +909,19 @@ msgstr "Só dispoñible mediante vale"
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:43
#, fuzzy
#| msgctxt "widget"
#| msgid "currently available: %s"
msgctxt "widget"
msgid "Not yet available"
msgstr "dispoñible actualmente: %s"
msgstr "Aínda non dispoñible"
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Not available anymore"
msgstr ""
msgstr "Xa non está dispoñible"
#: pretix/static/pretixpresale/js/widget/widget.js:42
#, fuzzy
#| msgctxt "widget"
#| msgid "currently available: %s"
msgctxt "widget"
msgid "Currently not available"
msgstr "dispoñible actualmente: %s"
msgstr "Non dispoñible actualmente"
#: pretix/static/pretixpresale/js/widget/widget.js:44
#, javascript-format
@@ -991,9 +954,6 @@ msgid "Open ticket shop"
msgstr "Abrir a tenda de tíckets"
#: pretix/static/pretixpresale/js/widget/widget.js:50
#, fuzzy
#| msgctxt "widget"
#| msgid "Resume checkout"
msgctxt "widget"
msgid "Checkout"
msgstr "Continuar co pagamento"
@@ -1053,17 +1013,14 @@ msgid "Close"
msgstr "Cerrar"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#, fuzzy
#| msgctxt "widget"
#| msgid "Resume checkout"
msgctxt "widget"
msgid "Close checkout"
msgstr "Continuar co pagamento"
msgstr "Pagamento pechado"
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr ""
msgstr "Non podes cancelar esta operación. Agarda a que remate a carga."
#: pretix/static/pretixpresale/js/widget/widget.js:64
msgctxt "widget"
@@ -1071,20 +1028,14 @@ msgid "Continue"
msgstr "Continuar"
#: pretix/static/pretixpresale/js/widget/widget.js:65
#, fuzzy
#| msgctxt "widget"
#| msgid "See variations"
msgctxt "widget"
msgid "Show variants"
msgstr "Ver variacións"
#: pretix/static/pretixpresale/js/widget/widget.js:66
#, fuzzy
#| msgctxt "widget"
#| msgid "See variations"
msgctxt "widget"
msgid "Hide variants"
msgstr "Ver variacións"
msgstr "Ocultar variantes"
#: pretix/static/pretixpresale/js/widget/widget.js:67
msgctxt "widget"
@@ -1133,6 +1084,9 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available "
"again."
msgstr ""
"Algunhas ou todas as categorías de entradas están esgotadas. Se queres, "
"podes engadirte á lista de espera. Despois avisarémosche se volven quedar "
"asentos dispoñibles."
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgctxt "widget"
@@ -1169,31 +1123,31 @@ msgstr "Dom"
#: pretix/static/pretixpresale/js/widget/widget.js:85
msgid "Monday"
msgstr ""
msgstr "Luns"
#: pretix/static/pretixpresale/js/widget/widget.js:86
msgid "Tuesday"
msgstr ""
msgstr "Martes"
#: pretix/static/pretixpresale/js/widget/widget.js:87
msgid "Wednesday"
msgstr ""
msgstr "Mércores"
#: pretix/static/pretixpresale/js/widget/widget.js:88
msgid "Thursday"
msgstr ""
msgstr "Xoves"
#: pretix/static/pretixpresale/js/widget/widget.js:89
msgid "Friday"
msgstr ""
msgstr "Venres"
#: pretix/static/pretixpresale/js/widget/widget.js:90
msgid "Saturday"
msgstr ""
msgstr "Sábado"
#: pretix/static/pretixpresale/js/widget/widget.js:91
msgid "Sunday"
msgstr ""
msgstr "Domingo"
#: pretix/static/pretixpresale/js/widget/widget.js:94
msgid "January"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-27 13:57+0000\n"
"PO-Revision-Date: 2025-11-18 17:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"PO-Revision-Date: 2025-12-03 23:00+0000\n"
"Last-Translator: SJang1 <git@sjang.dev>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/ko/"
">\n"
"Language: ko\n"
@@ -290,33 +290,27 @@ msgstr "묶음 상품은 그 자체로 또 다른 묶음을 포함 할 수 없
#: pretix/api/serializers/item.py:235
msgid "The program start must not be empty."
msgstr ""
msgstr "프로그램 시작일정은 비어 있어서는 안 됩니다."
#: pretix/api/serializers/item.py:239
msgid "The program end must not be empty."
msgstr ""
msgstr "프로그램 종료일정은 비어 있어서는 안 됩니다."
#: pretix/api/serializers/item.py:242 pretix/base/models/items.py:2321
#, fuzzy
#| msgid "The maximum date must not be before the minimum value."
msgid "The program end must not be before the program start."
msgstr "종료일(최대 날짜)은 시작일(최소값)보다 앞서면 안됩니다."
msgstr "종료일은 시작일보다 앞서면 안됩니다."
#: pretix/api/serializers/item.py:247 pretix/base/models/items.py:2315
#, fuzzy
#| msgid "You can not select a subevent if your event is not an event series."
msgid "You cannot use program times on an event series."
msgstr ""
"당신의이벤트가 이벤트 시리즈가 아닌 경우 하위 이벤트를 선택할 수 없습니다."
msgstr "이벤트 시리즈에 있는 시간은 사용하실 수 없습니다."
#: pretix/api/serializers/item.py:337
#, fuzzy
msgid ""
"Updating add-ons, bundles, program times or variations via PATCH/PUT is not "
"supported. Please use the dedicated nested endpoint."
msgstr ""
"추가 기능, 묶음 상품들, 또는 변형은 PATCH/PUT를 통해 업데이트 할 수 없습니"
"다. 전용 중첩은 마지막 지점에서 사용하세요"
"추가 기능, 묶음 상품들, 또는 변형은 PATCH/PUT를 통해 업데이트 할 수 없습니다."
" 전용 중첩은 마지막 지점에서 사용하세요."
#: pretix/api/serializers/item.py:345
msgid "Only admission products can currently be personalized."
@@ -573,22 +567,15 @@ msgid "Event series date deleted"
msgstr "이벤트 시리즈 날짜 삭제"
#: pretix/api/webhooks.py:374
#, fuzzy
#| msgid "Product name"
msgid "Product changed"
msgstr "제품"
msgstr "제품 변경됨"
#: pretix/api/webhooks.py:375
#, fuzzy
#| msgid ""
#| "Product changed (including product added or deleted and including changes "
#| "to nested objects like variations or bundles)"
msgid ""
"This includes product added or deleted and changes to nested objects like "
"variations or bundles."
msgstr ""
"제품 변경(제품 추가 또는 삭제, 변형 또는 번들과 같은 중첩된 객체에 대한 변경 "
"포함)"
msgstr "여기에는 추가되거나 삭제되거나 변경된 중첩오브젝트나 번들과 같은 사항이 포함"
"됩니다."
#: pretix/api/webhooks.py:380
msgid "Shop taken live"
@@ -623,16 +610,12 @@ msgid "Waiting list entry received voucher"
msgstr "대기자 명단 항목이 바우처를 받았습니다"
#: pretix/api/webhooks.py:412
#, fuzzy
#| msgid "Voucher code"
msgid "Voucher added"
msgstr "바우처 코드"
msgstr "바우처 추가됨"
#: pretix/api/webhooks.py:416
#, fuzzy
#| msgid "Voucher assigned"
msgid "Voucher changed"
msgstr "바우처 할당"
msgstr "바우처 변경됨"
#: pretix/api/webhooks.py:417
msgid ""
@@ -643,10 +626,8 @@ msgstr ""
"하지 않습니다."
#: pretix/api/webhooks.py:421
#, fuzzy
#| msgid "Voucher redeemed"
msgid "Voucher deleted"
msgstr "바우처 상환"
msgstr "바우처 제거됨"
#: pretix/api/webhooks.py:425
msgid "Customer account created"
@@ -671,7 +652,7 @@ msgstr "고객 계정 익명화되었습니다"
#: pretix/plugins/banktransfer/payment.py:513
#: pretix/presale/forms/customer.py:152
msgid "This field is required."
msgstr "이 필드는 필수입니다"
msgstr "이 필드는 필수입니다."
#: pretix/base/addressvalidation.py:213
msgid "Enter a postal code in the format XXX."
@@ -721,7 +702,7 @@ msgstr "비밀번호"
#: pretix/base/auth.py:176 pretix/base/auth.py:183
msgid "Your password must contain both numeric and alphabetic characters."
msgstr "비밀번호는 숫자와 알파벳 문자가 모두 포함되어야 합니다"
msgstr "비밀번호는 숫자와 알파벳 문자가 모두 포함되어야 합니다."
#: pretix/base/auth.py:202 pretix/base/auth.py:212
#, python-format
@@ -815,28 +796,21 @@ msgstr ""
"소를 확인해 주십시요."
#: pretix/base/datasync/datasync.py:263
#, fuzzy, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
#, python-brace-format
msgid ""
"Field \"{field_name}\" does not exist. Please check your {provider_name} "
"settings."
msgstr ""
"필드 \"{field_name}\"은 {available_inputs}에 유효하지 않습니다. "
"{provider_name} 설정을 확인해 주세요."
msgstr "필드 \"{field_name}\"은 존재하지 않습니다. 당신의 {provider_name} 설정을 확인"
"해 주세요."
#: pretix/base/datasync/datasync.py:270
#, fuzzy, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
#, python-brace-format
msgid ""
"Field \"{field_name}\" requires {required_input}, but only got "
"{available_inputs}. Please check your {provider_name} settings."
msgstr ""
"필드 \"{field_name}\" {available_inputs}에 유효하지 않습니다. "
"{provider_name} 설정을 확인해 주세요."
"필드 \"{field_name}\"는 {required_input}을 필요로 하지만, {available_inputs}"
"만 받았습니다. 당신의 {provider_name} 설정을 확인해 주세요."
#: pretix/base/datasync/datasync.py:281
#, python-brace-format
@@ -848,16 +822,12 @@ msgstr ""
"지 않았습니다"
#: pretix/base/datasync/sourcefields.py:128
#, fuzzy
#| msgid "Order positions"
msgid "Order position details"
msgstr "주문 위치"
msgstr "주문 위치 세부 정보"
#: pretix/base/datasync/sourcefields.py:129
#, fuzzy
#| msgid "Attendee email"
msgid "Attendee details"
msgstr "참석자 이메일"
msgstr "참석자 정보들"
#: pretix/base/datasync/sourcefields.py:130 pretix/base/exporters/answers.py:66
#: pretix/base/models/items.py:1766 pretix/control/navigation.py:172
@@ -867,10 +837,8 @@ msgid "Questions"
msgstr "질문들"
#: pretix/base/datasync/sourcefields.py:131
#, fuzzy
#| msgid "Product data"
msgid "Product details"
msgstr "상품 데이터"
msgstr "상품 정보"
#: pretix/base/datasync/sourcefields.py:132
#: pretix/control/templates/pretixcontrol/event/settings.html:279
@@ -895,17 +863,13 @@ msgid "Invoice address"
msgstr "송장 주소"
#: pretix/base/datasync/sourcefields.py:134
#, fuzzy
#| msgid "Meta information"
msgid "Event information"
msgstr "메타 정보(데이타에 대한 정보)"
msgstr "이벤트 정보"
#: pretix/base/datasync/sourcefields.py:135
#, fuzzy
#| msgid "Send recovery information"
msgctxt "subevent"
msgid "Event or date information"
msgstr "복구 정보를 전송하다"
msgstr "이벤트 또는 날짜 정보"
#: pretix/base/datasync/sourcefields.py:175
#: pretix/base/exporters/orderlist.py:605
@@ -930,10 +894,8 @@ msgstr "참석자 이름"
#: pretix/base/datasync/sourcefields.py:187
#: pretix/base/datasync/sourcefields.py:604
#: pretix/base/datasync/sourcefields.py:628
#, fuzzy
#| msgid "Attendee name"
msgid "Attendee"
msgstr "참석자 이름"
msgstr "참석자"
#: pretix/base/datasync/sourcefields.py:207
#: pretix/base/exporters/orderlist.py:612 pretix/base/forms/questions.py:687
@@ -947,10 +909,8 @@ msgid "Attendee email"
msgstr "참석자 이메일"
#: pretix/base/datasync/sourcefields.py:219
#, fuzzy
#| msgid "Attendee email"
msgid "Attendee or order email"
msgstr "참석자 이메일"
msgstr "참석자 또는 구매 이메일"
#: pretix/base/datasync/sourcefields.py:232
#: pretix/base/exporters/orderlist.py:613 pretix/base/pdf.py:189
@@ -963,28 +923,20 @@ msgid "Attendee company"
msgstr "참석자 회사"
#: pretix/base/datasync/sourcefields.py:241
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address street"
msgstr "참석자 주소"
msgstr "참석자 주소 도로명"
#: pretix/base/datasync/sourcefields.py:250
#, fuzzy
#| msgid "Attendee ZIP code"
msgid "Attendee address ZIP code"
msgstr "참석자 우편번호"
msgstr "참석자 주소 우편번호"
#: pretix/base/datasync/sourcefields.py:259
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address city"
msgstr "참석자 주소"
msgstr "참석자 주소 도시"
#: pretix/base/datasync/sourcefields.py:268
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address country"
msgstr "참석자 주소"
msgstr "참석자 주소 국가"
#: pretix/base/datasync/sourcefields.py:279
#: pretix/base/exporters/orderlist.py:653 pretix/base/pdf.py:347
@@ -1020,16 +972,12 @@ msgid "Invoice address country"
msgstr "송장 주소 국가"
#: pretix/base/datasync/sourcefields.py:353
#, fuzzy
#| msgid "Order total"
msgid "Order email"
msgstr "주문 합계"
msgstr "주문 이메일"
#: pretix/base/datasync/sourcefields.py:362
#, fuzzy
#| msgid "Order time"
msgid "Order email domain"
msgstr "주문 시간"
msgstr "주문 이메일 도메인"
#: pretix/base/datasync/sourcefields.py:371
#: pretix/base/exporters/invoices.py:203 pretix/base/exporters/invoices.py:332
@@ -1061,10 +1009,8 @@ msgid "Order code"
msgstr "주문 코드"
#: pretix/base/datasync/sourcefields.py:380
#, fuzzy
#| msgid "Event end date and time"
msgid "Event and order code"
msgstr "이벤트 종료 날짜 및 시간"
msgstr "이벤트와 주문 번호"
#: pretix/base/datasync/sourcefields.py:389
#: pretix/base/exporters/orderlist.py:263 pretix/base/notifications.py:201
@@ -1076,10 +1022,8 @@ msgid "Order total"
msgstr "주문 합계"
#: pretix/base/datasync/sourcefields.py:398
#, fuzzy
#| msgid "Product name and variation"
msgid "Product and variation name"
msgstr "제품명 및 변형"
msgstr "제품명 및 변형 이름"
#: pretix/base/datasync/sourcefields.py:410 pretix/base/exporters/items.py:57
#: pretix/base/exporters/orderlist.py:598
@@ -1089,16 +1033,12 @@ msgid "Product ID"
msgstr "상품 식별 아이디"
#: pretix/base/datasync/sourcefields.py:419
#, fuzzy
#| msgid "Count add-on products"
msgid "Product is admission product"
msgstr "추가된 제품을 포함합니다"
msgstr "상품은 입장 상품입니다"
#: pretix/base/datasync/sourcefields.py:428
#, fuzzy
#| msgid "Short form"
msgid "Event short form"
msgstr "짧은 형식"
msgstr "이벤트 짧은 형식"
#: pretix/base/datasync/sourcefields.py:437 pretix/base/exporters/events.py:57
#: pretix/base/exporters/orderlist.py:263
@@ -1141,10 +1081,8 @@ msgid "Order code and position number"
msgstr "주문 코드 및 위치 번호"
#: pretix/base/datasync/sourcefields.py:482
#, fuzzy
#| msgid "Ticket code"
msgid "Ticket price"
msgstr "티켓 코드"
msgstr "티켓 가격"
#: pretix/base/datasync/sourcefields.py:491 pretix/base/notifications.py:204
#: pretix/control/forms/filter.py:216 pretix/control/forms/modelimport.py:90
@@ -1152,22 +1090,16 @@ msgid "Order status"
msgstr "주문 상태"
#: pretix/base/datasync/sourcefields.py:500
#, fuzzy
#| msgid "Device status"
msgid "Ticket status"
msgstr "기기 상태"
msgstr "티켓 상태"
#: pretix/base/datasync/sourcefields.py:509
#, fuzzy
#| msgid "Purchase date and time"
msgid "Order date and time"
msgstr "구매 날짜 및 시간"
#: pretix/base/datasync/sourcefields.py:518
#, fuzzy
#| msgid "Printing date and time"
msgid "Payment date and time"
msgstr "인쇄 날짜 및 시간"
msgstr "결제 날짜 및 시간"
#: pretix/base/datasync/sourcefields.py:527
#: pretix/base/exporters/orderlist.py:272
@@ -1178,35 +1110,27 @@ msgid "Order locale"
msgstr "주문 지역 설정"
#: pretix/base/datasync/sourcefields.py:536
#, fuzzy
#| msgid "Order position"
msgid "Order position ID"
msgstr "주문 위치"
msgstr "주문 위치 ID"
#: pretix/base/datasync/sourcefields.py:545
#: pretix/base/exporters/orderlist.py:292
#, fuzzy
#| msgid "Order time"
msgid "Order link"
msgstr "주문 시간"
msgstr "주문 링크"
#: pretix/base/datasync/sourcefields.py:560
#, fuzzy
#| msgid "Ticket design"
msgid "Ticket link"
msgstr "티켓 디자인"
msgstr "티켓 링크"
#: pretix/base/datasync/sourcefields.py:578
#, fuzzy, python-brace-format
#| msgid "Check-in list {val}"
#, python-brace-format
msgid "Check-in datetime on list {}"
msgstr "체크인 목록 {val}"
msgstr "{} 리스트에 있는 체크인 날짜와 시간"
#: pretix/base/datasync/sourcefields.py:590
#, fuzzy, python-brace-format
#| msgid "Question {val}"
#, python-brace-format
msgid "Question: {name}"
msgstr "질문 {val}"
msgstr "질문: {name}"
#: pretix/base/datasync/sourcefields.py:604
#: pretix/base/datasync/sourcefields.py:614 pretix/base/settings.py:3642
@@ -2422,9 +2346,9 @@ msgid "Fees"
msgstr "수수료"
#: pretix/base/exporters/orderlist.py:277
#, fuzzy, python-brace-format
#, python-brace-format
msgid "Gross at {rate} % tax"
msgstr "세율{%}의 세금으로 총합"
msgstr "세율 {rate}%의 세금으로 총합"
#: pretix/base/exporters/orderlist.py:278
#, python-brace-format
@@ -2472,9 +2396,9 @@ msgid "External customer ID"
msgstr "외부고객 아이디"
#: pretix/base/exporters/orderlist.py:296
#, fuzzy, python-brace-format
#, python-brace-format
msgid "Paid by {method}"
msgstr "{방법}에 의해 결제됨"
msgstr "{method}에 의해 결제됨"
#: pretix/base/exporters/orderlist.py:458
#: pretix/base/exporters/orderlist.py:914
@@ -2677,10 +2601,8 @@ msgid "Check-in lists"
msgstr "체크인 목록"
#: pretix/base/exporters/orderlist.py:672
#, fuzzy
#| msgid "Additional footer link"
msgid "Position order link"
msgstr "추가 하단 링크"
msgstr "주문 링크 위치"
#: pretix/base/exporters/orderlist.py:841
msgid "Order transaction data"
@@ -3320,15 +3242,13 @@ msgid "Repeat password"
msgstr "반복 비밀번호"
#: pretix/base/forms/auth.py:220 pretix/base/forms/user.py:99
#, fuzzy
#| msgid "Email address"
msgid "Your email address"
msgstr "이메일 주소"
msgstr "당신의 이메일 주소"
#: pretix/base/forms/auth.py:327 pretix/control/forms/orders.py:1041
#: pretix/control/templates/pretixcontrol/shredder/download.html:53
msgid "Confirmation code"
msgstr ""
msgstr "확인 코드"
#: pretix/base/forms/questions.py:137 pretix/base/forms/questions.py:264
msgctxt "name_salutation"
@@ -3411,14 +3331,12 @@ msgstr ""
"야 할 수도 있습니다."
#: pretix/base/forms/questions.py:1181
#, fuzzy
#| msgid "Cancellation requested"
msgid "No invoice requested"
msgstr "취소 요청"
msgstr "청구서 요청되지 않음"
#: pretix/base/forms/questions.py:1183
msgid "Invoice transmission method"
msgstr ""
msgstr "청구서 전송 방식"
#: pretix/base/forms/questions.py:1329
msgid "You need to provide a company name."
@@ -3432,21 +3350,20 @@ msgstr "이름을 입력해야 합니다."
msgid ""
"If you enter an invoice address, you also need to select an invoice "
"transmission method."
msgstr ""
msgstr "당신이 청구서 주소를 입력하신다면, 청구서 수신 방법도 선택하셔야 합니다."
#: pretix/base/forms/questions.py:1385
#, fuzzy
#| msgid "The selected media type is not enabled in your organizer settings."
msgid ""
"The selected transmission type is not available in your country or for your "
"type of address."
msgstr "선택한 미디어 유형이 정리함 설정에서 활성화되지 않았습니다."
msgstr "선택한 전송 유형은 당신의 국가 또는 지역에서 이용하실 수 없습니다."
#: pretix/base/forms/questions.py:1394
msgid ""
"The selected type of invoice transmission requires a field that is currently "
"not available, please reach out to the organizer."
msgstr ""
msgstr "선택하신 청구서 전송 유형은 현재 사용할 수 없는 필드의 입력을 필요로 하니, 주"
"최자에게 문의 해 주세요."
#: pretix/base/forms/questions.py:1398
msgid "This field is required for the selected type of invoice transmission."
@@ -3466,10 +3383,8 @@ msgstr ""
"대가 대신 사용됩니다."
#: pretix/base/forms/user.py:77
#, fuzzy
#| msgid "Attendee email address"
msgid "Change email address"
msgstr "참석자 이메일 주소"
msgstr "이메일 주소 변경"
#: pretix/base/forms/user.py:83
msgid "Device name"
@@ -3518,16 +3433,12 @@ msgstr ""
"이 이메일 주소와 관련된 계정이 이미 있습니다. 다른 계정을 선택해 주세요."
#: pretix/base/forms/user.py:179
#, fuzzy
#| msgid "Email address"
msgid "Old email address"
msgstr "이메일 주소"
msgstr "이전 이메일 주소"
#: pretix/base/forms/user.py:180
#, fuzzy
#| msgid "Email address"
msgid "New email address"
msgstr "이메일 주소"
msgstr "이메일 주소"
#: pretix/base/forms/validators.py:51
msgid ""
@@ -3572,23 +3483,22 @@ msgstr "개별 고객"
#: pretix/base/invoicing/email.py:50
msgid "Email invoice directly to accounting department"
msgstr ""
msgstr "청구서를 회계 부서로 이메일로 바로 보내기"
#: pretix/base/invoicing/email.py:51
msgid ""
"If not selected, the invoice will be sent to you using the email address "
"listed above."
msgstr ""
msgstr "선택되지 않은 경우, 청구서는 위의 이메일 주소를 통해 당신에게 보내질 것 입니"
"다."
#: pretix/base/invoicing/email.py:55
#, fuzzy
#| msgid "Email address verified"
msgid "Email address for invoice"
msgstr "이메일 주소 확인"
msgstr "청구서용 이메일 주소"
#: pretix/base/invoicing/email.py:91
msgid "PDF via email"
msgstr ""
msgstr "이메일로 PDF"
#: pretix/base/invoicing/national.py:37
msgctxt "italian_invoice"
@@ -3613,11 +3523,9 @@ msgid "Address for certified electronic mail"
msgstr ""
#: pretix/base/invoicing/national.py:57
#, fuzzy
#| msgid "Recipient"
msgctxt "italian_invoice"
msgid "Recipient code"
msgstr "영수증"
msgstr "영수증 코드"
#: pretix/base/invoicing/national.py:81
msgctxt "italian_invoice"
@@ -3697,7 +3605,6 @@ msgid ""
"until {to_date}"
msgstr ""
"{from_date}\n"
"\n"
"{to_date}까지"
#: pretix/base/invoicing/pdf.py:609 pretix/base/services/mail.py:512
@@ -3777,10 +3684,10 @@ msgid "Single price: {net_price} net / {gross_price} gross"
msgstr "단일 가격: {net_price} 순 / {gross_price} 총합"
#: pretix/base/invoicing/pdf.py:901
#, fuzzy, python-brace-format
#, python-brace-format
msgctxt "invoice"
msgid "Single price: {price}"
msgstr "단일 가격: {가격}"
msgstr "단일 가격: {price}"
#: pretix/base/invoicing/pdf.py:944 pretix/base/invoicing/pdf.py:949
msgctxt "invoice"
@@ -3808,12 +3715,10 @@ msgid "Remaining amount"
msgstr "잔액"
#: pretix/base/invoicing/pdf.py:1009
#, fuzzy, python-brace-format
#| msgctxt "invoice"
#| msgid "Event date: {date_range}"
#, python-brace-format
msgctxt "invoice"
msgid "Invoice period: {daterange}"
msgstr "이벤트 날짜: {date_range}"
msgstr "청구서 기간: {daterange}"
#: pretix/base/invoicing/pdf.py:1040
msgctxt "invoice"
@@ -3836,13 +3741,12 @@ msgid "Included taxes"
msgstr "세금 포함"
#: pretix/base/invoicing/pdf.py:1100
#, fuzzy, python-brace-format
#, python-brace-format
msgctxt "invoice"
msgid ""
"Using the conversion rate of 1:{rate} as published by the {authority} on "
"{date}, this corresponds to:"
msgstr ""
"{날짜}에 {당국}에서 발표한 1:{세율}의 변환율을 사용하면 다음과 같습니다:"
msgstr "{date}에 {authority}에서 발표한 1:{rate}의 변환율을 사용하면 다음과 같습니다:"
#: pretix/base/invoicing/pdf.py:1115
#, fuzzy, python-brace-format
@@ -3850,9 +3754,8 @@ msgctxt "invoice"
msgid ""
"Using the conversion rate of 1:{rate} as published by the {authority} on "
"{date}, the invoice total corresponds to {total}."
msgstr ""
"{날짜}에 {당국}에서 게시한 1:{세율}의 변환율을 사용하면 송장 총액이 {총합}에 "
"해당합니다."
msgstr "{date}에 {authority}에서 게시한 1:{rate}의 변환율을 사용하면 송장 총액이 "
"{total}에 해당합니다."
#: pretix/base/invoicing/pdf.py:1129
msgid "Default invoice renderer (European-style letter)"

File diff suppressed because it is too large Load Diff

View File

@@ -46,12 +46,12 @@ from i18nfield.forms import I18nTextInput
from i18nfield.strings import LazyI18nString
from localflavor.generic.forms import BICFormField, IBANFormField
from localflavor.generic.validators import IBANValidator
from text_unidecode import unidecode
from pretix.base.forms import I18nMarkdownTextarea
from pretix.base.models import InvoiceAddress, Order, OrderPayment, OrderRefund
from pretix.base.payment import BasePaymentProvider
from pretix.base.templatetags.money import money_filter
from pretix.helpers.payment import generate_payment_qr_codes
from pretix.plugins.banktransfer.templatetags.ibanformat import ibanformat
from pretix.presale.views.cart import cart_session
@@ -313,51 +313,6 @@ class BankTransfer(BasePaymentProvider):
t += str(self.settings.get('bank_details', as_type=LazyI18nString))
return t
def swiss_qrbill(self, payment):
if not self.settings.get('bank_details_sepa_iban') or not self.settings.get('bank_details_sepa_iban')[:2] in ('CH', 'LI'):
return
if self.event.currency not in ('EUR', 'CHF'):
return
if not self.event.settings.invoice_address_from or not self.event.settings.invoice_address_from_country:
return
data_fields = [
'SPC',
'0200',
'1',
self.settings.get('bank_details_sepa_iban'),
'K',
self.settings.get('bank_details_sepa_name')[:70],
self.event.settings.invoice_address_from.replace('\n', ', ')[:70],
(self.event.settings.invoice_address_from_zipcode + ' ' + self.event.settings.invoice_address_from_city)[:70],
'',
'',
str(self.event.settings.invoice_address_from_country),
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
'', # rfu
str(payment.amount),
self.event.currency,
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'', # debtor address
'NON',
'', # structured reference
self._code(payment.order),
'EPD',
]
data_fields = [unidecode(d or '') for d in data_fields]
return '\r\n'.join(data_fields)
def payment_pending_render(self, request: HttpRequest, payment: OrderPayment):
template = get_template('pretixplugins/banktransfer/pending.html')
ctx = {
@@ -367,13 +322,18 @@ class BankTransfer(BasePaymentProvider):
'amount': payment.amount,
'payment_info': payment.info_data,
'settings': self.settings,
'swiss_qrbill': self.swiss_qrbill(payment),
'eu_barcodes': self.event.currency == 'EUR',
'payment_qr_codes': generate_payment_qr_codes(
event=self.event,
code=self._code(payment.order),
amount=payment.amount,
bank_details_sepa_bic=self.settings.get('bank_details_sepa_bic'),
bank_details_sepa_name=self.settings.get('bank_details_sepa_name'),
bank_details_sepa_iban=self.settings.get('bank_details_sepa_iban'),
) if self.settings.bank_details_type == "sepa" else None,
'pending_description': self.settings.get('pending_description', as_type=LazyI18nString),
'details': self.settings.get('bank_details', as_type=LazyI18nString),
'has_invoices': payment.order.invoices.exists(),
}
ctx['any_barcodes'] = ctx['swiss_qrbill'] or ctx['eu_barcodes']
return template.render(ctx, request=request)
def payment_control_render(self, request: HttpRequest, payment: OrderPayment) -> str:

View File

@@ -1,7 +1,6 @@
{% load i18n %}
{% load l10n %}
{% load commadecimal %}
{% load static %}
{% load dotdecimal %}
{% load ibanformat %}
{% load money %}
@@ -17,7 +16,7 @@
{% endblocktrans %}</p>
<div class="row">
<div class="{% if settings.bank_details_type == "sepa" %}col-md-6{% else %}col-md-12{% endif %} col-xs-12">
<div class="{% if payment_qr_codes %}col-md-6{% else %}col-md-12{% endif %} col-xs-12">
<dl class="dl-horizontal">
<dt>{% trans "Reference code (important):" %}</dt><dd><b>{{ code }}</b></dd>
<dt>{% trans "Amount:" %}</dt><dd>{{ amount|money:event.currency }}</dd>
@@ -36,94 +35,7 @@
{% trans "We will send you an email as soon as we received your payment." %}
</p>
</div>
{% if settings.bank_details_type == "sepa" and any_barcodes %}
<div class="tabcontainer col-md-6 col-sm-6 hidden-xs text-center js-only blank-after">
<div id="banktransfer_qrcodes_tabs_content" class="tabpanels blank-after">
{% if swiss_qrbill %}
<div id="banktransfer_qrcodes_qrbill"
role="tabpanel"
tabindex="0"
aria-labelledby="banktransfer_qrcodes_qrbill_tab"
>
<div class="banktransfer-swiss-cross-overlay" role="figure" aria-labelledby="banktransfer_qrcodes_qrbill_tab banktransfer_qrcodes_label">
<svg class="banktransfer-swiss-cross" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.8 19.8"><path stroke="#fff" stroke-width="1.436" d="M.7.7h18.4v18.4H.7z"/><path fill="#fff" d="M8.3 4h3.3v11H8.3z"/><path fill="#fff" d="M4.4 7.9h11v3.3h-11z"/></svg>
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">{{swiss_qrbill}}</script>
</div>
</div>
{% endif %}
{% if eu_barcodes %}
<div id="banktransfer_qrcodes_girocode"
role="tabpanel"
tabindex="0"
{{ swiss_qrbill|yesno:'hidden,' }}
aria-labelledby="banktransfer_qrcodes_girocode_tab"
>
<div role="figure" aria-labelledby="banktransfer_qrcodes_girocode_tab banktransfer_qrcodes_label">
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">BCD
002
2
SCT
{{ settings.bank_details_sepa_bic }}
{{ settings.bank_details_sepa_name|unidecode }}
{{ settings.bank_details_sepa_iban }}
{{ event.currency }}{{ amount|dotdecimal }}
{{ code }}
</script>
</div>
</div>
<div id="banktransfer_qrcodes_bezahlcode"
role="tabpanel"
tabindex="0"
hidden
aria-labelledby="banktransfer_qrcodes_bezahlcode_tab"
>
<a aria-label="{% trans "Open BezahlCode in your banking app to start the payment process." %}" href="bank://singlepaymentsepa?name={{ settings.bank_details_sepa_name|urlencode }}&iban={{ settings.bank_details_sepa_iban }}&bic={{ settings.bank_details_sepa_bic }}&amount={{ amount|commadecimal }}&reason={{ code }}&currency={{ event.currency }}">
<div role="figure" aria-labelledby="banktransfer_qrcodes_bezahlcode_tab banktransfer_qrcodes_label">
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">bank://singlepaymentsepa?name={{ settings.bank_details_sepa_name|urlencode }}&iban={{ settings.bank_details_sepa_iban }}&bic={{ settings.bank_details_sepa_bic }}&amount={{ amount|commadecimal }}&reason={{ code }}&currency={{ event.currency }}</script>
</div>
</a>
</div>
{% endif %}
</div>
<div id="banktransfer_qrcodes_tabs" role="tablist" aria-labelledby="banktransfer_qrcodes_label" class="blank-after btn-group">
{% if swiss_qrbill %}
<button
class="btn btn-default"
id="banktransfer_qrcodes_qrbill_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_qrbill"
aria-selected="true"
tabindex="-1">QR-bill</button>
{% endif %}
{% if eu_barcodes %}
<button
class="btn btn-default"
id="banktransfer_qrcodes_girocode_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_girocode"
aria-selected="{{ swiss_qrbill|yesno:"false,true" }}"
tabindex="-1">EPC-QR</button>
<button
class="btn btn-default"
id="banktransfer_qrcodes_bezahlcode_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_bezahlcode"
aria-selected="false"
tabindex="-1">BezahlCode</button>
{% endif %}
</div>
<p class="text-muted" id="banktransfer_qrcodes_label">
{% trans "Scan the QR code with your banking app" %}
</p>
</div>
{% if payment_qr_codes %}
{% include "pretixpresale/event/payment_qr_codes.html" %}
{% endif %}
</div>
{% if swiss_qrbill %}
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
{% endif %}
</div>

View File

@@ -711,7 +711,7 @@ class PaypalMethod(BasePaymentProvider):
description = '{prefix}{orderstring}{postfix}'.format(
prefix='{} '.format(self.settings.prefix) if self.settings.prefix else '',
orderstring=__('Order {order} for {event}').format(
event=request.event.name,
event=self.event.name,
order=payment.order.code
),
postfix=' {}'.format(self.settings.postfix) if self.settings.postfix else ''

View File

@@ -644,7 +644,9 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
FontFallbackParagraph(
_("Pending payments at {datetime}").format(
datetime=date_format(
df_start - datetime.timedelta.resolution,
(df_start - datetime.timedelta.resolution).astimezone(
self.timezone
),
"SHORT_DATETIME_FORMAT",
)
),
@@ -694,7 +696,9 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
Paragraph(
_("Pending payments at {datetime}").format(
datetime=date_format(
(df_end or now()) - datetime.timedelta.resolution,
((df_end or now()) - datetime.timedelta.resolution).astimezone(
self.timezone
),
"SHORT_DATETIME_FORMAT",
)
),
@@ -751,7 +755,9 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
Paragraph(
_("Total gift card value at {datetime}").format(
datetime=date_format(
df_start - datetime.timedelta.resolution,
(df_start - datetime.timedelta.resolution).astimezone(
self.timezone
),
"SHORT_DATETIME_FORMAT",
)
),
@@ -789,7 +795,9 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
Paragraph(
_("Total gift card value at {datetime}").format(
datetime=date_format(
(df_end or now()) - datetime.timedelta.resolution,
((df_end or now()) - datetime.timedelta.resolution).astimezone(
self.timezone
),
"SHORT_DATETIME_FORMAT",
)
),

View File

@@ -0,0 +1,44 @@
{% load i18n %}
{% load static %}
{% if payment_qr_codes %}
<div class="tabcontainer col-md-6 col-sm-6 hidden-xs text-center js-only blank-after">
<div id="banktransfer_qrcodes_tabs_content" class="tabpanels blank-after">
{% for code_info in payment_qr_codes %}
<div id="banktransfer_qrcodes_{{ code_info.id }}"
role="tabpanel"
tabindex="0"
{% if not forloop.first %}hidden{% endif %}
aria-labelledby="banktransfer_qrcodes_{{ code_info.id }}_tab"
>
{% if code_info.link %}<a aria-label="{{ code_info.link_aria_label }}" href="{{ code_info.link }}">{% endif %}
<div class="{{ code_info.css_class }}" role="figure" aria-labelledby="banktransfer_qrcodes_{{ code_info.id }}_tab banktransfer_qrcodes_label">
{{ code_info.html_prefix }}
<script type="text/plain" data-size="150" data-replace-with-qr data-desc="{% trans 'Scan this image with your banking apps QR-Reader to start the payment process.' %}">{{ code_info.qr_data }}</script>
</div>
{% if code_info.link %}</a>{% endif %}
</div>
{% endfor %}
</div>
<div id="banktransfer_qrcodes_tabs" role="tablist" aria-labelledby="banktransfer_qrcodes_label" class="blank-after btn-group">
{% for code_info in payment_qr_codes %}
<button
class="btn btn-default"
id="banktransfer_qrcodes_{{ code_info.id }}_tab"
type="button"
role="tab"
aria-controls="banktransfer_qrcodes_{{ code_info.id }}"
aria-selected="{{ forloop.first|yesno:"true,false" }}"
tabindex="-1">{{ code_info.label }}</button>
{% endfor %}
</div>
<p class="text-muted" id="banktransfer_qrcodes_label">
{% trans "Scan the QR code with your banking app" %}
</p>
</div>
{% for code_info in payment_qr_codes %}
{% if code_info.id == "qrbill" %}
<link rel="stylesheet" href="{% static "pretixplugins/banktransfer/swisscross.css" %}">
{% endif %}
{% endfor %}
{% endif %}

View File

@@ -438,9 +438,6 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
base_price_is='net' if event.settings.display_net_prices else 'gross') # backwards-compat
) if var.original_price or item.original_price else None
if not display_add_to_cart:
display_add_to_cart = not item.requires_seat and var.order_max > 0
var.current_unavailability_reason = var.unavailability_reason(has_voucher=voucher, subevent=subevent)
item.original_price = (
@@ -471,6 +468,8 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
item.best_variation_availability = max([v.cached_availability[0] for v in item.available_variations])
item._remove = not bool(item.available_variations)
if not item._remove and not display_add_to_cart:
display_add_to_cart = not item.requires_seat and any(v.order_max > 0 for v in item.available_variations)
if not quota_cache_existed and not voucher and not allow_addons and not base_qs_set and not filter_items and not filter_categories:
event.cache.set(quota_cache_key, quota_cache, 5)

View File

@@ -49,7 +49,7 @@ from django.views.decorators.cache import cache_page
from django.views.decorators.gzip import gzip_page
from django.views.decorators.http import condition
from django.views.i18n import (
JavaScriptCatalog, get_formats, js_catalog_template,
JavaScriptCatalog, get_formats, builtin_template_path,
)
from lxml import html
@@ -168,7 +168,8 @@ def generate_widget_js(version, lang):
'September', 'October', 'November', 'December'
)
catalog = dict((k, v) for k, v in catalog.items() if k.startswith('widget\u0004') or k in str_wl)
template = Engine().from_string(js_catalog_template)
with builtin_template_path("i18n_catalog.js").open(encoding="utf-8") as fh:
template = Engine().from_string(fh.read())
context = Context({
'catalog_str': indent(json.dumps(
catalog, sort_keys=True, indent=2)) if catalog else None,

View File

@@ -347,11 +347,53 @@ if HAS_CELERY:
CELERY_RESULT_BACKEND = config.get('celery', 'backend')
if HAS_CELERY_BROKER_TRANSPORT_OPTS:
CELERY_BROKER_TRANSPORT_OPTIONS = loads(config.get('celery', 'broker_transport_options'))
else:
CELERY_BROKER_TRANSPORT_OPTIONS = {}
if HAS_CELERY_BACKEND_TRANSPORT_OPTS:
CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = loads(config.get('celery', 'backend_transport_options'))
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
if CELERY_BROKER_URL.startswith("amqp://"):
# https://docs.celeryq.dev/en/latest/userguide/routing.html#routing-options-rabbitmq-priorities
# Enable priorities for all queues
CELERY_TASK_QUEUE_MAX_PRIORITY = 3
# On RabbitMQ, higher number is higher priority, and having less levels makes rabbitmq use less CPU and RAM
PRIORITY_CELERY_LOW = 1
PRIORITY_CELERY_MID = 2
PRIORITY_CELERY_HIGH = 3
PRIORITY_CELERY_LOWEST_FUNC = min
PRIORITY_CELERY_HIGHEST_FUNC = max
# Set default
CELERY_TASK_DEFAULT_PRIORITY = PRIORITY_CELERY_MID
elif CELERY_BROKER_URL.startswith("redis://"):
# https://docs.celeryq.dev/en/latest/userguide/routing.html#redis-message-priorities
CELERY_BROKER_TRANSPORT_OPTIONS.update({
"queue_order_strategy": "priority",
"sep": ":",
"priority_steps": [0, 4, 8]
})
# On redis, lower number is higher priority, and it appears that there are always levels 0-9 even though it
# is only really executed based on the 3 steps listed above.
PRIORITY_CELERY_LOW = 9
PRIORITY_CELERY_MID = 5
PRIORITY_CELERY_HIGH = 0
PRIORITY_CELERY_LOWEST_FUNC = max
PRIORITY_CELERY_HIGHEST_FUNC = min
CELERY_TASK_DEFAULT_PRIORITY = PRIORITY_CELERY_MID
else:
# No priority support assumed
PRIORITY_CELERY_LOW = 0
PRIORITY_CELERY_MID = 0
PRIORITY_CELERY_HIGH = 0
PRIORITY_CELERY_LOWEST_FUNC = min
PRIORITY_CELERY_HIGHEST_FUNC = max
else:
CELERY_TASK_ALWAYS_EAGER = True
PRIORITY_CELERY_LOW = 0
PRIORITY_CELERY_MID = 0
PRIORITY_CELERY_HIGH = 0
PRIORITY_CELERY_LOWEST_FUNC = min
PRIORITY_CELERY_HIGHEST_FUNC = max
CACHE_TICKETS_HOURS = config.getint('cache', 'tickets', fallback=24 * 3)
@@ -488,6 +530,7 @@ X_FRAME_OPTIONS = 'DENY'
# URL settings
ROOT_URLCONF = 'pretix.multidomain.maindomain_urlconf'
FORMS_URLFIELD_ASSUME_HTTPS = True # transitional for django 6.0
WSGI_APPLICATION = 'pretix.wsgi.application'

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Ebene_1"
viewBox="0 0 128 128"
version="1.1"
sodipodi:docname="mstile.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="4.125"
inkscape:cx="28.242424"
inkscape:cy="53.090909"
inkscape:window-width="2556"
inkscape:window-height="1239"
inkscape:window-x="0"
inkscape:window-y="180"
inkscape:window-maximized="1"
inkscape:current-layer="Ebene_1" />
<defs
id="defs1">
<style
id="style1">.cls-1{fill:#f8f8f8;}</style>
</defs>
<g
id="g2"
transform="matrix(0.47350597,0,0,0.47350597,11.541278,11.202115)">
<path
class="cls-1"
d="M 97.457879,82.258482 C 96.737879,82.358482 96.237879,82.558482 95.797879,82.758482 L 98.177879,99.668482 C 98.587879,99.748482 99.127879,99.798482 99.777879,99.708482 103.29788,99.208482 104.38788,96.068482 103.58788,90.318482 102.75788,84.448482 101.05788,81.758482 97.467879,82.258482 Z"
id="path1" />
<path
class="cls-1"
d="M 162.82788,60.358482 C 163.53788,60.188482 163.98788,59.598482 163.88788,58.878482 L 159.32788,26.438482 C 159.22788,25.718482 158.55788,25.218482 157.83788,25.318482 L 121.58788,30.408482 122.97788,40.298482 C 123.22788,42.048482 121.90788,43.788482 120.15788,44.038482 118.40788,44.288482 116.66788,42.968482 116.41788,41.218482 L 115.02788,31.328482 47.917879,40.768482 C 47.197879,40.868482 46.697879,41.538482 46.797879,42.258482 L 51.357879,74.698482 C 51.457879,75.418482 52.057879,75.868482 52.777879,75.828482 64.027879,74.908482 74.207879,82.928482 75.807879,94.288482 77.407879,105.64848 69.817879,116.09848 58.737879,118.24848 58.027879,118.41848 57.577879,119.00848 57.677879,119.72848 L 62.237879,152.16848 C 62.337879,152.88848 63.007879,153.38848 63.727879,153.28848 L 130.84788,143.85848 129.43788,133.83848 C 129.18788,132.02848 130.44788,130.35848 132.25788,130.09848 134.06788,129.83848 135.74788,131.16848 135.99788,132.91848 L 137.40788,142.93848 173.65788,137.84848 C 174.37788,137.74848 174.87788,137.07848 174.77788,136.35848 L 170.21788,103.91848 C 170.11788,103.19848 169.51788,102.74848 168.79788,102.78848 157.54788,103.70848 147.37788,95.748482 145.77788,84.388482 144.17788,73.028482 151.75788,62.518482 162.83788,60.358482 Z M 102.98788,105.10848 C 101.22788,105.35848 99.697879,105.36848 98.947879,105.27848 L 100.53788,116.56848 90.617879,117.95848 85.317879,80.228482 C 87.817879,78.608482 91.277879,77.198482 96.697879,76.428482 105.37788,75.208482 111.96788,79.008482 113.35788,88.868482 114.60788,97.748482 110.23788,104.07848 102.99788,105.09848 Z M 133.22788,113.24848 C 133.47788,114.99848 132.15788,116.73848 130.40788,116.98848 128.65788,117.23848 126.91788,115.91848 126.66788,114.16848 L 124.28788,97.248482 C 124.02788,95.378482 125.23788,93.768482 127.10788,93.508482 128.97788,93.248482 130.58788,94.518482 130.84788,96.328482 Z M 128.11788,76.878482 C 128.36788,78.688482 127.10788,80.358482 125.29788,80.618482 123.48788,80.878482 121.81788,79.608482 121.55788,77.798482 L 119.17788,60.878482 C 118.92788,59.068482 120.18788,57.398482 121.99788,57.138482 123.74788,56.888482 125.48788,58.208482 125.73788,59.958482 Z"
id="path2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1 +1 @@
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="933.333" height="933.333" viewBox="0 0 700.000000 700.000000"><path d="M0 109v109l4.3.1c33 .5 65.2 14.8 90 40.1 31.8 32.5 43.6 76.6 33.3 124.3-1.2 5.4-5.7 17.8-9 24.8-12.2 25.6-36.1 49.6-61.6 61.9-15.9 7.6-35.2 12.4-51.7 12.7L0 482v218h436.5v-60.5l9.2-.1c6-.1 9.3.3 9.6 1 .2.6.3 14.3.3 30.3l-.1 29.3H700V482l-4.7-.1c-32.5-.4-64.9-14.7-88.9-39.2-25.5-26.1-38.6-58.6-37.9-94.4.6-27.9 7.6-50 23.4-73.8 6.4-9.8 23.8-27 34.1-33.7 15.6-10.3 33.3-17.9 47.5-20.4 10-1.7 15.9-2.4 20.8-2.4h5.7V0H455.5v58h-19V.5L218.3.2 0 0v109zm454-6.1c1.3.1 1.5 5.7 1.5 44.6 0 43.8 0 44.5-2 44.6-7.9.3-15.7 0-16.3-.6-.4-.3-.7-20.3-.7-44.3v-43.7l4.5-.5c2.5-.3 6.1-.4 8-.3 1.9.1 4.2.2 5 .2zm1.5 178.6c0 24.5-.2 44.5-.5 44.6-.3.1-4.6.2-9.5.2l-9 .2V237h19v44.5zM287 256.4c37.8 3.1 65.2 23.6 75 55.9 3.6 12 4.4 18.2 4.4 34.2.1 18.9-.5 24-3.9 35.9-8.2 28.5-26.2 47.7-52 55.6-7.1 2.2-9.8 2.5-24 2.5-8.8-.1-17.8-.4-20.1-.9l-4-.8V510.5l-31.7.3-31.7.2V269.3l6.8-2.6c9.8-3.8 18.7-6.4 27.2-7.7 4.1-.7 8.2-1.4 9.1-1.5.9-.2 6.3-.7 12-1 5.7-.4 10.5-.8 10.6-.9.4-.3 14.8.2 22.3.8zm168.5 159.1c0 24.5-.2 44.6-.5 44.6-1.9.4-18 .4-18.1-.1-.4-1.5-.5-86.7-.1-87.8.3-.8 3.1-1.2 9.6-1.2h9.1v44.5zm-.1 133.7c-.1 24.4-.2 44.6-.3 44.9-.1.7-18.1.7-18.2 0-.4-1.6-.5-86.8-.1-87.9.3-.8 3.1-1.2 9.6-1.2h9.1l-.1 44.2z"/><path d="M264.5 293.9c-2 .8-2 1.8-2.1 54.5v53.8l2.5 1c1.4.5 5.8.8 9.8.5 20.6-1.5 29.5-18.2 29.6-55.7.2-37.8-9.2-54.5-30.5-54.8-4-.1-8.2.3-9.3.7z"/></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><defs><style>.cls-1{fill:#492267;}</style></defs><path class="cls-1" d="m50.67,56.95c-.72.1-1.22.3-1.66.5l2.38,16.91c.41.08.95.13,1.6.04,3.52-.5,4.61-3.64,3.81-9.39-.83-5.87-2.53-8.56-6.12-8.06Z"/><path class="cls-1" d="m116.04,35.05c.71-.17,1.16-.76,1.06-1.48L112.54,1.13c-.1-.72-.77-1.22-1.49-1.12l-37.5,5.27.73,5.22c.16,1.12-.62,2.15-1.74,2.31s-2.15-.62-2.31-1.74l-.73-5.22L1.13,15.46c-.72.1-1.22.77-1.12,1.49l4.56,32.44c.1.72.7,1.17,1.42,1.13,11.25-.92,21.43,7.1,23.03,18.46,1.6,11.36-5.99,21.81-17.07,23.96-.71.17-1.16.76-1.06,1.48l4.56,32.44c.1.72.77,1.22,1.49,1.12l68.37-9.61-.73-5.22c-.16-1.15.59-2.15,1.74-2.31s2.15.62,2.31,1.74l.73,5.22,37.5-5.27c.72-.1,1.22-.77,1.12-1.49l-4.56-32.44c-.1-.72-.7-1.17-1.42-1.13-11.25.92-21.42-7.04-23.02-18.4-1.6-11.36,5.98-21.87,17.06-24.03Zm-59.84,44.75c-1.76.25-3.29.26-4.04.17l1.59,11.29-9.92,1.39-5.3-37.73c2.5-1.62,5.96-3.03,11.38-3.8,8.68-1.22,15.27,2.58,16.66,12.44,1.25,8.88-3.12,15.21-10.36,16.23Zm30.73,20.71c.16,1.12-.62,2.15-1.74,2.31-1.12.16-2.15-.62-2.31-1.74l-1.47-10.44c-.16-1.12.62-2.15,1.74-2.31s2.16.66,2.31,1.74l1.47,10.44Zm-3.17-22.58c.15,1.08-.66,2.16-1.74,2.31s-2.16-.66-2.31-1.74l-1.47-10.44c-.16-1.15.59-2.15,1.74-2.31,1.12-.16,2.15.62,2.31,1.74l1.47,10.44Zm-3.16-22.45c.16,1.12-.62,2.15-1.74,2.31-1.12.16-2.15-.62-2.31-1.74l-1.47-10.44c-.16-1.12.62-2.15,1.74-2.31s2.16.66,2.31,1.74l1.47,10.44Zm-3.17-22.58c.15,1.08-.66,2.16-1.74,2.31s-2.16-.66-2.31-1.74l-1.47-10.44c-.16-1.15.59-2.15,1.74-2.31s2.15.62,2.31,1.74l1.47,10.44Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" viewBox="0 0 109.594 109.594"><g transform="scale(.94461)"><path d="M45.52 48.98c-.74 0-1.28.13-1.75.27v17.43c.4.13.94.27 1.61.27 3.63 0 5.18-3.03 5.18-8.95s-1.35-9.02-5.05-9.02z" fill="#3b1c4a"/><path d="M114.72 36.13c.74-.07 1.28-.61 1.28-1.35V1.35c0-.74-.61-1.35-1.35-1.35H75.99v5.38c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V0H1.35C.61 0 0 .61 0 1.35v33.44c0 .74.54 1.28 1.28 1.35 11.51.67 20.66 10.23 20.66 21.94s-9.15 21.2-20.66 21.8c-.74.07-1.28.61-1.28 1.35v33.44c0 .74.61 1.35 1.35 1.35h70.48v-5.38c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09v5.38h38.66c.74 0 1.35-.61 1.35-1.35V81.23c0-.74-.54-1.28-1.28-1.35-11.51-.67-20.66-10.16-20.66-21.87s9.15-21.26 20.66-21.87zM47.87 72.87c-1.82 0-3.36-.2-4.1-.4v11.64H33.54V45.22C36.3 43.94 40 43 45.58 43c8.95 0 15.07 4.78 15.07 14.94 0 9.15-5.32 14.94-12.78 14.94zm28.12 25.3c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V87.4c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V64.12c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09zm0-23.15c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V40.97c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V17.69c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09z" fill="#3b1c4a"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 116 116"><path fill="#f8f8f8" d="M45.16 48.35c-.74 0-1.28.13-1.75.27v17.43c.4.13.94.27 1.61.27 3.63 0 5.18-3.03 5.18-8.95s-1.35-9.02-5.05-9.02Z"/><path fill="#f8f8f8" d="M114.36 35.5c.74-.07 1.28-.61 1.28-1.35V.71c0-.74-.61-1.35-1.35-1.35H76.93v10.2c0 1.8-1.58 3.38-3.38 3.38s-3.38-1.58-3.38-3.38V-.63H.98C.24-.63-.36-.03-.36.71v33.44c0 .74.54 1.28 1.28 1.35 11.51.67 20.66 10.23 20.66 21.94S12.42 78.63.92 79.23c-.74.07-1.28.61-1.28 1.35v33.44c0 .74.61 1.35 1.35 1.35h69.19v-10.33c0-1.86 1.52-3.38 3.38-3.38s3.38 1.58 3.38 3.38v10.33h37.36c.74 0 1.35-.61 1.35-1.35V80.58c0-.74-.54-1.28-1.28-1.35-11.51-.67-20.66-10.16-20.66-21.87s9.15-21.26 20.66-21.87ZM47.51 72.24c-1.82 0-3.36-.2-4.1-.4v11.64H33.18V44.59c2.76-1.28 6.46-2.22 12.04-2.22 8.95 0 15.07 4.78 15.07 14.94 0 9.15-5.32 14.94-12.78 14.94Zm29.41 12.53c0 1.8-1.58 3.38-3.38 3.38s-3.38-1.58-3.38-3.38V67.33c0-1.93 1.45-3.38 3.38-3.38s3.38 1.52 3.38 3.38v17.44Zm0-37.49c0 1.86-1.52 3.38-3.38 3.38s-3.38-1.52-3.38-3.38V29.84c0-1.86 1.52-3.38 3.38-3.38s3.38 1.58 3.38 3.38v17.44Z"/></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><defs><style>.cls-1{fill:#f8f8f8;}</style></defs><path class="cls-1" d="m50.67,56.95c-.72.1-1.22.3-1.66.5l2.38,16.91c.41.08.95.13,1.6.04,3.52-.5,4.61-3.64,3.81-9.39-.83-5.87-2.53-8.56-6.12-8.06Z"/><path class="cls-1" d="m116.04,35.05c.71-.17,1.16-.76,1.06-1.48L112.54,1.13c-.1-.72-.77-1.22-1.49-1.12l-36.25,5.09,1.39,9.89c.25,1.75-1.07,3.49-2.82,3.74-1.75.25-3.49-1.07-3.74-2.82l-1.39-9.89L1.13,15.46c-.72.1-1.22.77-1.12,1.49l4.56,32.44c.1.72.7,1.17,1.42,1.13,11.25-.92,21.43,7.1,23.03,18.46s-5.99,21.81-17.07,23.96c-.71.17-1.16.76-1.06,1.48l4.56,32.44c.1.72.77,1.22,1.49,1.12l67.12-9.43-1.41-10.02c-.25-1.81,1.01-3.48,2.82-3.74s3.49,1.07,3.74,2.82l1.41,10.02,36.25-5.09c.72-.1,1.22-.77,1.12-1.49l-4.56-32.44c-.1-.72-.7-1.17-1.42-1.13-11.25.92-21.42-7.04-23.02-18.4-1.6-11.36,5.98-21.87,17.06-24.03Zm-59.84,44.75c-1.76.25-3.29.26-4.04.17l1.59,11.29-9.92,1.39-5.3-37.73c2.5-1.62,5.96-3.03,11.38-3.8,8.68-1.22,15.27,2.58,16.66,12.44,1.25,8.88-3.12,15.21-10.36,16.23Zm30.24,8.14c.25,1.75-1.07,3.49-2.82,3.74s-3.49-1.07-3.74-2.82l-2.38-16.92c-.26-1.87.95-3.48,2.82-3.74s3.48,1.01,3.74,2.82l2.38,16.92Zm-5.11-36.37c.25,1.81-1.01,3.48-2.82,3.74s-3.48-1.01-3.74-2.82l-2.38-16.92c-.25-1.81,1.01-3.48,2.82-3.74,1.75-.25,3.49,1.07,3.74,2.82l2.38,16.92Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Ebene_1"
viewBox="0 0 128 128"
version="1.1"
sodipodi:docname="pretix-icon-white-on-purple.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="4.125"
inkscape:cx="101.69697"
inkscape:cy="80.727273"
inkscape:window-width="2556"
inkscape:window-height="1275"
inkscape:window-x="0"
inkscape:window-y="144"
inkscape:window-maximized="1"
inkscape:current-layer="Ebene_1" /><defs
id="defs1"><style
id="style1">.cls-1{fill:#f8f8f8;}</style><style
id="style1-9">.cls-1{fill:#492267;}</style></defs><rect
style="fill:#492267;stroke-width:1.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:13.3;paint-order:fill markers stroke;fill-opacity:1"
id="rect2"
width="128"
height="128"
x="0"
y="0" /><path
class="cls-1"
d="M 53.800397,58.604437 C 53.249481,58.680953 52.8669,58.833985 52.530229,58.987018 L 54.351313,71.925899 C 54.665029,71.987112 55.078217,72.02537 55.575572,71.956506 58.26894,71.573925 59.102966,69.171318 58.490837,64.771639 57.855753,60.280141 56.554978,58.221856 53.808048,58.604437 Z"
id="path1"
style="stroke-width:0.765162" /><g
id="g2"
transform="matrix(0.76558824,0,0,0.76558824,15.00618,15.00618)"
style="fill:#f8f8f8;fill-opacity:1"><path
class="cls-1"
d="M 50.67,56.95 C 49.95,57.05 49.45,57.25 49.01,57.45 L 51.39,74.36 C 51.8,74.44 52.34,74.49 52.99,74.4 56.51,73.9 57.6,70.76 56.8,65.01 55.97,59.14 54.27,56.45 50.68,56.95 Z"
id="path1-3"
style="fill:#f8f8f8;fill-opacity:1" /><path
class="cls-1"
d="M 116.04,35.05 C 116.75,34.88 117.2,34.29 117.1,33.57 L 112.54,1.13 C 112.44,0.41 111.77,-0.09 111.05,0.01 L 73.55,5.28 74.28,10.5 C 74.44,11.62 73.66,12.65 72.54,12.81 71.42,12.97 70.39,12.19 70.23,11.07 L 69.5,5.85 1.13,15.46 C 0.41,15.56 -0.09,16.23 0.01,16.95 L 4.57,49.39 C 4.67,50.11 5.27,50.56 5.99,50.52 17.24,49.6 27.42,57.62 29.02,68.98 30.62,80.34 23.03,90.79 11.95,92.94 11.24,93.11 10.79,93.7 10.89,94.42 L 15.45,126.86 C 15.55,127.58 16.22,128.08 16.94,127.98 L 85.31,118.37 84.58,113.15 C 84.42,112 85.17,111 86.32,110.84 87.47,110.68 88.47,111.46 88.63,112.58 L 89.36,117.8 126.86,112.53 C 127.58,112.43 128.08,111.76 127.98,111.04 L 123.42,78.6 C 123.32,77.88 122.72,77.43 122,77.47 110.75,78.39 100.58,70.43 98.98,59.07 97.38,47.71 104.96,37.2 116.04,35.04 Z M 56.2,79.8 C 54.44,80.05 52.91,80.06 52.16,79.97 L 53.75,91.26 43.83,92.65 38.53,54.92 C 41.03,53.3 44.49,51.89 49.91,51.12 58.59,49.9 65.18,53.7 66.57,63.56 67.82,72.44 63.45,78.77 56.21,79.79 Z M 86.93,100.51 C 87.09,101.63 86.31,102.66 85.19,102.82 84.07,102.98 83.04,102.2 82.88,101.08 L 81.41,90.64 C 81.25,89.52 82.03,88.49 83.15,88.33 84.27,88.17 85.31,88.99 85.46,90.07 Z M 83.76,77.93 C 83.91,79.01 83.1,80.09 82.02,80.24 80.94,80.39 79.86,79.58 79.71,78.5 L 78.24,68.06 C 78.08,66.91 78.83,65.91 79.98,65.75 81.1,65.59 82.13,66.37 82.29,67.49 Z M 80.6,55.48 C 80.76,56.6 79.98,57.63 78.86,57.79 77.74,57.95 76.71,57.17 76.55,56.05 L 75.08,45.61 C 74.92,44.49 75.7,43.46 76.82,43.3 77.94,43.14 78.98,43.96 79.13,45.04 Z M 77.43,32.9 C 77.58,33.98 76.77,35.06 75.69,35.21 74.61,35.36 73.53,34.55 73.38,33.47 L 71.91,23.03 C 71.75,21.88 72.5,20.88 73.65,20.72 74.8,20.56 75.8,21.34 75.96,22.46 Z"
id="path2-6"
style="fill:#f8f8f8;fill-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" viewBox="0 0 109.594 109.594"><g transform="scale(.94461)"><path d="M45.52 48.98c-.74 0-1.28.13-1.75.27v17.43c.4.13.94.27 1.61.27 3.63 0 5.18-3.03 5.18-8.95s-1.35-9.02-5.05-9.02z" fill="#3b1c4a"/><path d="M114.72 36.13c.74-.07 1.28-.61 1.28-1.35V1.35c0-.74-.61-1.35-1.35-1.35H75.99v5.38c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V0H1.35C.61 0 0 .61 0 1.35v33.44c0 .74.54 1.28 1.28 1.35 11.51.67 20.66 10.23 20.66 21.94s-9.15 21.2-20.66 21.8c-.74.07-1.28.61-1.28 1.35v33.44c0 .74.61 1.35 1.35 1.35h70.48v-5.38c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09v5.38h38.66c.74 0 1.35-.61 1.35-1.35V81.23c0-.74-.54-1.28-1.28-1.35-11.51-.67-20.66-10.16-20.66-21.87s9.15-21.26 20.66-21.87zM47.87 72.87c-1.82 0-3.36-.2-4.1-.4v11.64H33.54V45.22C36.3 43.94 40 43 45.58 43c8.95 0 15.07 4.78 15.07 14.94 0 9.15-5.32 14.94-12.78 14.94zm28.12 25.3c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V87.4c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V64.12c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09zm0-23.15c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V40.97c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V17.69c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09z" fill="#3b1c4a"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><defs><style>.cls-1{fill:#492267;}</style></defs><path class="cls-1" d="m50.67,56.95c-.72.1-1.22.3-1.66.5l2.38,16.91c.41.08.95.13,1.6.04,3.52-.5,4.61-3.64,3.81-9.39-.83-5.87-2.53-8.56-6.12-8.06Z"/><path class="cls-1" d="m116.04,35.05c.71-.17,1.16-.76,1.06-1.48L112.54,1.13c-.1-.72-.77-1.22-1.49-1.12l-37.5,5.27.73,5.22c.16,1.12-.62,2.15-1.74,2.31s-2.15-.62-2.31-1.74l-.73-5.22L1.13,15.46c-.72.1-1.22.77-1.12,1.49l4.56,32.44c.1.72.7,1.17,1.42,1.13,11.25-.92,21.43,7.1,23.03,18.46,1.6,11.36-5.99,21.81-17.07,23.96-.71.17-1.16.76-1.06,1.48l4.56,32.44c.1.72.77,1.22,1.49,1.12l68.37-9.61-.73-5.22c-.16-1.15.59-2.15,1.74-2.31s2.15.62,2.31,1.74l.73,5.22,37.5-5.27c.72-.1,1.22-.77,1.12-1.49l-4.56-32.44c-.1-.72-.7-1.17-1.42-1.13-11.25.92-21.42-7.04-23.02-18.4-1.6-11.36,5.98-21.87,17.06-24.03Zm-59.84,44.75c-1.76.25-3.29.26-4.04.17l1.59,11.29-9.92,1.39-5.3-37.73c2.5-1.62,5.96-3.03,11.38-3.8,8.68-1.22,15.27,2.58,16.66,12.44,1.25,8.88-3.12,15.21-10.36,16.23Zm30.73,20.71c.16,1.12-.62,2.15-1.74,2.31-1.12.16-2.15-.62-2.31-1.74l-1.47-10.44c-.16-1.12.62-2.15,1.74-2.31s2.16.66,2.31,1.74l1.47,10.44Zm-3.17-22.58c.15,1.08-.66,2.16-1.74,2.31s-2.16-.66-2.31-1.74l-1.47-10.44c-.16-1.15.59-2.15,1.74-2.31,1.12-.16,2.15.62,2.31,1.74l1.47,10.44Zm-3.16-22.45c.16,1.12-.62,2.15-1.74,2.31-1.12.16-2.15-.62-2.31-1.74l-1.47-10.44c-.16-1.12.62-2.15,1.74-2.31s2.16.66,2.31,1.74l1.47,10.44Zm-3.17-22.58c.15,1.08-.66,2.16-1.74,2.31s-2.16-.66-2.31-1.74l-1.47-10.44c-.16-1.15.59-2.15,1.74-2.31s2.15.62,2.31,1.74l1.47,10.44Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="254.156" height="109.594" version="1.1"><g transform="scale(1.9856)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#f8f8f8"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#f8f8f8"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.94v2.48c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.16v-2.48c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12V55h-.16 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.38 18.56c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12v-5.12c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zm0-11.01c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12V8.34c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zM90.11 23.8h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#f8f8f8"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 69.21"><path style="fill: #faf8fc;" d="M40.1,37.97c.37,2.66-.13,4.12-1.76,4.34-.3.04-.55.02-.74-.02l-1.1-7.82c.2-.09.44-.18.77-.23,1.66-.23,2.45,1.01,2.83,3.73ZM60.79,30.87c-1.36.19-1.91,1.53-1.47,3.78l3.61-1.06c-.25-2-.96-2.88-2.14-2.72ZM114.57,27.5c.74,5.26,5.45,8.94,10.65,8.51.34-.02.61.19.66.52l2.11,15.01c.05.33-.19.64-.52.69l-48.7,6.84h0l.15-.02-.34-2.42c-.08-.58-.62-.99-1.21-.91-.59.08-.99.61-.91,1.21l.34,2.42.15-.02h0L7.84,69.05c-.33.05-.64-.19-.69-.52l-2.11-15.01c-.05-.33.16-.61.49-.68,5.13-1,8.64-5.83,7.9-11.09-.74-5.26-5.45-8.97-10.66-8.54-.34.02-.61-.19-.66-.52L0,17.66c-.05-.33.19-.64.52-.69L69.49,7.28l.34,2.42c.08.58.62.99,1.21.91s.99-.62.91-1.21l-.34-2.42L120.16.16c.33-.05.64.19.69.52l2.11,15.01c.05.33-.16.61-.49.68-5.13,1-8.63,5.87-7.89,11.12ZM44.63,37.3c-.64-4.56-3.69-6.32-7.71-5.76-2.51.35-4.11,1.01-5.27,1.76l2.45,17.46,4.59-.65-.73-5.23c.35.04,1.05.04,1.87-.08,3.35-.47,5.37-3.4,4.8-7.51ZM53.46,29.28c-.46.03-.91.07-1.34.13-2.96.39-4.94,1.03-6.17,1.88l1.72,12.27,4.59-.65-1.44-10.24c.57-.3,1.39-.41,2.3-.11l.34-3.28ZM60.61,28.24c-4.08.57-5.77,3.68-5.22,7.57.55,3.9,3.19,6.45,7.45,5.85,2.42-.34,3.86-1.01,4.94-1.71l-1.42-2.67c-.67.46-1.84.97-3.38,1.18-1.66.23-2.48-.36-2.98-1.71l7.43-2.12c-.38-4.44-2.71-7.01-6.81-6.4ZM77.12,46.23c-.08-.56-.64-.99-1.21-.91-.58.08-.99.62-.91,1.21l.68,4.83c.08.58.62.99,1.21.91.58-.08.99-.62.91-1.21l-.68-4.83ZM75.65,35.77c-.08-.58-.62-.99-1.21-.91s-.99.61-.91,1.21l.68,4.83c.08.56.64.99,1.21.91.56-.08.99-.64.91-1.21l-.68-4.83ZM74.19,25.38c-.08-.56-.64-.99-1.21-.91s-.99.62-.91,1.21l.68,4.83c.08.58.62.99,1.21.91s.99-.62.91-1.21l-.68-4.83ZM72.73,14.93c-.08-.58-.62-.99-1.21-.91s-.99.61-.91,1.21l.68,4.83c.08.56.64.99,1.21.91s.99-.64.91-1.21l-.68-4.83ZM87.74,24.65l-1.87.26-.53-3.81-4.43,1.79.37,2.66-1.36.19.42,2.99,1.36-.19.87,6.22c.31,2.24,1.89,3.83,4.82,3.42,1.06-.15,1.83-.54,2.14-.76l-.39-2.81c-.35.17-.52.26-.85.3-.69.1-1.08-.22-1.21-1.18l-.82-5.83,1.87-.26h.03s-.42-3-.42-3ZM94.42,23.74l-4.59.65,1.83,12.99,4.59-.65-1.83-12.99ZM93.97,19.92c-.16-1.15-1.35-1.91-2.68-1.72-1.3.18-2.23,1.24-2.07,2.39s1.35,1.94,2.65,1.75c1.33-.19,2.25-1.27,2.09-2.42ZM110.72,34.73l-5.12-6.43,2.93-6.54-4.26.6-1.26,3.5h-.06s-2.03-3.03-2.03-3.03l-4.77.67,4.54,5.77-3.14,7.28,4.62-.65,1.22-3.84h.06s2.17,3.35,2.17,3.35l5.11-.69Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="254.156" height="109.594" version="1.1"><g transform="matrix(1.9856 0 0 1.9856 0 .193)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#3b1c4a"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#3b1c4a"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.81v2.48c0 .55-.45.99-.99.99s-.99-.45-.99-.99V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.03v-2.48c0-.57.43-.99.99-.99s.99.45.99.99V55h-.03 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.26 18.56c0 .55-.45.99-.99.99s-.99-.45-.99-.99v-5.12c0-.55.45-.99.99-.99s.99.46.99.99zm0-11.07c0 .53-.46.99-.99.99s-.99-.46-.99-.99v-5.12c0-.57.43-.99.99-.99s.99.45.99.99zm0-11.01c0 .55-.45.99-.99.99s-.99-.45-.99-.99v-5.12c0-.55.45-.99.99-.99s.99.46.99.99zm0-11.07c0 .53-.46.99-.99.99s-.99-.46-.99-.99V8.34c0-.57.43-.99.99-.99s.99.45.99.99zm14.3 10.34h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#3b1c4a"/></g></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 70"><defs><style>.cls-1{fill:#492267;}</style></defs><path class="cls-1" d="m37.27,34.63c-.33.05-.57.14-.77.23l1.1,7.82c.19.03.44.06.74.02,1.63-.23,2.14-1.69,1.76-4.34-.38-2.72-1.17-3.96-2.83-3.73Z"/><path class="cls-1" d="m60.79,31.26c-1.36.19-1.91,1.53-1.47,3.78l3.61-1.06c-.25-2-.96-2.88-2.14-2.72Z"/><path class="cls-1" d="m122.47,16.77c.33-.08.54-.35.49-.68l-2.11-15.01c-.05-.33-.36-.57-.69-.52l-48.67,6.84.34,2.42c.07.52-.29,1-.8,1.07s-1-.29-1.07-.8l-.34-2.42L.53,17.37c-.33.05-.57.36-.52.69l2.11,15.01c.05.33.32.54.66.52,5.21-.42,9.92,3.29,10.66,8.54.74,5.26-2.77,10.09-7.9,11.09-.33.08-.54.35-.49.68l2.11,15.01c.05.33.36.57.69.52l69.12-9.71h-.03s-.34-2.41-.34-2.41c-.08-.53.27-.99.8-1.07s1,.29,1.07.8l.34,2.42h-.03s48.7-6.84,48.7-6.84c.33-.05.57-.36.52-.69l-2.11-15.01c-.05-.33-.32-.54-.66-.52-5.21.42-9.92-3.26-10.65-8.51-.74-5.26,2.77-10.12,7.89-11.12Zm-82.63,28.43c-.82.11-1.52.12-1.87.08l.73,5.23-4.59.65-2.45-17.46c1.16-.75,2.76-1.4,5.27-1.76,4.02-.56,7.07,1.19,7.71,5.76.58,4.11-1.44,7.04-4.8,7.51Zm13.28-12.25c-.91-.3-1.72-.19-2.3.11l1.44,10.24-4.59.65-1.72-12.27c1.24-.85,3.21-1.5,6.17-1.88.42-.06.88-.09,1.34-.13l-.34,3.28Zm14.31,2.09l-7.43,2.12c.5,1.35,1.32,1.94,2.98,1.71,1.54-.22,2.7-.72,3.38-1.18l1.42,2.67c-1.07.71-2.52,1.37-4.94,1.71-4.26.6-6.9-1.96-7.45-5.85-.55-3.9,1.14-7,5.22-7.57,4.1-.61,6.44,1.96,6.81,6.4Zm10.26,16.43c.07.52-.29,1-.8,1.07s-1-.29-1.07-.8l-.68-4.83c-.07-.52.29-1,.8-1.07s1,.31,1.07.8l.68,4.83Zm-1.47-10.45c.07.5-.31,1-.8,1.07s-1-.31-1.07-.8l-.68-4.83c-.08-.53.27-.99.8-1.07s1,.29,1.07.8l.68,4.83Zm-1.46-10.39c.07.52-.29,1-.8,1.07s-1-.29-1.07-.8l-.68-4.83c-.07-.52.29-1,.8-1.07s1,.31,1.07.8l.68,4.83Zm-1.47-10.45c.07.5-.31,1-.8,1.07s-1-.31-1.07-.8l-.68-4.83c-.08-.53.27-.99.8-1.07s1,.29,1.07.8l.68,4.83Zm14.88,7.86h-.03s-1.87.27-1.87.27l.82,5.83c.14.97.52,1.28,1.21,1.18.33-.05.51-.13.85-.3l.39,2.81c-.31.23-1.08.61-2.14.76-2.93.41-4.51-1.18-4.82-3.42l-.87-6.22-1.36.19-.42-2.99,1.36-.19-.37-2.66,4.43-1.79.53,3.81,1.87-.26.42,2.99Zm8.09,9.09l-4.59.65-1.83-12.99,4.59-.65,1.83,12.99Zm-4.36-14.39c-1.3.18-2.49-.61-2.65-1.75-.16-1.15.77-2.2,2.07-2.39s2.51.57,2.68,1.72-.76,2.23-2.09,2.42Zm13.73,13.07l-2.17-3.36h-.06s-1.22,3.85-1.22,3.85l-4.62.65,3.14-7.28-4.54-5.77,4.77-.67,2.03,3.04h.06s1.26-3.51,1.26-3.51l4.26-.6-2.93,6.54,5.12,6.43-5.11.69Z"/></svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 135.467 135.467"><g transform="matrix(7.9501 0 0 7.9501 -115.332 -24.995)"><rect width="17.04" height="17.04" x="14.507" y="3.144" fill="#3b1c4a" color="#000" overflow="visible" rx="1.664" ry="1.664" style="marker:none"/><g fill="#fff"><path d="M17.702 6.339v2.13h-1.065v-2.13c0-.588.477-1.065 1.065-1.065h2.13v1.065h-2.13m10.65-1.065c.588 0 1.065.477 1.065 1.065v2.13h-1.065v-2.13h-2.13V5.274h2.13m-10.65 9.585v2.13h2.13v1.065h-2.13a1.065 1.065 0 0 1-1.065-1.065v-2.13h1.065m10.65 2.13v-2.13h1.065v2.13c0 .588-.477 1.065-1.065 1.065h-2.13v-1.065z"/><path d="M22.606 7.253c-1.563 0-2.6.256-3.367.617v10.867h2.856V15.49c.21.06.631.12 1.142.12 2.09 0 3.577-1.623 3.577-4.178 0-2.84-1.698-4.179-4.208-4.179zm-.015 1.668c1.037 0 1.398.828 1.398 2.526 0 1.653-.42 2.494-1.443 2.494a1.17 1.17 0 0 1-.451-.075v-4.87c.135-.045.286-.075.496-.075z" color="#000" overflow="visible" style="marker:none" transform="matrix(.53249 0 0 .53249 10.765 5.453)"/></g></g><path fill="none" d="M-115.332-24.995H75.47v190.802h-190.802z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 135.467 135.467"><g transform="matrix(7.9501 0 0 7.9501 -115.332 -24.995)"><rect width="17.04" height="17.04" x="14.507" y="3.144" fill="#492267" color="#000" overflow="visible" rx="1.664" ry="1.664" style="marker:none"/><g fill="#fff"><path d="M17.702 6.339v2.13h-1.065v-2.13c0-.588.477-1.065 1.065-1.065h2.13v1.065h-2.13m10.65-1.065c.588 0 1.065.477 1.065 1.065v2.13h-1.065v-2.13h-2.13V5.274h2.13m-10.65 9.585v2.13h2.13v1.065h-2.13a1.065 1.065 0 0 1-1.065-1.065v-2.13h1.065m10.65 2.13v-2.13h1.065v2.13c0 .588-.477 1.065-1.065 1.065h-2.13v-1.065z"/><path d="M22.606 7.253c-1.563 0-2.6.256-3.367.617v10.867h2.856V15.49c.21.06.631.12 1.142.12 2.09 0 3.577-1.623 3.577-4.178 0-2.84-1.698-4.179-4.208-4.179zm-.015 1.668c1.037 0 1.398.828 1.398 2.526 0 1.653-.42 2.494-1.443 2.494a1.17 1.17 0 0 1-.451-.075v-4.87c.135-.045.286-.075.496-.075z" color="#000" overflow="visible" style="marker:none" transform="matrix(.53249 0 0 .53249 10.765 5.453)"/></g></g><path fill="none" d="M-115.332-24.995H75.47v190.802h-190.802z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -82,6 +82,9 @@ $(function () {
if ('label' in options) {
dependent.closest(".form-group").find(".control-label").text(options.label);
}
if ('helptext_visible' in options) {
dependent.closest(".form-group").find(".help-block").toggle(options.helptext_visible);
}
const required = 'required' in options && visible && (
(options.required === 'if_any' && isAnyRequired) ||

View File

@@ -67,7 +67,7 @@ $panel-success-heading-bg: var(--pretix-brand-success-tint-50);
$panel-danger-border: var(--pretix-brand-danger-tint-50);
$panel-danger-heading-bg: var(--pretix-brand-danger-tint-50);
$panel-warning-border: var(--pretix-brand-warning-tint-50);
$panel-warning-heading-bg: var(--pretix-brand-warning-tine-50);
$panel-warning-heading-bg: var(--pretix-brand-warning-tint-50);
$panel-default-border: #e5e5e5 !default;
$panel-default-heading-bg: #e5e5e5 !default;

View File

@@ -36,10 +36,21 @@ nav.navbar {
margin: 0;
border: 0;
}
.navbar-brand {
position: relative;
padding-bottom: 10px;
padding-top: 10px;
padding-left: 10px;
}
.navbar-brand span {
padding: 4px 8px 6px; // Optical alignment next to logo
display: inline-block;
}
.navbar-brand img {
height: 100%;
width: auto;
display: inline;
height: 100%;
display: inline-block;
vertical-align: top;
}
#side-menu img.fa-img {

View File

@@ -34,7 +34,7 @@ def test_no_invoice_address(client):
'data': [],
'state': {'label': 'State', 'required': False, 'visible': False},
'street': {'required': 'if_any'},
'vat_id': {'required': False, 'visible': True},
'vat_id': {'helptext_visible': True, 'label': 'VAT ID', 'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
@@ -44,7 +44,7 @@ def test_no_invoice_address(client):
'data': [],
'state': {'label': 'State', 'required': False, 'visible': False},
'street': {'required': 'if_any'},
'vat_id': {'required': False, 'visible': False},
'vat_id': {'helptext_visible': True, 'label': 'VAT ID', 'required': False, 'visible': False},
'zipcode': {'required': False}
}
@@ -98,7 +98,7 @@ def test_provider_only_email_available(client, event):
'transmission_peppol_participant_id': {'required': False, 'visible': False},
'transmission_type': {'visible': False},
'transmission_types': [{'code': 'email', 'name': 'Email'}],
'vat_id': {'required': False, 'visible': True},
'vat_id': {'helptext_visible': True, 'label': 'VAT ID', 'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
@@ -123,7 +123,7 @@ def test_provider_italy_sdi_not_enforced_when_optional(client, event):
'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},
'vat_id': {'helptext_visible': True, 'label': 'VAT ID / P.IVA', 'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
@@ -148,7 +148,7 @@ def test_provider_italy_sdi_enforced_individual(client, event):
'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},
'vat_id': {'helptext_visible': True, 'label': 'VAT ID / P.IVA', 'required': False, 'visible': True},
'zipcode': {'required': True}
}
@@ -174,11 +174,37 @@ def test_provider_italy_sdi_enforced_business(client, event):
'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},
'vat_id': {'helptext_visible': False, 'label': 'VAT ID / P.IVA', 'required': True, 'visible': True},
'zipcode': {'required': True}
}
@pytest.mark.django_db
def test_vat_id_enforced(client, event):
response = client.get(
'/js_helpers/address_form/?country=GR&invoice=true&organizer=org&event=ev'
'&is_business=business'
)
assert response.status_code == 200
d = response.json()
del d['data']
assert d == {
'city': {'required': 'if_any'},
'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': True},
'transmission_types': [{'code': 'email', 'name': 'Email'}, {'code': 'peppol', 'name': 'Peppol'}],
'vat_id': {'helptext_visible': False, 'label': 'VAT ID / TIN', 'required': True, 'visible': True},
'zipcode': {'required': 'if_any'}
}
@pytest.mark.django_db
def test_email_peppol_choice(client, event):
response = client.get(
@@ -203,7 +229,7 @@ def test_email_peppol_choice(client, event):
{'code': 'email', 'name': 'Email'},
{'code': 'peppol', 'name': 'Peppol'},
],
'vat_id': {'required': False, 'visible': True},
'vat_id': {'helptext_visible': True, 'label': 'VAT ID', 'required': False, 'visible': True},
'zipcode': {'required': 'if_any'}
}
@@ -229,6 +255,6 @@ def test_email_peppol_choice(client, event):
{'code': 'email', 'name': 'Email'},
{'code': 'peppol', 'name': 'Peppol'},
],
'vat_id': {'required': False, 'visible': True},
'vat_id': {'helptext_visible': True, 'label': 'VAT ID', 'required': False, 'visible': True},
'zipcode': {'required': True}
}

View File

@@ -24,7 +24,7 @@ import responses
from requests import Timeout
from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
VATIDFinalError, VATIDTemporaryError, normalize_vat_id, validate_vat_id,
)
@@ -51,6 +51,18 @@ def test_eu_country_mismatch():
validate_vat_id('AT12345', 'DE')
@responses.activate
def test_normalize():
assert normalize_vat_id('AT U 12345678', 'AT') == 'ATU12345678'
assert normalize_vat_id('U12345678', 'AT') == 'ATU12345678'
assert normalize_vat_id('IT.123.456.789.00', 'IT') == 'IT12345678900'
assert normalize_vat_id('12345678900', 'IT') == 'IT12345678900'
assert normalize_vat_id('123456789MVA', 'NO') == "NO123456789MVA"
assert normalize_vat_id('CHE 123456789 MWST', 'CH') == "CHE123456789"
# Bad combination is left for validation
assert normalize_vat_id('ATU12345678', 'IT') == 'ATU12345678'
@responses.activate
def test_eu_server_down():
def _callback(request):

View File

@@ -0,0 +1,141 @@
#
# 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 timedelta
from decimal import Decimal
import pytest
from django.utils.timezone import now
from pretix.base.models import Event, Organizer
from pretix.helpers.payment import generate_payment_qr_codes
@pytest.fixture
def env():
o = Organizer.objects.create(name='Verein für Testzwecke e.V.', slug='testverein')
event = Event.objects.create(
organizer=o, name='Testveranstaltung', slug='testveranst',
date_from=now() + timedelta(days=10),
live=True, is_public=False, currency='EUR',
)
event.settings.invoice_address_from = 'Verein für Testzwecke e.V.'
event.settings.invoice_address_from_zipcode = '1234'
event.settings.invoice_address_from_city = 'Testhausen'
event.settings.invoice_address_from_country = 'CH'
return o, event
@pytest.mark.django_db
def test_payment_qr_codes_euro(env):
o, event = env
codes = generate_payment_qr_codes(
event=event,
code='TESTVERANST-12345',
amount=Decimal('123.00'),
bank_details_sepa_bic='BYLADEM1MIL',
bank_details_sepa_iban='DE37796500000069799047',
bank_details_sepa_name='Verein für Testzwecke e.V.',
)
assert len(codes) == 2
assert codes[0]['label'] == 'EPC-QR'
assert codes[0]['qr_data'] == '''BCD
002
2
SCT
BYLADEM1MIL
Verein fur Testzwecke e.V.
DE37796500000069799047
EUR123.00
TESTVERANST-12345
'''
assert codes[1]['label'] == 'BezahlCode'
assert codes[1]['qr_data'] == ('bank://singlepaymentsepa?name=Verein%20f%C3%BCr%20Testzwecke%20e.V.&iban=DE37796500000069799047'
'&bic=BYLADEM1MIL&amount=123%2C00&reason=TESTVERANST-12345&currency=EUR')
@pytest.mark.django_db
def test_payment_qr_codes_swiss(env):
o, event = env
codes = generate_payment_qr_codes(
event=event,
code='TESTVERANST-12345',
amount=Decimal('123.00'),
bank_details_sepa_bic='TESTCHXXXXX',
bank_details_sepa_iban='CH6389144757654882127',
bank_details_sepa_name='Verein für Testzwecke e.V.',
)
assert codes[0]['label'] == 'QR-bill'
assert codes[0]['qr_data'] == "\r\n".join([
"SPC",
"0200",
"1",
"CH6389144757654882127",
"K",
"Verein fur Testzwecke e.V.",
"Verein fur Testzwecke e.V.",
"1234 Testhausen",
"",
"",
"CH",
"",
"",
"",
"",
"",
"",
"",
"123.00",
"EUR",
"",
"",
"",
"",
"",
"",
"",
"NON",
"",
"TESTVERANST-12345",
"EPD",
])
@pytest.mark.django_db
def test_payment_qr_codes_spayd(env):
o, event = env
codes = generate_payment_qr_codes(
event=event,
code='TESTVERANST-12345',
amount=Decimal('123.00'),
bank_details_sepa_bic='TESTCZXXXXX',
bank_details_sepa_iban='CZ7450513769129174398769',
bank_details_sepa_name='Verein für Testzwecke e.V.',
)
assert len(codes) == 2
assert codes[0]['label'] == 'SPAYD'
assert codes[0]['qr_data'] == 'SPD*1.0*ACC:CZ7450513769129174398769*AM:123.00*CC:EUR*MSG:TESTVERANST-12345'
assert codes[1]['label'] == 'EPC-QR'

View File

@@ -1428,6 +1428,29 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(cp2.expires, now() + self.cart_reservation_time)
self.assertEqual(cp2.max_extend, now() + 11 * self.cart_reservation_time)
def test_expired_cart_extend_fails_partially_on_bundled(self):
start_time = datetime.datetime(2024, 1, 1, 10, 00, 00, tzinfo=datetime.timezone.utc)
max_extend = start_time + 11 * self.cart_reservation_time
self.quota_shirts.size = 0
self.quota_shirts.save()
with scopes_disabled():
cp1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=max_extend, max_extend=max_extend
)
cp2 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.shirt, variation=self.shirt_blue,
price=23, expires=max_extend, max_extend=max_extend, addon_to=cp1, is_bundled=True,
)
with freezegun.freeze_time(max_extend + timedelta(hours=1)):
response = self.client.post('/%s/%s/cart/extend' % (self.orga.slug, self.event.slug), {
}, follow=True)
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertIn('no longer available', doc.select('.alert-danger')[0].text)
with scopes_disabled():
self.assertFalse(CartPosition.objects.filter(id=cp1.id).exists())
self.assertFalse(CartPosition.objects.filter(id=cp2.id).exists())
def test_subevent_renew_expired_successfully(self):
self.event.has_subevents = True
self.event.save()

View File

@@ -411,6 +411,69 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
with scopes_disabled():
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert ia.vat_id == "AT123456"
assert not ia.vat_id_validated
def test_reverse_charge_vatid_required(self):
self.event.settings.invoice_address_vatid = True
self.event.settings.invoice_address_vatid_required_countries = ["AT"]
with scopes_disabled():
CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
resp = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': 'Foo',
'name': 'Bar',
'street': 'Baz',
'zipcode': '1234',
'city': 'Here',
'country': 'AT',
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
assert 'has-error' in resp.content.decode()
def test_reverse_charge_vatid_check_unavailable_but_required(self):
self.tr19.eu_reverse_charge = True
self.tr19.home_country = Country('DE')
self.tr19.save()
self.event.settings.invoice_address_vatid = True
self.event.settings.invoice_address_vatid_required_countries = ["AT"]
with scopes_disabled():
cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
def raiser(*args, **kwargs):
raise VATIDTemporaryError('temp')
mock_validate.side_effect = raiser
self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
'company': 'Foo',
'name': 'Bar',
'street': 'Baz',
'zipcode': '1234',
'city': 'Here',
'country': 'AT',
'vat_id': 'AT123456',
'email': 'admin@localhost',
'transmission_type': 'email',
}, follow=True)
cr1.refresh_from_db()
assert cr1.price == Decimal('23.00')
with scopes_disabled():
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert ia.vat_id == "AT123456"
assert not ia.vat_id_validated
def test_reverse_charge_keep_gross(self):
@@ -448,6 +511,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
with scopes_disabled():
ia = InvoiceAddress.objects.get(pk=self.client.session['carts'][self.session_key].get('invoice_address'))
assert ia.vat_id == "AT123456"
assert ia.vat_id_validated
def test_custom_tax_rules(self):
@@ -1452,7 +1516,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'transmission_type': 'it_sdi',
'vat_id': '',
}, follow=True)
assert "This field is required for the selected type" in response.content.decode()
assert "This field is required" in response.content.decode()
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'is_business': 'business',
@@ -1468,6 +1532,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
'state': 'MI',
'email': 'admin@localhost',
'transmission_type': 'email',
'vat_id': 'IT01234567890',
}, follow=True)
assert "must be used for this country" in response.content.decode()