Compare commits

..

26 Commits

Author SHA1 Message Date
Mira Weller
b6baf11c6d Add test case 2025-10-07 13:56:36 +02:00
Mira Weller
cfbe00d24d Fix unhandled exception in datasync code in case order should not be synced (PRETIXEU-C9H) 2025-10-07 13:47:56 +02:00
Martin Gross
22f351cb89 OCM: Ignore already canceled addons in remaining item calculation (Z#23209824) 2025-10-02 10:30:33 +02:00
Raphael Michel
2611ff74a5 Badges: Fix incorrect offsets for DURABLE 8334-02 2025-10-02 09:57:59 +02:00
Hijiri Umemoto
cc1c7e1c23 Translations: Update Japanese
Currently translated at 100.0% (6076 of 6076 strings)

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

powered by weblate
2025-10-02 09:50:39 +02:00
CVZ-es
e2eedac93b Translations: Update Spanish
Currently translated at 100.0% (6076 of 6076 strings)

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

powered by weblate
2025-10-02 09:50:39 +02:00
CVZ-es
432064c3ae Translations: Update French
Currently translated at 100.0% (6076 of 6076 strings)

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

powered by weblate
2025-10-02 09:50:39 +02:00
Raphael Michel
457115f4ca Add refund comment to gift card transaction text (Z#23208349) (#5499)
* Add refund comment to gift card transaction text (Z#23208349)

* Update src/pretix/base/models/giftcards.py

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

---------

Co-authored-by: luelista <weller@rami.io>
2025-10-02 09:38:32 +02:00
luelista
9d5563018e Add "bulk" argument to order_placed signal (#5505)
* datasync: add immediate parameter to enqueue_order

* interactive argument for order_placed signal

The ``interactive`` argument specifies whether the order was
placed interactively, by a customer (as opposed to via a bulk
import or the REST API).

* use bulk=True instead of interactive=False to mark bulk imports
2025-10-02 09:36:02 +02:00
luelista
425f4da1f1 datasync: add immediate parameter to enqueue_order (#5504) 2025-10-02 09:34:56 +02:00
dependabot[bot]
aa0ea27d6c Update isort requirement from ==6.0.* to ==6.1.* (#5507) 2025-10-02 09:32:10 +02:00
dependabot[bot]
5a2219124a Bump @babel/core from 7.28.3 to 7.28.4 in /src/pretix/static/npm_dir (#5506)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.28.3 to 7.28.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.4/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 7.28.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 09:32:02 +02:00
luelista
f79813ea32 datasync: return a reference to newly create queue item (#5502) 2025-09-30 17:25:25 +02:00
luelista
ba62db7a19 Fix version number (2024 -> 2025) 2025-09-30 16:01:27 +02:00
Jan Van Haver
aa8b699b89 Translations: Update Dutch
Currently translated at 96.9% (5892 of 6076 strings)

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

powered by weblate
2025-09-30 09:19:26 +02:00
dependabot[bot]
6adabd54dc Update beautifulsoup4 requirement from ==4.13.* to ==4.14.*
Updates the requirements on [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/bs4/) to permit the latest version.

---
updated-dependencies:
- dependency-name: beautifulsoup4
  dependency-version: 4.14.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-30 09:19:21 +02:00
Raphael Michel
e61f8035d3 Fix typo 2025-09-29 16:21:33 +02:00
Raphael Michel
fc4a9406e1 Invoicing: Allow plugins to add data (#5452)
* Allow plugins to add data to invoices

* Add documentation
2025-09-29 13:15:42 +02:00
Raphael Michel
9d2ef94389 Invoicing: Configurable service date 2025-09-29 13:15:42 +02:00
Raphael Michel
c060322f2f Translations: Update Catalan
Currently translated at 31.1% (1895 of 6076 strings)

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

powered by weblate
2025-09-29 10:43:22 +02:00
Yasunobu YesNo Kawaguchi
7629bbbb6a Translations: Update Japanese
Currently translated at 99.8% (6064 of 6076 strings)

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

powered by weblate
2025-09-29 10:43:22 +02:00
Raphael Michel
21d084c3fa Translations: Update Italian
Currently translated at 37.2% (2261 of 6076 strings)

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

powered by weblate
2025-09-29 10:43:22 +02:00
Raphael Michel
b8d09a15e2 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6076 of 6076 strings)

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

powered by weblate
2025-09-29 10:43:22 +02:00
Raphael Michel
af2d35cc5a Translations: Update German
Currently translated at 100.0% (6076 of 6076 strings)

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

powered by weblate
2025-09-29 10:43:22 +02:00
Yasunobu YesNo Kawaguchi
cbe18608e4 Translations: Update Japanese
Currently translated at 100.0% (6068 of 6068 strings)

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

powered by weblate
2025-09-29 10:43:22 +02:00
Raphael Michel
37d0a0de22 Bump version to 2024.9.0.dev0 2025-09-29 09:37:14 +02:00
46 changed files with 1236 additions and 1050 deletions

View File

@@ -80,17 +80,12 @@ lines list of objects The actual invo
for all invoice lines for all invoice lines
created before this field was introduced as well as for created before this field was introduced as well as for
all lines not created by a fee (e.g. a product). all lines not created by a fee (e.g. a product).
event_date_from datetime Start date of the (sub)event this line was created for as it period_start datetime Start date of the service or delivery period of the invoice line.
was set during invoice creation. Can be ``null`` for all invoice Can be ``null`` if not known.
lines created before this was introduced as well as for lines in ├ period_end datetime End date of the service or delivery period of the invoice line.
an event series not created by a product (e.g. shipping or Can be ``null`` if not known.
cancellation fees). ├ event_date_from datetime Deprecated alias of ``period_start``.
├ event_date_to datetime End date of the (sub)event this line was created for as it ├ event_date_to datetime Deprecated alias of ``period_end``.
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
an event series not created by a product (e.g. shipping or
cancellation fees) as well as whenever the respective (sub)event
has no end date set.
├ event_location string Location of the (sub)event this line was created for as it ├ event_location string Location of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in lines created before this was introduced as well as for lines in
@@ -274,6 +269,8 @@ List of all invoices
"fee_internal_type": null, "fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z", "event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null, "event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg", "event_location": "Heidelberg",
"attendee_name": null, "attendee_name": null,
"gross_value": "23.00", "gross_value": "23.00",
@@ -420,6 +417,8 @@ Fetching individual invoices
"fee_internal_type": null, "fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z", "event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null, "event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg", "event_location": "Heidelberg",
"attendee_name": null, "attendee_name": null,
"gross_value": "23.00", "gross_value": "23.00",

View File

@@ -23,7 +23,7 @@ There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals .. automodule:: pretix.base.signals
:no-index: :no-index:
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text :members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, build_invoice_data, invoice_line_text
Check-ins Check-ins
""""""""" """""""""

View File

@@ -28,7 +28,7 @@ classifiers = [
dependencies = [ dependencies = [
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab "arabic-reshaper==3.0.0", # Support for Arabic in reportlab
"babel", "babel",
"BeautifulSoup4==4.13.*", "BeautifulSoup4==4.14.*",
"bleach==6.2.*", "bleach==6.2.*",
"celery==5.5.*", "celery==5.5.*",
"chardet==5.2.*", "chardet==5.2.*",
@@ -113,7 +113,7 @@ dev = [
"fakeredis==2.31.*", "fakeredis==2.31.*",
"flake8==7.3.*", "flake8==7.3.*",
"freezegun", "freezegun",
"isort==6.0.*", "isort==6.1.*",
"pep8-naming==0.15.*", "pep8-naming==0.15.*",
"potypo", "potypo",
"pytest-asyncio>=0.24", "pytest-asyncio>=0.24",

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 # 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/>. # <https://www.gnu.org/licenses/>.
# #
__version__ = "2025.8.2" __version__ = "2025.9.0.dev0"

View File

@@ -805,6 +805,7 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_reissue_after_modify', 'invoice_reissue_after_modify',
'invoice_include_free', 'invoice_include_free',
'invoice_generate', 'invoice_generate',
'invoice_period',
'invoice_numbers_consecutive', 'invoice_numbers_consecutive',
'invoice_numbers_prefix', 'invoice_numbers_prefix',
'invoice_numbers_prefix_cancellations', 'invoice_numbers_prefix_cancellations',

View File

@@ -1757,12 +1757,14 @@ class LinePositionField(serializers.IntegerField):
class InlineInvoiceLineSerializer(I18nAwareModelSerializer): class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
position = LinePositionField(read_only=True) position = LinePositionField(read_only=True)
event_date_from = serializers.DateTimeField(read_only=True, source="period_start")
event_date_to = serializers.DateTimeField(read_only=True, source="period_end")
class Meta: class Meta:
model = InvoiceLine model = InvoiceLine
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from', fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'tax_name', 'fee_type', 'event_date_to', 'period_start', 'period_end', 'gross_value', 'tax_value', 'tax_rate', 'tax_code',
'fee_internal_type', 'event_location') 'tax_name', 'fee_type', 'fee_internal_type', 'event_location')
class InvoiceSerializer(I18nAwareModelSerializer): class InvoiceSerializer(I18nAwareModelSerializer):

View File

@@ -743,7 +743,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
user=request.user if request.user.is_authenticated else None, user=request.user if request.user.is_authenticated else None,
auth=request.auth, auth=request.auth,
) )
order_placed.send(self.request.event, order=order) order_placed.send(self.request.event, order=order, bulk=False)
if order.status == Order.STATUS_PAID: if order.status == Order.STATUS_PAID:
order_paid.send(self.request.event, order=order) order_paid.send(self.request.event, order=order)
order.log_action( order.log_action(

View File

@@ -106,7 +106,7 @@ class OutboundSyncProvider:
return str(cls.identifier) return str(cls.identifier)
@classmethod @classmethod
def enqueue_order(cls, order, triggered_by, not_before=None): def enqueue_order(cls, order, triggered_by, not_before=None, immediate=False):
""" """
Adds an order to the sync queue. May only be called on derived classes which define an ``identifier`` attribute. Adds an order to the sync queue. May only be called on derived classes which define an ``identifier`` attribute.
@@ -119,10 +119,14 @@ class OutboundSyncProvider:
:param order: the Order that should be synced :param order: the Order that should be synced
:param triggered_by: the reason why the order should be synced, e.g. name of the signal :param triggered_by: the reason why the order should be synced, e.g. name of the signal
(currently only used internally for logging) (currently only used internally for logging)
:param immediate: whether a new sync task should run immediately for this order, instead
of waiting for the next periodic_task interval
:return: Return a tuple (queue_item, created), where created is a boolean
specifying whether a new queue item was created.
""" """
if not hasattr(cls, 'identifier'): if not hasattr(cls, 'identifier'):
raise TypeError('Call this method on a derived class that defines an "identifier" attribute.') raise TypeError('Call this method on a derived class that defines an "identifier" attribute.')
OrderSyncQueue.objects.update_or_create( queue_item, created = OrderSyncQueue.objects.update_or_create(
order=order, order=order,
sync_provider=cls.identifier, sync_provider=cls.identifier,
in_flight=False, in_flight=False,
@@ -133,6 +137,10 @@ class OutboundSyncProvider:
"need_manual_retry": None, "need_manual_retry": None,
}, },
) )
if immediate:
from pretix.base.services.datasync import sync_single
sync_single.apply_async(args=(queue_item.pk,))
return queue_item, created
@classmethod @classmethod
def get_external_link_info(cls, event, external_link_href, external_link_display_name): def get_external_link_info(cls, event, external_link_href, external_link_display_name):
@@ -383,7 +391,7 @@ class OutboundSyncProvider:
def sync_order(self, order): def sync_order(self, order):
if not self.should_sync_order(order): if not self.should_sync_order(order):
logger.debug("Skipping order %r", order) logger.debug("Skipping order %r", order)
return return {}
logger.debug("Syncing order %r", order) logger.debug("Syncing order %r", order)
positions = list( positions = list(

View File

@@ -24,7 +24,6 @@ from itertools import groupby
from smtplib import SMTPResponseException from smtplib import SMTPResponseException
from typing import TypeVar from typing import TypeVar
import bleach
import css_inline import css_inline
from django.conf import settings from django.conf import settings
from django.core.mail.backends.smtp import EmailBackend from django.core.mail.backends.smtp import EmailBackend
@@ -35,10 +34,7 @@ from django.utils.translation import get_language, gettext_lazy as _
from pretix.base.models import Event from pretix.base.models import Event
from pretix.base.signals import register_html_mail_renderers from pretix.base.signals import register_html_mail_renderers
from pretix.base.templatetags.rich_text import ( from pretix.base.templatetags.rich_text import markdown_compile_email
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
markdown_compile_email, truelink_callback,
)
from pretix.helpers.format import SafeFormatter, format_map from pretix.helpers.format import SafeFormatter, format_map
from pretix.base.services.placeholders import ( # noqa from pretix.base.services.placeholders import ( # noqa
@@ -137,24 +133,13 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
def template_name(self): def template_name(self):
raise NotImplementedError() raise NotImplementedError()
def compile_markdown(self, plaintext, context=None): def compile_markdown(self, plaintext):
return markdown_compile_email(plaintext, context=context) return markdown_compile_email(plaintext)
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str: def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
body_md = self.compile_markdown(plain_body, context) body_md = self.compile_markdown(plain_body)
if context: if context:
linker = bleach.Linker( body_md = format_map(body_md, context=context, mode=SafeFormatter.MODE_RICH_TO_HTML)
url_re=URL_RE,
email_re=EMAIL_RE,
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True
)
body_md = format_map(
body_md,
context=context,
mode=SafeFormatter.MODE_RICH_TO_HTML,
linkifier=linker
)
htmlctx = { htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME, 'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL, 'site_url': settings.SITE_URL,

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 # 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/>. # <https://www.gnu.org/licenses/>.
# #
import datetime
import logging import logging
import math import math
import re import re
@@ -526,6 +527,20 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
textobject.textLine(self._normalize(self._upper(pgettext('invoice', 'Event')))) textobject.textLine(self._normalize(self._upper(pgettext('invoice', 'Event'))))
canvas.drawText(textobject) canvas.drawText(textobject)
def _date_range_in_header(self):
if self.invoice.event.has_subevents or not self.invoice.event.settings.show_dates_on_frontpage:
return None, None
tz = self.invoice.event.timezone
show_end_date = (
self.invoice.event.settings.show_date_to and
self.invoice.event.date_to and
self.invoice.event.date_to.astimezone(tz).date() != self.invoice.event.date_from.astimezone(tz).date()
)
if show_end_date:
return self.invoice.event.date_from.astimezone(tz).date(), self.invoice.event.date_to.astimezone(tz).date()
else:
return self.invoice.event.date_from.astimezone(tz).date(), None
def _draw_event(self, canvas): def _draw_event(self, canvas):
def shorten(txt): def shorten(txt):
txt = str(txt) txt = str(txt)
@@ -539,25 +554,17 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
p_size = p.wrap(self.event_width, self.event_height) p_size = p.wrap(self.event_width, self.event_height)
return txt return txt
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage: d_from, d_to = self._date_range_in_header()
tz = self.invoice.event.timezone if d_from and d_to:
show_end_date = ( p_str = (
self.invoice.event.settings.show_date_to and shorten(self.invoice.event.name) + '\n' +
self.invoice.event.date_to and pgettext('invoice', '{from_date}\nuntil {to_date}').format(
self.invoice.event.date_to.astimezone(tz).date() != self.invoice.event.date_from.astimezone(tz).date() from_date=date_format(d_from, "DATE_FORMAT"),
to_date=date_format(d_to, "DATE_FORMAT"),
)
) )
if show_end_date: elif d_from:
p_str = ( p_str = shorten(self.invoice.event.name) + '\n' + date_format(d_from, "DATE_FORMAT")
shorten(self.invoice.event.name) + '\n' +
pgettext('invoice', '{from_date}\nuntil {to_date}').format(
from_date=self.invoice.event.get_date_from_display(show_times=False),
to_date=self.invoice.event.get_date_to_display(show_times=False)
)
)
else:
p_str = (
shorten(self.invoice.event.name) + '\n' + self.invoice.event.get_date_from_display(show_times=False)
)
else: else:
p_str = shorten(self.invoice.event.name) p_str = shorten(self.invoice.event.name)
@@ -685,7 +692,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
return story return story
def _get_story(self, doc): def _get_story(self, doc):
has_taxes = any(il.tax_value for il in self.invoice.lines.all()) or self.invoice.reverse_charge all_lines = list(self.invoice.lines.all())
has_taxes = any(il.tax_value for il in all_lines) or self.invoice.reverse_charge
header_dates = self._date_range_in_header()
tz = self.invoice.event.timezone
has_multiple_service_dates = len(set(
(il.period_start, il.period_end) for il in all_lines
)) > 1
request_show_service_date = False
story = [ story = [
NextPageTemplate('FirstPage'), NextPageTemplate('FirstPage'),
@@ -729,15 +743,75 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
)] )]
def _group_key(line): def _group_key(line):
return (line.description, line.tax_rate, line.tax_name, line.net_value, line.gross_value, line.subevent_id, return (line.description, line.tax_rate, line.tax_name, line.net_value, line.gross_value, line.subevent,
line.event_date_from, line.event_date_to) line.period_start, line.period_end)
def day(dt: datetime.datetime) -> datetime.date:
if dt is None:
return None
return dt.astimezone(tz).date()
total = Decimal('0.00') total = Decimal('0.00')
for (description, tax_rate, tax_name, net_value, gross_value, *ignored), lines in addon_aware_groupby( for (description, tax_rate, tax_name, net_value, gross_value, subevent, period_start, period_end), lines in addon_aware_groupby(
self.invoice.lines.all(), all_lines,
key=_group_key, key=_group_key,
is_addon=lambda l: l.description.startswith(" +"), is_addon=lambda l: l.description.startswith(" +"),
): ):
# Try to be clever and figure out when organizers would want to show the period. This heuristic is
# not perfect and the only "fully correct" way would be to include the period on every line always,
# however this will cause confusion (a) due to useless repetition of the same date all over the invoice
# (b) due to not respecting the show_date_to setting of events in cases where we could have respected it.
# Still, we want to show the date explicitly if its different to the event or invoice date.
period_start_day = day(period_start)
period_end_day = day(period_end)
if period_start and period_end and period_end_day != period_start_day:
# It's a multi-day period, such as the validity of the ticket or an event date period
if period_start_day == header_dates[0] and period_end_day == header_dates[1]:
# This is the exact event period we already printed in the header, no need to repeat it.
period_line = ""
elif (self.event.has_subevents and subevent and day(subevent.date_from) == period_start_day and
day(subevent.date_to) == period_end_day):
# For subevents, build_invoice already includes the date in the description in the event-default format.
period_line = ""
else:
period_line = f"{date_format(period_start_day, 'SHORT_DATE_FORMAT')} {date_format(period_end_day, 'SHORT_DATE_FORMAT')}"
elif period_start or period_end:
# It's a single-day period
delivery_day = period_end_day or period_start_day
if delivery_day in header_dates:
# This is the event date we already printed in the header, no need to repeat it.
period_line = ""
elif self.event.has_subevents and subevent and delivery_day in (day(subevent.date_from), day(subevent.date_to)):
# For subevents, build_invoice already includes the date in the description in the event-default format.
period_line = ""
elif (delivery_day == self.invoice.date) and header_dates[0] is None:
# This is a shop that doesn't show the date of the event in the header, and the period is the invoice
# date. We assume that this is an 'everything is executed immediately' situation and do not want to
# confuse with showing additional dates on the invoice. This is the case that is not guaranteed to be
# correct in all cases and might need to change in the future. If customers have legal concerns, a
# quick fix is including a sentence like "Delivery date is the invoice date unless otherwise indicated:"
# in a custom text on the invoice.
period_line = ""
else:
period_line = date_format(delivery_day, 'SHORT_DATE_FORMAT')
else:
# No period known
period_line = ""
if not has_multiple_service_dates and period_line:
# Group together at the end of the invoice
request_show_service_date = period_line
elif period_line:
description += "\n" + period_line
lines = list(lines) lines = list(lines)
if has_taxes: if has_taxes:
if len(lines) > 1: if len(lines) > 1:
@@ -746,6 +820,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
gross_price=money_filter(gross_value, self.invoice.event.currency), gross_price=money_filter(gross_value, self.invoice.event.currency),
) )
description = description + "\n" + single_price_line description = description + "\n" + single_price_line
tdata.append(( tdata.append((
FontFallbackParagraph( FontFallbackParagraph(
self._clean_text(description, tags=['br']), self._clean_text(description, tags=['br']),
@@ -850,6 +925,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
story.append(Spacer(1, 10 * mm)) story.append(Spacer(1, 10 * mm))
if request_show_service_date:
story.append(FontFallbackParagraph(
self._normalize(pgettext('invoice', 'Invoice period: {daterange}').format(daterange=request_show_service_date)),
self.stylesheet['Normal']
))
if self.invoice.payment_provider_text: if self.invoice.payment_provider_text:
story.append(FontFallbackParagraph( story.append(FontFallbackParagraph(
self._normalize(self.invoice.payment_provider_text), self._normalize(self.invoice.payment_provider_text),

View File

@@ -0,0 +1,78 @@
# Generated by Django 4.2.17 on 2025-09-08 08:14
from django.core.cache import cache
from django.db import migrations
def set_invoice_period(apps, schema_editor):
EventSettingsStore = apps.get_model("pretixbase", "Event_SettingsStore")
ev_seen = set()
insert_queue = []
flush_queue = []
def store():
EventSettingsStore.objects.bulk_create(
insert_queue,
update_conflicts=True,
update_fields=["value"],
unique_fields=["object", "key"],
)
cache.delete_many(flush_queue)
flush_queue.clear()
insert_queue.clear()
# Existing events that use pretix-zugferd and have explicitly disabled delivery dates
for setting in EventSettingsStore.objects.filter(key="zugferd_include_delivery_date", value="False"):
flush_queue.append("hierarkey_{}_{}".format("event", setting.object_id))
insert_queue.append(
EventSettingsStore(
object_id=setting.object_id,
key="invoice_period",
value="invoice_date",
)
)
ev_seen.add(setting.object_id)
if len(insert_queue) > 1000:
store()
# Existing events that previously hid their date on invoices
for setting in EventSettingsStore.objects.filter(key="show_dates_on_frontpage", value="False"):
if setting.object_id in ev_seen:
continue
flush_queue.append("hierarkey_{}_{}".format("event", setting.object_id))
insert_queue.append(
EventSettingsStore(
object_id=setting.object_id,
key="invoice_period",
value="auto_no_event",
)
)
if len(insert_queue) > 1000:
store()
store()
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0288_invoice_transmission"),
]
operations = [
migrations.RenameField(
model_name="invoiceline",
old_name="event_date_to",
new_name="period_end",
),
migrations.RenameField(
model_name="invoiceline",
old_name="event_date_from",
new_name="period_start",
),
migrations.RunPython(
set_invoice_period,
migrations.RunPython.noop,
)
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.17 on 2025-09-09 09:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0289_invoiceline_period"),
]
operations = [
migrations.AddField(
model_name="invoice",
name="plugin_data",
field=models.JSONField(default=dict),
),
]

View File

@@ -195,20 +195,21 @@ class GiftCardTransaction(models.Model):
return response return response
if self.order_id: if self.order_id:
if not self.text: if not customer_facing:
if not customer_facing: return format_html(
return format_html( '<a href="{}">{}</a> {}',
'<a href="{}">{}</a>', reverse(
reverse( "control:event.order",
"control:event.order", kwargs={
kwargs={ "event": self.order.event.slug,
"event": self.order.event.slug, "organizer": self.order.event.organizer.slug,
"organizer": self.order.event.organizer.slug, "code": self.order.code,
"code": self.order.code, }
} ),
), self.order.full_code,
self.order.full_code self.text or "",
) )
elif not self.text:
return self.order.full_code return self.order.full_code
else: else:
return self.text return self.text

View File

@@ -33,6 +33,7 @@
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
import string import string
import warnings
from decimal import Decimal from decimal import Decimal
import pycountry import pycountry
@@ -201,6 +202,7 @@ class Invoice(models.Model):
transmission_info = models.JSONField(null=True, blank=True) transmission_info = models.JSONField(null=True, blank=True)
file = models.FileField(null=True, blank=True, upload_to=invoice_filename, max_length=255) file = models.FileField(null=True, blank=True, upload_to=invoice_filename, max_length=255)
plugin_data = models.JSONField(default=dict)
objects = ScopedManager(organizer='event__organizer') objects = ScopedManager(organizer='event__organizer')
@@ -404,10 +406,10 @@ class InvoiceLine(models.Model):
:type tax_name: str :type tax_name: str
:param subevent: The subevent this line refers to :param subevent: The subevent this line refers to
:type subevent: SubEvent :type subevent: SubEvent
:param event_date_from: Event date of the (sub)event at the time the invoice was created :param period_start: Start if service period invoiced
:type event_date_from: datetime :type period_start: datetime
:param event_date_to: Event end date of the (sub)event at the time the invoice was created :param period_end: End of service period invoiced
:type event_date_to: datetime :type period_end: datetime
:param event_location: Event location of the (sub)event at the time the invoice was created :param event_location: Event location of the (sub)event at the time the invoice was created
:type event_location: str :type event_location: str
:param item: The item this line refers to :param item: The item this line refers to
@@ -426,8 +428,8 @@ class InvoiceLine(models.Model):
tax_name = models.CharField(max_length=190) tax_name = models.CharField(max_length=190)
tax_code = models.CharField(max_length=190, null=True, blank=True) tax_code = models.CharField(max_length=190, null=True, blank=True)
subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT) subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT)
event_date_from = models.DateTimeField(null=True) period_start = models.DateTimeField(null=True)
event_date_to = models.DateTimeField(null=True) period_end = models.DateTimeField(null=True)
event_location = models.TextField(null=True, blank=True) event_location = models.TextField(null=True, blank=True)
item = models.ForeignKey('Item', null=True, blank=True, on_delete=models.PROTECT) item = models.ForeignKey('Item', null=True, blank=True, on_delete=models.PROTECT)
variation = models.ForeignKey('ItemVariation', null=True, blank=True, on_delete=models.PROTECT) variation = models.ForeignKey('ItemVariation', null=True, blank=True, on_delete=models.PROTECT)
@@ -444,3 +446,35 @@ class InvoiceLine(models.Model):
def __str__(self): def __str__(self):
return 'Line {} of invoice {}'.format(self.position, self.invoice) return 'Line {} of invoice {}'.format(self.position, self.invoice)
@property
def event_date_from(self):
warnings.warn(
'InvoiceLine.event_date_from is deprecated, use period_start instead,',
category=DeprecationWarning,
)
return self.period_start
@event_date_from.setter
def event_date_from(self, value):
warnings.warn(
'InvoiceLine.event_date_from is deprecated, use period_start instead,',
category=DeprecationWarning,
)
self.period_start = value
@property
def event_date_to(self):
warnings.warn(
'InvoiceLine.event_date_to is deprecated, use period_end instead,',
category=DeprecationWarning,
)
return self.period_end
@event_date_to.setter
def event_date_to(self, value):
warnings.warn(
'InvoiceLine.event_date_to is deprecated, use period_end instead,',
category=DeprecationWarning,
)
self.period_to = value

View File

@@ -1627,6 +1627,7 @@ class GiftCardPayment(BasePaymentProvider):
order=refund.order, order=refund.order,
refund=refund, refund=refund,
acceptor=self.event.organizer, acceptor=self.event.organizer,
text=refund.comment,
) )
refund.info_data = { refund.info_data = {
'gift_card': gc.pk, 'gift_card': gc.pk,

View File

@@ -61,7 +61,9 @@ from pretix.base.models.tax import EU_CURRENCIES
from pretix.base.services.tasks import ( from pretix.base.services.tasks import (
TransactionAwareProfiledEventTask, TransactionAwareTask, TransactionAwareProfiledEventTask, TransactionAwareTask,
) )
from pretix.base.signals import invoice_line_text, periodic_task from pretix.base.signals import (
build_invoice_data, invoice_line_text, periodic_task,
)
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers.database import OF_SELF, rolledback_transaction from pretix.helpers.database import OF_SELF, rolledback_transaction
from pretix.helpers.models import modelcopy from pretix.helpers.models import modelcopy
@@ -82,6 +84,10 @@ def build_invoice(invoice: Invoice) -> Invoice:
lp = invoice.order.payments.last() lp = invoice.order.payments.last()
min_period_start = None
max_period_end = None
now_dt = now()
with (language(invoice.locale, invoice.event.settings.region)): with (language(invoice.locale, invoice.event.settings.region)):
invoice.invoice_from = invoice.event.settings.get('invoice_address_from') invoice.invoice_from = invoice.event.settings.get('invoice_address_from')
invoice.invoice_from_name = invoice.event.settings.get('invoice_address_from_name') invoice.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
@@ -208,7 +214,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
positions = list( positions = list(
invoice.order.positions.select_related('addon_to', 'item', 'tax_rule', 'subevent', 'variation').annotate( invoice.order.positions.select_related('addon_to', 'item', 'tax_rule', 'subevent', 'variation').annotate(
addon_c=Count('addons') addon_c=Count('addons')
).prefetch_related('answers', 'answers__options', 'answers__question').order_by('positionid', 'id') ).prefetch_related(
'answers', 'answers__options', 'answers__question', 'granted_memberships',
).order_by('positionid', 'id')
) )
reverse_charge = False reverse_charge = False
@@ -267,6 +275,10 @@ def build_invoice(invoice: Invoice) -> Invoice:
location=_location_oneliner(location) location=_location_oneliner(location)
) )
period_start, period_end = _service_period_for_position(invoice, p, now_dt)
min_period_start = min(min_period_start or period_start, period_start)
max_period_end = min(max_period_end or period_end, period_end)
InvoiceLine.objects.create( InvoiceLine.objects.create(
position=i, position=i,
invoice=invoice, invoice=invoice,
@@ -277,8 +289,8 @@ def build_invoice(invoice: Invoice) -> Invoice:
item=p.item, item=p.item,
variation=p.variation, variation=p.variation,
attendee_name=p.attendee_name if invoice.event.settings.invoice_attendee_name else None, attendee_name=p.attendee_name if invoice.event.settings.invoice_attendee_name else None,
event_date_from=p.subevent.date_from if invoice.event.has_subevents else invoice.event.date_from, period_start=period_start,
event_date_to=p.subevent.date_to if invoice.event.has_subevents else invoice.event.date_to, period_end=period_end,
event_location=location if invoice.event.settings.invoice_event_location else None, event_location=location if invoice.event.settings.invoice_event_location else None,
tax_rate=p.tax_rate, tax_rate=p.tax_rate,
tax_code=p.tax_code, tax_code=p.tax_code,
@@ -301,13 +313,29 @@ def build_invoice(invoice: Invoice) -> Invoice:
fee_title = _(fee.get_fee_type_display()) fee_title = _(fee.get_fee_type_display())
if fee.description: if fee.description:
fee_title += " - " + fee.description fee_title += " - " + fee.description
if min_period_start and max_period_end:
# Consider fees to have the same service period as the products sold
period_start = min_period_start
period_end = max_period_end
else:
# Usually can only happen if everything except a cancellation fee is removed
if invoice.event.settings.invoice_period in ("auto", "auto_no_event", "event_date") and not invoice.event.has_subevents:
# Non-series event, let's be backwards-compatible and tag everything with the event period
period_start = invoice.event.date_from
period_end = invoice.event.date_to
else:
# We could try to work from the canceled positions, but it doesn't really make sense. A cancellation
# fee is not "delivered" at the event date, it is rather effective right now.
period_start = period_end = now()
InvoiceLine.objects.create( InvoiceLine.objects.create(
position=i + offset, position=i + offset,
invoice=invoice, invoice=invoice,
description=fee_title, description=fee_title,
gross_value=fee.value, gross_value=fee.value,
event_date_from=None if invoice.event.has_subevents else invoice.event.date_from, period_start=period_start,
event_date_to=None if invoice.event.has_subevents else invoice.event.date_to, period_end=period_end,
event_location=( event_location=(
None if invoice.event.has_subevents None if invoice.event.has_subevents
else (str(invoice.event.location) else (str(invoice.event.location)
@@ -336,6 +364,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
invoice.reverse_charge = reverse_charge invoice.reverse_charge = reverse_charge
invoice.save() invoice.save()
build_invoice_data.send(sender=invoice.event, invoice=invoice)
return invoice return invoice
@@ -351,6 +380,55 @@ def build_cancellation(invoice: Invoice):
return invoice return invoice
def _service_period_for_position(invoice, position, invoice_dt):
if invoice.event.settings.invoice_period in ("auto", "auto_no_event"):
if position.valid_from and position.valid_until:
period_start = position.valid_from
period_end = position.valid_until
elif position.valid_from:
period_start = position.valid_from
period_end = position.valid_from # weird, but we have nothing else to base this on
elif position.valid_until:
period_start = min(invoice.order.datetime, position.valid_until)
period_end = position.valid_until
elif memberships := list(position.granted_memberships.all()):
period_start = min(m.date_start for m in memberships)
period_end = max(m.date_end for m in memberships)
elif invoice.event.has_subevents:
if position.subevent:
period_start = position.subevent.date_from
period_end = position.subevent.date_to
else:
# Currently impossible case, but might not be in the future and never makes
# sense to use the event date here
period_start = invoice_dt
period_end = invoice_dt
elif invoice.event.settings.invoice_period == "auto_no_event":
period_start = invoice_dt
period_end = invoice_dt
else:
period_start = invoice.event.date_from
period_end = invoice.event.date_to
elif invoice.event.settings.invoice_period == "order_date":
period_start = invoice.order.datetime
period_end = invoice.order.datetime
elif invoice.event.settings.invoice_period == "event_date":
if position.subevent:
period_start = position.subevent.date_from
period_end = position.subevent.date_to
else:
period_start = invoice.event.date_from
period_end = invoice.event.date_to
elif invoice.event.settings.invoice_period == "invoice_date":
period_start = period_end = invoice_dt
else:
raise ValueError(f"Invalid invoice period setting '{invoice.event.settings.invoice_period}'")
if not period_end:
period_end = period_start
return period_start, period_end
def generate_cancellation(invoice: Invoice, trigger_pdf=True): def generate_cancellation(invoice: Invoice, trigger_pdf=True):
if invoice.canceled: if invoice.canceled:
raise ValueError("Invoice should not be canceled twice.") raise ValueError("Invoice should not be canceled twice.")
@@ -456,6 +534,12 @@ def build_preview_invoice_pdf(event):
if not locale or locale == '__user__': if not locale or locale == '__user__':
locale = event.settings.locale locale = event.settings.locale
if event.settings.invoice_period in ("auto", "auto_no_event", "event_date"):
period_start = event.date_from
period_end = event.date_to or event.date_from
else:
period_start = period_end = timezone.now()
with rolledback_transaction(), language(locale, event.settings.region): with rolledback_transaction(), language(locale, event.settings.region):
order = event.orders.create( order = event.orders.create(
status=Order.STATUS_PENDING, datetime=timezone.now(), status=Order.STATUS_PENDING, datetime=timezone.now(),
@@ -506,8 +590,8 @@ def build_preview_invoice_pdf(event):
invoice=invoice, description=_("Sample product {}").format(i + 1), invoice=invoice, description=_("Sample product {}").format(i + 1),
gross_value=tax.gross, tax_value=tax.tax, gross_value=tax.gross, tax_value=tax.tax,
tax_rate=tax.rate, tax_name=tax.name, tax_code=tax.code, tax_rate=tax.rate, tax_name=tax.name, tax_code=tax.code,
event_date_from=event.date_from, period_start=period_start,
event_date_to=event.date_to, period_end=period_end,
event_location=event.settings.invoice_event_location, event_location=event.settings.invoice_event_location,
) )
else: else:
@@ -515,8 +599,8 @@ def build_preview_invoice_pdf(event):
InvoiceLine.objects.create( InvoiceLine.objects.create(
invoice=invoice, description=_("Sample product A"), invoice=invoice, description=_("Sample product A"),
gross_value=100, tax_value=0, tax_rate=0, tax_code=None, gross_value=100, tax_value=0, tax_rate=0, tax_code=None,
event_date_from=event.date_from, period_start=period_start,
event_date_to=event.date_to, period_end=period_end,
event_location=event.settings.invoice_event_location, event_location=event.settings.invoice_event_location,
) )

View File

@@ -222,7 +222,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
'invoice_company': '' 'invoice_company': ''
}) })
renderer = ClassicMailRenderer(None, organizer) renderer = ClassicMailRenderer(None, organizer)
body_plain = render_mail(template, context, placeholder_mode=SafeFormatter.MODE_RICH_TO_PLAIN) content_plain = body_plain = render_mail(template, context)
subject = str(subject).format_map(TolerantDict(context)) subject = str(subject).format_map(TolerantDict(context))
sender = ( sender = (
sender or sender or
@@ -316,7 +316,6 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
with override(timezone): with override(timezone):
try: try:
content_plain = render_mail(template, context, placeholder_mode=None)
if plain_text_only: if plain_text_only:
body_html = None body_html = None
elif 'context' in inspect.signature(renderer.render).parameters: elif 'context' in inspect.signature(renderer.render).parameters:
@@ -750,11 +749,11 @@ def mail_send(*args, **kwargs):
mail_send_task.apply_async(args=args, kwargs=kwargs) mail_send_task.apply_async(args=args, kwargs=kwargs)
def render_mail(template, context, placeholder_mode=SafeFormatter.MODE_RICH_TO_PLAIN): def render_mail(template, context):
if isinstance(template, LazyI18nString): if isinstance(template, LazyI18nString):
body = str(template) body = str(template)
if context and placeholder_mode: if context:
body = format_map(body, context, mode=placeholder_mode) body = format_map(body, context, mode=SafeFormatter.MODE_IGNORE_RICH)
else: else:
tpl = get_template(template) tpl = get_template(template)
body = tpl.render(context) body = tpl.render(context)

View File

@@ -221,7 +221,7 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
for o in orders: for o in orders:
with language(o.locale, event.settings.region): with language(o.locale, event.settings.region):
order_placed.send(event, order=o) order_placed.send(event, order=o, bulk=True)
if o.status == Order.STATUS_PAID: if o.status == Order.STATUS_PAID:
order_paid.send(event, order=o) order_paid.send(event, order=o)

View File

@@ -1091,7 +1091,7 @@ def _create_order(event: Event, *, email: str, positions: List[CartPosition], no
for msg in meta_info.get('confirm_messages', []): for msg in meta_info.get('confirm_messages', []):
order.log_action('pretix.event.order.consent', data={'msg': msg}) order.log_action('pretix.event.order.consent', data={'msg': msg})
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
return order, payments return order, payments
@@ -2825,7 +2825,7 @@ class OrderChangeManager:
def _check_complete_cancel(self): def _check_complete_cancel(self):
current = self.order.positions.count() current = self.order.positions.count()
cancels = sum([ cancels = sum([
1 + o.position.addons.count() for o in self._operations if isinstance(o, self.CancelOperation) 1 + o.position.addons.filter(canceled=False).count() for o in self._operations if isinstance(o, self.CancelOperation)
]) + len([ ]) + len([
o for o in self._operations if isinstance(o, self.SplitOperation) o for o in self._operations if isinstance(o, self.SplitOperation)
]) ])

View File

@@ -26,7 +26,7 @@ from decimal import Decimal
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.html import escape, mark_safe from django.utils.html import escape
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -123,10 +123,6 @@ class BaseRichTextPlaceholder(BaseTextPlaceholder):
def identifier(self): def identifier(self):
return self._identifier return self._identifier
@property
def allowed_in_plain_content(self):
return False
@property @property
def required_context(self): def required_context(self):
return self._args return self._args
@@ -198,33 +194,6 @@ class SimpleButtonPlaceholder(BaseRichTextPlaceholder):
return f'{text}: {url}' return f'{text}: {url}'
class MarkdownTextPlaceholder(BaseRichTextPlaceholder):
def __init__(self, identifier, args, func, sample, inline):
super().__init__(identifier, args)
self._func = func
self._sample = sample
self._snippet = inline
@property
def allowed_in_plain_content(self):
return self._snippet
def render_plain(self, **context):
return self._func(**{k: context[k] for k in self._args})
def render_html(self, **context):
return mark_safe(markdown_compile_email(self.render_plain(**context), snippet=self._snippet))
def render_sample_plain(self, event):
if callable(self._sample):
return self._sample(event)
else:
return self._sample
def render_sample_html(self, event):
return mark_safe(markdown_compile_email(self.render_sample_plain(event), snippet=self._snippet))
class PlaceholderContext(SafeFormatter): class PlaceholderContext(SafeFormatter):
""" """
Holds the contextual arguments and corresponding list of available placeholders for formatting Holds the contextual arguments and corresponding list of available placeholders for formatting
@@ -605,7 +574,7 @@ def base_placeholders(sender, **kwargs):
'invoice_company', ['invoice_address'], lambda invoice_address: invoice_address.company or '', 'invoice_company', ['invoice_address'], lambda invoice_address: invoice_address.company or '',
_('Sample Corporation') _('Sample Corporation')
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join( 'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join(
'* {} - {}'.format( '* {} - {}'.format(
order.full_code, order.full_code,
@@ -635,7 +604,6 @@ def base_placeholders(sender, **kwargs):
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd', 'hash': 'stuvwxy2z'} {'code': 'OPKSB', 'secret': '09pjdksflosk3njd', 'hash': 'stuvwxy2z'}
] ]
), ),
inline=False,
), ),
SimpleFunctionalTextPlaceholder( SimpleFunctionalTextPlaceholder(
'hours', ['event', 'waiting_list_entry'], lambda event, waiting_list_entry: 'hours', ['event', 'waiting_list_entry'], lambda event, waiting_list_entry:
@@ -650,13 +618,12 @@ def base_placeholders(sender, **kwargs):
'code', ['waiting_list_voucher'], lambda waiting_list_voucher: waiting_list_voucher.code, 'code', ['waiting_list_voucher'], lambda waiting_list_voucher: waiting_list_voucher.code,
'68CYU2H6ZTP3WLK5' '68CYU2H6ZTP3WLK5'
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
# join vouchers with two spaces at end of line so markdown-parser inserts a <br> # join vouchers with two spaces at end of line so markdown-parser inserts a <br>
'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list), 'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list),
'68CYU2H6ZTP3WLK5 \n7MB94KKPVEPSMVF2', ' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
inline=False,
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
# join vouchers with two spaces at end of line so markdown-parser inserts a <br> # join vouchers with two spaces at end of line so markdown-parser inserts a <br>
'voucher_url_list', ['event', 'voucher_list'], 'voucher_url_list', ['event', 'voucher_list'],
lambda event, voucher_list: ' \n'.join([ lambda event, voucher_list: ' \n'.join([
@@ -671,7 +638,6 @@ def base_placeholders(sender, **kwargs):
) + '?voucher=' + c ) + '?voucher=' + c
for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2'] for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2']
]), ]),
inline=False,
), ),
SimpleFunctionalTextPlaceholder( SimpleFunctionalTextPlaceholder(
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={ 'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
@@ -690,13 +656,13 @@ def base_placeholders(sender, **kwargs):
'comment', ['comment'], lambda comment: comment, 'comment', ['comment'], lambda comment: comment,
_('An individual text with a reason can be inserted here.'), _('An individual text with a reason can be inserted here.'),
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
'payment_info', ['order', 'payments'], _placeholder_payments, 'payment_info', ['order', 'payments'], _placeholder_payments,
_('The amount has been charged to your card.'), inline=False, _('The amount has been charged to your card.'),
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
'payment_info', ['payment_info'], lambda payment_info: payment_info, 'payment_info', ['payment_info'], lambda payment_info: payment_info,
_('Please transfer money to this bank account: 9999-9999-9999-9999'), inline=False, _('Please transfer money to this bank account: 9999-9999-9999-9999'),
), ),
SimpleFunctionalTextPlaceholder( SimpleFunctionalTextPlaceholder(
'attendee_name', ['position'], lambda position: position.attendee_name, 'attendee_name', ['position'], lambda position: position.attendee_name,
@@ -753,13 +719,13 @@ def base_placeholders(sender, **kwargs):
)) ))
for k, v in sender.meta_data.items(): for k, v in sender.meta_data.items():
ph.append(MarkdownTextPlaceholder( ph.append(SimpleFunctionalTextPlaceholder(
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k], 'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],
v, inline=True, v
)) ))
ph.append(MarkdownTextPlaceholder( ph.append(SimpleFunctionalTextPlaceholder(
'meta_%s' % k, ['event_or_subevent'], lambda event_or_subevent, k=k: event_or_subevent.meta_data[k], 'meta_%s' % k, ['event_or_subevent'], lambda event_or_subevent, k=k: event_or_subevent.meta_data[k],
v, inline=True, v
)) ))
return ph return ph
@@ -787,7 +753,7 @@ def get_available_placeholders(event, base_parameters, rich=False):
if not isinstance(val, (list, tuple)): if not isinstance(val, (list, tuple)):
val = [val] val = [val]
for v in val: for v in val:
if isinstance(v, BaseRichTextPlaceholder) and not rich and not v.allowed_in_plain_content: if isinstance(v, BaseRichTextPlaceholder) and not rich:
continue continue
if all(rp in base_parameters for rp in v.required_context): if all(rp in base_parameters for rp in v.required_context):
params[v.identifier] = v params[v.identifier] = v
@@ -809,13 +775,13 @@ def get_sample_context(event, context_parameters, rich=True):
) )
) )
elif str(sample).strip().startswith('* ') or str(sample).startswith(' '): elif str(sample).strip().startswith('* ') or str(sample).startswith(' '):
context_dict[k] = mark_safe('<div class="placeholder" title="{}">{}</div>'.format( context_dict[k] = '<div class="placeholder" title="{}">{}</div>'.format(
lbl, lbl,
markdown_compile_email(str(sample)) markdown_compile_email(str(sample))
)) )
else: else:
context_dict[k] = mark_safe('<span class="placeholder" title="{}">{}</span>'.format( context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
lbl, lbl,
escape(sample) escape(sample)
)) )
return context_dict return context_dict

View File

@@ -1098,6 +1098,35 @@ DEFAULTS = {
help_text=_("Invoices will never be automatically generated for free orders.") help_text=_("Invoices will never be automatically generated for free orders.")
) )
}, },
'invoice_period': {
'default': 'auto',
'type': str,
'form_class': forms.ChoiceField,
'serializer_class': serializers.ChoiceField,
'serializer_kwargs': dict(
choices=(
('auto', _('Automatic based on ticket-specific validity, membership validity, event series date, or event date')),
('auto_no_event', _('Automatic, but prefer invoice date over event date')),
('event_date', _('Event date')),
('order_date', _('Order date')),
('invoice_date', _('Invoice date')),
),
),
'form_kwargs': dict(
label=_("Date of service"),
widget=forms.RadioSelect,
choices=(
('auto', _('Automatic based on ticket-specific validity, membership validity, event series date, or event date')),
('auto_no_event', _('Automatic, but prefer invoice date over event date')),
('event_date', _('Event date')),
('order_date', _('Order date')),
('invoice_date', _('Invoice date')),
),
help_text=_("This controls what dates are shown on the invoice, but is especially important for "
"electronic invoicing."),
required=True,
)
},
'invoice_reissue_after_modify': { 'invoice_reissue_after_modify': {
'default': 'False', 'default': 'False',
'type': bool, 'type': bool,

View File

@@ -596,6 +596,18 @@ multiple events. Receivers should return a subclass of pretix.base.exporter.Base
The ``sender`` keyword argument will contain an organizer. The ``sender`` keyword argument will contain an organizer.
""" """
build_invoice_data = EventPluginSignal()
"""
Arguments: ``invoice``
This signal is sent out every time an invoice is built, after the invoice model was created
and filled and before the PDF generation task is started. You can use this to make changes
to the invoice, but we recommend to mostly use it to add content to ``Invoice.plugin_data``.
You are responsible for saving any changes to the database.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
validate_order = EventPluginSignal() validate_order = EventPluginSignal()
""" """
Arguments: ``payments``, ``positions``, ``email``, ``locale``, ``invoice_address``, Arguments: ``payments``, ``positions``, ``email``, ``locale``, ``invoice_address``,
@@ -653,12 +665,13 @@ As with all event-plugin signals, the ``sender`` keyword argument will contain t
order_placed = EventPluginSignal() order_placed = EventPluginSignal()
""" """
Arguments: ``order`` Arguments: ``order``, ``bulk``
This signal is sent out every time an order is placed. The order object is given This signal is sent out every time an order is placed. The order object is given
as the first argument. This signal is *not* sent out if an order is created through as the first argument. The ``bulk`` argument specifies whether the order was placed
splitting an existing order, so you can not expect to see all orders by listening as part of a bulk action, e.g. an import from a file.
to this signal. This signal is *not* sent out if an order is created through splitting an existing order,
so you can not expect to see all orders by listening to this signal.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event. As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
""" """

View File

@@ -44,7 +44,6 @@ from django.conf import settings
from django.core import signing from django.core import signing
from django.urls import reverse from django.urls import reverse
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.utils.html import escape
from django.utils.http import url_has_allowed_host_and_scheme from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from markdown import Extension from markdown import Extension
@@ -53,8 +52,6 @@ from markdown.postprocessors import Postprocessor
from markdown.treeprocessors import UnescapeTreeprocessor from markdown.treeprocessors import UnescapeTreeprocessor
from tlds import tld_set from tlds import tld_set
from pretix.helpers.format import SafeFormatter, format_map
register = template.Library() register = template.Library()
ALLOWED_TAGS_SNIPPET = { ALLOWED_TAGS_SNIPPET = {
@@ -297,44 +294,27 @@ class LinkifyAndCleanExtension(Extension):
) )
def markdown_compile_email(source, allowed_tags=None, allowed_attributes=ALLOWED_ATTRIBUTES, snippet=False, context=None): def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes=ALLOWED_ATTRIBUTES):
if allowed_tags is None:
allowed_tags = ALLOWED_TAGS_SNIPPET if snippet else ALLOWED_TAGS
context_callbacks = []
if context:
# This is a workaround to fix placeholders in URL targets
def context_callback(attrs, new=False):
if (None, "href") in attrs and "{" in attrs[None, "href"]:
# Do not use MODE_RICH_TO_HTML to avoid recursive linkification
attrs[None, "href"] = escape(format_map(attrs[None, "href"], context=context, mode=SafeFormatter.MODE_RICH_TO_PLAIN))
return attrs
context_callbacks.append(context_callback)
linker = bleach.Linker( linker = bleach.Linker(
url_re=URL_RE, url_re=URL_RE,
email_re=EMAIL_RE, email_re=EMAIL_RE,
callbacks=context_callbacks + DEFAULT_CALLBACKS + [truelink_callback, abslink_callback], callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True parse_email=True
) )
exts = [
'markdown.extensions.sane_lists',
'markdown.extensions.tables',
EmailNl2BrExtension(),
LinkifyAndCleanExtension(
linker,
tags=set(allowed_tags),
attributes=allowed_attributes,
protocols=ALLOWED_PROTOCOLS,
strip=snippet,
)
]
if snippet:
exts.append(SnippetExtension())
return markdown.markdown( return markdown.markdown(
source, source,
extensions=exts extensions=[
'markdown.extensions.sane_lists',
'markdown.extensions.tables',
EmailNl2BrExtension(),
LinkifyAndCleanExtension(
linker,
tags=set(allowed_tags),
attributes=allowed_attributes,
protocols=ALLOWED_PROTOCOLS,
strip=False,
)
]
) )

View File

@@ -857,6 +857,7 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
'invoice_show_payments', 'invoice_show_payments',
'invoice_reissue_after_modify', 'invoice_reissue_after_modify',
'invoice_generate', 'invoice_generate',
'invoice_period',
'invoice_attendee_name', 'invoice_attendee_name',
'invoice_event_location', 'invoice_event_location',
'invoice_include_expire_date', 'invoice_include_expire_date',

View File

@@ -308,8 +308,8 @@ class VoucherBulkForm(VoucherForm):
) )
Recipient = namedtuple('Recipient', 'email number name tag') Recipient = namedtuple('Recipient', 'email number name tag')
def _set_field_placeholders(self, fn, base_parameters, rich=False): def _set_field_placeholders(self, fn, base_parameters):
placeholders = get_available_placeholders(self.instance.event, base_parameters, rich=rich) placeholders = get_available_placeholders(self.instance.event, base_parameters)
ht = format_placeholders_help_text(placeholders, self.instance.event) ht = format_placeholders_help_text(placeholders, self.instance.event)
if self.fields[fn].help_text: if self.fields[fn].help_text:
@@ -345,7 +345,7 @@ class VoucherBulkForm(VoucherForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._set_field_placeholders('send_subject', ['event', 'name']) self._set_field_placeholders('send_subject', ['event', 'name'])
self._set_field_placeholders('send_message', ['event', 'voucher_list', 'name'], rich=True) self._set_field_placeholders('send_message', ['event', 'voucher_list', 'name'])
with language(self.instance.event.settings.locale, self.instance.event.settings.region): with language(self.instance.event.settings.locale, self.instance.event.settings.region):
for f in ("send_subject", "send_message"): for f in ("send_subject", "send_message"):

View File

@@ -15,6 +15,19 @@
{% bootstrap_field form.invoice_email_attachment layout="control" %} {% bootstrap_field form.invoice_email_attachment layout="control" %}
{% bootstrap_field form.invoice_email_organizer layout="control" %} {% bootstrap_field form.invoice_email_organizer layout="control" %}
{% bootstrap_field form.invoice_language layout="control" %} {% bootstrap_field form.invoice_language layout="control" %}
{% bootstrap_field form.invoice_period layout="control" %}
{% if not request.event.settings.show_dates_on_frontpage %}
<div data-display-dependency="input[name=invoice_period][value=auto],input[name=invoice_period][value=event_date]">
<div class="alert alert-warning dynamic">
{% blocktrans trimmed %}
You configured that your shop is not an event and the event date should not be shown.
Therefore, we recommend that you set the date of service to a different option.
{% endblocktrans %}
</div>
</div>
{% endif %}
{% bootstrap_field form.invoice_include_free layout="control" %} {% bootstrap_field form.invoice_include_free layout="control" %}
{% bootstrap_field form.invoice_show_payments layout="control" %} {% bootstrap_field form.invoice_show_payments layout="control" %}
{% bootstrap_field form.invoice_reissue_after_modify layout="control" %} {% bootstrap_field form.invoice_reissue_after_modify layout="control" %}

View File

@@ -22,8 +22,6 @@
import logging import logging
from string import Formatter from string import Formatter
from django.utils.html import conditional_escape
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -42,14 +40,14 @@ class SafeFormatter(Formatter):
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
(b) does not allow any unwanted shenanigans like attribute access or format specifiers. (b) does not allow any unwanted shenanigans like attribute access or format specifiers.
""" """
MODE_IGNORE_RICH = 0
MODE_RICH_TO_PLAIN = 1 MODE_RICH_TO_PLAIN = 1
MODE_RICH_TO_HTML = 2 MODE_RICH_TO_HTML = 2
def __init__(self, context, raise_on_missing=False, mode=MODE_RICH_TO_PLAIN, linkifier=None): def __init__(self, context, raise_on_missing=False, mode=MODE_IGNORE_RICH):
self.context = context self.context = context
self.raise_on_missing = raise_on_missing self.raise_on_missing = raise_on_missing
self.mode = mode self.mode = mode
self.linkifier = linkifier
def get_field(self, field_name, args, kwargs): def get_field(self, field_name, args, kwargs):
return self.get_value(field_name, args, kwargs), field_name return self.get_value(field_name, args, kwargs), field_name
@@ -57,28 +55,22 @@ class SafeFormatter(Formatter):
def get_value(self, key, args, kwargs): def get_value(self, key, args, kwargs):
if not self.raise_on_missing and key not in self.context: if not self.raise_on_missing and key not in self.context:
return '{' + str(key) + '}' return '{' + str(key) + '}'
return self.context[key] r = self.context[key]
if isinstance(r, PlainHtmlAlternativeString):
def _prepare_value(self, value): if self.mode == self.MODE_IGNORE_RICH:
if isinstance(value, PlainHtmlAlternativeString): return '{' + str(key) + '}'
if self.mode == self.MODE_RICH_TO_PLAIN: elif self.mode == self.MODE_RICH_TO_PLAIN:
return value.plain return r.plain
elif self.mode == self.MODE_RICH_TO_HTML: elif self.mode == self.MODE_RICH_TO_HTML:
return value.html return r.html
else: return r
value = str(value)
if self.mode == self.MODE_RICH_TO_HTML:
value = conditional_escape(value)
if self.linkifier:
value = self.linkifier.linkify(value)
return value
def format_field(self, value, format_spec): def format_field(self, value, format_spec):
# Ignore format_spec # Ignore format_spec
return super().format_field(self._prepare_value(value), '') return super().format_field(value, '')
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None): def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_IGNORE_RICH):
if not isinstance(template, str): if not isinstance(template, str):
template = str(template) template = str(template)
return SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template) return SafeFormatter(context, raise_on_missing, mode=mode).format(template)

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n" "POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-04-22 15:36+0000\n" "PO-Revision-Date: 2025-09-29 07:39+0000\n"
"Last-Translator: Paul Berschick <paul@plainschwarz.com>\n" "Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix/" "Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix/"
"ca/>\n" "ca/>\n"
"Language: ca\n" "Language: ca\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.11\n" "X-Generator: Weblate 5.13.3\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -35318,11 +35318,6 @@ msgstr ""
"Tingueu en compte que heu de fer el pagament per a finalitzar el procés." "Tingueu en compte que heu de fer el pagament per a finalitzar el procés."
#: pretix/presale/templates/pretixpresale/event/order.html:55 #: pretix/presale/templates/pretixpresale/event/order.html:55
#, fuzzy
#| msgid ""
#| "Please bookmark or save the link to this exact page if you want to access "
#| "your order later. We also sent you an email containing the link to the "
#| "address you specified."
msgid "" msgid ""
"Please bookmark or save the link to this exact page if you want to access " "Please bookmark or save the link to this exact page if you want to access "
"your order later. We also sent you an email to the address you specified " "your order later. We also sent you an email to the address you specified "

View File

@@ -5,7 +5,7 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n" "POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-09-23 16:13+0000\n" "PO-Revision-Date: 2025-09-26 13:02+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n" "Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/" "Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
">\n" ">\n"
@@ -808,28 +808,23 @@ msgstr ""
"Sie zunächst die E-Mail-Adresse in Ihrem Kundenkonto." "Sie zunächst die E-Mail-Adresse in Ihrem Kundenkonto."
#: pretix/base/datasync/datasync.py:255 #: pretix/base/datasync/datasync.py:255
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" does not exist. Please check your {provider_name} " "Field \"{field_name}\" does not exist. Please check your {provider_name} "
"settings." "settings."
msgstr "" msgstr ""
"Feld \"{field_name}\" ist nicht gültig für {available_inputs}. Bitte prüfen " "Feld \"{field_name}\" existiert nicht. Bitte prüfen Sie die Einstellungen "
"Sie die Einstellungen für {provider_name}." "für {provider_name}."
#: pretix/base/datasync/datasync.py:262 #: pretix/base/datasync/datasync.py:262
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" requires {required_input}, but only got " "Field \"{field_name}\" requires {required_input}, but only got "
"{available_inputs}. Please check your {provider_name} settings." "{available_inputs}. Please check your {provider_name} settings."
msgstr "" msgstr ""
"Feld \"{field_name}\" ist nicht gültig für {available_inputs}. Bitte prüfen " "Feld \"{field_name}\" erfordert, {required_input}, aber nur "
"Sie die Einstellungen für {provider_name}." "{available_inputs} sind vorhanden. Bitte prüfen Sie die Einstellungen für "
"{provider_name}."
#: pretix/base/datasync/datasync.py:273 #: pretix/base/datasync/datasync.py:273
#, python-brace-format #, python-brace-format
@@ -3548,6 +3543,10 @@ msgid ""
"in accordance with the procedures and terms set forth in No. 89757/2018 of " "in accordance with the procedures and terms set forth in No. 89757/2018 of "
"April 30, 2018, issued by the Director of the Revenue Agency." "April 30, 2018, issued by the Director of the Revenue Agency."
msgstr "" msgstr ""
"Diese PDF-Datei ist eine Sichtkopie der Rechnung und stellt keine Rechnung "
"für Umsatzsteuerzwecke dar. Die Rechnung wurde im XML-Format ausgestellt und "
"im Einklang mit den Regularien aus dem Dekret Nr. 89757/2018 vom 30. April "
"2018 des Direktors der Finanzbehörde übermittelt."
#: pretix/base/invoicing/pdf.py:141 #: pretix/base/invoicing/pdf.py:141
#, python-format #, python-format
@@ -3817,12 +3816,9 @@ msgid "Peppol participant ID"
msgstr "Peppol-Teilnehmer-ID" msgstr "Peppol-Teilnehmer-ID"
#: pretix/base/invoicing/peppol.py:170 #: pretix/base/invoicing/peppol.py:170
#, fuzzy
#| msgctxt "italian_invoice"
#| msgid "Fiscal code"
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
msgid "Visual copy" msgid "Visual copy"
msgstr "Steuernummer" msgstr "Sichtkopie"
#: pretix/base/invoicing/peppol.py:175 #: pretix/base/invoicing/peppol.py:175
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
@@ -3831,6 +3827,9 @@ msgid ""
"invoice for VAT purposes. The original invoice is issued in XML format and " "invoice for VAT purposes. The original invoice is issued in XML format and "
"transmitted through the Peppol network." "transmitted through the Peppol network."
msgstr "" msgstr ""
"Diese PDF-Datei ist eine Sichtkopie der Rechnung und stellt keine Rechnung "
"für Umsatzsteuerzwecke dar. Die Originalrechnung wurde im XML-Format "
"ausgestellt und über das Peppol-Netzwerk übertragen."
#: pretix/base/logentrytype_registry.py:43 #: pretix/base/logentrytype_registry.py:43
msgid "" msgid ""
@@ -14406,10 +14405,8 @@ msgid "Canceled (fully or with paid fee)"
msgstr "Storniert (komplett oder mit Gebühr)" msgstr "Storniert (komplett oder mit Gebühr)"
#: pretix/control/forms/filter.py:228 #: pretix/control/forms/filter.py:228
#, fuzzy
#| msgid "Cancel this position"
msgid "Canceled (at least one position)" msgid "Canceled (at least one position)"
msgstr "Diese Position stornieren" msgstr "storniert (mindestens eine Position)"
#: pretix/control/forms/filter.py:229 #: pretix/control/forms/filter.py:229
msgid "Cancellation requested" msgid "Cancellation requested"
@@ -17149,10 +17146,9 @@ msgid "The voucher has been deleted."
msgstr "Der Gutschein wurde gelöscht." msgstr "Der Gutschein wurde gelöscht."
#: pretix/control/logdisplay.py:584 #: pretix/control/logdisplay.py:584
#, fuzzy, python-brace-format #, python-brace-format
#| msgid "The voucher has been sent to {email} through the waiting list."
msgid "The voucher has been assigned to {email} through the waiting list." msgid "The voucher has been assigned to {email} through the waiting list."
msgstr "Der Gutschein wurde über die Warteliste an {email} verschickt." msgstr "Der Gutschein wurde über die Warteliste an {email} zugewiesen."
#: pretix/control/logdisplay.py:593 #: pretix/control/logdisplay.py:593
#, python-brace-format #, python-brace-format
@@ -27691,11 +27687,11 @@ msgstr ""
"ausgeführt." "ausgeführt."
#: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104 #: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104
#, fuzzy
#| msgid "The voucher \"{voucher}\" has been used in the meantime."
msgid "" msgid ""
"The sync job could not be found. It may have been processed in the meantime." "The sync job could not be found. It may have been processed in the meantime."
msgstr "Der Gutschein \"{voucher}\" wurde zwischenzeitlich verwendet." msgstr ""
"Der Synchronisationsauftrag wurde nicht gefunden. Möglicherweise wurde er "
"zwischenzeitlich bearbeitet."
#: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107 #: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107
msgid "The sync job is already in progress." msgid "The sync job is already in progress."
@@ -28689,12 +28685,12 @@ msgstr ""
"im Anhang dieser E-Mail finden Sie einen neuen Bericht für {name}." "im Anhang dieser E-Mail finden Sie einen neuen Bericht für {name}."
#: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207 #: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid "" msgid ""
"Your user account does not have sufficient permission to run this report, " "Your user account does not have sufficient permission to run this report, "
"therefore you cannot schedule it." "therefore you cannot schedule it."
msgstr "Sie haben keine Berechtigung, um diesen Export durchzuführen." msgstr ""
"Sie haben nicht genug Berechtigungen um diesen Bericht auszuführen, daher "
"können Sie ihn nicht einplanen."
#: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259 #: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259
msgid "" msgid ""
@@ -29061,17 +29057,13 @@ msgstr ""
"Der Termin kann nicht gelöscht werden, da es bereits Bestellungen dafür gibt." "Der Termin kann nicht gelöscht werden, da es bereits Bestellungen dafür gibt."
#: pretix/control/views/subevents.py:203 #: pretix/control/views/subevents.py:203
#, fuzzy
#| msgid ""
#| "The channel could not be deleted as some constraints (e.g. data created "
#| "by plug-ins) did not allow it."
msgctxt "subevent" msgctxt "subevent"
msgid "" msgid ""
"The date could not be deleted as some constraints (e.g. data created by plug-" "The date could not be deleted as some constraints (e.g. data created by plug-"
"ins) did not allow it. The date was disabled instead." "ins) did not allow it. The date was disabled instead."
msgstr "" msgstr ""
"Der Verkaufskanal konnte nicht gelöscht werden, da einige Bedingungen (z.B. " "Der Termin konnte nicht gelöscht werden, da einige Bedingungen (z.B. Daten "
"von Plugins erstellte Daten) es nicht erlauben." "von Plugins) es verhindert haben. Der Termin wurde stattdessen deaktiviert."
#: pretix/control/views/subevents.py:207 #: pretix/control/views/subevents.py:207
msgctxt "subevent" msgctxt "subevent"
@@ -33875,18 +33867,12 @@ msgstr ""
"Bestellung bestätigt werden kann und einen gültigen Vertrag darstellt." "Bestellung bestätigt werden kann und einen gültigen Vertrag darstellt."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192
#, fuzzy
#| msgid ""
#| "After you submitted your order using the button below, it will require "
#| "approval by the event organizer before it can be confirmed and forms a "
#| "valid contract."
msgid "" msgid ""
"After you submitted your order using the button below, it will require " "After you submitted your order using the button below, it will require "
"approval by the event organizer." "approval by the event organizer."
msgstr "" msgstr ""
"Nachdem Sie Ihre Bestellung mit dem untenstehenden Button abgeschickt haben, " "Nachdem Sie Ihre Bestellung mit dem untenstehenden Button abgeschickt haben, "
"erfordert sie noch eine Freigabe durch den Veranstalter, bevor die " "erfordert sie noch eine Freigabe durch den Veranstalter."
"Bestellung bestätigt werden kann und einen gültigen Vertrag darstellt."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195
msgid "" msgid ""
@@ -36491,10 +36477,8 @@ msgid "The selected date does not exist in this event series."
msgstr "Der ausgewählte Termin gehört nicht zu dieser Veranstaltungsreihe." msgstr "Der ausgewählte Termin gehört nicht zu dieser Veranstaltungsreihe."
#: pretix/presale/views/widget.py:412 #: pretix/presale/views/widget.py:412
#, fuzzy
#| msgid "The selected seat \"{seat}\" is not available."
msgid "The selected date is not available." msgid "The selected date is not available."
msgstr "Der ausgewählte Sitzplatz \"{seat}\" ist nicht verfügbar." msgstr "Der gewählte Termin ist nicht verfügbar."
#: pretix/presale/views/widget.py:476 #: pretix/presale/views/widget.py:476
#, python-format #, python-format

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n" "POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-09-23 16:13+0000\n" "PO-Revision-Date: 2025-09-26 13:02+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n" "Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/" "Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix/de_Informal/>\n" "pretix/pretix/de_Informal/>\n"
@@ -809,28 +809,23 @@ msgstr ""
"zunächst die E-Mail-Adresse in deinem Kundenkonto." "zunächst die E-Mail-Adresse in deinem Kundenkonto."
#: pretix/base/datasync/datasync.py:255 #: pretix/base/datasync/datasync.py:255
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" does not exist. Please check your {provider_name} " "Field \"{field_name}\" does not exist. Please check your {provider_name} "
"settings." "settings."
msgstr "" msgstr ""
"Feld \"{field_name}\" ist nicht gültig für {available_inputs}. Bitte prüfe " "Feld \"{field_name}\" existiert nicht. Bitte prüfe die Einstellungen für "
"die Einstellungen für {provider_name}." "{provider_name}."
#: pretix/base/datasync/datasync.py:262 #: pretix/base/datasync/datasync.py:262
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" requires {required_input}, but only got " "Field \"{field_name}\" requires {required_input}, but only got "
"{available_inputs}. Please check your {provider_name} settings." "{available_inputs}. Please check your {provider_name} settings."
msgstr "" msgstr ""
"Feld \"{field_name}\" ist nicht gültig für {available_inputs}. Bitte prüfe " "Feld \"{field_name}\" erfordert {required_input}, aber hat nur "
"die Einstellungen für {provider_name}." "{available_inputs} sind verfügbar. Bitte prüfe die Einstellungen für "
"{provider_name}."
#: pretix/base/datasync/datasync.py:273 #: pretix/base/datasync/datasync.py:273
#, python-brace-format #, python-brace-format
@@ -3548,6 +3543,10 @@ msgid ""
"in accordance with the procedures and terms set forth in No. 89757/2018 of " "in accordance with the procedures and terms set forth in No. 89757/2018 of "
"April 30, 2018, issued by the Director of the Revenue Agency." "April 30, 2018, issued by the Director of the Revenue Agency."
msgstr "" msgstr ""
"Diese PDF-Datei ist eine Sichtkopie der Rechnung und stellt keine Rechnung "
"für Umsatzsteuerzwecke dar. Die Rechnung wurde im XML-Format ausgestellt und "
"im Einklang mit den Regularien aus dem Dekret Nr. 89757/2018 vom 30. April "
"2018 des Direktors der Finanzbehörde übermittelt."
#: pretix/base/invoicing/pdf.py:141 #: pretix/base/invoicing/pdf.py:141
#, python-format #, python-format
@@ -3817,12 +3816,9 @@ msgid "Peppol participant ID"
msgstr "Peppol-Teilnehmer-ID" msgstr "Peppol-Teilnehmer-ID"
#: pretix/base/invoicing/peppol.py:170 #: pretix/base/invoicing/peppol.py:170
#, fuzzy
#| msgctxt "italian_invoice"
#| msgid "Fiscal code"
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
msgid "Visual copy" msgid "Visual copy"
msgstr "Steuernummer" msgstr "Sichtkopie"
#: pretix/base/invoicing/peppol.py:175 #: pretix/base/invoicing/peppol.py:175
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
@@ -3831,6 +3827,9 @@ msgid ""
"invoice for VAT purposes. The original invoice is issued in XML format and " "invoice for VAT purposes. The original invoice is issued in XML format and "
"transmitted through the Peppol network." "transmitted through the Peppol network."
msgstr "" msgstr ""
"Diese PDF-Datei ist eine Sichtkopie der Rechnung und stellt keine Rechnung "
"für Umsatzsteuerzwecke dar. Die Originalrechnung wurde im XML-Format "
"ausgestellt und über das Peppol-Netzwerk übertragen."
#: pretix/base/logentrytype_registry.py:43 #: pretix/base/logentrytype_registry.py:43
msgid "" msgid ""
@@ -14382,10 +14381,8 @@ msgid "Canceled (fully or with paid fee)"
msgstr "Storniert (komplett oder mit Gebühr)" msgstr "Storniert (komplett oder mit Gebühr)"
#: pretix/control/forms/filter.py:228 #: pretix/control/forms/filter.py:228
#, fuzzy
#| msgid "Cancel this position"
msgid "Canceled (at least one position)" msgid "Canceled (at least one position)"
msgstr "Diese Position stornieren" msgstr "Storniert (mindestens eine Position)"
#: pretix/control/forms/filter.py:229 #: pretix/control/forms/filter.py:229
msgid "Cancellation requested" msgid "Cancellation requested"
@@ -17125,10 +17122,9 @@ msgid "The voucher has been deleted."
msgstr "Der Gutschein wurde gelöscht." msgstr "Der Gutschein wurde gelöscht."
#: pretix/control/logdisplay.py:584 #: pretix/control/logdisplay.py:584
#, fuzzy, python-brace-format #, python-brace-format
#| msgid "The voucher has been sent to {email} through the waiting list."
msgid "The voucher has been assigned to {email} through the waiting list." msgid "The voucher has been assigned to {email} through the waiting list."
msgstr "Der Gutschein wurde über die Warteliste an {email} verschickt." msgstr "Der Gutschein wurde über die Warteliste an {email} zugewiesen."
#: pretix/control/logdisplay.py:593 #: pretix/control/logdisplay.py:593
#, python-brace-format #, python-brace-format
@@ -27650,11 +27646,11 @@ msgstr ""
"ausgeführt." "ausgeführt."
#: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104 #: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104
#, fuzzy
#| msgid "The voucher \"{voucher}\" has been used in the meantime."
msgid "" msgid ""
"The sync job could not be found. It may have been processed in the meantime." "The sync job could not be found. It may have been processed in the meantime."
msgstr "Der Gutschein \"{voucher}\" wurde zwischenzeitlich verwendet." msgstr ""
"Der Synchronisationsauftrag wurde nicht gefunden. Möglicherweise wurde er "
"zwischenzeitlich bearbeitet."
#: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107 #: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107
msgid "The sync job is already in progress." msgid "The sync job is already in progress."
@@ -28645,12 +28641,12 @@ msgstr ""
"im Anhang dieser E-Mail findest du einen neuen Bericht für {name}." "im Anhang dieser E-Mail findest du einen neuen Bericht für {name}."
#: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207 #: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid "" msgid ""
"Your user account does not have sufficient permission to run this report, " "Your user account does not have sufficient permission to run this report, "
"therefore you cannot schedule it." "therefore you cannot schedule it."
msgstr "Du hast nicht die nötige Berechtigung, um diesen Export durchzuführen." msgstr ""
"Du hast nicht genug Berechtigungen um diesen Bericht auszuführen, daher "
"kannst du ihn nicht einplanen."
#: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259 #: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259
msgid "" msgid ""
@@ -29017,17 +29013,13 @@ msgstr ""
"Der Termin kann nicht gelöscht werden, da es bereits Bestellungen dafür gibt." "Der Termin kann nicht gelöscht werden, da es bereits Bestellungen dafür gibt."
#: pretix/control/views/subevents.py:203 #: pretix/control/views/subevents.py:203
#, fuzzy
#| msgid ""
#| "The channel could not be deleted as some constraints (e.g. data created "
#| "by plug-ins) did not allow it."
msgctxt "subevent" msgctxt "subevent"
msgid "" msgid ""
"The date could not be deleted as some constraints (e.g. data created by plug-" "The date could not be deleted as some constraints (e.g. data created by plug-"
"ins) did not allow it. The date was disabled instead." "ins) did not allow it. The date was disabled instead."
msgstr "" msgstr ""
"Der Verkaufskanal konnte nicht gelöscht werden, da einige Bedingungen (z.B. " "Der Termin konnte nicht gelöscht werden, da einige Bedingungen (z.B. Daten "
"von Plugins erstellte Daten) es nicht erlauben." "von Plugins) es verhindert haben. Der Termin wurde stattdessen deaktiviert."
#: pretix/control/views/subevents.py:207 #: pretix/control/views/subevents.py:207
msgctxt "subevent" msgctxt "subevent"
@@ -33820,18 +33812,12 @@ msgstr ""
"Bestellung bestätigt werden kann und einen gültigen Vertrag darstellt." "Bestellung bestätigt werden kann und einen gültigen Vertrag darstellt."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192
#, fuzzy
#| msgid ""
#| "After you submitted your order using the button below, it will require "
#| "approval by the event organizer before it can be confirmed and forms a "
#| "valid contract."
msgid "" msgid ""
"After you submitted your order using the button below, it will require " "After you submitted your order using the button below, it will require "
"approval by the event organizer." "approval by the event organizer."
msgstr "" msgstr ""
"Nachdem du deine Bestellung mit dem untenstehenden Button abgeschickt hast, " "Nachdem du deine Bestellung mit dem untenstehenden Button abgeschickt hast, "
"erfordert sie noch eine Freigabe durch den Veranstalter, bevor die " "erfordert sie noch eine Freigabe durch den Veranstalter."
"Bestellung bestätigt werden kann und einen gültigen Vertrag darstellt."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195
msgid "" msgid ""
@@ -36431,10 +36417,8 @@ msgid "The selected date does not exist in this event series."
msgstr "Der ausgewählte Termin gehört nicht zu dieser Veranstaltungsreihe." msgstr "Der ausgewählte Termin gehört nicht zu dieser Veranstaltungsreihe."
#: pretix/presale/views/widget.py:412 #: pretix/presale/views/widget.py:412
#, fuzzy
#| msgid "The selected seat \"{seat}\" is not available."
msgid "The selected date is not available." msgid "The selected date is not available."
msgstr "Der ausgewählte Sitzplatz \"{seat}\" ist nicht verfügbar." msgstr "Der gewählte Termin ist nicht verfügbar."
#: pretix/presale/views/widget.py:476 #: pretix/presale/views/widget.py:476
#, python-format #, python-format

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n" "POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-08-30 02:00+0000\n" "PO-Revision-Date: 2025-09-30 16:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n" "Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/" "Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n" "es/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13\n" "X-Generator: Weblate 5.13.3\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -810,28 +810,23 @@ msgstr ""
"Primero verifique la dirección de correo electrónico en su cuenta de cliente." "Primero verifique la dirección de correo electrónico en su cuenta de cliente."
#: pretix/base/datasync/datasync.py:255 #: pretix/base/datasync/datasync.py:255
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" does not exist. Please check your {provider_name} " "Field \"{field_name}\" does not exist. Please check your {provider_name} "
"settings." "settings."
msgstr "" msgstr ""
"El campo «{field_name}» no es válido para {available_inputs}. Comprueba la " "El campo «{field_name}» no existe. Comprueba la configuración de "
"configuración de {provider_name}." "{provider_name}."
#: pretix/base/datasync/datasync.py:262 #: pretix/base/datasync/datasync.py:262
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" requires {required_input}, but only got " "Field \"{field_name}\" requires {required_input}, but only got "
"{available_inputs}. Please check your {provider_name} settings." "{available_inputs}. Please check your {provider_name} settings."
msgstr "" msgstr ""
"El campo «{field_name}» no es válido para {available_inputs}. Comprueba la " "El campo «{field_name}» requiere {required_input}, pero solo se ha "
"configuración de {provider_name}." "introducido {available_inputs}. Comprueba la configuración de "
"{provider_name}."
#: pretix/base/datasync/datasync.py:273 #: pretix/base/datasync/datasync.py:273
#, python-brace-format #, python-brace-format
@@ -3559,6 +3554,11 @@ msgid ""
"in accordance with the procedures and terms set forth in No. 89757/2018 of " "in accordance with the procedures and terms set forth in No. 89757/2018 of "
"April 30, 2018, issued by the Director of the Revenue Agency." "April 30, 2018, issued by the Director of the Revenue Agency."
msgstr "" msgstr ""
"Este documento PDF es una copia visual de la factura y no constituye una "
"factura a efectos del IVA. La factura se emite en formato XML, transmitida "
"de acuerdo con los procedimientos y términos establecidos en el n.º 89757/"
"2018, de 30 de abril de 2018, emitido por el Director de la Agencia "
"Tributaria."
#: pretix/base/invoicing/pdf.py:141 #: pretix/base/invoicing/pdf.py:141
#, python-format #, python-format
@@ -3829,12 +3829,9 @@ msgid "Peppol participant ID"
msgstr "Identificador de participante Peppol" msgstr "Identificador de participante Peppol"
#: pretix/base/invoicing/peppol.py:170 #: pretix/base/invoicing/peppol.py:170
#, fuzzy
#| msgctxt "italian_invoice"
#| msgid "Fiscal code"
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
msgid "Visual copy" msgid "Visual copy"
msgstr "Código fiscal" msgstr "Copia visual"
#: pretix/base/invoicing/peppol.py:175 #: pretix/base/invoicing/peppol.py:175
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
@@ -3843,6 +3840,9 @@ msgid ""
"invoice for VAT purposes. The original invoice is issued in XML format and " "invoice for VAT purposes. The original invoice is issued in XML format and "
"transmitted through the Peppol network." "transmitted through the Peppol network."
msgstr "" msgstr ""
"Este documento PDF es una copia visual de la factura y no constituye una "
"factura a efectos del IVA. La factura original se emite en formato XML y se "
"transmite a través de la red Peppol."
#: pretix/base/logentrytype_registry.py:43 #: pretix/base/logentrytype_registry.py:43
msgid "" msgid ""
@@ -14387,10 +14387,8 @@ msgid "Canceled (fully or with paid fee)"
msgstr "Cancelado (totalmente o con tarifa pagada)" msgstr "Cancelado (totalmente o con tarifa pagada)"
#: pretix/control/forms/filter.py:228 #: pretix/control/forms/filter.py:228
#, fuzzy
#| msgid "Cancel this position"
msgid "Canceled (at least one position)" msgid "Canceled (at least one position)"
msgstr "Cancelar posición" msgstr "Cancelado (al menos una posición)"
#: pretix/control/forms/filter.py:229 #: pretix/control/forms/filter.py:229
msgid "Cancellation requested" msgid "Cancellation requested"
@@ -17130,10 +17128,9 @@ msgid "The voucher has been deleted."
msgstr "El vale de compra fue eliminado." msgstr "El vale de compra fue eliminado."
#: pretix/control/logdisplay.py:584 #: pretix/control/logdisplay.py:584
#, fuzzy, python-brace-format #, python-brace-format
#| msgid "The voucher has been sent to {email} through the waiting list."
msgid "The voucher has been assigned to {email} through the waiting list." msgid "The voucher has been assigned to {email} through the waiting list."
msgstr "El vale se ha enviado a {email} a través de la lista de espera." msgstr "El vale se ha asignado a {email} a través de la lista de espera."
#: pretix/control/logdisplay.py:593 #: pretix/control/logdisplay.py:593
#, python-brace-format #, python-brace-format
@@ -27677,11 +27674,11 @@ msgstr ""
"próximos minutos." "próximos minutos."
#: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104 #: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104
#, fuzzy
#| msgid "The voucher \"{voucher}\" has been used in the meantime."
msgid "" msgid ""
"The sync job could not be found. It may have been processed in the meantime." "The sync job could not be found. It may have been processed in the meantime."
msgstr "El vale de compra {voucher} ya ha sido utilizado." msgstr ""
"No se ha encontrado la tarea de sincronización. Es posible que se haya "
"procesado mientras tanto."
#: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107 #: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107
msgid "The sync job is already in progress." msgid "The sync job is already in progress."
@@ -28674,12 +28671,12 @@ msgstr ""
"programado para {name}." "programado para {name}."
#: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207 #: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid "" msgid ""
"Your user account does not have sufficient permission to run this report, " "Your user account does not have sufficient permission to run this report, "
"therefore you cannot schedule it." "therefore you cannot schedule it."
msgstr "No tiene permiso suficiente para realizar esta exportación." msgstr ""
"Su cuenta de usuario no tiene permisos suficientes para ejecutar este "
"informe, por lo que no puede programarlo."
#: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259 #: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259
msgid "" msgid ""
@@ -29044,17 +29041,14 @@ msgid "A date can not be deleted if orders already have been placed."
msgstr "No se puede borrar una fecha si ya se han realizado pedidos." msgstr "No se puede borrar una fecha si ya se han realizado pedidos."
#: pretix/control/views/subevents.py:203 #: pretix/control/views/subevents.py:203
#, fuzzy
#| msgid ""
#| "The channel could not be deleted as some constraints (e.g. data created "
#| "by plug-ins) did not allow it."
msgctxt "subevent" msgctxt "subevent"
msgid "" msgid ""
"The date could not be deleted as some constraints (e.g. data created by plug-" "The date could not be deleted as some constraints (e.g. data created by plug-"
"ins) did not allow it. The date was disabled instead." "ins) did not allow it. The date was disabled instead."
msgstr "" msgstr ""
"El canal no ha podido borrarse porque algunas restricciones (por ejemplo, " "La fecha no se pudo eliminar debido a que algunas restricciones (por "
"datos creados por plug-ins) no lo permitían." "ejemplo, datos creados por complementos) no lo permitían. En su lugar, se "
"desactivó la fecha."
#: pretix/control/views/subevents.py:207 #: pretix/control/views/subevents.py:207
msgctxt "subevent" msgctxt "subevent"
@@ -33878,18 +33872,12 @@ msgstr ""
"formar un contrato válido." "formar un contrato válido."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192
#, fuzzy
#| msgid ""
#| "After you submitted your order using the button below, it will require "
#| "approval by the event organizer before it can be confirmed and forms a "
#| "valid contract."
msgid "" msgid ""
"After you submitted your order using the button below, it will require " "After you submitted your order using the button below, it will require "
"approval by the event organizer." "approval by the event organizer."
msgstr "" msgstr ""
"Después de enviar su pedido usando el botón a continuación, requerirá la " "Después de enviar el pedido mediante el botón que aparece a continuación, "
"aprobación del organizador del evento antes de que pueda confirmarse y " "será necesario que el organizador del evento lo apruebe."
"formar un contrato válido."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195
msgid "" msgid ""
@@ -36448,10 +36436,8 @@ msgid "The selected date does not exist in this event series."
msgstr "La fecha seleccionada no existe en esta serie de eventos." msgstr "La fecha seleccionada no existe en esta serie de eventos."
#: pretix/presale/views/widget.py:412 #: pretix/presale/views/widget.py:412
#, fuzzy
#| msgid "The selected seat \"{seat}\" is not available."
msgid "The selected date is not available." msgid "The selected date is not available."
msgstr "El asiento seleccionado {seat} no está disponible." msgstr "La fecha seleccionada no está disponible."
#: pretix/presale/views/widget.py:476 #: pretix/presale/views/widget.py:476
#, python-format #, python-format

View File

@@ -4,8 +4,8 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n" "POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-08-29 17:00+0000\n" "PO-Revision-Date: 2025-09-30 16:00+0000\n"
"Last-Translator: patch-works-be <webmaster@patch-works.be>\n" "Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/" "Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n" ">\n"
"Language: fr\n" "Language: fr\n"
@@ -13,7 +13,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n" "Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.13\n" "X-Generator: Weblate 5.13.3\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -810,28 +810,22 @@ msgstr ""
"mail dans votre espace client." "mail dans votre espace client."
#: pretix/base/datasync/datasync.py:255 #: pretix/base/datasync/datasync.py:255
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" does not exist. Please check your {provider_name} " "Field \"{field_name}\" does not exist. Please check your {provider_name} "
"settings." "settings."
msgstr "" msgstr ""
"Le champ « {field_name} » n'est pas valide pour {available_inputs}. Veuillez " "Le champ « {field_name} » n'existe pas. Veuillez vérifier vos paramètres "
"vérifier vos paramètres {provider_name}." "pour {provider_name}."
#: pretix/base/datasync/datasync.py:262 #: pretix/base/datasync/datasync.py:262
#, fuzzy, python-brace-format #, python-brace-format
#| msgid ""
#| "Field \"{field_name}\" is not valid for {available_inputs}. Please check "
#| "your {provider_name} settings."
msgid "" msgid ""
"Field \"{field_name}\" requires {required_input}, but only got " "Field \"{field_name}\" requires {required_input}, but only got "
"{available_inputs}. Please check your {provider_name} settings." "{available_inputs}. Please check your {provider_name} settings."
msgstr "" msgstr ""
"Le champ « {field_name} » n'est pas valide pour {available_inputs}. Veuillez " "Le champ « {field_name} » nécessite {required_input}, mais n'a reçu que "
"vérifier vos paramètres {provider_name}." "{available_inputs}. Veuillez vérifier vos paramètres pour {provider_name}."
#: pretix/base/datasync/datasync.py:273 #: pretix/base/datasync/datasync.py:273
#, python-brace-format #, python-brace-format
@@ -3564,6 +3558,10 @@ msgid ""
"in accordance with the procedures and terms set forth in No. 89757/2018 of " "in accordance with the procedures and terms set forth in No. 89757/2018 of "
"April 30, 2018, issued by the Director of the Revenue Agency." "April 30, 2018, issued by the Director of the Revenue Agency."
msgstr "" msgstr ""
"Ce document PDF est une copie visuelle de la facture et ne constitue pas une "
"facture aux fins de la TVA. La facture est émise au format XML, transmise "
"conformément aux procédures et conditions énoncées dans le n° 89757/2018 du "
"30 avril 2018, émis par le directeur de l'Agence des recettes."
#: pretix/base/invoicing/pdf.py:141 #: pretix/base/invoicing/pdf.py:141
#, python-format #, python-format
@@ -3834,12 +3832,9 @@ msgid "Peppol participant ID"
msgstr "Identifiant participant Peppol" msgstr "Identifiant participant Peppol"
#: pretix/base/invoicing/peppol.py:170 #: pretix/base/invoicing/peppol.py:170
#, fuzzy
#| msgctxt "italian_invoice"
#| msgid "Fiscal code"
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
msgid "Visual copy" msgid "Visual copy"
msgstr "Code fiscal" msgstr "Copie visuelle"
#: pretix/base/invoicing/peppol.py:175 #: pretix/base/invoicing/peppol.py:175
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
@@ -3848,6 +3843,9 @@ msgid ""
"invoice for VAT purposes. The original invoice is issued in XML format and " "invoice for VAT purposes. The original invoice is issued in XML format and "
"transmitted through the Peppol network." "transmitted through the Peppol network."
msgstr "" msgstr ""
"Ce document PDF est une reproduction visuelle de la facture et ne constitue "
"pas une facture au sens de la TVA. La facture originale est émise au format "
"XML et transmise via le réseau Peppol."
#: pretix/base/logentrytype_registry.py:43 #: pretix/base/logentrytype_registry.py:43
msgid "" msgid ""
@@ -14513,10 +14511,8 @@ msgid "Canceled (fully or with paid fee)"
msgstr "Annulé (entièrement ou avec des frais payés)" msgstr "Annulé (entièrement ou avec des frais payés)"
#: pretix/control/forms/filter.py:228 #: pretix/control/forms/filter.py:228
#, fuzzy
#| msgid "Cancel this position"
msgid "Canceled (at least one position)" msgid "Canceled (at least one position)"
msgstr "Annuler cette position" msgstr "Annulé (au moins pour une position)"
#: pretix/control/forms/filter.py:229 #: pretix/control/forms/filter.py:229
msgid "Cancellation requested" msgid "Cancellation requested"
@@ -17268,10 +17264,9 @@ msgid "The voucher has been deleted."
msgstr "Le bon a été supprimé." msgstr "Le bon a été supprimé."
#: pretix/control/logdisplay.py:584 #: pretix/control/logdisplay.py:584
#, fuzzy, python-brace-format #, python-brace-format
#| msgid "The voucher has been sent to {email} through the waiting list."
msgid "The voucher has been assigned to {email} through the waiting list." msgid "The voucher has been assigned to {email} through the waiting list."
msgstr "Le bon a été envoyé à {email} via la liste d'attente." msgstr "Le bon a été attribué à {email} via de la liste d'attente."
#: pretix/control/logdisplay.py:593 #: pretix/control/logdisplay.py:593
#, python-brace-format #, python-brace-format
@@ -27886,11 +27881,11 @@ msgstr ""
"les prochaines minutes." "les prochaines minutes."
#: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104 #: pretix/control/views/datasync.py:90 pretix/control/views/datasync.py:104
#, fuzzy
#| msgid "The voucher \"{voucher}\" has been used in the meantime."
msgid "" msgid ""
"The sync job could not be found. It may have been processed in the meantime." "The sync job could not be found. It may have been processed in the meantime."
msgstr "Le bon de réduction \"{voucher}\" a été utilisé entre-temps." msgstr ""
"La tâche de synchronisation est introuvable. Elle a peut-être été traitée "
"entre-temps."
#: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107 #: pretix/control/views/datasync.py:93 pretix/control/views/datasync.py:107
msgid "The sync job is already in progress." msgid "The sync job is already in progress."
@@ -28894,14 +28889,12 @@ msgstr ""
"planifié pour {name}." "planifié pour {name}."
#: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207 #: pretix/control/views/orders.py:2849 pretix/control/views/organizer.py:2207
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid "" msgid ""
"Your user account does not have sufficient permission to run this report, " "Your user account does not have sufficient permission to run this report, "
"therefore you cannot schedule it." "therefore you cannot schedule it."
msgstr "" msgstr ""
"Vous ne disposez pas des autorisations suffisantes pour effectuer cette " "Votre compte utilisateur ne dispose pas des autorisations suffisantes pour "
"exportation." "exécuter ce rapport, vous ne pouvez donc pas le planifier."
#: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259 #: pretix/control/views/orders.py:2902 pretix/control/views/organizer.py:2259
msgid "" msgid ""
@@ -29272,17 +29265,14 @@ msgid "A date can not be deleted if orders already have been placed."
msgstr "Une date ne peut pas être supprimée si des ordres ont déjà été passés." msgstr "Une date ne peut pas être supprimée si des ordres ont déjà été passés."
#: pretix/control/views/subevents.py:203 #: pretix/control/views/subevents.py:203
#, fuzzy
#| msgid ""
#| "The channel could not be deleted as some constraints (e.g. data created "
#| "by plug-ins) did not allow it."
msgctxt "subevent" msgctxt "subevent"
msgid "" msgid ""
"The date could not be deleted as some constraints (e.g. data created by plug-" "The date could not be deleted as some constraints (e.g. data created by plug-"
"ins) did not allow it. The date was disabled instead." "ins) did not allow it. The date was disabled instead."
msgstr "" msgstr ""
"Le canal de vente n'a pas pu être supprimé car certaines contraintes (par " "La date n'a pas pu être supprimée car certaines contraintes (par exemple, "
"exemple, les données créées par les plug-ins) ne le permettent pas." "les données créées par les plug-ins) ne le permettaient pas. La date a donc "
"été désactivée."
#: pretix/control/views/subevents.py:207 #: pretix/control/views/subevents.py:207
msgctxt "subevent" msgctxt "subevent"
@@ -34147,18 +34137,12 @@ msgstr ""
"puisse être confirmée et forme un contrat valide." "puisse être confirmée et forme un contrat valide."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:192
#, fuzzy
#| msgid ""
#| "After you submitted your order using the button below, it will require "
#| "approval by the event organizer before it can be confirmed and forms a "
#| "valid contract."
msgid "" msgid ""
"After you submitted your order using the button below, it will require " "After you submitted your order using the button below, it will require "
"approval by the event organizer." "approval by the event organizer."
msgstr "" msgstr ""
"Après avoir soumis votre commande en utilisant le bouton ci-dessous, elle " "Une fois que vous aurez soumis votre commande à l'aide du bouton ci-dessous, "
"nécessitera lapprobation de lorganisateur de lévénement avant quelle " "celle-ci devra être approuvée par l'organisateur de l'événement."
"puisse être confirmée et forme un contrat valide."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195 #: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:195
msgid "" msgid ""
@@ -36776,10 +36760,8 @@ msgid "The selected date does not exist in this event series."
msgstr "La date sélectionnée nexiste pas dans cette série dévénements." msgstr "La date sélectionnée nexiste pas dans cette série dévénements."
#: pretix/presale/views/widget.py:412 #: pretix/presale/views/widget.py:412
#, fuzzy
#| msgid "The selected seat \"{seat}\" is not available."
msgid "The selected date is not available." msgid "The selected date is not available."
msgstr "La place {seat} sélectionné n'est pas disponible." msgstr "La date sélectionnée n'est pas disponible."
#: pretix/presale/views/widget.py:476 #: pretix/presale/views/widget.py:476
#, python-format #, python-format

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n" "POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-09-10 21:00+0000\n" "PO-Revision-Date: 2025-09-26 13:02+0000\n"
"Last-Translator: Davide Wayan Mores <racecondition99@gmail.com>\n" "Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/" "Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
"it/>\n" "it/>\n"
"Language: it\n" "Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.2\n" "X-Generator: Weblate 5.13.3\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -3588,6 +3588,10 @@ msgid ""
"in accordance with the procedures and terms set forth in No. 89757/2018 of " "in accordance with the procedures and terms set forth in No. 89757/2018 of "
"April 30, 2018, issued by the Director of the Revenue Agency." "April 30, 2018, issued by the Director of the Revenue Agency."
msgstr "" msgstr ""
"Il presente documento pdf non costituisce fattura ai fini IVA. La fattura si "
"intende emessa nel formato xml trasmesso nei modi e termini di cui al "
"Provvedimento n. 89757/2018 del 30 aprile 2018 del direttore dell'Agenzia "
"delle Entrate."
#: pretix/base/invoicing/pdf.py:141 #: pretix/base/invoicing/pdf.py:141
#, python-format #, python-format

File diff suppressed because it is too large Load Diff

View File

@@ -7,16 +7,16 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n" "POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-08-26 19:00+0000\n" "PO-Revision-Date: 2025-09-30 01:00+0000\n"
"Last-Translator: Jan Van Haver <jan.van.haver@gmail.com>\n" "Last-Translator: Jan Van Haver <jan.van.haver@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/" "Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
">\n" "\n"
"Language: nl\n" "Language: nl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13\n" "X-Generator: Weblate 5.13.3\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -3808,6 +3808,8 @@ msgstr "Evenementdatum: {date_range}"
msgid "" msgid ""
"A Peppol participant ID always starts with a prefix, followed by a colon (:)." "A Peppol participant ID always starts with a prefix, followed by a colon (:)."
msgstr "" msgstr ""
"Een Peppol deelnemer-ID begint altijd met een prefix, gevolgd door een "
"dubbele punt (:)."
#: pretix/base/invoicing/peppol.py:132 #: pretix/base/invoicing/peppol.py:132
#, python-format #, python-format
@@ -3815,6 +3817,8 @@ msgid ""
"The Peppol participant ID prefix %(number)s is not known to our system. " "The Peppol participant ID prefix %(number)s is not known to our system. "
"Please reach out to us if you are sure this ID is correct." "Please reach out to us if you are sure this ID is correct."
msgstr "" msgstr ""
"De prefix van de Peppol deelnemer-ID %(number)s is niet bekend in ons "
"systeem. Neem contact met ons op als u zeker weet dat deze ID correct is."
#: pretix/base/invoicing/peppol.py:136 #: pretix/base/invoicing/peppol.py:136
#, python-format #, python-format
@@ -3822,17 +3826,18 @@ msgid ""
"The Peppol participant ID does not match the validation rules for the prefix " "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." "%(number)s. Please reach out to us if you are sure this ID is correct."
msgstr "" msgstr ""
"De Peppol deelnemer-ID komt niet overeen met de validatieregels voor het "
"prefix %(number)s. Neem contact met ons op als u zeker weet dat deze ID "
"correct is."
#: pretix/base/invoicing/peppol.py:156 #: pretix/base/invoicing/peppol.py:156
msgid "Peppol participant ID" msgid "Peppol participant ID"
msgstr "" msgstr "Peppol deelnemer-ID"
#: pretix/base/invoicing/peppol.py:170 #: pretix/base/invoicing/peppol.py:170
#, fuzzy
#| msgid "Gift card code"
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
msgid "Visual copy" msgid "Visual copy"
msgstr "Cadeauboncode" msgstr "Visuele kopie"
#: pretix/base/invoicing/peppol.py:175 #: pretix/base/invoicing/peppol.py:175
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
@@ -3841,6 +3846,9 @@ msgid ""
"invoice for VAT purposes. The original invoice is issued in XML format and " "invoice for VAT purposes. The original invoice is issued in XML format and "
"transmitted through the Peppol network." "transmitted through the Peppol network."
msgstr "" msgstr ""
"Dit PDF-document is een visuele kopie van de factuur en vormt geen factuur "
"voor BTW-doeleinden. De originele factuur wordt opgemaakt in XML-formaat en "
"verzonden via het Peppol-netwerk."
#: pretix/base/logentrytype_registry.py:43 #: pretix/base/logentrytype_registry.py:43
msgid "" msgid ""

View File

@@ -170,7 +170,7 @@ OPTIONS = OrderedDict([
'cols': 2, 'cols': 2,
'rows': 6, 'rows': 6,
'margins': [28.5 * mm, 30 * mm, 28.5 * mm, 30 * mm], 'margins': [28.5 * mm, 30 * mm, 28.5 * mm, 30 * mm],
'offsets': [93 * mm, 60 * mm], 'offsets': [75 * mm, 40 * mm],
'pagesize': pagesizes.A4, 'pagesize': pagesizes.A4,
}), }),
('herma_50x80', { ('herma_50x80', {

View File

@@ -8,7 +8,7 @@
"name": "pretix", "name": "pretix",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@babel/core": "^7.28.3", "@babel/core": "^7.28.4",
"@babel/preset-env": "^7.28.3", "@babel/preset-env": "^7.28.3",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
@@ -18,18 +18,6 @@
"vue-template-compiler": "^2.7.16" "vue-template-compiler": "^2.7.16"
} }
}, },
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
"integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.1.0",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -53,20 +41,20 @@
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3", "@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.3", "@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.3", "@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.3", "@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.2", "@babel/types": "^7.28.4",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
@@ -115,15 +103,6 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@babel/helper-annotate-as-pure": { "node_modules/@babel/helper-annotate-as-pure": {
"version": "7.27.3", "version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
@@ -401,23 +380,23 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"dependencies": { "dependencies": {
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/types": "^7.28.2" "@babel/types": "^7.28.4"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.2" "@babel/types": "^7.28.4"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -1475,16 +1454,16 @@
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0", "@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.3", "@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/types": "^7.28.2", "@babel/types": "^7.28.4",
"debug": "^4.3.1" "debug": "^4.3.1"
}, },
"engines": { "engines": {
@@ -1492,9 +1471,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.2", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.27.1"
@@ -1504,15 +1483,21 @@
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.1.1", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/trace-mapping": "^0.3.24"
}, }
"engines": { },
"node": ">=6.0.0" "node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
} }
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
@@ -1523,14 +1508,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
@@ -3802,15 +3779,6 @@
} }
}, },
"dependencies": { "dependencies": {
"@ampproject/remapping": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
"integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
"requires": {
"@jridgewell/gen-mapping": "^0.1.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -3827,20 +3795,20 @@
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==" "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="
}, },
"@babel/core": { "@babel/core": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"requires": { "requires": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3", "@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.3", "@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.3", "@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.3", "@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.2", "@babel/types": "^7.28.4",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
@@ -3870,17 +3838,6 @@
"@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28", "@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2" "jsesc": "^3.0.2"
},
"dependencies": {
"@jridgewell/gen-mapping": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"requires": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
}
} }
}, },
"@babel/helper-annotate-as-pure": { "@babel/helper-annotate-as-pure": {
@@ -4074,20 +4031,20 @@
} }
}, },
"@babel/helpers": { "@babel/helpers": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"requires": { "requires": {
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/types": "^7.28.2" "@babel/types": "^7.28.4"
} }
}, },
"@babel/parser": { "@babel/parser": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"requires": { "requires": {
"@babel/types": "^7.28.2" "@babel/types": "^7.28.4"
} }
}, },
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": { "@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
@@ -4718,35 +4675,44 @@
} }
}, },
"@babel/traverse": { "@babel/traverse": {
"version": "7.28.3", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
"requires": { "requires": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3", "@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0", "@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.3", "@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2", "@babel/template": "^7.27.2",
"@babel/types": "^7.28.2", "@babel/types": "^7.28.4",
"debug": "^4.3.1" "debug": "^4.3.1"
} }
}, },
"@babel/types": { "@babel/types": {
"version": "7.28.2", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"requires": { "requires": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.27.1"
} }
}, },
"@jridgewell/gen-mapping": { "@jridgewell/gen-mapping": {
"version": "0.1.1", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"requires": { "requires": {
"@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/trace-mapping": "^0.3.24"
}
},
"@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"requires": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
} }
}, },
"@jridgewell/resolve-uri": { "@jridgewell/resolve-uri": {
@@ -4754,11 +4720,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
}, },
"@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
},
"@jridgewell/sourcemap-codec": { "@jridgewell/sourcemap-codec": {
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",

View File

@@ -4,7 +4,7 @@
"private": true, "private": true,
"scripts": {}, "scripts": {},
"dependencies": { "dependencies": {
"@babel/core": "^7.28.3", "@babel/core": "^7.28.4",
"@babel/preset-env": "^7.28.3", "@babel/preset-env": "^7.28.3",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",

View File

@@ -231,7 +231,9 @@ TEST_INVOICE_RES = {
"description": "Budget Ticket<br />Attendee: Peter", "description": "Budget Ticket<br />Attendee: Peter",
'subevent': None, 'subevent': None,
'event_date_from': '2017-12-27T10:00:00Z', 'event_date_from': '2017-12-27T10:00:00Z',
'event_date_to': None, 'event_date_to': '2017-12-27T10:00:00Z',
'period_start': '2017-12-27T10:00:00Z',
'period_end': '2017-12-27T10:00:00Z',
'event_location': None, 'event_location': None,
'attendee_name': 'Peter', 'attendee_name': 'Peter',
'item': None, 'item': None,
@@ -249,7 +251,9 @@ TEST_INVOICE_RES = {
"description": "Payment fee", "description": "Payment fee",
'subevent': None, 'subevent': None,
'event_date_from': '2017-12-27T10:00:00Z', 'event_date_from': '2017-12-27T10:00:00Z',
'event_date_to': None, 'event_date_to': '2017-12-27T10:00:00Z',
'period_start': '2017-12-27T10:00:00Z',
'period_end': '2017-12-27T10:00:00Z',
'event_location': None, 'event_location': None,
'attendee_name': None, 'attendee_name': None,
'fee_type': "payment", 'fee_type': "payment",

View File

@@ -608,7 +608,9 @@ def test_order_create_invoice(token_client, organizer, event, order):
'description': 'Budget Ticket<br />Attendee: Peter', 'description': 'Budget Ticket<br />Attendee: Peter',
'subevent': None, 'subevent': None,
'event_date_from': '2017-12-27T10:00:00Z', 'event_date_from': '2017-12-27T10:00:00Z',
'event_date_to': None, 'event_date_to': '2017-12-27T10:00:00Z',
'period_start': '2017-12-27T10:00:00Z',
'period_end': '2017-12-27T10:00:00Z',
'event_location': None, 'event_location': None,
'fee_type': None, 'fee_type': None,
'fee_internal_type': None, 'fee_internal_type': None,
@@ -626,7 +628,9 @@ def test_order_create_invoice(token_client, organizer, event, order):
'description': 'Payment fee', 'description': 'Payment fee',
'subevent': None, 'subevent': None,
'event_date_from': '2017-12-27T10:00:00Z', 'event_date_from': '2017-12-27T10:00:00Z',
'event_date_to': None, 'event_date_to': '2017-12-27T10:00:00Z',
'period_start': '2017-12-27T10:00:00Z',
'period_end': '2017-12-27T10:00:00Z',
'event_location': None, 'event_location': None,
'fee_type': "payment", 'fee_type': "payment",
'fee_internal_type': None, 'fee_internal_type': None,

View File

@@ -327,6 +327,26 @@ def test_enqueue_order_twice(event):
SimpleOrderSync.enqueue_order(order, 'testcase_2nd') SimpleOrderSync.enqueue_order(order, 'testcase_2nd')
class DoNothingSync(SimpleOrderSync):
def should_sync_order(self, order):
return False
@pytest.mark.django_db
def test_should_not_sync(event):
_register_with_fake_plugin_name(datasync_providers, DoNothingSync, 'testplugin')
DoNothingSync.fake_api_client = FakeSyncAPI()
for order in event.orders.order_by("code").all():
DoNothingSync.enqueue_order(order, 'testcase')
sync_all()
assert DoNothingSync.fake_api_client.fake_database == {}
StaticMappingWithAssociations = namedtuple('StaticMappingWithAssociations', ( StaticMappingWithAssociations = namedtuple('StaticMappingWithAssociations', (
'id', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mappings', 'association_mappings' 'id', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mappings', 'association_mappings'
)) ))

View File

@@ -33,7 +33,7 @@
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
import json import json
from datetime import date, timedelta from datetime import date, datetime, timedelta, timezone
from decimal import Decimal from decimal import Decimal
import pytest import pytest
@@ -42,6 +42,7 @@ from django.utils.itercompat import is_iterable
from django.utils.timezone import now from django.utils.timezone import now
from django_countries.fields import Country from django_countries.fields import Country
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString
from pretix.base.invoice import addon_aware_groupby from pretix.base.invoice import addon_aware_groupby
from pretix.base.models import ( from pretix.base.models import (
@@ -62,7 +63,8 @@ def env():
with scope(organizer=o): with scope(organizer=o):
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=datetime(2024, 12, 1, 9, 0, 0, tzinfo=timezone.utc),
plugins='pretix.plugins.banktransfer'
) )
o = Order.objects.create( o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test', code='FOO', event=event, email='dummy@dummy.test',
@@ -660,3 +662,154 @@ def test_addon_aware_groupby():
[True, 102, 3.00], [True, 102, 3.00],
]], ]],
] ]
@pytest.mark.django_db
@pytest.mark.parametrize("period", ["auto", "event_date"])
def test_period_from_event_start(env, period):
event, order = env
event.settings.invoice_period = period
inv = generate_invoice(order)
l1 = inv.lines.first()
assert l1.period_start == event.date_from
assert l1.period_end == event.date_from
@pytest.mark.django_db
@pytest.mark.parametrize("period", ["auto", "event_date"])
def test_period_from_event_range(env, period):
event, order = env
event.date_to = event.date_from + timedelta(days=1)
event.settings.invoice_period = period
inv = generate_invoice(order)
l1 = inv.lines.first()
assert l1.period_start == event.date_from
assert l1.period_end == event.date_to
@pytest.mark.django_db
@pytest.mark.parametrize("period", ["auto", "auto_no_event"])
def test_period_from_ticket_validity(env, period):
event, order = env
p1 = order.positions.first()
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
p1.save()
event.date_to = event.date_from + timedelta(days=1)
event.settings.invoice_period = period
inv = generate_invoice(order)
l1 = inv.lines.first()
assert l1.period_start == p1.valid_from
assert l1.period_end == p1.valid_until
@pytest.mark.django_db
@pytest.mark.parametrize("period", ["auto", "auto_no_event"])
def test_period_from_subevent(env, period):
event, order = env
event.has_subevents = True
event.save()
se1 = event.subevents.create(
name=event.name,
active=True,
date_from=datetime((now().year + 1), 7, 31, 9, 0, 0, tzinfo=timezone.utc),
date_to=datetime((now().year + 1), 7, 31, 17, 0, 0, tzinfo=timezone.utc),
)
p1 = order.positions.first()
p1.subevent = se1
p1.save()
event.date_to = event.date_from + timedelta(days=1)
event.settings.invoice_period = period
inv = generate_invoice(order)
l1 = inv.lines.first()
assert l1.period_start == se1.date_from
assert l1.period_end == se1.date_to
@pytest.mark.django_db
@pytest.mark.parametrize("period", ["auto", "auto_no_event"])
def test_period_from_memberships(env, period):
event, order = env
event.date_to = event.date_from + timedelta(days=1)
event.settings.invoice_period = period
p1 = order.positions.first()
membershiptype = event.organizer.membership_types.create(
name=LazyI18nString({"en": "Week pass"}),
transferable=True,
allow_parallel_usage=False,
max_usages=15,
)
customer = event.organizer.customers.create(
identifier="8WSAJCJ",
email="foo@example.org",
name_parts={"_legacy": "Foo"},
name_cached="Foo",
is_verified=False,
)
m = customer.memberships.create(
membership_type=membershiptype,
granted_in=p1,
date_start=datetime(2021, 4, 1, 0, 0, 0, 0, tzinfo=timezone.utc),
date_end=datetime(2021, 4, 8, 23, 59, 59, 999999, tzinfo=timezone.utc),
attendee_name_parts={
"_scheme": "given_family",
'given_name': 'John',
'family_name': 'Doe',
}
)
inv = generate_invoice(order)
l1 = inv.lines.first()
assert l1.period_start == m.date_start
assert l1.period_end == m.date_end
@pytest.mark.django_db
def test_period_auto_no_event_from_invoice(env):
event, order = env
event.settings.invoice_period = "auto_no_event"
inv = generate_invoice(order)
l1 = inv.lines.first()
assert abs(l1.period_start - now()) < timedelta(seconds=10)
assert abs(l1.period_end - now()) < timedelta(seconds=10)
@pytest.mark.django_db
def test_period_always_invoice_date(env):
event, order = env
p1 = order.positions.first()
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
p1.save()
event.settings.invoice_period = "invoice_date"
inv = generate_invoice(order)
l1 = inv.lines.first()
assert abs(l1.period_start - now()) < timedelta(seconds=10)
assert abs(l1.period_end - now()) < timedelta(seconds=10)
@pytest.mark.django_db
def test_period_always_event_date(env):
event, order = env
p1 = order.positions.first()
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
p1.save()
event.settings.invoice_period = "event_date"
inv = generate_invoice(order)
l1 = inv.lines.first()
assert l1.period_start == event.date_from
assert l1.period_end == event.date_from
@pytest.mark.django_db
def test_period_always_order_date(env):
event, order = env
p1 = order.positions.first()
p1.valid_from = datetime(2025, 1, 1, 0, 0, 0, tzinfo=event.timezone)
p1.valid_until = datetime(2025, 12, 31, 23, 59, 59, tzinfo=event.timezone)
p1.save()
event.settings.invoice_period = "order_date"
inv = generate_invoice(order)
l1 = inv.lines.first()
assert l1.period_start == order.datetime
assert l1.period_end == order.datetime

View File

@@ -33,8 +33,6 @@
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
import os import os
import re
from email.mime.text import MIMEText
import pytest import pytest
from django.conf import settings from django.conf import settings
@@ -42,9 +40,7 @@ from django.core import mail as djmail
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_scopes import scope from django_scopes import scope
from i18nfield.strings import LazyI18nString
from pretix.base.email import get_email_context
from pretix.base.models import Event, Organizer, User from pretix.base.models import Event, Organizer, User
from pretix.base.services.mail import mail from pretix.base.services.mail import mail
@@ -52,14 +48,10 @@ from pretix.base.services.mail import mail
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy')
prop1 = o.meta_properties.get_or_create(name="Test")[0]
prop2 = o.meta_properties.get_or_create(name="Website")[0]
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now() date_from=now()
) )
event.meta_values.update_or_create(property=prop1, defaults={'value': "*Beep*"})
event.meta_values.update_or_create(property=prop2, defaults={'value': "https://example.com"})
user = User.objects.create_user('dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
user.email = 'dummy@dummy.dummy' user.email = 'dummy@dummy.dummy'
user.save() user.save()
@@ -166,109 +158,8 @@ def test_send_mail_with_user_locale(env):
def test_sendmail_placeholder(env): def test_sendmail_placeholder(env):
djmail.outbox = [] djmail.outbox = []
event, user, organizer = env event, user, organizer = env
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', {"event": event.name}, event) mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', {"event": event}, event)
assert len(djmail.outbox) == 1 assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email] assert djmail.outbox[0].to == [user.email]
assert djmail.outbox[0].subject == 'Dummy Test subject' assert djmail.outbox[0].subject == 'Dummy Test subject'
def _extract_html(mail):
for content, mimetype in mail.alternatives:
if "multipart/related" in mimetype:
for sp in content._payload:
if isinstance(sp, MIMEText):
return sp._payload
break
elif "text/html" in mimetype:
return content
@pytest.mark.django_db
def test_placeholder_html_rendering_from_template(env):
djmail.outbox = []
event, user, organizer = env
event.name = "<strong>event & co. kg</strong>"
event.save()
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456",
), event)
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
assert '**Meta**: *Beep*' in djmail.outbox[0].body
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
assert '&lt;' not in djmail.outbox[0].body
assert '&amp;' not in djmail.outbox[0].body
html = _extract_html(djmail.outbox[0])
assert '<strong>event' not in html
assert 'Event name: &lt;strong&gt;event &amp; co. kg&lt;/strong&gt;' in html
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
assert '<strong>Meta</strong>: <em>Beep</em>' in html
assert re.search(
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
assert re.search(
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
@pytest.mark.django_db
def test_placeholder_html_rendering_from_string(env):
template = LazyI18nString({
"en": "Event name: {event}\n\nPayment info:\n{payment_info}\n\n**Meta**: {meta_Test}\n\n"
"Event website: [{event}](https://example.org/{event_slug})\n\n"
"Other website: [{event}]({meta_Website})\n\n"
"URL: {url}\n\n"
"URL with text: <a href=\"{url}\">Test</a>"
})
djmail.outbox = []
event, user, organizer = env
event.name = "<strong>event & co. kg</strong>"
event.save()
ctx = get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456",
)
ctx["url"] = "https://google.com"
mail('dummy@dummy.dummy', '{event} Test subject', template, ctx, event)
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
assert '**Meta**: *Beep*' in djmail.outbox[0].body
assert 'URL: https://google.com' in djmail.outbox[0].body
assert 'URL with text: <a href="https://google.com">Test</a>' in djmail.outbox[0].body
assert '&lt;' not in djmail.outbox[0].body
assert '&amp;' not in djmail.outbox[0].body
html = _extract_html(djmail.outbox[0])
assert '<strong>event' not in html
assert 'Event name: &lt;strong&gt;event &amp; co. kg&lt;/strong&gt;' in html
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
assert '<strong>Meta</strong>: <em>Beep</em>' in html
assert re.search(
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
assert re.search(
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
assert re.search(
r'URL: <a href="https://google.com" rel="noopener" style="[^"]+" target="_blank">https://google.com</a>',
html
)
assert re.search(
r'URL with text: <a href="https://google.com" rel="noopener" style="[^"]+" target="_blank">Test</a>',
html
)

View File

@@ -40,5 +40,6 @@ def test_format_alternatives():
) )
} }
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_IGNORE_RICH) == "Foo {bar}"
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_PLAIN) == "Foo plain text" assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_PLAIN) == "Foo plain text"
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_HTML) == "Foo <span>HTML version</span>" assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_HTML) == "Foo <span>HTML version</span>"

View File

@@ -54,7 +54,7 @@ def test_sales_channel_all(event, item, order, checkin_list):
mode=AutoCheckinRule.MODE_PLACED, mode=AutoCheckinRule.MODE_PLACED,
all_sales_channels=True, all_sales_channels=True,
) )
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
@@ -67,12 +67,12 @@ def test_sales_channel_limit(event, item, order, checkin_list):
all_sales_channels=False, all_sales_channels=False,
) )
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert not order.positions.first().checkins.exists() assert not order.positions.first().checkins.exists()
acr.limit_sales_channels.add(order.sales_channel) acr.limit_sales_channels.add(order.sales_channel)
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
@@ -84,7 +84,7 @@ def test_items_all(event, item, order, checkin_list):
mode=AutoCheckinRule.MODE_PLACED, mode=AutoCheckinRule.MODE_PLACED,
all_products=True, all_products=True,
) )
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
@@ -97,12 +97,12 @@ def test_items_limit(event, item, order, checkin_list):
all_products=False, all_products=False,
) )
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert not order.positions.first().checkins.exists() assert not order.positions.first().checkins.exists()
acr.limit_products.add(item) acr.limit_products.add(item)
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
@@ -124,7 +124,7 @@ def test_variations_limit_mixed_order(event, item, order, checkin_list):
) )
acr.limit_variations.add(var) acr.limit_variations.add(var)
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
assert not order.positions.last().checkins.exists() assert not order.positions.last().checkins.exists()
@@ -143,19 +143,19 @@ def test_variations_limit(event, item, order, checkin_list):
all_products=False, all_products=False,
) )
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert not order.positions.first().checkins.exists() assert not order.positions.first().checkins.exists()
acr.limit_variations.add(var) acr.limit_variations.add(var)
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
order.positions.first().checkins.all().delete() order.positions.first().checkins.all().delete()
acr.limit_products.add(item) acr.limit_products.add(item)
acr.limit_variations.clear() acr.limit_variations.clear()
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
@@ -170,7 +170,7 @@ def test_mode_placed(event, item, order, checkin_list):
order_paid.send(event, order=order) order_paid.send(event, order=order)
assert not order.positions.first().checkins.exists() assert not order.positions.first().checkins.exists()
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert order.positions.first().checkins.exists() assert order.positions.first().checkins.exists()
@@ -182,7 +182,7 @@ def test_mode_paid(event, item, order, checkin_list):
mode=AutoCheckinRule.MODE_PAID, mode=AutoCheckinRule.MODE_PAID,
) )
order_placed.send(event, order=order) order_placed.send(event, order=order, bulk=False)
assert not order.positions.first().checkins.exists() assert not order.positions.first().checkins.exists()
order_paid.send(event, order=order) order_paid.send(event, order=order)

View File

@@ -1,13 +1,4 @@
{% load i18n %} {% load i18n %}
This is a test file for sending mails. This is a test file for sending mails.
Event name: {event}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
The language code used for rendering this email is {{ LANGUAGE_CODE }}. The language code used for rendering this email is {{ LANGUAGE_CODE }}.
Payment info:
{payment_info}
**Meta**: {meta_Test}
Event website: [{event}](https://example.org/{event_slug})
Other website: [{event}]({meta_Website})