Compare commits

...

43 Commits

Author SHA1 Message Date
Raphael Michel
ac9f67ee15 Improve backend UI if not qualified for invoice 2026-01-26 09:30:10 +01:00
Raphael Michel
15c2c39ac4 Invoices: Allow issuing invoices only to businesses
In situations where every invoice has a significant accounting cost and
consumers usually do not need invoices, this can save a lot of money or
effort.
2026-01-26 09:30:10 +01:00
Raphael Michel
5c8e785a6f Fix typo from merge conflict resolving 2026-01-26 09:29:22 +01:00
Raphael Michel
8e61ac6071 Invoice address: Add convenient autofill for Pepppol in Belgium (Z#23220397) (#5809)
* Invoice address: Add convenient autofill for Pepppol in Belgium (Z#23220397)

* Update src/pretix/static/pretixbase/js/addressform.js

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>

---------

Co-authored-by: Richard Schreiber <schreiber@pretix.eu>
2026-01-26 09:12:07 +01:00
Raphael Michel
c3fd3a0838 Scheduled exports: Add copy button (Z#23221224) (#5823)
* Scheduled exports: Add copy button (Z#23221224)

* Update button label
2026-01-26 08:46:25 +01:00
Phin Wolkwitz
0d6e1e2271 Prefetch program times, add test for query count (#5822) 2026-01-26 08:38:44 +01:00
Raphael Michel
0af011eed4 Web check-in: Show addons of ticket (Z#23220213) (#5827)
* Web check-in: Show addons of ticket (Z#23220213)

* Update src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/components/app.vue

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

---------

Co-authored-by: luelista <weller@rami.io>
2026-01-26 08:37:54 +01:00
Kian Cross
a0dae48cec Prevent double-clicks on SSO login providers (#5842) 2026-01-26 08:31:30 +01:00
Ruud Hendrickx
a53795ea88 Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Ruud Hendrickx
f1c0f24e25 Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Ruud Hendrickx
980f4712a7 Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Ruud Hendrickx
bc8a8d8851 Translations: Update Dutch
Currently translated at 100.0% (254 of 254 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Ruud Hendrickx
10ec4d6c29 Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Vajda Tamás
584345cb99 Translations: Update Hungarian
Currently translated at 40.1% (102 of 254 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Vajda Tamás
88545bcd05 Translations: Update Hungarian
Currently translated at 10.6% (657 of 6193 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Ruud Hendrickx
f034f4cde4 Translations: Update Dutch
Currently translated at 100.0% (254 of 254 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Ruud Hendrickx
fc6475b0bc Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-26 08:26:17 +01:00
Lukas Bockstaller
aecc87ccdb handle open ended datetime ranges (#5838) 2026-01-23 12:25:28 +01:00
Raphael Michel
059179aecb Fix babel locale discovery for zh_Hans_US 2026-01-23 11:36:12 +01:00
Raphael Michel
fd72e18a7f Overview export: Allow to skip empty lines (Z#23219200) (#5825) 2026-01-23 11:18:07 +01:00
Raphael Michel
baac963fa8 API: Fix crash in check-in API (PRETIXEU-CT1) (#5806) 2026-01-23 11:17:21 +01:00
Ruud Hendrickx
461ab2472f Translations: Update Dutch
Currently translated at 100.0% (254 of 254 strings)

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

powered by weblate
2026-01-23 09:00:20 +01:00
Ruud Hendrickx
29d98f4182 Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-23 09:00:20 +01:00
Raphael Michel
4f989cbe8a Order export: Add voucher_budget_use (Z#23218461) 2026-01-22 21:26:42 +01:00
Raphael Michel
23559e0711 Scheduled export: Move error message for missing permissions
This error message mostly occurs when working in admin mode and this
change allows our support team to still see what the form looks like to
guide users through, even if they can't save.
2026-01-22 21:26:33 +01:00
Ruud Hendrickx
8787f79274 Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Jiří Pastrňák
a7072d3b5b Translations: Update Czech
Currently translated at 70.0% (4340 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Ruud Hendrickx
ff47ee7d68 Translations: Update Dutch
Currently translated at 100.0% (254 of 254 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Ruud Hendrickx
2c321f401d Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Ruud Hendrickx
180b92c87f Translations: Update Dutch (informal) (nl_Informal)
Currently translated at 69.4% (4298 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Ruud Hendrickx
c99751b319 Translations: Update Dutch
Currently translated at 100.0% (6193 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Ruud Hendrickx
1f4205a9d9 Translations: Update Dutch
Currently translated at 96.1% (5952 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Ruud Hendrickx
9e694982cf Translations: Update Dutch (informal) (nl_Informal)
Currently translated at 67.8% (4199 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Jiří Pastrňák
ea5dbb05c2 Translations: Update Czech
Currently translated at 70.0% (4339 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
Linnea Thelander
e2ede76468 Translations: Update Swedish
Currently translated at 89.9% (5573 of 6193 strings)

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

powered by weblate
2026-01-22 10:32:36 +01:00
dependabot[bot]
498f5760af Update sentry-sdk requirement from ==2.49.* to ==2.50.* (#5828)
Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/2.49.0...2.50.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 10:20:41 +01:00
dependabot[bot]
3b9ae7e560 Bump pycparser from 2.23 to 3.0 (#5832)
Bumps [pycparser](https://github.com/eliben/pycparser) from 2.23 to 3.0.
- [Release notes](https://github.com/eliben/pycparser/releases)
- [Commits](https://github.com/eliben/pycparser/compare/release_v2.23...release_v3.00)

---
updated-dependencies:
- dependency-name: pycparser
  dependency-version: '3.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 10:20:32 +01:00
dependabot[bot]
c08e3c054a Bump markdown from 3.10 to 3.10.1 (#5833)
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.10 to 3.10.1.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.10.0...3.10.1)

---
updated-dependencies:
- dependency-name: markdown
  dependency-version: 3.10.1
  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>
2026-01-22 10:20:24 +01:00
Raphael Michel
815e31d9a0 Resolve syntax warning in Pyton 3.14 2026-01-20 12:15:49 +01:00
Lukas Bockstaller
ed618f2f32 add tiered availability by time (Z#23204747) (#5737)
* add tiered availability by time

* replace bitwise operator

* rephrase help text
2026-01-20 10:32:17 +01:00
Lukas Bockstaller
a900e11ce0 Reduce queries for waitinglist autoassign n+1 (PRETIXEU-BJJ) (#5819)
* baseline of 574 queries

* reuse event from wle for locked_wle

reduces amount of queries to 556

* keep event accross refresh from db

drops queries from 556 to 471, halving the amount of queries for direct fetches for the event

* make numbers of queries reproducible by prewarming ContentTypeCache

* fix oversight

* correct number of queries to 335

* remove debug tooling

* remove assert_num_queries
2026-01-20 10:31:58 +01:00
Richard Schreiber
112d5da792 Localize state names (#5744)
* Localize state names in js-helper

* localize statename in address-confirm

* add localized state_name to AbstractPosition and AttendeeProfile

* use state_for_address in order export
2026-01-20 10:13:20 +01:00
Richard Schreiber
ceb2e13d27 Remove autofocus from only button in cart-extend confirm-dialog (#5821) 2026-01-20 09:53:16 +01:00
47 changed files with 2005 additions and 2073 deletions

View File

@@ -65,7 +65,7 @@ dependencies = [
"kombu==5.6.*",
"libsass==0.23.*",
"lxml",
"markdown==3.10", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
"markdown==3.10.1", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*",
"oauthlib==3.3.*",
@@ -80,7 +80,7 @@ dependencies = [
"protobuf==6.33.*",
"psycopg2-binary",
"pycountry",
"pycparser==2.23",
"pycparser==3.0",
"pycryptodome==3.23.*",
"pypdf==6.5.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
@@ -92,7 +92,7 @@ dependencies = [
"redis==7.1.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.49.*",
"sentry-sdk==2.50.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",

View File

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

View File

@@ -704,6 +704,16 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
if 'answers.question' in self.context['expand']:
self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True)
if 'addons' in self.context['expand']:
# Experimental feature, undocumented on purpose for now in case we need to remove it again
# for performance reasons
subl = CheckinListOrderPositionSerializer(read_only=True, many=True, context={
**self.context,
'expand': [v for v in self.context['expand'] if v != 'addons'],
'pdf_data': False,
})
self.fields['addons'] = subl
class OrderPaymentTypeField(serializers.Field):
# TODO: Remove after pretix 2.2

View File

@@ -381,15 +381,21 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
qs = qs.filter(reduce(operator.or_, lists_qs))
prefetch_related = [
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
]
select_related = [
'item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat'
]
if pdf_data:
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
# Don't add to list, we don't want to propagate to addons
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
'event',
@@ -404,32 +410,39 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
)
)
))
).select_related(
'item', 'variation', 'item__category', 'addon_to', 'order', 'order__invoice_address', 'seat'
)
else:
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat')
if expand and 'subevent' in expand:
qs = qs.prefetch_related(
prefetch_related += [
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values'
)
]
if expand and 'item' in expand:
qs = qs.prefetch_related('item', 'item__addons', 'item__bundles', 'item__meta_values',
'item__variations').select_related('item__tax_rule')
prefetch_related += [
'item', 'item__addons', 'item__bundles', 'item__meta_values',
'item__variations',
]
select_related.append('item__tax_rule')
if expand and 'variation' in expand:
qs = qs.prefetch_related('variation', 'variation__meta_values')
prefetch_related += [
'variation', 'variation__meta_values',
]
if expand and 'addons' in expand:
prefetch_related += [
Prefetch('addons', OrderPosition.objects.prefetch_related(*prefetch_related).select_related(*select_related)),
]
else:
prefetch_related += [
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
]
if pdf_data:
select_related.remove("order") # Don't need it twice on this queryset
qs = qs.prefetch_related(*prefetch_related).select_related(*select_related)
return qs
@@ -966,6 +979,7 @@ class CheckinRPCSearchView(ListAPIView):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['expand'] = self.request.query_params.getlist('expand')
ctx['organizer'] = self.request.organizer
ctx['pdf_data'] = False
return ctx

View File

@@ -106,7 +106,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
'variations', 'addons', 'bundles', 'meta_values', 'meta_values__property',
'variations__meta_values', 'variations__meta_values__property',
'require_membership_types', 'variations__require_membership_types',
'limit_sales_channels', 'variations__limit_sales_channels',
'limit_sales_channels', 'variations__limit_sales_channels', 'program_times'
).all()
def perform_create(self, serializer):

View File

@@ -2031,7 +2031,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
else:
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
c = generate_cancellation(inv)
if inv.order.status != Order.STATUS_CANCELED:
if invoice_qualified(order):
inv = generate_invoice(order)
else:
inv = c

View File

@@ -364,7 +364,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state,
order.invoice_address.state_for_address,
order.invoice_address.custom_field,
order.invoice_address.vat_id,
]
@@ -515,7 +515,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state,
order.invoice_address.state_for_address,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:
@@ -617,6 +617,7 @@ class OrderListExporter(MultiSheetListExporter):
_('Country'),
pgettext('address', 'State'),
_('Voucher'),
_('Voucher budget usage'),
_('Pseudonymization ID'),
_('Ticket secret'),
_('Seat ID'),
@@ -732,8 +733,9 @@ class OrderListExporter(MultiSheetListExporter):
op.zipcode or '',
op.city or '',
op.country if op.country else '',
op.state or '',
op.state_for_address or '',
op.voucher.code if op.voucher else '',
op.voucher_budget_use if op.voucher_budget_use else '',
op.pseudonymization_id,
op.secret,
]
@@ -797,7 +799,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state,
order.invoice_address.state_for_address,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:

View File

@@ -141,7 +141,7 @@ def get_babel_locale():
for locale in try_locales:
if localedata.exists(locale):
return locale
return localedata.normalize_locale(locale)
return "en"

View File

@@ -349,7 +349,7 @@ class AttendeeProfile(models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return sd.name
return _(sd.name)
return self.state
@property

View File

@@ -594,10 +594,11 @@ class Item(LoggedModel):
on_delete=models.SET_NULL,
verbose_name=_("Only show after sellout of"),
help_text=_("If you select a product here, this product will only be shown when that product is "
"sold out. If combined with the option to hide sold-out products, this allows you to "
"swap out products for more expensive ones once the cheaper option is sold out. There might "
"be a short period in which both products are visible while all tickets of the referenced "
"product are reserved, but not yet sold.")
"no longer available. This will happen either because the other product has sold out or because "
"the time is outside of the sales window for the other product. If combined with the option "
"to hide sold-out products, this allows you to swap out products for more expensive ones once "
"the cheaper option is sold out. There might be a short period in which both products are visible "
"while all tickets of the referenced product are reserved, but not yet sold.")
)
hidden_if_item_available_mode = models.CharField(
choices=UNAVAIL_MODES,

View File

@@ -1675,7 +1675,7 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return sd.name
return _(sd.name)
return self.state
@property
@@ -3480,7 +3480,7 @@ class InvoiceAddress(models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return sd.name
return _(sd.name)
return self.state
@property

View File

@@ -159,6 +159,7 @@ class WaitingListEntry(LoggedModel):
if availability[1] is None or availability[1] < 1:
raise WaitingListException(_('This product is currently not available.'))
event = self.event
ev = self.subevent or self.event
if ev.seat_category_mappings.filter(product=self.item).exists():
# Generally, we advertise the waiting list to be based on quotas only. This makes it dangerous
@@ -191,6 +192,7 @@ class WaitingListEntry(LoggedModel):
with transaction.atomic():
locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
locked_wle.event = event
if locked_wle.voucher:
raise WaitingListException(_('A voucher has already been sent to this person.'))
e = locked_wle.email
@@ -227,6 +229,7 @@ class WaitingListEntry(LoggedModel):
locked_wle.save()
self.refresh_from_db()
self.event = event
with language(self.locale, self.event.settings.region):
self.send_mail(

View File

@@ -521,9 +521,20 @@ def invoice_pdf_task(invoice: int):
def invoice_qualified(order: Order):
if order.total == Decimal('0.00') or order.require_approval or \
order.sales_channel.identifier not in order.event.settings.get('invoice_generate_sales_channels'):
if order.total == Decimal('0.00'):
return False
if order.require_approval:
return False
if order.sales_channel.identifier not in order.event.settings.invoice_generate_sales_channels:
return False
if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
return False
if order.event.settings.invoice_generate_only_business:
try:
ia = order.invoice_address
return ia.is_business
except InvoiceAddress.DoesNotExist:
return False
return True

View File

@@ -112,7 +112,8 @@ def dictsum(*dicts) -> dict:
def order_overview(
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False,
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None,
skip_empty_lines=False,
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
items = event.items.all().select_related(
'category', # for re-grouping
@@ -205,13 +206,21 @@ def order_overview(
for l in states.keys():
var.num[l] = num[l].get((item.id, variid), (0, 0, 0))
var.num['total'] = num['total'].get((item.id, variid), (0, 0, 0))
var._skip = all(v[0] == 0 for v in var.num.values())
for l in states.keys():
item.num[l] = tuplesum(var.num[l] for var in item.all_variations)
item.num['total'] = tuplesum(var.num['total'] for var in item.all_variations)
if skip_empty_lines:
item.all_variations = [v for v in item.all_variations if not v._skip]
item._skip = not item.all_variations
else:
for l in states.keys():
item.num[l] = num[l].get((item.id, None), (0, 0, 0))
item.num['total'] = num['total'].get((item.id, None), (0, 0, 0))
item._skip = all(v[0] == 0 for v in item.num.values())
if skip_empty_lines:
items = [i for i in items if not i._skip]
nonecat = ItemCategory(name=_('Uncategorized'))
# Regroup those by category

View File

@@ -1225,6 +1225,15 @@ DEFAULTS = {
'default': json.dumps(['web']),
'type': list
},
'invoice_generate_only_business': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Only issue invoices to business customers"),
)
},
'invoice_address_from': {
'default': '',
'type': str,

View File

@@ -82,7 +82,7 @@ def _info(cc):
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
return {
'data': [
{'name': s.name, 'code': s.code[3:]}
{'name': gettext(s.name), 'code': s.code[3:]}
for s in sorted(statelist, key=lambda s: s.name)
],
**info,

View File

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

View File

@@ -1315,10 +1315,10 @@ class QuestionAnswerFilterForm(forms.Form):
if date_range is not None:
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone)
opqs = opqs.filter(
subevent__date_from__gte=d_start,
subevent__date_from__lt=d_end
)
if d_start:
opqs = opqs.filter(subevent__date_from__gte=d_start)
if d_end:
opqs = opqs.filter(subevent__date_from__lt=d_end)
s = fdata.get("status", Order.STATUS_PENDING + Order.STATUS_PAID)
if s != "":

View File

@@ -12,6 +12,7 @@
<legend>{% trans "Invoice generation" %}</legend>
{% bootstrap_field form.invoice_generate layout="control" %}
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
{% bootstrap_field form.invoice_generate_only_business layout="control" %}
{% bootstrap_field form.invoice_email_attachment layout="control" %}
{% bootstrap_field form.invoice_email_organizer layout="control" %}
{% bootstrap_field form.invoice_language layout="control" %}

View File

@@ -353,7 +353,7 @@
data-toggle="tooltip"
title="{% trans 'Generate a cancellation document for this invoice and create a new invoice with a new invoice number.' %}"
{% endif %}>
{% if order.status == "c" %}
{% if order.status == "c" or not invoice_qualified %}
{% trans "Generate cancellation" %}
{% else %}
{% trans "Cancel and reissue" %}

View File

@@ -22,7 +22,7 @@
{{ s.owner.fullname|default:s.owner.email }}
</span>
</div>
<div class="col-lg-5 col-md-6 col-xs-12">
<div class="col-lg-4 col-md-5 col-xs-12">
{% if s.schedule_next_run %}
<span class="fa fa-clock-o fa-fw"></span>
{% trans "Next run:" %}
@@ -53,7 +53,7 @@
{{ s.mail_subject }}
</span>
</div>
<div class="col-lg-2 col-md-2 col-xs-12 text-right">
<div class="col-lg-3 col-md-3 col-xs-12 text-right">
<form action="{% url "control:event.orders.export.do" event=request.event.slug organizer=request.organizer.slug %}"
method="post" class="form-horizontal" data-asynctask data-asynctask-download
data-asynctask-long>
@@ -73,6 +73,9 @@
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
<span class="fa fa-edit"></span>
</a>
<a href="?identifier={{ s.export_identifier }}&scheduled_copy_from={{ s.pk }}" class="btn btn-default" title="{% trans "Copy" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
{% endif %}
<a href="{% url "control:event.orders.export.scheduled.delete" event=request.event.slug organizer=request.event.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
<span class="fa fa-trash"></span>

View File

@@ -42,7 +42,11 @@
<div class="form-group submit-group">
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
class="btn btn-primary btn-save" data-no-asynctask>
{% trans "Save" %}
{% if scheduled_copy_from %}
{% trans "Save copy" %}
{% else %}
{% trans "Save" %}
{% endif %}
</button>
</div>
{% else %}

View File

@@ -22,7 +22,7 @@
{{ s.owner.fullname|default:s.owner.email }}
</span>
</div>
<div class="col-lg-5 col-md-6 col-xs-12">
<div class="col-lg-4 col-md-5 col-xs-12">
{% if s.schedule_next_run %}
<span class="fa fa-clock-o fa-fw"></span>
{% trans "Next run:" %}
@@ -53,7 +53,7 @@
{{ s.mail_subject }}
</span>
</div>
<div class="col-lg-2 col-md-2 col-xs-12 text-right">
<div class="col-lg-3 col-md-3 col-xs-12 text-right">
<form action="{% url "control:organizer.export.do" organizer=request.organizer.slug %}"
method="post" class="form-horizontal" data-asynctask data-asynctask-download
data-asynctask-long>
@@ -73,6 +73,9 @@
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
<span class="fa fa-edit"></span>
</a>
<a href="?identifier={{ s.export_identifier }}&scheduled_copy_from={{ s.pk }}" class="btn btn-default" title="{% trans "Copy" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
{% endif %}
<a href="{% url "control:organizer.export.scheduled.delete" organizer=request.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
<span class="fa fa-trash"></span>

View File

@@ -43,7 +43,11 @@
<div class="form-group submit-group">
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
class="btn btn-primary btn-save" data-no-asynctask>
{% trans "Save" %}
{% if scheduled_copy_from %}
{% trans "Save copy" %}
{% else %}
{% trans "Save" %}
{% endif %}
</button>
</div>
{% else %}

View File

@@ -363,7 +363,7 @@ class Forgot(TemplateView):
else:
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions.'))
return redirect('control:auth.forgot')
return redirect('control:auth.forgot')
else:
return self.get(request, *args, **kwargs)

View File

@@ -33,6 +33,7 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import copy
import json
import logging
import mimetypes
@@ -508,9 +509,10 @@ class OrderView(EventPermissionRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['can_generate_invoice'] = invoice_qualified(self.order) and (
ctx['invoice_qualified'] = invoice_qualified(self.order)
ctx['can_generate_invoice'] = ctx['invoice_qualified'] and (
self.request.event.settings.invoice_generate in ('admin', 'user', 'paid', 'user_paid', 'True')
) and self.order.status in (Order.STATUS_PAID, Order.STATUS_PENDING) and (
) and (
not self.order.invoices.exists()
or self.order.invoices.filter(is_cancellation=True).count() >= self.order.invoices.filter(is_cancellation=False).count()
)
@@ -1741,14 +1743,15 @@ class OrderInvoiceReissue(OrderView):
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
else:
c = generate_cancellation(inv)
if order.status not in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
if invoice_qualified(order):
inv = generate_invoice(order)
messages.success(self.request, _('The invoice has been reissued.'))
else:
inv = c
messages.success(self.request, _('The invoice has been canceled.'))
order.log_action('pretix.event.order.invoice.reissued', user=self.request.user, data={
'invoice': inv.pk
})
messages.success(self.request, _('The invoice has been reissued.'))
return redirect(self.get_order_url())
def get(self, *args, **kwargs): # NOQA
@@ -2677,8 +2680,8 @@ class ExportMixin:
if id != ex.identifier:
continue
if self.scheduled:
initial = dict(self.scheduled.export_form_data)
if self.scheduled or self.scheduled_copy_from:
initial = dict((self.scheduled or self.scheduled_copy_from).export_form_data)
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
test_form.fields = ex.export_form_fields
@@ -2721,6 +2724,11 @@ class ExportMixin:
elif "scheduled" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled"))
@cached_property
def scheduled_copy_from(self):
if "scheduled_copy_from" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled_copy_from"))
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['exporters'] = self.exporters
@@ -2790,7 +2798,16 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
@transaction.atomic()
def post(self, request, *args, **kwargs):
if request.POST.get("schedule") == "save":
if self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
if not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
return super().get(request, *args, **kwargs)
elif self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
self.schedule_form.instance.export_identifier = self.exporter.identifier
self.schedule_form.instance.export_form_data = self.exporter.form.cleaned_data
self.schedule_form.instance.schedule_rrule = str(self.rrule_form.to_rrule())
@@ -2829,6 +2846,8 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
def rrule_form(self):
if self.scheduled:
initial = RRuleForm.initial_from_rrule(self.scheduled.schedule_rrule)
elif self.scheduled_copy_from:
initial = RRuleForm.initial_from_rrule(self.scheduled_copy_from.schedule_rrule)
else:
initial = {}
return RRuleForm(
@@ -2839,11 +2858,15 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
@cached_property
def schedule_form(self):
instance = self.scheduled or ScheduledEventExport(
event=self.request.event,
owner=self.request.user,
)
if not self.scheduled:
if self.scheduled_copy_from:
instance = copy.copy(self.scheduled_copy_from)
instance.pk = None
else:
instance = self.scheduled or ScheduledEventExport(
event=self.request.event,
owner=self.request.user,
)
if not self.scheduled and not self.scheduled_copy_from:
initial = {
"mail_subject": gettext("Export: {title}").format(title=self.exporter.verbose_name),
"mail_template": gettext("Hello,\n\nattached to this email, you can find a new scheduled report for {name}.").format(
@@ -2868,18 +2891,11 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if "schedule" in self.request.POST or self.scheduled:
if "schedule" in self.request.POST and not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
else:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
if "schedule" in self.request.POST or self.scheduled or self.scheduled_copy_from:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
ctx['scheduled_copy_from'] = self.scheduled_copy_from
elif not self.exporter:
for s in ctx['scheduled']:
try:

View File

@@ -32,6 +32,7 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import copy
import json
import logging
import re
@@ -1943,8 +1944,8 @@ class ExportMixin:
for ex in self.exporters:
if id != ex.identifier:
continue
if self.scheduled:
initial = dict(self.scheduled.export_form_data)
if self.scheduled or self.scheduled_copy_from:
initial = dict((self.scheduled or self.scheduled_copy_from).export_form_data)
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
test_form.fields = ex.export_form_fields
@@ -2047,6 +2048,11 @@ class ExportMixin:
elif "scheduled" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled"))
@cached_property
def scheduled_copy_from(self):
if "scheduled_copy_from" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled_copy_from"))
class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, TemplateView):
known_errortypes = ['ExportError', 'ExportEmptyError']
@@ -2113,7 +2119,16 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
@transaction.atomic()
def post(self, request, *args, **kwargs):
if request.POST.get("schedule") == "save":
if self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
if not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
return super().get(request, *args, **kwargs)
elif self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
self.schedule_form.instance.export_identifier = self.exporter.identifier
self.schedule_form.instance.export_form_data = self.exporter.form.cleaned_data
self.schedule_form.instance.schedule_rrule = str(self.rrule_form.to_rrule())
@@ -2151,6 +2166,8 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
def rrule_form(self):
if self.scheduled:
initial = RRuleForm.initial_from_rrule(self.scheduled.schedule_rrule)
elif self.scheduled_copy_from:
initial = RRuleForm.initial_from_rrule(self.scheduled_copy_from.schedule_rrule)
else:
initial = {}
return RRuleForm(
@@ -2162,11 +2179,15 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
@cached_property
def schedule_form(self):
instance = self.scheduled or ScheduledOrganizerExport(
organizer=self.request.organizer,
owner=self.request.user,
timezone=str(get_current_timezone()),
)
if self.scheduled_copy_from:
instance = copy.copy(self.scheduled_copy_from)
instance.pk = None
else:
instance = self.scheduled or ScheduledOrganizerExport(
organizer=self.request.organizer,
owner=self.request.user,
timezone=str(get_current_timezone()),
)
if not self.scheduled:
initial = {
"mail_subject": gettext("Export: {title}").format(title=self.exporter.verbose_name),
@@ -2199,18 +2220,10 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if "schedule" in self.request.POST or self.scheduled:
if "schedule" in self.request.POST and not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
else:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
if "schedule" in self.request.POST or self.scheduled or self.scheduled_copy_from:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
ctx['scheduled_copy_from'] = self.scheduled_copy_from
elif not self.exporter:
for s in ctx['scheduled']:
try:

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2026-01-11 22:00+0000\n"
"PO-Revision-Date: 2026-01-21 21:00+0000\n"
"Last-Translator: Jiří Pastrňák <jiri@pastrnak.email>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/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.15.1\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -1623,7 +1623,7 @@ msgstr "Čas konce události"
#: pretix/base/models/event.py:1536 pretix/control/forms/subevents.py:494
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:274
msgid "Admission time"
msgstr "Vstupní čas"
msgstr "Vstup"
#: pretix/base/exporters/events.py:65 pretix/base/models/event.py:634
#: pretix/base/models/event.py:1545 pretix/control/forms/subevents.py:93
@@ -14592,7 +14592,7 @@ msgstr "Zaplaceno do"
#: pretix/plugins/reports/exporters.py:396
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:23
msgid "Paid"
msgstr "Placené"
msgstr "Zaplaceno"
#: pretix/control/forms/filter.py:1304
msgctxt "subevent"
@@ -35218,10 +35218,8 @@ msgid "Save selection"
msgstr "Uložit volbu"
#: pretix/presale/templates/pretixpresale/fragment_modals.html:145
#, fuzzy
#| msgid "You did not select any products."
msgid "You didn't select any ticket."
msgstr "Nevybrali jste žádné produkty."
msgstr "Nevybrali jste žádné vstupenky."
#: pretix/presale/templates/pretixpresale/fragment_modals.html:146
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2025-05-17 18:00+0000\n"
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
"PO-Revision-Date: 2026-01-23 15:00+0000\n"
"Last-Translator: Vajda Tamás <vajda.tamas@szwg.hu>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix/"
"hu/>\n"
"Language: hu\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.11.4\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -37,11 +37,11 @@ msgstr "arab"
#: pretix/_base_settings.py:91
msgid "Basque"
msgstr ""
msgstr "Baszk"
#: pretix/_base_settings.py:92
msgid "Catalan"
msgstr ""
msgstr "Katalán"
#: pretix/_base_settings.py:93
msgid "Chinese (simplified)"
@@ -57,7 +57,7 @@ msgstr "cseh"
#: pretix/_base_settings.py:96
msgid "Croatian"
msgstr ""
msgstr "Horvát"
#: pretix/_base_settings.py:97
msgid "Danish"
@@ -89,7 +89,7 @@ msgstr "görög"
#: pretix/_base_settings.py:104
msgid "Hebrew"
msgstr ""
msgstr "Héber"
#: pretix/_base_settings.py:105
msgid "Indonesian"
@@ -101,7 +101,7 @@ msgstr "olasz"
#: pretix/_base_settings.py:107
msgid "Japanese"
msgstr ""
msgstr "Japán"
#: pretix/_base_settings.py:108
msgid "Latvian"
@@ -133,11 +133,11 @@ msgstr "orosz"
#: pretix/_base_settings.py:115
msgid "Slovak"
msgstr ""
msgstr "Szlovák"
#: pretix/_base_settings.py:116
msgid "Swedish"
msgstr ""
msgstr "Svéd"
#: pretix/_base_settings.py:117
msgid "Spanish"
@@ -145,7 +145,7 @@ msgstr "spanyol"
#: pretix/_base_settings.py:118
msgid "Spanish (Latin America)"
msgstr ""
msgstr "Spanyol (Latin-Amerikai változat)"
#: pretix/_base_settings.py:119
msgid "Turkish"
@@ -305,7 +305,7 @@ msgstr "A fizetés túl későn történt meg az elfogadáshoz."
#: pretix/api/serializers/item.py:242 pretix/base/models/items.py:2321
msgid "The program end must not be before the program start."
msgstr ""
msgstr "A program vége időpont nem lehet a kezdő időpont előtt."
#: pretix/api/serializers/item.py:247 pretix/base/models/items.py:2315
#, fuzzy
@@ -369,12 +369,12 @@ msgstr "Ezt a fajta kérdést nem lehet megmutatni becsekkolás közben."
msgid ""
"A medium with the same identifier and type already exists in your organizer "
"account."
msgstr ""
msgstr "Már létező médium (azonos azonosító és típus)."
#: pretix/api/serializers/order.py:85
#, python-brace-format
msgid "\"{input}\" is not a valid choice."
msgstr ""
msgstr "\"{input}\" nem választható."
#: pretix/api/serializers/order.py:1453 pretix/api/views/cart.py:224
#: pretix/base/services/orders.py:1605
@@ -397,38 +397,38 @@ msgstr "Nem áll elegendő \"{}\" kvóta rendelkezésre a művelet végrehajtás
#: pretix/api/serializers/organizer.py:145
#: pretix/control/forms/organizer.py:925 pretix/presale/forms/customer.py:458
msgid "An account with this email address is already registered."
msgstr ""
msgstr "Ezzel az email címmel már létezik regisztráció."
#: pretix/api/serializers/organizer.py:278
#: pretix/control/forms/organizer.py:761
msgid ""
"A gift card with the same secret already exists in your or an affiliated "
"organizer account."
msgstr ""
msgstr "Ezzel a titkos kulccsal már létezik ajándékkártya."
#: pretix/api/serializers/organizer.py:369
#: pretix/control/views/organizer.py:1042
msgid "pretix account invitation"
msgstr ""
msgstr "pretix ügyfél meghívó"
#: pretix/api/serializers/organizer.py:391
#: pretix/control/views/organizer.py:1141
msgid "This user already has been invited for this team."
msgstr ""
msgstr "Ez a felhasználó már meghívásra került ebbe a csoportba."
#: pretix/api/serializers/organizer.py:407
#: pretix/control/views/organizer.py:1158
msgid "This user already has permissions for this team."
msgstr ""
msgstr "A felhasználó már rendelkezik hozzáféréssel."
#: pretix/api/views/cart.py:209
msgid ""
"The specified voucher has already been used the maximum number of times."
msgstr ""
msgstr "A kupon elérte a felhasználhatóságának felső határát."
#: pretix/api/views/checkin.py:627 pretix/api/views/checkin.py:634
msgid "Medium connected to other event"
msgstr ""
msgstr "A médium rendezvényhez csatlakoztatása megtörtént"
#: pretix/api/views/oauth.py:107 pretix/control/logdisplay.py:764
#, python-brace-format
@@ -436,21 +436,23 @@ msgid ""
"The application \"{application_name}\" has been authorized to access your "
"account."
msgstr ""
"A(z) \"{application_name}\" applikáció mostantól eléréssel rendelkezik a "
"fiókodhoz."
#: pretix/api/views/order.py:609 pretix/control/views/orders.py:1608
#: pretix/presale/views/order.py:736 pretix/presale/views/order.py:816
msgid "You cannot generate an invoice for this order."
msgstr ""
msgstr "Ehhez a megrendeléshez nem készíthető számla."
#: pretix/api/views/order.py:614 pretix/control/views/orders.py:1610
#: pretix/presale/views/order.py:738 pretix/presale/views/order.py:818
msgid "An invoice for this order already exists."
msgstr ""
msgstr "Ehhez a megrendeléshez már készült számla."
#: pretix/api/views/order.py:640 pretix/control/views/orders.py:1788
#: pretix/control/views/users.py:145
msgid "There was an error sending the mail. Please try again later."
msgstr ""
msgstr "Az email küldése sikertelen. Kérjük, próbálja meg később."
#: pretix/api/views/order.py:720 pretix/base/services/cart.py:223
#: pretix/base/services/orders.py:190 pretix/presale/views/order.py:800
@@ -461,22 +463,22 @@ msgstr "\"{}\" termékhez nincs kvóta rendelve."
#: pretix/api/webhooks.py:263 pretix/base/notifications.py:233
msgid "New order placed"
msgstr ""
msgstr "Új megrendelés került rögzítésre"
#: pretix/api/webhooks.py:267 pretix/base/notifications.py:239
msgid "New order requires approval"
msgstr ""
msgstr "Az új megrendelés jóváhagyásra vár"
#: pretix/api/webhooks.py:271 pretix/base/notifications.py:245
msgid "Order marked as paid"
msgstr ""
msgstr "A megrendelés fizetett státuszba került"
#: pretix/api/webhooks.py:275 pretix/base/models/checkin.py:355
#: pretix/base/notifications.py:251
#: pretix/control/templates/pretixcontrol/event/mail.html:114
#: pretix/control/views/orders.py:1569
msgid "Order canceled"
msgstr ""
msgstr "A megrendelés lemondásra került"
#: pretix/api/webhooks.py:279 pretix/base/notifications.py:257
msgid "Order reactivated"
@@ -614,29 +616,23 @@ msgstr ""
#: pretix/api/webhooks.py:401
msgid "Waiting list entry changed"
msgstr ""
msgstr "A várólista eleme megváltozott."
#: pretix/api/webhooks.py:405
#, fuzzy
#| msgid "The selected seat \"{seat}\" is not available."
msgid "Waiting list entry deleted"
msgstr "A kiválasztott \"{seat}\" ülés nem elérhető."
msgstr "A várólista eleme törlésre került."
#: pretix/api/webhooks.py:409
msgid "Waiting list entry received voucher"
msgstr ""
msgstr "A várólista eleme kupont kapott"
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Voucher code"
msgid "Voucher added"
msgstr "Kuponkód"
msgstr "Kuponkód hozzáadásra került"
#: pretix/api/webhooks.py:417
#, fuzzy
#| msgid "Voucher code"
msgid "Voucher changed"
msgstr "Kuponkód"
msgstr "Kuponkód megváltozott"
#: pretix/api/webhooks.py:418
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:13+0000\n"
"PO-Revision-Date: 2024-11-28 06:00+0000\n"
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
"PO-Revision-Date: 2026-01-23 15:00+0000\n"
"Last-Translator: Vajda Tamás <vajda.tamas@szwg.hu>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
"js/hu/>\n"
"Language: hu\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.8.3\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -317,7 +317,7 @@ msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
msgid "Order canceled"
msgstr ""
msgstr "A megrendelés lemondásra került"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list"

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:13+0000\n"
"PO-Revision-Date: 2025-06-10 04:00+0000\n"
"Last-Translator: Tim Maurizio Dullaart <Tim.maurizio@gmail.com>\n"
"PO-Revision-Date: 2026-01-24 01:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
"nl/>\n"
"Language: nl\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.11.4\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -63,7 +63,7 @@ msgstr "iDEAL"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"
msgstr "SEPA Automatische Incasso"
msgstr "SEPA-incasso"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
msgid "Bancontact"
@@ -160,7 +160,7 @@ msgstr "Betaalde bestellingen"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
msgid "Total revenue"
msgstr "Totaal omzet"
msgstr "Totale omzet"
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:15
msgid "Contacting Stripe …"
@@ -180,7 +180,7 @@ msgstr "Kies een inchecklijst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
msgid "No active check-in lists found."
msgstr "Geen actieve inchecklijsten gevonden."
msgstr "Geen actieve check-inlijsten gevonden."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
msgid "Switch check-in list"
@@ -245,11 +245,11 @@ msgstr "Goedkeuring in afwachting"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
msgid "Redeemed"
msgstr "Gebruikt"
msgstr "Ingewisseld"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
msgid "Cancel"
msgstr "Annuleer"
msgstr "Annuleren"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
@@ -310,7 +310,7 @@ msgstr "Bestelling geannuleerd"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list"
msgstr "Ticket-code op de lijst is niet eenduidig"
msgstr "Ticketcode op de lijst is niet eenduidig"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
msgid "Order not approved"
@@ -389,14 +389,14 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:162
#: pretix/static/pretixcontrol/js/ui/mail.js:21
msgid "The request took too long. Please try again."
msgstr "De aanvraag duurde te lang, probeer het alstublieft opnieuw."
msgstr "De aanvraag duurde te lang. Probeer het opnieuw."
#: pretix/static/pretixbase/js/asynctask.js:188
#: pretix/static/pretixcontrol/js/ui/mail.js:26
msgid ""
"We currently cannot reach the server. Please try again. Error code: {code}"
msgstr ""
"De server is op dit moment niet bereikbaar, probeer het alstublieft opnieuw. "
"De server is op dit moment niet bereikbaar. Probeer het alstublieft opnieuw. "
"Foutcode: {code}"
#: pretix/static/pretixbase/js/asynctask.js:216
@@ -466,11 +466,11 @@ msgstr "Huidige datum en tijd"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
msgstr "Huidige dag van de week (1 = Maandag, 7 = Zondag)"
msgstr "Huidige dag van de week (1 = maandag, 7 = zondag)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
msgid "Current entry status"
msgstr "Huidige toegangstatus"
msgstr "Huidige toegangsstatus"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
msgid "Number of previous entries"
@@ -530,7 +530,7 @@ msgstr "Toegangstijd evenement"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:187
msgid "custom date and time"
msgstr "Aangepaste datum en tijd"
msgstr "aangepaste datum en tijd"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:188
msgid "custom time"
@@ -550,7 +550,7 @@ msgstr "minuten"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
msgid "Duplicate"
msgstr "duplicaat"
msgstr "Duplicaat"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status"
@@ -564,11 +564,11 @@ msgstr "afwezig"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!"
msgstr ""
msgstr "Fout: product niet gevonden!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!"
msgstr ""
msgstr "Fout: variant niet gevonden!"
#: pretix/static/pretixcontrol/js/ui/editor.js:171
msgid "Check-in QR"
@@ -588,7 +588,7 @@ msgstr "Tekstobject (verouderd)"
#: pretix/static/pretixcontrol/js/ui/editor.js:911
msgid "Text box"
msgstr "Tekstkader"
msgstr "Tekstvak"
#: pretix/static/pretixcontrol/js/ui/editor.js:913
msgid "Barcode area"
@@ -617,7 +617,7 @@ msgstr "Opslaan mislukt."
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
msgid "Error while uploading your PDF file, please try again."
msgstr "Probleem bij het uploaden van het PDF-bestand, probeer het opnieuw."
msgstr "Probleem bij het uploaden van het PDF-bestand. Probeer het opnieuw."
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
msgid "Do you really want to leave the editor without saving your changes?"
@@ -638,7 +638,7 @@ msgstr "Onbekende fout."
#: pretix/static/pretixcontrol/js/ui/main.js:309
msgid "Your color has great contrast and will provide excellent accessibility."
msgstr ""
"Uw kleur heeft een goed contrast, en zal zorgen voor een uitstekende "
"Uw kleur heeft een goed contrast en zal zorgen voor een uitstekende "
"toegankelijkheid."
#: pretix/static/pretixcontrol/js/ui/main.js:313
@@ -646,16 +646,16 @@ msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility "
"requirements."
msgstr ""
"Uw kleur heeft een redelijk contrast, en is voldoende voor de minimale "
"toegankelijkheids eisen."
"Uw kleur heeft een redelijk contrast en is voldoende voor de minimale "
"toegankelijkheidseisen."
#: pretix/static/pretixcontrol/js/ui/main.js:317
msgid ""
"Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted."
msgstr ""
"Uw kleur heeft een te weinig contrast met wit. De toegankelijkheid van jouw "
"site wordt beïnvloed."
"Uw kleur heeft te weinig contrast met wit. De toegankelijkheid van uw site "
"wordt negatief beïnvloed."
#: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463
@@ -676,11 +676,11 @@ msgstr "Alleen geselecteerde"
#: pretix/static/pretixcontrol/js/ui/main.js:839
msgid "Enter page number between 1 and %(max)s."
msgstr "voer een pagina nummer tussen 1 en %(max)s in."
msgstr "Voer een paginanummer tussen 1 en %(max)s in."
#: pretix/static/pretixcontrol/js/ui/main.js:842
msgid "Invalid page number."
msgstr "Ongeldig pagina nummer."
msgstr "Ongeldig paginanummer."
#: pretix/static/pretixcontrol/js/ui/main.js:1000
msgid "Use a different name internally"
@@ -692,7 +692,7 @@ msgstr "Klik om te sluiten"
#: pretix/static/pretixcontrol/js/ui/main.js:1121
msgid "You have unsaved changes!"
msgstr "U heeft nog niet opgeslagen wijzigingen!"
msgstr "Sommige wijzigingen zijn nog niet opgeslagen!"
#: pretix/static/pretixcontrol/js/ui/orderchange.js:25
msgid "Calculating default price…"
@@ -731,7 +731,7 @@ msgstr "Winkelwagen is verlopen"
#: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire."
msgstr "Uw winkelwagen staat op het punt om te verlopen."
msgstr "Uw winkelwagen verloopt bijna."
#: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute."
@@ -754,7 +754,7 @@ msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?"
msgstr "Wilt u de reservererings periode vernieuwen?"
msgstr "Wilt u de reserveringsperiode vernieuwen?"
#: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation"
@@ -893,7 +893,7 @@ msgstr "incl. belasting"
#: pretix/static/pretixpresale/js/widget/widget.js:37
msgctxt "widget"
msgid "plus taxes"
msgstr "excl. belasting"
msgstr "plus belasting"
#: pretix/static/pretixpresale/js/widget/widget.js:38
#, javascript-format
@@ -960,9 +960,7 @@ msgstr "Afrekenen"
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr ""
"De winkelwagen kon niet gemaakt worden. Probeer het alstublieft later "
"opnieuw."
msgstr "De winkelwagen kon niet aangemaakt worden. Probeer het later opnieuw."
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
@@ -971,8 +969,8 @@ msgid ""
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
"Uw winkelwagen kon niet worden aangemaakt omdat er op dit moment te veel "
"gebruikers actief zijn in deze ticketwinkel. Klik op \"Doorgaan\" om dit "
"opnieuw te proberen in een nieuw tabblad."
"gebruikers actief zijn in deze ticketwinkel. Klik op \"Doorgaan\" om opnieuw "
"te proberen in een nieuw tabblad."
#: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget"
@@ -985,8 +983,8 @@ msgid ""
"You currently have an active cart for this event. If you select more "
"products, they will be added to your existing cart."
msgstr ""
"U heeft momenteel een actieve winkelwagen voor dit evenement. Als u meer "
"producten selecteert worden deze toegevoegd aan uw bestaande winkelwagen."
"U hebt momenteel een actieve winkelwagen voor dit evenement. Als u meer "
"producten selecteert, worden deze toegevoegd aan uw bestaande winkelwagen."
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgctxt "widget"
@@ -996,12 +994,12 @@ msgstr "Doorgaan met afrekenen"
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgctxt "widget"
msgid "Redeem a voucher"
msgstr "Verzilver een voucher"
msgstr "Voucher inwisselen"
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgctxt "widget"
msgid "Redeem"
msgstr "Verzilveren"
msgstr "Inwisselen"
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgctxt "widget"
@@ -1016,18 +1014,17 @@ msgstr "Sluiten"
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgctxt "widget"
msgid "Close checkout"
msgstr "Stoppen met afrekenen"
msgstr "Afrekenen sluiten"
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr ""
"U kan niet deze actie annuleren. Alstublieft wacht tot het laden is voltooid."
msgstr "U kunt deze actie niet annuleren. Wacht tot het laden is voltooid."
#: pretix/static/pretixpresale/js/widget/widget.js:64
msgctxt "widget"
msgid "Continue"
msgstr "Ga verder"
msgstr "Doorgaan"
#: pretix/static/pretixpresale/js/widget/widget.js:65
msgctxt "widget"
@@ -1086,9 +1083,9 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available "
"again."
msgstr ""
"Sommige of alle tiketcategorieën zijn op heden uitverkocht. Als je wilt, kun "
"je jezelf toevoegen aan de wachtlijst. We zullen je informeren wanneer er "
"weer plaatsen beschikbaar zijn."
"Sommige of alle ticketcategorieën zijn uitverkocht. Als u wilt, kunt u zich "
"op de wachtlijst zetten. We zullen u informeren wanneer er weer plaatsen "
"beschikbaar zijn."
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgctxt "widget"
@@ -1125,31 +1122,31 @@ msgstr "Zo"
#: pretix/static/pretixpresale/js/widget/widget.js:85
msgid "Monday"
msgstr "Maandag"
msgstr "maandag"
#: pretix/static/pretixpresale/js/widget/widget.js:86
msgid "Tuesday"
msgstr "Dinsdag"
msgstr "dinsdag"
#: pretix/static/pretixpresale/js/widget/widget.js:87
msgid "Wednesday"
msgstr "Woensdag"
msgstr "woensdag"
#: pretix/static/pretixpresale/js/widget/widget.js:88
msgid "Thursday"
msgstr "Donderdag"
msgstr "donderdag"
#: pretix/static/pretixpresale/js/widget/widget.js:89
msgid "Friday"
msgstr "Vrijdag"
msgstr "vrijdag"
#: pretix/static/pretixpresale/js/widget/widget.js:90
msgid "Saturday"
msgstr "Zaterdag"
msgstr "zaterdag"
#: pretix/static/pretixpresale/js/widget/widget.js:91
msgid "Sunday"
msgstr "Zondag"
msgstr "zondag"
#: pretix/static/pretixpresale/js/widget/widget.js:94
msgid "January"

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2026-01-16 09:22+0000\n"
"Last-Translator: Richard Schreiber <schreiber@rami.io>\n"
"PO-Revision-Date: 2026-01-16 22:00+0000\n"
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
"sv/>\n"
"Language: sv\n"
@@ -35839,9 +35839,11 @@ msgid "A payment of %(total)s is still pending for this order."
msgstr "Tack för din bokning på %(total)s."
#: pretix/presale/templates/pretixpresale/event/order.html:97
#, fuzzy, python-format
#, python-format
msgid "Please complete your payment before %(date)s"
msgstr "Tack för din bokning!"
msgstr ""
"Om din betalning inte gick igenom, se till att uppdatera din "
"betalningsinformation innan %(date)s via knappen nedan."
#: pretix/presale/templates/pretixpresale/event/order.html:108
msgid "Re-try payment or choose another payment method"

View File

@@ -337,7 +337,8 @@ class OverviewReport(Report):
date_until=d_end,
subevent_date_from=sd_start,
subevent_date_until=sd_end,
fees=True
fees=True,
skip_empty_lines=form_data.get("skip_empty_lines")
)
def _table_story(self, doc, form_data, net=False):
@@ -478,6 +479,10 @@ class OverviewReport(Report):
'Use the "Accounting report" in the export section instead.'
))
)
f.fields['skip_empty_lines'] = forms.BooleanField(
label=_("Skip empty lines"),
required=False,
)
return f.fields

View File

@@ -38,6 +38,7 @@
<div class="details">
<code>{{ checkResult.position.order }}-{{ checkResult.position.positionid }}</code>
<h4>{{ checkResult.position.attendee_name }}</h4>
<div v-if="checkResultAddons" class="addons">{{ checkResultAddons }}</div>
<span v-if="checkResultSubevent">{{ checkResultSubevent }}<br></span>
<span class="secret">{{ checkResult.position.secret }}</span>
<span v-if="checkResult.position.seat"><br>{{ checkResult.position.seat.name }}</span>
@@ -265,6 +266,16 @@ export default {
const date = moment.utc(this.checkinlist.subevent.date_from).tz(this.$root.timezone).format(this.$root.datetime_format)
return `${name} · ${date}`
},
checkResultAddons() {
if (!this.checkResult) return ''
if (!this.checkResult.position.addons) return ''
return this.checkResult.position.addons.map((addon) => {
if (addon.variation) {
return `+ ${addon.item.internal_name || i18nstring_localize(addon.item.name)} ${i18nstring_localize(addon.variation.value)}`
}
return "+ " + (addon.item.internal_name || i18nstring_localize(addon.item.name));
}).join("\n")
},
checkResultSubevent() {
if (!this.checkResult) return ''
if (!this.checkResult.position.subevent) return ''
@@ -369,7 +380,7 @@ export default {
this.$refs.input.blur()
})
let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question'
let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question&expand=addons'
if (untrusted) {
url += '&untrusted_input=true'
}

View File

@@ -92,6 +92,9 @@ a.searchresult, .check-result {
word-break: break-word;
color: $text-muted;
}
.addons {
white-space: pre-line;
}
}
.check-result-status {

View File

@@ -519,7 +519,7 @@
<dialog role="alertdialog" id="cart-extend-confirmation-dialog" class="inline-dialog" aria-labelledby="cart-deadline">
<form method="dialog">
<p>
<button class="btn btn-success" autofocus value="OK">
<button class="btn btn-success" value="OK">
<span role="img" aria-label="{% trans "OK" %}.">
{% icon "check" %}
</span>

View File

@@ -60,7 +60,7 @@
{% endif %}
{% if providers %}
<ul class="list-inline text-center blank-after">
<ul id="customer-account-login-providers" class="list-inline text-center blank-after">
{% for provider in providers %}
<li>
<a href="{% eventurl request.organizer "presale:organizer.customer.login" provider=provider.pk %}?{{ request.META.QUERY_STRING }}"

View File

@@ -311,7 +311,8 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
)
else:
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
item._dependency_available = q[0] == Quota.AVAILABILITY_OK
time_available = item.hidden_if_item_available.is_available()
item._dependency_available = (q[0] == Quota.AVAILABILITY_OK) and time_available
if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN:
item._remove = True
continue

View File

@@ -166,6 +166,23 @@ $(function () {
};
update();
dependencies.on("change", update);
if (dependents.vat_id && dependents.transmission_type && dependents.transmission_peppol_participant_id) {
// In Belgium, the VAT ID is built from "BE" + the company ID. The Peppol ID also needs to be built
// from the company ID with ID scheme 0208. We can save users some knowing and typing by filling this in!
if (!dependents.transmission_peppol_participant_id.val()) {
const fill_peppol_id = function () {
const vatId = dependents.vat_id.val();
if (vatId && vatId.startsWith("BE") && dependents.transmission_type.val() === "peppol" && autofill_peppol_id) {
dependents.transmission_peppol_participant_id.val("0201:" + vatId.substring(2))
}
}
dependents.vat_id.add(dependents.transmission_type).on("change", fill_peppol_id);
dependents.transmission_peppol_participant_id.one("change", () => {
dependents.vat_id.add(dependents.transmission_type).unbind("change", fill_peppol_id)
});
}
}
});
});

View File

@@ -731,6 +731,11 @@ $(function () {
$(countInput).trigger("change");
});
});
$("#customer-account-login-providers a").click(function () {
// Prevent double-submit, see also https://github.com/pretix/pretix/issues/5836
$(this).addClass("disabled");
});
});
function copy_answers(elements, answers) {

View File

@@ -737,6 +737,19 @@ def test_question_expand(token_client, organizer, clist, event, order, question)
assert resp.data["position"]["answers"][0]["question"]["question"]["en"] == "Size"
@pytest.mark.django_db
def test_addons_expand(token_client, organizer, clist, event, order, question, other_item):
with scopes_disabled():
p = order.positions.first()
question[0].save()
p.answers.create(question=question[0], answer="3")
resp = _redeem(token_client, organizer, clist, p.secret, {"answers": {question[0].pk: ""}}, query="?expand=addons&expand=item")
assert resp.status_code == 201
assert resp.data["status"] == "ok"
assert resp.data["position"]["addons"][0]["item"]["id"] == other_item.pk
@pytest.mark.django_db
def test_store_failed(token_client, organizer, clist, event, order):
with scopes_disabled():
@@ -932,6 +945,12 @@ def test_search(token_client, organizer, event, clist, clist_all, item, other_it
assert resp.status_code == 200
assert [p1] == resp.data['results']
with django_assert_max_num_queries(25):
resp = token_client.get(
'/api/v1/organizers/{}/checkinrpc/search/?list={}&search=z3fsn8jyu&expand=item'.format(organizer.slug, clist_all.pk))
assert resp.status_code == 200
assert resp.data['results'][0]['item']['name']
@pytest.mark.django_db
def test_search_no_list(token_client, organizer, event, clist, clist_all, item, other_item, order):

View File

@@ -50,6 +50,7 @@ from pretix.base.models import (
QuestionOption, Quota,
)
from pretix.base.models.orders import OrderFee
from pretix.testutils.queries import assert_num_queries
@pytest.fixture
@@ -422,6 +423,13 @@ def test_item_list(token_client, organizer, event, team, item):
assert [] == resp.data['results']
@pytest.mark.django_db
def test_item_list_queries(token_client, organizer, event, team, item, item3):
with assert_num_queries(18):
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/'.format(organizer.slug, event.slug))
assert resp.status_code == 200
@pytest.mark.django_db
def test_item_detail(token_client, organizer, event, team, item):
res = dict(TEST_ITEM_RES)

View File

@@ -612,6 +612,25 @@ def test_sales_channels_qualify(env):
assert invoice_qualified(order) is False
@pytest.mark.django_db
def test_business_only(env):
event, order = env
event.settings.set('invoice_generate', 'admin')
event.settings.set('invoice_generate_only_business', True)
order.total = Decimal('42.00')
ia = InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street', is_business=True,
zipcode='12345', city='London', country_old='England', country='', order=order)
assert invoice_qualified(order) is True
ia.is_business = False
ia.save()
# Order with default Sales Channel (web)
assert invoice_qualified(order) is False
def test_addon_aware_groupby():
def is_addon(item):
is_addon, id, price = item

View File

@@ -59,6 +59,7 @@ def test_get_locale():
("pt-pt", "PT", "pt-pt", "pt-pt", "pt_PT"),
("es", "MX", "es-mx", "es", "es_MX"),
("es-419", "MX", "es-419", "es-419", "es_MX"),
("zh-hans", "US", "zh-hans", "zh-hans", "zh_Hans"),
("zh-hans", "CN", "zh-hans", "zh-hans", "zh_Hans_CN"),
("zh-hant", "TW", "zh-hant", "zh-hant", "zh_Hant_TW"),
],

View File

@@ -46,6 +46,7 @@ from django.core.exceptions import ValidationError
from django.test import TestCase
from django.utils.timezone import now
from django_scopes import scopes_disabled
from freezegun import freeze_time
from tests.base import SoupTest
from tests.testdummy.signals import FoobarSalesChannel
@@ -272,6 +273,48 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertNotIn("Early-bird", resp.rendered_content)
def tiered_availability_by_date_and_quota(self, q1_size, q2_size, time_offset, expected_phase):
current_time = now()
with scopes_disabled():
q1 = Quota.objects.create(event=self.event, name='Phase 1', size=q1_size)
item1 = Item.objects.create(
event=self.event,
name='Phase 1',
default_price=0,
available_from=current_time,
available_until=current_time + datetime.timedelta(days=1),
available_from_mode=Item.UNAVAIL_MODE_HIDDEN,
available_until_mode=Item.UNAVAIL_MODE_HIDDEN,
hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN,
)
q1.items.add(item1)
q2 = Quota.objects.create(event=self.event, name='Phase 2', size=q2_size)
item2 = Item.objects.create(
event=self.event,
name='Phase 2',
default_price=0,
available_from=current_time + datetime.timedelta(days=0),
available_until=current_time + datetime.timedelta(days=2),
available_from_mode=Item.UNAVAIL_MODE_HIDDEN,
available_until_mode=Item.UNAVAIL_MODE_HIDDEN,
hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN,
hidden_if_item_available=item1
)
q2.items.add(item2)
with freeze_time(current_time + time_offset):
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertIn(expected_phase, resp.rendered_content)
def test_tiered_availability_by_date_and_quota_phase1_available(self):
self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(seconds=1), "Phase 1")
def test_tiered_availability_by_date_and_quota_phase1_sold_out(self):
self.tiered_availability_by_date_and_quota(0, 1, datetime.timedelta(seconds=1), "Phase 2")
def test_tiered_availability_by_date_and_quota_phase1_timed_out(self):
self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(days=1, hours=1), "Phase 2")
def test_subevents_inactive_unknown(self):
self.event.has_subevents = True
self.event.save()