Compare commits

..

14 Commits

Author SHA1 Message Date
Richard Schreiber
b9811c0202 Fix placeholder interpolation in voucher error message 2025-09-09 16:36:27 +02:00
Danny Adair
cd6fbd886c Add support for New Zealand (en-nz) date/time formats (#5449)
- Implement en-NZ specific formatting in daterange.py
- Create en_NZ/formats.py with NZ-compliant formats
2025-09-09 15:05:51 +02:00
Raphael Michel
0bb390f0a9 Voucher log: Update wording for waiting list (Z#23206690) (#5454) 2025-09-09 15:05:45 +02:00
Raphael Michel
0183f3d40f Invoice email transmission: Handle permanent failures (Z#23205576) (#5420)
* Invoice email transmission: Handle permanent failures (Z#23205576)

* Add missing raise branch

* Fix missing file

* Fix missing license header
2025-09-09 10:21:58 +02:00
dependabot[bot]
82fcc4fe42 Update pytest-mock requirement from ==3.14.* to ==3.15.*
Updates the requirements on [pytest-mock](https://github.com/pytest-dev/pytest-mock) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.14.0...v3.15.0)

---
updated-dependencies:
- dependency-name: pytest-mock
  dependency-version: 3.15.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 10:21:52 +02:00
Yasunobu YesNo Kawaguchi
d42f8ece53 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-09 10:12:26 +02:00
Alois Pospíšil
a8bffbd402 Translations: Update Czech
Currently translated at 70.9% (4306 of 6068 strings)

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

powered by weblate
2025-09-09 10:12:26 +02:00
Alois Pospíšil
991b116026 Translations: Update Czech
Currently translated at 70.9% (4306 of 6068 strings)

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

powered by weblate
2025-09-09 10:12:26 +02:00
Alois Pospíšil
2374d9b78c Translations: Update Czech
Currently translated at 93.6% (236 of 252 strings)

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

powered by weblate
2025-09-09 10:12:26 +02:00
Alois Pospíšil
80785bee54 Translations: Update Czech
Currently translated at 70.9% (4306 of 6068 strings)

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

powered by weblate
2025-09-09 10:12:26 +02:00
Yasunobu YesNo Kawaguchi
ea530ac6bf 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-09 10:12:26 +02:00
Alois Pospíšil
2dd8cc82f2 Translations: Update Czech
Currently translated at 70.5% (4284 of 6068 strings)

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

powered by weblate
2025-09-09 10:12:26 +02:00
Richard Schreiber
38fae12c37 Fix waitingDialog being shown on browser history back (#5437)
* Fix waitingDialog being shown on browser history back

* Revert "Fix waitingDialog being shown on browser history back"

This reverts commit 1f56d97c69.

* Use pageshow-event as suggested by luelista
2025-09-09 08:31:03 +02:00
Richard Schreiber
e34a3ab2ce Fix html-based form errors not being scrolled to in iOS/Safari (#5448) 2025-09-09 08:20:54 +02:00
30 changed files with 425 additions and 742 deletions

View File

@@ -80,12 +80,17 @@ lines list of objects The actual invo
for all invoice lines
created before this field was introduced as well as for
all lines not created by a fee (e.g. a product).
period_start datetime Start date of the service or delivery period of the invoice line.
Can be ``null`` if not known.
├ period_end datetime End date of the service or delivery period of the invoice line.
Can be ``null`` if not known.
├ event_date_from datetime Deprecated alias of ``period_start``.
├ event_date_to datetime Deprecated alias of ``period_end``.
event_date_from datetime Start date of the (sub)event this line was created for as it
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).
├ event_date_to datetime End date of the (sub)event this line was created for as it
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
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
@@ -269,8 +274,6 @@ List of all invoices
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg",
"attendee_name": null,
"gross_value": "23.00",
@@ -417,8 +420,6 @@ Fetching individual invoices
"fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg",
"attendee_name": null,
"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
: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, build_invoice_data, 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, invoice_line_text
Check-ins
"""""""""

View File

@@ -120,7 +120,7 @@ dev = [
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.14.*",
"pytest-mock==3.15.*",
"pytest-sugar",
"pytest-xdist==3.8.*",
"pytest==8.4.*",

View File

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

View File

@@ -1757,14 +1757,12 @@ class LinePositionField(serializers.IntegerField):
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
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:
model = InvoiceLine
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
'event_date_to', 'period_start', 'period_end', 'gross_value', 'tax_value', 'tax_rate', 'tax_code',
'tax_name', 'fee_type', 'fee_internal_type', 'event_location')
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'tax_name', 'fee_type',
'fee_internal_type', 'event_location')
class InvoiceSerializer(I18nAwareModelSerializer):

View File

@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import datetime
import logging
import re
import unicodedata
@@ -523,20 +522,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
textobject.textLine(self._normalize(self._upper(pgettext('invoice', 'Event'))))
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 shorten(txt):
txt = str(txt)
@@ -550,17 +535,25 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
p_size = p.wrap(self.event_width, self.event_height)
return txt
d_from, d_to = self._date_range_in_header()
if d_from and d_to:
p_str = (
shorten(self.invoice.event.name) + '\n' +
pgettext('invoice', '{from_date}\nuntil {to_date}').format(
from_date=date_format(d_from, "DATE_FORMAT"),
to_date=date_format(d_to, "DATE_FORMAT"),
)
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
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()
)
elif d_from:
p_str = shorten(self.invoice.event.name) + '\n' + date_format(d_from, "DATE_FORMAT")
if show_end_date:
p_str = (
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:
p_str = shorten(self.invoice.event.name)
@@ -664,12 +657,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
def _get_story(self, doc):
has_taxes = any(il.tax_value for il in self.invoice.lines.all()) 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 self.invoice.lines.all()
)) > 1
request_show_service_date = False
story = [
NextPageTemplate('FirstPage'),
@@ -713,73 +700,15 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
)]
def _group_key(line):
return (line.description, line.tax_rate, line.tax_name, line.net_value, line.gross_value, line.subevent,
line.period_start, line.period_end)
def day(dt: datetime.datetime) -> datetime.date:
if dt is None:
return None
return dt.astimezone(tz).date()
return (line.description, line.tax_rate, line.tax_name, line.net_value, line.gross_value, line.subevent_id,
line.event_date_from, line.event_date_to)
total = Decimal('0.00')
for (description, tax_rate, tax_name, net_value, gross_value, subevent, period_start, period_end), lines in addon_aware_groupby(
for (description, tax_rate, tax_name, net_value, gross_value, *ignored), lines in addon_aware_groupby(
self.invoice.lines.all(),
key=_group_key,
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.
if period_start and period_end and day(period_end) != day(period_start):
# It's a multi-day period, such as the validity of the ticket or an event date period
if day(period_start) == header_dates[0] and day(period_end) == 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) == day(period_start) and
day(subevent.date_to) == day(period_end)):
# For subevents, build_invoice already includes the date in the description in the event-default format.
period_line = ""
else:
period_line = f"{date_format(day(period_start), 'SHORT_DATE_FORMAT')} {date_format(day(period_end), 'SHORT_DATE_FORMAT')}"
elif period_start or period_end:
# It's a single-day period
delivery_day = day(period_end or period_start)
if delivery_day in (header_dates[0], header_dates[1]):
# 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
else:
description += "\n" + period_line
lines = list(lines)
if has_taxes:
if len(lines) > 1:
@@ -788,7 +717,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
gross_price=money_filter(gross_value, self.invoice.event.currency),
)
description = description + "\n" + single_price_line
tdata.append((
FontFallbackParagraph(
self._clean_text(description, tags=['br']),
@@ -893,12 +821,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
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:
story.append(FontFallbackParagraph(
self._normalize(self.invoice.payment_provider_text),

View File

@@ -1,80 +0,0 @@
# 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"],
)
for f in flush_queue:
cache.delete(f)
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",
)
)
ev_seen.add(setting.object_id)
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

@@ -1,18 +0,0 @@
# 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

@@ -33,7 +33,6 @@
# License for the specific language governing permissions and limitations under the License.
import string
import warnings
from decimal import Decimal
import pycountry
@@ -43,6 +42,7 @@ from django.db.models.functions import Cast
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext
from django_scopes import ScopedManager
@@ -201,7 +201,6 @@ class Invoice(models.Model):
transmission_info = models.JSONField(null=True, blank=True)
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')
@@ -370,6 +369,22 @@ class Invoice(models.Model):
from pretix.base.invoicing.transmission import transmission_types
return transmission_types.get(identifier=self.transmission_type)[0]
def set_transmission_failed(self, provider, data):
self.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
self.transmission_date = now()
if not self.transmission_provider and provider:
self.transmission_provider = provider
self.save(update_fields=["transmission_status", "transmission_date", "transmission_provider"])
self.order.log_action(
"pretix.event.order.invoice.sending_failed",
data={
"full_invoice_no": self.full_invoice_no,
"transmission_provider": provider,
"transmission_type": self.transmission_type,
"data": data,
}
)
class InvoiceLine(models.Model):
"""
@@ -389,10 +404,10 @@ class InvoiceLine(models.Model):
:type tax_name: str
:param subevent: The subevent this line refers to
:type subevent: SubEvent
:param period_start: Start if service period invoiced
:type period_start: datetime
:param period_end: End of service period invoiced
:type period_end: datetime
:param event_date_from: Event date of the (sub)event at the time the invoice was created
:type event_date_from: datetime
:param event_date_to: Event end date of the (sub)event at the time the invoice was created
:type event_date_to: datetime
:param event_location: Event location of the (sub)event at the time the invoice was created
:type event_location: str
:param item: The item this line refers to
@@ -411,8 +426,8 @@ class InvoiceLine(models.Model):
tax_name = models.CharField(max_length=190)
tax_code = models.CharField(max_length=190, null=True, blank=True)
subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT)
period_start = models.DateTimeField(null=True)
period_end = models.DateTimeField(null=True)
event_date_from = models.DateTimeField(null=True)
event_date_to = models.DateTimeField(null=True)
event_location = models.TextField(null=True, blank=True)
item = models.ForeignKey('Item', null=True, blank=True, on_delete=models.PROTECT)
variation = models.ForeignKey('ItemVariation', null=True, blank=True, on_delete=models.PROTECT)
@@ -429,35 +444,3 @@ class InvoiceLine(models.Model):
def __str__(self):
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

@@ -61,9 +61,7 @@ from pretix.base.models.tax import EU_CURRENCIES
from pretix.base.services.tasks import (
TransactionAwareProfiledEventTask, TransactionAwareTask,
)
from pretix.base.signals import (
build_invoice_data, invoice_line_text, periodic_task,
)
from pretix.base.signals import invoice_line_text, periodic_task
from pretix.celery_app import app
from pretix.helpers.database import OF_SELF, rolledback_transaction
from pretix.helpers.models import modelcopy
@@ -84,10 +82,6 @@ def build_invoice(invoice: Invoice) -> Invoice:
lp = invoice.order.payments.last()
min_period_start = None
max_period_end = None
now_dt = now()
with (language(invoice.locale, invoice.event.settings.region)):
invoice.invoice_from = invoice.event.settings.get('invoice_address_from')
invoice.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
@@ -214,9 +208,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
positions = list(
invoice.order.positions.select_related('addon_to', 'item', 'tax_rule', 'subevent', 'variation').annotate(
addon_c=Count('addons')
).prefetch_related(
'answers', 'answers__options', 'answers__question', 'granted_memberships',
).order_by('positionid', 'id')
).prefetch_related('answers', 'answers__options', 'answers__question').order_by('positionid', 'id')
)
reverse_charge = False
@@ -275,10 +267,6 @@ def build_invoice(invoice: Invoice) -> Invoice:
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(
position=i,
invoice=invoice,
@@ -289,8 +277,8 @@ def build_invoice(invoice: Invoice) -> Invoice:
item=p.item,
variation=p.variation,
attendee_name=p.attendee_name if invoice.event.settings.invoice_attendee_name else None,
period_start=period_start,
period_end=period_end,
event_date_from=p.subevent.date_from if invoice.event.has_subevents else invoice.event.date_from,
event_date_to=p.subevent.date_to if invoice.event.has_subevents else invoice.event.date_to,
event_location=location if invoice.event.settings.invoice_event_location else None,
tax_rate=p.tax_rate,
tax_code=p.tax_code,
@@ -313,29 +301,13 @@ def build_invoice(invoice: Invoice) -> Invoice:
fee_title = _(fee.get_fee_type_display())
if 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(
position=i + offset,
invoice=invoice,
description=fee_title,
gross_value=fee.value,
period_start=period_start,
period_end=period_end,
event_date_from=None if invoice.event.has_subevents else invoice.event.date_from,
event_date_to=None if invoice.event.has_subevents else invoice.event.date_to,
event_location=(
None if invoice.event.has_subevents
else (str(invoice.event.location)
@@ -364,7 +336,6 @@ def build_invoice(invoice: Invoice) -> Invoice:
invoice.reverse_charge = reverse_charge
invoice.save()
build_invoice_data.send(sender=invoice.event, invoice=invoice)
return invoice
@@ -380,49 +351,6 @@ def build_cancellation(invoice: 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 or position.valid_until:
period_start = position.valid_from or now()
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):
if invoice.canceled:
raise ValueError("Invoice should not be canceled twice.")
@@ -528,12 +456,6 @@ def build_preview_invoice_pdf(event):
if not locale or locale == '__user__':
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):
order = event.orders.create(
status=Order.STATUS_PENDING, datetime=timezone.now(),
@@ -584,8 +506,8 @@ def build_preview_invoice_pdf(event):
invoice=invoice, description=_("Sample product {}").format(i + 1),
gross_value=tax.gross, tax_value=tax.tax,
tax_rate=tax.rate, tax_name=tax.name, tax_code=tax.code,
period_start=period_start,
period_end=period_end,
event_date_from=event.date_from,
event_date_to=event.date_to,
event_location=event.settings.invoice_event_location,
)
else:
@@ -593,8 +515,8 @@ def build_preview_invoice_pdf(event):
InvoiceLine.objects.create(
invoice=invoice, description=_("Sample product A"),
gross_value=100, tax_value=0, tax_rate=0, tax_code=None,
period_start=period_start,
period_end=period_end,
event_date_from=event.date_from,
event_date_to=event.date_to,
event_location=event.settings.invoice_event_location,
)
@@ -742,20 +664,7 @@ def transmit_invoice(sender, invoice_id, allow_retransmission=True, **kwargs):
break
if not provider:
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
invoice.transmission_date = now()
invoice.save(update_fields=["transmission_status", "transmission_date"])
invoice.order.log_action(
"pretix.event.order.invoice.sending_failed",
data={
"full_invoice_no": invoice.full_invoice_no,
"transmission_provider": None,
"transmission_type": invoice.transmission_type,
"data": {
"reason": "no_provider",
},
}
)
invoice.set_transmission_failed(provider=None, data={"reason": "no_provider"})
return
if invoice.order.testmode and not provider.testmode_supported:
@@ -776,18 +685,7 @@ def transmit_invoice(sender, invoice_id, allow_retransmission=True, **kwargs):
provider.transmit(invoice)
except Exception as e:
logger.exception(f"Transmission of invoice {invoice.pk} failed with exception.")
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
invoice.transmission_date = now()
invoice.save(update_fields=["transmission_status", "transmission_date"])
invoice.order.log_action(
"pretix.event.order.invoice.sending_failed",
data={
"full_invoice_no": invoice.full_invoice_no,
"transmission_provider": None,
"transmission_type": invoice.transmission_type,
"data": {
"reason": "exception",
"exception": str(e),
},
}
)
invoice.set_transmission_failed(provider=provider.identifier, data={
"reason": "exception",
"exception": str(e),
})

View File

@@ -495,7 +495,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
email = email_filter.send_chained(event, 'message', message=email, order=order, user=user)
invoices_sent = []
invoices_to_mark_transmitted = []
if invoices:
invoices = Invoice.objects.filter(pk__in=invoices)
for inv in invoices:
@@ -516,7 +516,23 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
inv.file.file.read(),
'application/pdf'
)
invoices_sent.append(inv)
if inv.transmission_type == "email":
# Mark invoice as sent when it was sent to the requested address *either* at the time of
# invoice creation *or* as of right now.
expected_recipients = [
(inv.invoice_to_transmission_info or {}).get("transmission_email_address")
or inv.order.email,
]
try:
expected_recipients.append(
(inv.order.invoice_address.transmission_info or {}).get("transmission_email_address")
or inv.order.email
)
except InvoiceAddress.DoesNotExist:
pass
if any(t in expected_recipients for t in to):
invoices_to_mark_transmitted.append(inv)
except:
logger.exception('Could not attach invoice to email')
pass
@@ -589,6 +605,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
'invoices': [],
}
)
for i in invoices_to_mark_transmitted:
i.set_transmission_failed(provider="email_pdf", data={
"reason": "exception",
"exception": "SMTP code {}, max retries exceeded".format(e.smtp_code),
})
raise e
logger.exception('Error sending email')
@@ -602,6 +623,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
'invoices': [],
}
)
for i in invoices_to_mark_transmitted:
i.set_transmission_failed(provider="email_pdf", data={
"reason": "exception",
"exception": "SMTP code {}".format(e.smtp_code),
})
raise SendMailException('Failed to send an email to {}.'.format(to))
except smtplib.SMTPRecipientsRefused as e:
@@ -633,6 +659,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
'invoices': [],
}
)
for i in invoices_to_mark_transmitted:
i.set_transmission_failed(provider="email_pdf", data={
"reason": "exception",
"exception": "SMTP error",
})
raise SendMailException('Failed to send an email to {}.'.format(to))
except Exception as e:
@@ -650,6 +681,11 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
'invoices': [],
}
)
for i in invoices_to_mark_transmitted:
i.set_transmission_failed(provider="email_pdf", data={
"reason": "exception",
"exception": "Internal error",
})
raise e
if log_target:
log_target.log_action(
@@ -661,59 +697,52 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
'invoices': [],
}
)
for i in invoices_to_mark_transmitted:
i.set_transmission_failed(provider="email_pdf", data={
"reason": "exception",
"exception": "Internal error",
})
logger.exception('Error sending email')
raise SendMailException('Failed to send an email to {}.'.format(to))
else:
for i in invoices_sent:
if i.transmission_type == "email":
# Mark invoice as sent when it was sent to the requested address *either* at the time of invoice
# creation *or* as of right now.
expected_recipients = [
(i.invoice_to_transmission_info or {}).get("transmission_email_address") or i.order.email,
]
try:
expected_recipients.append((i.order.invoice_address.transmission_info or {}).get("transmission_email_address") or i.order.email)
except InvoiceAddress.DoesNotExist:
pass
if not any(t in expected_recipients for t in to):
continue
if i.transmission_status != Invoice.TRANSMISSION_STATUS_COMPLETED:
i.transmission_date = now()
i.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
i.transmission_provider = "email_pdf"
i.transmission_info = {
"sent": [
{
"recipients": to,
"datetime": now().isoformat(),
}
]
}
i.save(update_fields=[
"transmission_date", "transmission_provider", "transmission_status",
"transmission_info"
])
elif i.transmission_provider == "email_pdf":
i.transmission_info["sent"].append(
for i in invoices_to_mark_transmitted:
if i.transmission_status != Invoice.TRANSMISSION_STATUS_COMPLETED:
i.transmission_date = now()
i.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
i.transmission_provider = "email_pdf"
i.transmission_info = {
"sent": [
{
"recipients": to,
"datetime": now().isoformat(),
}
)
i.save(update_fields=[
"transmission_info"
])
i.order.log_action(
"pretix.event.order.invoice.sent",
data={
"full_invoice_no": i.full_invoice_no,
"transmission_provider": "email_pdf",
"transmission_type": "email",
"data": {
"recipients": [to],
},
]
}
i.save(update_fields=[
"transmission_date", "transmission_provider", "transmission_status",
"transmission_info"
])
elif i.transmission_provider == "email_pdf":
i.transmission_info["sent"].append(
{
"recipients": to,
"datetime": now().isoformat(),
}
)
i.save(update_fields=[
"transmission_info"
])
i.order.log_action(
"pretix.event.order.invoice.sent",
data={
"full_invoice_no": i.full_invoice_no,
"transmission_provider": "email_pdf",
"transmission_type": "email",
"data": {
"recipients": [to],
},
}
)
def mail_send(*args, **kwargs):

View File

@@ -1098,35 +1098,6 @@ DEFAULTS = {
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': {
'default': 'False',
'type': bool,

View File

@@ -596,18 +596,6 @@ multiple events. Receivers should return a subclass of pretix.base.exporter.Base
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()
"""
Arguments: ``payments``, ``positions``, ``email``, ``locale``, ``invoice_address``,

View File

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

View File

@@ -236,7 +236,7 @@ class VoucherForm(I18nModelForm):
try:
Voucher.clean_max_usages(data, self.instance.redeemed)
except ValidationError as e:
raise ValidationError({"max_usages": e.message})
raise ValidationError({"max_usages": e})
check_quota = Voucher.clean_quota_needs_checking(
data, self.initial_instance_data,
item_changed=data.get('itemvar') != self.initial.get('itemvar'),

View File

@@ -581,7 +581,7 @@ class CoreOrderLogEntryType(OrderLogEntryType):
'The voucher has been set to expire because the recipient removed themselves from the waiting list.'),
'pretix.voucher.changed': _('The voucher has been changed.'),
'pretix.voucher.deleted': _('The voucher has been deleted.'),
'pretix.voucher.added.waitinglist': _('The voucher has been sent to {email} through the waiting list.'),
'pretix.voucher.added.waitinglist': _('The voucher has been assigned to {email} through the waiting list.'),
})
class CoreVoucherLogEntryType(VoucherLogEntryType):
pass

View File

@@ -15,19 +15,6 @@
{% bootstrap_field form.invoice_email_attachment layout="control" %}
{% bootstrap_field form.invoice_email_organizer 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_show_payments layout="control" %}
{% bootstrap_field form.invoice_reissue_after_modify layout="control" %}

View File

@@ -64,6 +64,16 @@ def daterange(df, dt, as_html=False):
return format_html(base_format, _date(df, "j."), mark_safe(until.strip()), _date(dt, "j. F Y"))
elif df.year == dt.year:
return format_html(base_format, _date(df, "j. F"), until, _date(dt, "j. F Y"))
elif lng == "en-nz":
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
# Mon, 15 January 2024
return format_html(base_format, _date(df, "D, j F Y"))
elif df.year == dt.year and df.month == dt.month:
# 1 3 January 2024
return format_html(base_format, _date(df, "j"), until, _date(dt, "j F Y"))
elif df.year == dt.year:
# 1 January 3 April 2024
return format_html(base_format, _date(df, "j F"), until, _date(dt, "j F Y"))
elif lng.startswith("en"):
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
return format_html(base_format, _date(df, "D, N jS, Y"))

View File

@@ -0,0 +1,21 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#

View File

@@ -0,0 +1,50 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
# Date according to https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
# Following NZ government guidance from https://www.digital.govt.nz/standards-and-guidance/design-and-ux/content-design-guidance/writing-style/numbers
DATE_FORMAT = "j F Y" # 12 December 2015
DATETIME_FORMAT = "j F Y, g:ia" # 12 December 2015, 5:30pm
TIME_FORMAT = "g:ia" # 5:30pm
YEAR_MONTH_FORMAT = "F Y" # December 2015
MONTH_DAY_FORMAT = "j F" # 12 December
SHORT_DATE_FORMAT = "j F Y" # same as DATE_FORMAT per guidance
SHORT_DATETIME_FORMAT = "j F Y, g:ia"
WEEKDAY_FORMAT = "l" # Monday
WEEKDAY_DATE_FORMAT = "l, j F Y" # Friday, 23 November 2018
WEEK_FORMAT = "\\W W, o" # ISO week: "W 52, 2024"
WEEK_DAY_FORMAT = "D, j M" # Abbrev weekday and month: "Mon, 5 Feb"
SHORT_MONTH_DAY_FORMAT = "j/n" # Numeric day/month: "5/2"
# Parsing inputs; keep d/m/Y and ISO
DATE_INPUT_FORMATS = [
"%d/%m/%Y",
"%Y-%m-%d",
"%d/%m/%y",
]
TIME_INPUT_FORMATS = [
"%I:%M%p", # 5:30pm
"%H:%M:%S",
"%H:%M:%S.%f",
"%H:%M",
]

View File

@@ -8,20 +8,20 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-05-16 17:00+0000\n"
"Last-Translator: David <davemachala@gmail.com>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/"
">\n"
"PO-Revision-Date: 2025-09-09 04:00+0000\n"
"Last-Translator: Alois Pospíšil <alois.pospisil@gmail.com>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
"\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 5.11.4\n"
"X-Generator: Weblate 5.13.2\n"
#: pretix/_base_settings.py:87
msgid "English"
msgstr "Angličtina"
msgstr "AngličDodací adresatina"
#: pretix/_base_settings.py:88
msgid "German"
@@ -145,7 +145,7 @@ msgstr "Španělština"
#: pretix/_base_settings.py:118
msgid "Spanish (Latin America)"
msgstr ""
msgstr "uzamčeno (pouze obchody)"
#: pretix/_base_settings.py:119
msgid "Turkish"
@@ -688,7 +688,7 @@ msgstr "{system} Uživatel"
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:31
#: pretix/presale/templates/pretixpresale/event/order.html:300
msgid "Email"
msgstr "Email"
msgstr "E-mail"
#: pretix/base/auth.py:157 pretix/base/forms/auth.py:164
#: pretix/base/forms/auth.py:218 pretix/base/models/auth.py:675
@@ -1083,7 +1083,7 @@ msgstr "Krátká forma"
#: pretix/control/templates/pretixcontrol/events/index.html:68
#: pretix/control/templates/pretixcontrol/organizers/detail.html:64
msgid "Event name"
msgstr "Název akce"
msgstr "Název události"
#: pretix/base/datasync/sourcefields.py:446
#: pretix/base/exporters/invoices.py:326
@@ -1107,7 +1107,7 @@ msgstr "Datum ukončení akce"
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:12
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:20
msgid "Voucher code"
msgstr "Kód poukázky"
msgstr "Kód poukazu"
#: pretix/base/datasync/sourcefields.py:473 pretix/base/pdf.py:116
msgid "Order code and position number"
@@ -1254,7 +1254,7 @@ msgstr "Nahrání souborů s odpověďmi na otázky"
#: pretix/plugins/reports/exporters.py:662
msgctxt "export_category"
msgid "Order data"
msgstr "Údaje o objednávce"
msgstr "Data objednávky"
#: pretix/base/exporters/answers.py:56
msgid ""
@@ -1414,7 +1414,7 @@ msgstr "Celé jméno"
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:35
#: pretix/presale/templates/pretixpresale/event/order.html:312
msgid "Name"
msgstr "Jméno"
msgstr "Název"
#: pretix/base/exporters/customers.py:77 pretix/base/models/customers.py:99
msgid "Account active"
@@ -2310,7 +2310,7 @@ msgstr ""
#: pretix/base/exporters/waitinglist.py:115 pretix/control/forms/event.py:1671
#: pretix/control/forms/organizer.py:116
msgid "Event slug"
msgstr "Odkaz akce"
msgstr "Zkratka události"
#: pretix/base/exporters/orderlist.py:262
#: pretix/base/exporters/orderlist.py:452
@@ -2419,7 +2419,7 @@ msgstr "Čísla faktur"
#: pretix/control/forms/filter.py:585
#: pretix/control/templates/pretixcontrol/order/index.html:190
msgid "Sales channel"
msgstr "B2B Kanál"
msgstr "Prodejní kanál"
#: pretix/base/exporters/orderlist.py:286
#: pretix/base/exporters/orderlist.py:630 pretix/base/models/orders.py:277
@@ -2579,12 +2579,12 @@ msgstr "ID sedadla"
#: pretix/base/exporters/orderlist.py:622
#: pretix/plugins/checkinlists/exporters.py:527
msgid "Seat name"
msgstr "Název sedadla"
msgstr "Název místa"
#: pretix/base/exporters/orderlist.py:623
#: pretix/plugins/checkinlists/exporters.py:528
msgid "Seat zone"
msgstr "Sedací zóna"
msgstr "dd.MM HH:mm"
#: pretix/base/exporters/orderlist.py:624
#: pretix/plugins/checkinlists/exporters.py:529
@@ -3568,7 +3568,7 @@ msgstr "Příjemce"
#, python-format
msgctxt "invoice"
msgid "Page %d of %d"
msgstr "Stránka %d z %d"
msgstr ""
#: pretix/base/invoicing/pdf.py:378
msgctxt "invoice"
@@ -4780,7 +4780,7 @@ msgstr ""
#: pretix/base/models/event.py:583 pretix/base/models/organizer.py:89
msgid "The slug may only contain letters, numbers, dots and dashes."
msgstr "Krátká forma může obsahovat pouze písmena, číslice, tečky a pomlčky."
msgstr "Zkratka smí obsahovat pouze písmena, čísla, tečky a pomlčky."
#: pretix/base/models/event.py:600 pretix/base/models/event.py:1495
msgid "Show in lists"
@@ -4837,7 +4837,7 @@ msgstr "Série událostí"
#: pretix/base/models/event.py:652 pretix/base/models/event.py:1544
msgid "Seating plan"
msgstr "Plán usazení"
msgstr "Plán míst k sezení"
#: pretix/base/models/event.py:659 pretix/base/models/items.py:675
msgid "Sell on all sales channels"
@@ -5866,7 +5866,7 @@ msgstr "Upload souborů"
#: pretix/base/models/items.py:1657
#: pretix/control/templates/pretixcontrol/event/settings.html:240
msgid "Date and time"
msgstr "Datum a hodina"
msgstr "Datum a čas"
#: pretix/base/models/items.py:1658
msgid "Country code (ISO 3166-1 alpha-2)"
@@ -6027,7 +6027,7 @@ msgstr ""
#: pretix/base/models/items.py:1961
#: pretix/control/templates/pretixcontrol/items/question.html:90
msgid "Answer"
msgstr "Odpověď"
msgstr ""
#: pretix/base/models/items.py:1985
#, python-brace-format
@@ -6667,7 +6667,6 @@ msgstr "Pozvání do týmu \"{team}\" pro \"{email}\""
#: pretix/base/models/organizer.py:604
#: pretix/control/templates/pretixcontrol/organizers/channels.html:23
#, fuzzy
msgid "Identifier"
msgstr "Identifikátor"
@@ -8003,7 +8002,7 @@ msgstr "Přízemí, řada 3, sedadlo 4"
#: pretix/base/pdf.py:500 pretix/base/pdf.py:506
#: pretix/control/forms/orders.py:344
msgid "General admission"
msgstr "Všeobecné vstupné"
msgstr "včetně všech daní"
#: pretix/base/pdf.py:503
msgid "Seat: zone"
@@ -8175,7 +8174,7 @@ msgstr "Nevybrali jste žádné produkty."
#: pretix/base/services/cart.py:106
msgid "Unknown cart position."
msgstr "Neznámá pozice produktu ve vozíku."
msgstr "Neznámá pozice produktu v košíku."
#: pretix/base/services/cart.py:107
msgctxt "subevent"
@@ -8381,7 +8380,8 @@ msgid ""
"Applying a voucher to the whole cart should not be combined with other "
"operations."
msgstr ""
"Použití poukazu na celý vozík by se nemělo kombinovat s jinými operacemi."
"Použití poukazu na všechny produkty v košíku by se nemělo kombinovat s "
"jinými operacemi."
#: pretix/base/services/cart.py:179
msgid ""
@@ -16471,7 +16471,7 @@ msgstr "Bylo změněno nastavení metody stahování lístků."
#: pretix/control/logdisplay.py:492
msgid "Blocked manually"
msgstr "Ručně zablokováno"
msgstr "Ručně blokováno"
#: pretix/control/logdisplay.py:494
msgid "Blocked because of an API integration"
@@ -26626,7 +26626,7 @@ msgstr ""
#: pretix/plugins/stripe/views.py:682
#: pretix/plugins/ticketoutputpdf/views.py:132
msgid "We could not save your changes. See below for details."
msgstr ""
msgstr "Vaše změny se nepodařilo uložit. Podrobnosti najdete níže."
#: pretix/control/views/checkin.py:420 pretix/control/views/checkin.py:457
msgid "The requested list does not exist."
@@ -30204,7 +30204,7 @@ msgstr "Stránka %d z %d"
#: pretix/plugins/reports/exporters.py:211
#, python-format
msgid "Page %d"
msgstr "Strana %d"
msgstr "Strana %dd"
#: pretix/plugins/reports/exporters.py:213
#, python-format
@@ -31294,7 +31294,7 @@ msgstr "giropay přes Stripe"
#: pretix/plugins/stripe/payment.py:1480
msgid "giropay"
msgstr "giropay"
msgstr "Smazat"
#: pretix/plugins/stripe/payment.py:1483
msgid ""
@@ -31499,37 +31499,37 @@ msgstr ""
#: pretix/plugins/stripe/signals.py:108
#, python-brace-format
msgid "Dispute updated. Reason: {}"
msgstr ""
msgstr "Spor aktualizován. Důvod: {}"
#: pretix/plugins/stripe/signals.py:110
#, python-brace-format
msgid "Dispute closed. Status: {}"
msgstr ""
msgstr "Spor uzavřen. Stav: {}"
#: pretix/plugins/stripe/signals.py:113
#, python-brace-format
msgid "Stripe reported an event: {}"
msgstr ""
msgstr "Stripe nahlásil událost: {}"
#: pretix/plugins/stripe/signals.py:124
msgid "Stripe Connect: Client ID"
msgstr ""
msgstr "Stripe Connect: ID klienta"
#: pretix/plugins/stripe/signals.py:131
msgid "Stripe Connect: Secret key"
msgstr ""
msgstr "Stripe Connect: tajný klíč"
#: pretix/plugins/stripe/signals.py:138
msgid "Stripe Connect: Publishable key"
msgstr ""
msgstr "Stripe Connect: veřejný klíč"
#: pretix/plugins/stripe/signals.py:145
msgid "Stripe Connect: Secret key (test)"
msgstr ""
msgstr "Stripe Connect: tajný klíč (test)"
#: pretix/plugins/stripe/signals.py:152
msgid "Stripe Connect: Publishable key (test)"
msgstr ""
msgstr "Stripe Connect: veřejný klíč (test)"
#: pretix/plugins/stripe/signals.py:178
#: pretix/plugins/stripe/templates/pretixplugins/stripe/oauth_disconnect.html:3
@@ -31556,7 +31556,7 @@ msgstr "Celková částka bude vybrána z vašeho bankovního účtu."
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:18
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:23
msgid "Banking Institution"
msgstr ""
msgstr "bankovní instituce"
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html:20
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_form_sepadirectdebit.html:25
@@ -31635,6 +31635,16 @@ msgid ""
"statement that you can obtain from your bank. You agree to receive "
"notifications for future debits up to 2 days before they occur."
msgstr ""
"Poskytnutím svých platebních údajů a potvrzením této platby opravňujete (A) "
"%(sepa_creditor_name)s a společnost Stripe, našeho poskytovatele platebních "
"služeb a/nebo PPRO, jeho místního poskytovatele služeb, aby vaší bance "
"posílali pokyny k inkasu vašeho účtu a (B) vaši banku, aby váš účet "
"inkasovala v souladu s těmito pokyny. V rámci vašich práv máte nárok na "
"vrácení peněz od své banky podle podmínek vaší smlouvy s bankou. Nárok na "
"vrácení peněz musí být uplatněn do 8 týdnů od data, kdy byl vaš účet "
"zatížen. Vaše práva jsou vysvětlená v prohlášení, které můžete získat od své "
"banky. Souhlasíte s tím, že budete dostávat oznámení o budoucích inkasech až "
"2 dny předtím, než k nim dojde."
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html:7
msgid "Charge ID"
@@ -31655,10 +31665,8 @@ msgid "Payer name"
msgstr "Jméno poplatníka"
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html:91
#, fuzzy
#| msgid "Payment fee"
msgid "Payment receipt"
msgstr "Poplatek za platbu"
msgstr "Potvrzení o platbě"
#: pretix/plugins/stripe/templates/pretixplugins/stripe/oauth_disconnect.html:12
msgid "Do you really want to disconnect your Stripe account?"
@@ -31669,9 +31677,8 @@ msgid "Disconnect"
msgstr "Odpojit"
#: pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html:6
#, fuzzy
msgid "Payment instructions"
msgstr "Nastavení platby"
msgstr "Pokyny k platbě"
#: pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html:9
msgid ""
@@ -31700,8 +31707,8 @@ msgid ""
"We're waiting for an answer from the payment provider regarding your "
"payment. Please contact us if this takes more than a few days."
msgstr ""
"Čekáme na odpověď poskytovatele platební služby ohledně vaší platby. Pokud "
"to bude trvat déle než několik dní, kontaktujte nás prosím."
"Čekáme na odpověď od poskytovatele plateb ohledně vaší platby. Pokud to bude "
"trvat déle než několik dní, kontaktujte nás."
#: pretix/plugins/stripe/templates/pretixplugins/stripe/pending.html:42
msgid ""
@@ -31731,7 +31738,7 @@ msgstr "Transakci se nepodařilo dokončit z následujícího důvodu:"
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html:22
#, python-format
msgid "Confirm payment: %(code)s"
msgstr "Potvrzení platby: %(code)s"
msgstr "Potvrdit platbu: %(code)s"
#: pretix/plugins/stripe/templates/pretixplugins/stripe/sca_return.html:20
msgid "Confirming your payment…"
@@ -31778,7 +31785,7 @@ msgstr ""
#: pretix/plugins/stripe/views.py:590 pretix/plugins/stripe/views.py:593
msgid "Sorry, there was an error in the payment process."
msgstr "Omlouváme se, při plat došlo k chybě."
msgstr "Omlouváme se, při platebním procesu došlo k chybě."
#: pretix/plugins/ticketoutputpdf/apps.py:44
#: pretix/plugins/ticketoutputpdf/apps.py:47
@@ -32238,10 +32245,9 @@ msgid "No other variations of this product exist."
msgstr "Žádné další varianty tohoto produktu neexistují."
#: pretix/presale/forms/organizer.py:70
#, fuzzy
msgctxt "filter_empty"
msgid "all"
msgstr "Všechny"
msgstr "Vše"
#: pretix/presale/forms/renderers.py:51
msgctxt "form"
@@ -32387,10 +32393,8 @@ msgstr "Informace"
#: pretix/presale/templates/pretixpresale/event/base.html:222
#: pretix/presale/templates/pretixpresale/organizers/base.html:100
#, fuzzy
#| msgid "Contact:"
msgid "Contact"
msgstr "Kontakt:"
msgstr "Kontakt"
#: pretix/presale/templates/pretixpresale/event/base.html:225
#: pretix/presale/templates/pretixpresale/fragment_modals.html:118
@@ -33139,13 +33143,13 @@ msgstr "Pokračujte k platbě"
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:63
msgid "Empty cart"
msgstr "Vyprázdnit vozík"
msgstr "Vyprázdnit košík"
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:68
#: pretix/presale/templates/pretixpresale/event/index.html:248
#: pretix/presale/templates/pretixpresale/event/voucher_form.html:16
msgid "Redeem a voucher"
msgstr "Uplatnit poukázku"
msgstr "Uplatnit poukaz"
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:71
msgid "We're applying this voucher to your cart..."
@@ -33154,7 +33158,7 @@ msgstr "Uplatňujeme tento poukaz ve vašem košíku..."
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:79
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:27
msgid "Redeem voucher"
msgstr "Uplatnění poukázky"
msgstr "Uplatnit poukaz"
#: pretix/presale/templates/pretixpresale/event/fragment_change_confirm.html:10
msgid "Change summary"
@@ -33700,10 +33704,6 @@ msgstr ""
"vám také zaslali e-mail s odkazem."
#: pretix/presale/templates/pretixpresale/event/order.html:59
#, fuzzy
#| msgid ""
#| "Please save the following link if you want to access your order later. We "
#| "also sent you an email containing the link to the address you specified."
msgid ""
"Please save the following link if you want to access your order later. We "
"also sent you an email to the address you specified containing the link."
@@ -34485,6 +34485,8 @@ msgid ""
"Please tick a checkbox or enter a quantity for one of the ticket types to "
"add to the cart."
msgstr ""
"Zaškrtněte políčko nebo zadejte množství pro jeden z typů vstupenek, které "
"chcete přidat do košíku."
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:82
#, python-format
@@ -34603,10 +34605,8 @@ msgid "Change password"
msgstr "Změnit heslo"
#: pretix/presale/templates/pretixpresale/organizers/customer_base.html:41
#, fuzzy
#| msgid "Customer account registration"
msgid "customer account information"
msgstr "Registrace účtu zákazníka"
msgstr "informace o zákaznickém účtu"
#: pretix/presale/templates/pretixpresale/organizers/customer_info.html:6
msgid "Account information"
@@ -34640,10 +34640,8 @@ msgid "transferable"
msgstr "Přenos"
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html:42
#, fuzzy
#| msgid "not answered"
msgid "not transferable"
msgstr "není odpovězeno"
msgstr "nepřenosné"
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html:122
#, fuzzy
@@ -34929,9 +34927,8 @@ msgid "You are not allowed to access time machine mode."
msgstr "Na tuto stránku nemáte přístup."
#: pretix/presale/views/event.py:975
#, fuzzy
msgid "This feature is only available in test mode."
msgstr "Tuto dárkovou poukázku lze použít pouze v testovacím režimu."
msgstr "Objednávku nesmíte změnit tak, aby vyžadovala vrácení peněz."
#: pretix/presale/views/event.py:992
msgid "Time machine disabled!"
@@ -35011,9 +35008,8 @@ msgid "You may not change your order in a way that changes the total price."
msgstr "Objednávku nemůžete změnit tak, aby se změnila celková cena."
#: pretix/presale/views/order.py:1634
#, fuzzy
msgid "You may not change your order in a way that would require a refund."
msgstr "Objednávku nesmíte změnit tak, aby se snížila celková cena."
msgstr "Objednávku nesmíte změnit tak, aby vyžadovala vrácení peněz."
#: pretix/presale/views/order.py:1642
msgid ""
@@ -35024,17 +35020,14 @@ msgstr ""
"objednávku měnit tak, aby se zvýšila celková cena."
#: pretix/presale/views/order.py:1648
#, fuzzy
#| msgid ""
#| "You may not change your order in a way that increases the total price "
#| "since payments are no longer being accepted for this event."
msgid ""
"You may not change your order in a way that requires additional payment "
"while we are processing your current payment. Please check back after your "
"current payment has been accepted."
msgstr ""
"Vzhledem k tomu, že platby za tuto akci již nepřijímáme, nemůžete svou "
"objednávku měnit tak, aby se zvýšila celková cena."
"Nemůžete změnit svou objednávku způsobem, který vyžaduje dodatečnou platbu, "
"zatímco zpracováváme vaši aktuální platbu. Vraťte se prosím poté, co bude "
"vaše platba přijata."
#: pretix/presale/views/order.py:1664 pretix/presale/views/order.py:1695
msgid "You cannot change this order."
@@ -35075,6 +35068,8 @@ msgid ""
"No ticket types are available for the waiting list, have a look at the "
"ticket shop instead."
msgstr ""
"Pro čekací listinu nejsou k dispozici žádné typy vstupenek, podívejte se "
"místo toho do obchodu se vstupenkami."
#: pretix/presale/views/waiting.py:137 pretix/presale/views/waiting.py:161
msgid "Waiting lists are disabled for this event."

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:02+0000\n"
"PO-Revision-Date: 2025-05-16 17:00+0000\n"
"Last-Translator: David <davemachala@gmail.com>\n"
"PO-Revision-Date: 2025-09-08 18:57+0000\n"
"Last-Translator: Alois Pospíšil <alois.pospisil@gmail.com>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
"cs/>\n"
"Language: cs\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 5.11.4\n"
"X-Generator: Weblate 5.13\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -640,19 +640,15 @@ msgid "Unknown error."
msgstr "Neznámá chyba."
#: pretix/static/pretixcontrol/js/ui/main.js:292
#, fuzzy
#| msgid "Your color has great contrast and is very easy to read!"
msgid "Your color has great contrast and will provide excellent accessibility."
msgstr "Tato barva má velmi dobrý kontrast a je velmi dobře čitelná!"
msgstr "Tato barva má velmi dobrý kontrast a je velmi dobře čitelná."
#: pretix/static/pretixcontrol/js/ui/main.js:296
#, fuzzy
#| msgid "Your color has decent contrast and is probably good-enough to read!"
msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility "
"requirements."
msgstr ""
"Tato barva má slušný kontrast a pravděpodobně je dostatečně dobře čitelná!"
"Tato barva má slušný kontrast a pravděpodobně je dostatečně dobře čitelná."
#: pretix/static/pretixcontrol/js/ui/main.js:300
msgid ""
@@ -735,7 +731,7 @@ msgstr "Nákupní košík vypršel"
#: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire."
msgstr ""
msgstr "Nákupní košík brzy vyprší."
#: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute."
@@ -748,10 +744,8 @@ msgstr[2] ""
"Produkty v nákupním košíku jsou pro vás rezervovány na dalších {num} minut."
#: pretix/static/pretixpresale/js/ui/cart.js:83
#, fuzzy
#| msgid "Cart expired"
msgid "Your cart has expired."
msgstr "Nákupní košík vypršel"
msgstr "Nákupní košík vypršel."
#: pretix/static/pretixpresale/js/ui/cart.js:86
#, fuzzy
@@ -811,12 +805,12 @@ msgstr "Zvýšit počet"
#: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget"
msgid "Filter events by"
msgstr ""
msgstr "Filtrovat události"
#: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget"
msgid "Filter"
msgstr ""
msgstr "Filtrovat"
#: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget"
@@ -933,7 +927,7 @@ msgstr "Již není k dispozici"
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Currently not available"
msgstr "Momentálně není k dispozici."
msgstr "Momentálně není k dispozici"
#: pretix/static/pretixpresale/js/widget/widget.js:44
#, javascript-format
@@ -968,7 +962,7 @@ msgstr "Obchod vstupenek otevřit"
#: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget"
msgid "Checkout"
msgstr "Checkout"
msgstr "Přejít k platbě"
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
@@ -1090,7 +1084,7 @@ msgstr "Předchozí týden"
#: pretix/static/pretixpresale/js/widget/widget.js:74
msgctxt "widget"
msgid "Open seat selection"
msgstr "Otevřete výběr sedadla"
msgstr "Otevřete výběr míst"
#: pretix/static/pretixpresale/js/widget/widget.js:75
msgctxt "widget"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-08-28 23:00+0000\n"
"PO-Revision-Date: 2025-09-09 04:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.13\n"
"X-Generator: Weblate 5.13.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -1384,7 +1384,7 @@ msgstr "言語"
#: pretix/control/templates/pretixcontrol/organizers/customer.html:72
#: pretix/control/templates/pretixcontrol/organizers/reusable_medium.html:68
msgid "Notes"
msgstr "注記"
msgstr "備考"
#: pretix/base/exporters/customers.py:100
#: pretix/base/exporters/customers.py:101 pretix/base/exporters/events.py:83
@@ -4413,7 +4413,7 @@ msgstr "障害を持つ"
#: pretix/base/models/customers.py:310 pretix/base/models/orders.py:1538
#: pretix/base/models/orders.py:3280 pretix/base/settings.py:1150
msgid "Company name"
msgstr "企業名"
msgstr "会社名"
#: pretix/base/models/customers.py:314 pretix/base/models/orders.py:1542
#: pretix/base/models/orders.py:3287 pretix/base/settings.py:83
@@ -14119,7 +14119,7 @@ msgstr "検索クエリ"
#: pretix/control/templates/pretixcontrol/organizers/giftcard_acceptance_list.html:133
#: pretix/control/templates/pretixcontrol/organizers/reusable_medium.html:39
msgid "active"
msgstr "アクティブ"
msgstr "有効"
#: pretix/control/forms/filter.py:1499
#: pretix/control/templates/pretixcontrol/organizers/customer.html:44
@@ -20414,7 +20414,7 @@ msgstr "理由"
#: pretix/control/templates/pretixcontrol/event/tax_edit.html:137
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:251
msgid "Add a new rule"
msgstr "新しいルールを追加する"
msgstr "新しいルールを追加"
#: pretix/control/templates/pretixcontrol/event/tax_edit.html:153
#: pretix/control/templates/pretixcontrol/organizers/edit.html:391

View File

@@ -48,8 +48,13 @@ function async_task_on_success(data) {
history.replaceState({}, "pretix", async_task_old_url);
}
}
if (!async_task_dont_redirect)
if (!async_task_dont_redirect) {
$(window).one("pageshow", function (e) {
// hide waitingDialog when using browser's history back
waitingDialog.hide();
});
location.href = data.redirect;
}
$(this).trigger('pretix:async-task-success', data);
}

View File

@@ -225,6 +225,16 @@ var form_handlers = function (el) {
};
function setup_basics(el) {
el.find("form").attr("novalidate", true).on("submit", function (e) {
if (!this.checkValidity()) {
var input = this.querySelector(":invalid:not(fieldset)");
(input.labels[0] || input).scrollIntoView();
// only use reportValidity, which usually sets focus on element
// input.focus() opens dropdowns, which is not what we want
input.reportValidity();
e.preventDefault();
}
});
el.find("input[data-toggle=radiocollapse]").change(function () {
$($(this).attr("data-parent")).find(".collapse.in").collapse('hide');
$($(this).attr("data-target")).collapse('show');

View File

@@ -0,0 +1,31 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import smtplib
from django.core.mail.backends.locmem import EmailBackend
class FailingEmailBackend(EmailBackend):
def send_messages(self, email_messages):
raise smtplib.SMTPRecipientsRefused({
'recipient@example.org': (450, b'Recipient unknown')
})

View File

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

View File

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

View File

@@ -33,7 +33,7 @@
# License for the specific language governing permissions and limitations under the License.
import json
from datetime import date, datetime, timedelta, timezone
from datetime import date, timedelta
from decimal import Decimal
import pytest
@@ -42,7 +42,6 @@ from django.utils.itercompat import is_iterable
from django.utils.timezone import now
from django_countries.fields import Country
from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString
from pretix.base.invoice import addon_aware_groupby
from pretix.base.models import (
@@ -63,8 +62,7 @@ def env():
with scope(organizer=o):
event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy',
date_from=datetime(2024, 12, 1, 9, 0, 0, tzinfo=timezone.utc),
plugins='pretix.plugins.banktransfer'
date_from=now(), plugins='pretix.plugins.banktransfer'
)
o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
@@ -662,154 +660,3 @@ def test_addon_aware_groupby():
[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

@@ -29,7 +29,7 @@ import pytest
from django.conf import settings
from django.core import mail as djmail
from django.db.models import F, Sum
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils.timezone import make_aware, now
from django_countries.fields import Country
from django_scopes import scope
@@ -811,6 +811,57 @@ def test_mark_invoices_as_sent(event):
assert i.transmission_provider == "email_pdf"
@pytest.mark.django_db(transaction=True)
@override_settings(EMAIL_BACKEND='pretix.testutils.mail.FailingEmailBackend')
def test_mark_invoices_as_failed(event):
djmail.outbox = []
event.settings.invoice_address_asked = True
event.settings.invoice_address_required = True
event.settings.invoice_generate = "True"
event.settings.invoice_email_attachment = True
o1 = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=now(), expires=now() - timedelta(days=10),
total=10, locale='en',
sales_channel=event.organizer.sales_channels.get(identifier="web"),
)
ticket = Item.objects.create(event=event, name='Early-bird ticket',
default_price=Decimal('23.00'), admission=True)
OrderPosition.objects.create(
order=o1, item=ticket, variation=None, price=Decimal("23.00"),
attendee_name_parts={'full_name': "Peter"},
positionid=1
)
ia = InvoiceAddress.objects.create(
order=o1,
is_business=True,
country=Country('AT'),
transmission_type="email",
transmission_info={
"transmission_email_address": "invoice@example.org",
}
)
o1.create_transactions()
i = generate_invoice(o1)
assert i.transmission_type == "email"
assert i.transmission_status == Invoice.TRANSMISSION_STATUS_PENDING
assert not i.transmission_provider
# If no other address is there, order address will be accepted
ia.transmission_info = {}
ia.save()
o1.send_mail(
subject=LazyI18nString({"en": "Hey"}),
template=LazyI18nString({"en": "Just wanted to send this invoice"}),
context={},
invoices=[i]
)
i.refresh_from_db()
assert i.transmission_status == Invoice.TRANSMISSION_STATUS_FAILED
assert i.transmission_provider == "email_pdf"
class PaymentReminderTests(TestCase):
def setUp(self):
super().setUp()