mirror of
https://github.com/pretix/pretix.git
synced 2025-12-11 01:22:28 +00:00
Compare commits
11 Commits
subevent-e
...
fix-rrule
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22d8158356 | ||
|
|
bff0f54bf8 | ||
|
|
50c1c9c724 | ||
|
|
802268df46 | ||
|
|
a823f261f3 | ||
|
|
59a754f913 | ||
|
|
82eca01e5c | ||
|
|
943f594b6b | ||
|
|
15cbb3a416 | ||
|
|
f447e7b9c4 | ||
|
|
dcf473c543 |
@@ -91,7 +91,7 @@ dependencies = [
|
||||
"redis==6.4.*",
|
||||
"reportlab==4.4.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.38.*",
|
||||
"sentry-sdk==2.40.*",
|
||||
"sepaxml==2.6.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
|
||||
@@ -764,7 +764,13 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
||||
) and not order.invoices.last()
|
||||
invoice = None
|
||||
if gen_invoice:
|
||||
invoice = generate_invoice(order, trigger_pdf=True)
|
||||
try:
|
||||
invoice = generate_invoice(order, trigger_pdf=True)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
# Refresh serializer only after running signals
|
||||
prefetch_related_objects([order], self._positions_prefetch(request))
|
||||
|
||||
@@ -391,7 +391,7 @@ class OutboundSyncProvider:
|
||||
def sync_order(self, order):
|
||||
if not self.should_sync_order(order):
|
||||
logger.debug("Skipping order %r", order)
|
||||
return
|
||||
return {}
|
||||
|
||||
logger.debug("Syncing order %r", order)
|
||||
positions = list(
|
||||
|
||||
@@ -125,6 +125,7 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
|
||||
identifier = 'invoices'
|
||||
verbose_name = _('All invoices')
|
||||
description = _('Download all invoices created by the system as a ZIP file of PDF files.')
|
||||
repeatable_read = False
|
||||
|
||||
def render(self, form_data: dict, output_file=None):
|
||||
qs = self.invoices_queryset(form_data).filter(shredded=False)
|
||||
|
||||
@@ -1162,7 +1162,7 @@ class QuotaListExporter(ListExporter):
|
||||
yield headers
|
||||
|
||||
quotas = list(self.event.quotas.select_related('subevent'))
|
||||
qa = QuotaAvailability(full_results=True, allow_repeatable_read=False)
|
||||
qa = QuotaAvailability(full_results=True, allow_repeatable_read=True)
|
||||
qa.queue(*quotas)
|
||||
qa.compute()
|
||||
|
||||
|
||||
@@ -1969,14 +1969,20 @@ class OrderPayment(models.Model):
|
||||
self.order.invoice_dirty
|
||||
)
|
||||
if gen_invoice:
|
||||
if invoices:
|
||||
last_i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if not last_i.canceled:
|
||||
generate_cancellation(last_i)
|
||||
invoice = generate_invoice(
|
||||
self.order,
|
||||
trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment
|
||||
)
|
||||
try:
|
||||
if invoices:
|
||||
last_i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if not last_i.canceled:
|
||||
generate_cancellation(last_i)
|
||||
invoice = generate_invoice(
|
||||
self.order,
|
||||
trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
transmit_invoice_task = invoice_transmission_separately(invoice)
|
||||
transmit_invoice_mail = not transmit_invoice_task and self.order.event.settings.invoice_email_attachment and self.order.email
|
||||
|
||||
@@ -671,6 +671,7 @@ def send_invoices_to_organizer(sender, **kwargs):
|
||||
event=i.event,
|
||||
invoices=[i],
|
||||
auto_email=True,
|
||||
plain_text_only=True,
|
||||
)
|
||||
i.sent_to_organizer = True
|
||||
else:
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from typing import List
|
||||
|
||||
@@ -43,6 +44,8 @@ from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.base.signals import order_paid, order_placed
|
||||
from pretix.celery_app import app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _validate(cf: CachedFile, charset: str, cols: List[ImportColumn], settings: dict):
|
||||
try:
|
||||
@@ -233,7 +236,13 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
|
||||
(event.settings.get('invoice_generate') == 'paid' and o.status == Order.STATUS_PAID)
|
||||
) and not o.invoices.last()
|
||||
if gen_invoice:
|
||||
generate_invoice(o, trigger_pdf=True)
|
||||
try:
|
||||
generate_invoice(o, trigger_pdf=True)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
o.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
except DataImportError:
|
||||
raise ValidationError(_('We were not able to process your request completely as the server was too busy. '
|
||||
'Please try again.'))
|
||||
|
||||
@@ -264,7 +264,13 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
|
||||
|
||||
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
||||
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
||||
generate_invoice(order)
|
||||
try:
|
||||
generate_invoice(order)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
|
||||
def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_pending: bool=None, user: User=None, auth=None):
|
||||
@@ -312,7 +318,13 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_p
|
||||
if was_expired:
|
||||
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
||||
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
||||
generate_invoice(order)
|
||||
try:
|
||||
generate_invoice(order)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
order.create_transactions()
|
||||
|
||||
with transaction.atomic():
|
||||
@@ -397,13 +409,19 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
|
||||
|
||||
if order.event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
|
||||
if not invoice:
|
||||
invoice = generate_invoice(
|
||||
order,
|
||||
# send_mail will trigger PDF generation later
|
||||
trigger_pdf=not transmit_invoice_mail
|
||||
)
|
||||
if transmit_invoice_task:
|
||||
transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False))
|
||||
try:
|
||||
invoice = generate_invoice(
|
||||
order,
|
||||
# send_mail will trigger PDF generation later
|
||||
trigger_pdf=not transmit_invoice_mail
|
||||
)
|
||||
if transmit_invoice_task:
|
||||
transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False))
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
if send_mail:
|
||||
with language(order.locale, order.event.settings.region):
|
||||
@@ -608,7 +626,13 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
||||
order.save(update_fields=['status', 'cancellation_date', 'total'])
|
||||
|
||||
if cancel_invoice and i:
|
||||
invoices.append(generate_invoice(order))
|
||||
try:
|
||||
invoices.append(generate_invoice(order))
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
else:
|
||||
order.status = Order.STATUS_CANCELED
|
||||
order.cancellation_date = now()
|
||||
@@ -1306,13 +1330,19 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
)
|
||||
)
|
||||
if invoice_required:
|
||||
invoice = generate_invoice(
|
||||
order,
|
||||
# send_mail will trigger PDF generation later
|
||||
trigger_pdf=not transmit_invoice_mail
|
||||
)
|
||||
if transmit_invoice_task:
|
||||
transmit_invoice.apply_async(args=(event.pk, invoice.pk, False))
|
||||
try:
|
||||
invoice = generate_invoice(
|
||||
order,
|
||||
# send_mail will trigger PDF generation later
|
||||
trigger_pdf=not transmit_invoice_mail
|
||||
)
|
||||
if transmit_invoice_task:
|
||||
transmit_invoice.apply_async(args=(event.pk, invoice.pk, False))
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
if order.email:
|
||||
if order.require_approval:
|
||||
@@ -2701,7 +2731,13 @@ class OrderChangeManager:
|
||||
)
|
||||
|
||||
if split_order.total != Decimal('0.00') and self.order.invoices.filter(is_cancellation=False).last():
|
||||
generate_invoice(split_order)
|
||||
try:
|
||||
generate_invoice(split_order)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
split_order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
order_split.send(sender=self.order.event, original=self.order, split_order=split_order)
|
||||
return split_order
|
||||
@@ -2812,15 +2848,27 @@ class OrderChangeManager:
|
||||
|
||||
if order_now_qualified:
|
||||
if invoice_should_be_generated_now:
|
||||
if i and not i.canceled:
|
||||
self._invoices.append(generate_cancellation(i))
|
||||
self._invoices.append(generate_invoice(self.order))
|
||||
try:
|
||||
if i and not i.canceled:
|
||||
self._invoices.append(generate_cancellation(i))
|
||||
self._invoices.append(generate_invoice(self.order))
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
elif invoice_should_be_generated_later:
|
||||
self.order.invoice_dirty = True
|
||||
self.order.save(update_fields=["invoice_dirty"])
|
||||
else:
|
||||
if i and not i.canceled:
|
||||
self._invoices.append(generate_cancellation(i))
|
||||
try:
|
||||
if i and not i.canceled:
|
||||
self._invoices.append(generate_cancellation(i))
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
def _check_complete_cancel(self):
|
||||
current = self.order.positions.count()
|
||||
@@ -3246,8 +3294,14 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
||||
has_active_invoice = i and not i.canceled
|
||||
|
||||
if has_active_invoice and order.total != oldtotal:
|
||||
generate_cancellation(i)
|
||||
generate_invoice(order)
|
||||
try:
|
||||
generate_cancellation(i)
|
||||
generate_invoice(order)
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
new_invoice_created = True
|
||||
|
||||
elif (not has_active_invoice or order.invoice_dirty) and invoice_qualified(order):
|
||||
@@ -3255,13 +3309,19 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
||||
order.event.settings.get('invoice_generate') == 'paid' and
|
||||
new_payment.payment_provider.requires_invoice_immediately
|
||||
):
|
||||
if has_active_invoice:
|
||||
generate_cancellation(i)
|
||||
i = generate_invoice(order)
|
||||
new_invoice_created = True
|
||||
order.log_action('pretix.event.order.invoice.generated', data={
|
||||
'invoice': i.pk
|
||||
})
|
||||
try:
|
||||
if has_active_invoice:
|
||||
generate_cancellation(i)
|
||||
i = generate_invoice(order)
|
||||
new_invoice_created = True
|
||||
order.log_action('pretix.event.order.invoice.generated', data={
|
||||
'invoice': i.pk
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
|
||||
order.create_transactions()
|
||||
return old_fee, new_fee, fee, new_payment, new_invoice_created
|
||||
|
||||
@@ -390,8 +390,7 @@ class QuotaFormSet(I18nInlineFormSet):
|
||||
use_required_attribute=False,
|
||||
locales=self.locales,
|
||||
event=self.event,
|
||||
items=self.items,
|
||||
searchable_selection=self.searchable_selection,
|
||||
items=self.items
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
@@ -455,7 +455,7 @@ class OrderDataSyncSuccessLogEntryType(OrderDataSyncLogEntryType):
|
||||
links.append(", ".join(
|
||||
prov.get_external_link_html(logentry.event, obj['external_link_href'], obj['external_link_display_name'])
|
||||
for obj in objs
|
||||
if obj and 'external_link_href' in obj and 'external_link_display_name' in obj
|
||||
if obj and obj.get('external_link_href') and obj.get('external_link_display_name')
|
||||
))
|
||||
|
||||
return mark_safe(escape(super().display(logentry, data)) + "".join("<p>" + link + "</p>" for link in links))
|
||||
@@ -522,6 +522,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
|
||||
'pretix.event.order.customer.changed': _('The customer account has been changed.'),
|
||||
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
|
||||
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
|
||||
'pretix.event.order.invoice.failed': _('The invoice could not be generated.'),
|
||||
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
|
||||
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
|
||||
'pretix.event.order.invoice.sent': _('The invoice {full_invoice_no} has been sent.'),
|
||||
|
||||
@@ -487,32 +487,30 @@
|
||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||
</p>
|
||||
{% for f in itemvar_forms %}
|
||||
<div data-itemvar="{{ f.item.id }}{% if f.variation %}-{{ f.variation.id }}{% endif %}">
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<br>
|
||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||
</div>
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<div class="col-md-4 col-md-offset-3">
|
||||
<label for="{{ f.rel_available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
||||
{% bootstrap_field f.rel_available_from form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.rel_available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
||||
{% bootstrap_field f.rel_available_until form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<br>
|
||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<div class="col-md-4 col-md-offset-3">
|
||||
<label for="{{ f.rel_available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
||||
{% bootstrap_field f.rel_available_from form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.rel_available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
||||
{% bootstrap_field f.rel_available_until form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -627,7 +625,7 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group submit-group-sticky">
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
|
||||
@@ -363,7 +363,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group submit-group-sticky">
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
|
||||
@@ -130,32 +130,30 @@
|
||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||
</p>
|
||||
{% for f in itemvar_forms %}
|
||||
<div data-itemvar="{{ f.item.id }}{% if f.variation %}-{{ f.variation.id }}{% endif %}">
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<br>
|
||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||
</div>
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
</label>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<div class="col-md-4 col-md-offset-3">
|
||||
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
||||
{% bootstrap_field f.available_from form_group_class="foo" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
||||
{% bootstrap_field f.available_until form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<br>
|
||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<div class="col-md-4 col-md-offset-3">
|
||||
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
||||
{% bootstrap_field f.available_from form_group_class="foo" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
||||
{% bootstrap_field f.available_until form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -284,7 +282,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group submit-group submit-group-sticky">
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
|
||||
@@ -78,8 +78,8 @@ class ControlSyncJob(OrderView):
|
||||
prov, meta = datasync_providers.get(active_in=self.request.event, identifier=provider)
|
||||
|
||||
if self.request.POST.get("queue_sync") == "true":
|
||||
prov.enqueue_order(self.order, 'user')
|
||||
messages.success(self.request, _('The sync job has been enqueued and will run in the next minutes.'))
|
||||
prov.enqueue_order(self.order, 'user', immediate=True)
|
||||
messages.success(self.request, _('The sync job has been set to run as soon as possible.'))
|
||||
elif self.request.POST.get("cancel_job"):
|
||||
with transaction.atomic():
|
||||
try:
|
||||
|
||||
@@ -36,6 +36,7 @@ import copy
|
||||
import hmac
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
@@ -98,6 +99,8 @@ from pretix.presale.views import (
|
||||
from pretix.presale.views.event import get_grouped_items
|
||||
from pretix.presale.views.robots import NoSearchIndexViewMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OrderDetailMixin(NoSearchIndexViewMixin):
|
||||
|
||||
@@ -734,11 +737,18 @@ class OrderInvoiceCreate(EventViewMixin, OrderDetailMixin, View):
|
||||
elif self.order.invoices.exists():
|
||||
messages.error(self.request, _('An invoice for this order already exists.'))
|
||||
else:
|
||||
i = generate_invoice(self.order)
|
||||
self.order.log_action('pretix.event.order.invoice.generated', data={
|
||||
'invoice': i.pk
|
||||
})
|
||||
messages.success(self.request, _('The invoice has been generated.'))
|
||||
try:
|
||||
i = generate_invoice(self.order)
|
||||
self.order.log_action('pretix.event.order.invoice.generated', data={
|
||||
'invoice': i.pk
|
||||
})
|
||||
messages.success(self.request, _('The invoice has been generated.'))
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.'))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
|
||||
@@ -807,24 +817,37 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem
|
||||
elif self.order.invoices.exists():
|
||||
messages.error(self.request, _('An invoice for this order already exists.'))
|
||||
else:
|
||||
i = generate_invoice(self.order)
|
||||
self.order.log_action('pretix.event.order.invoice.generated', data={
|
||||
'invoice': i.pk
|
||||
})
|
||||
messages.success(self.request, _('The invoice has been generated.'))
|
||||
try:
|
||||
i = generate_invoice(self.order)
|
||||
self.order.log_action('pretix.event.order.invoice.generated', data={
|
||||
'invoice': i.pk
|
||||
})
|
||||
messages.success(self.request, _('The invoice has been generated.'))
|
||||
except Exception as e:
|
||||
logger.exception("Could not generate invoice.")
|
||||
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.'))
|
||||
elif self.request.event.settings.invoice_reissue_after_modify:
|
||||
if self.invoice_form.changed_data:
|
||||
inv = self.order.invoices.last()
|
||||
if inv and not inv.canceled and not inv.shredded:
|
||||
c = generate_cancellation(inv)
|
||||
if self.order.status != Order.STATUS_CANCELED:
|
||||
inv = generate_invoice(self.order)
|
||||
else:
|
||||
inv = c
|
||||
self.order.log_action('pretix.event.order.invoice.reissued', data={
|
||||
'invoice': inv.pk
|
||||
try:
|
||||
inv = self.order.invoices.last()
|
||||
if inv and not inv.canceled and not inv.shredded:
|
||||
c = generate_cancellation(inv)
|
||||
if self.order.status != Order.STATUS_CANCELED:
|
||||
inv = generate_invoice(self.order)
|
||||
else:
|
||||
inv = c
|
||||
self.order.log_action('pretix.event.order.invoice.reissued', data={
|
||||
'invoice': inv.pk
|
||||
})
|
||||
messages.success(self.request, _('The invoice has been reissued.'))
|
||||
except Exception as e:
|
||||
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||
"exception": str(e)
|
||||
})
|
||||
messages.success(self.request, _('The invoice has been reissued.'))
|
||||
logger.exception("Could not generate invoice.")
|
||||
|
||||
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk})
|
||||
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
||||
|
||||
@@ -330,9 +330,11 @@ var ajaxErrDialog = {
|
||||
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
||||
+ gettext("Close message") + "</a>");
|
||||
$("body").addClass("ajaxerr has-modal-dialog");
|
||||
$("#ajaxerr").prop("hidden", false);
|
||||
},
|
||||
hide: function () {
|
||||
"use strict";
|
||||
$("body").removeClass("ajaxerr has-modal-dialog");
|
||||
$("#ajaxerr").prop("hidden", true);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ var i18nToString = function (i18nstring) {
|
||||
$(document).ajaxError(function (event, jqXHR, settings, thrownError) {
|
||||
waitingDialog.hide();
|
||||
var c = $(jqXHR.responseText).filter('.container');
|
||||
if (jqXHR.responseText.indexOf("<!-- pretix-login-marker -->") !== -1) {
|
||||
if (jqXHR.responseText && jqXHR.responseText.indexOf("<!-- pretix-login-marker -->") !== -1) {
|
||||
location.href = '/control/login?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
|
||||
} else if (c.length > 0) {
|
||||
ajaxErrDialog.show(c.first().html());
|
||||
@@ -485,6 +485,7 @@ var form_handlers = function (el) {
|
||||
theme: "bootstrap",
|
||||
language: $("body").attr("data-select2-locale"),
|
||||
data: JSON.parse($(this.getAttribute('data-select2-src')).text()),
|
||||
width: '100%',
|
||||
}).val(selectedValue).trigger('change');
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ $(document).on("pretix:bind-forms", function () {
|
||||
}
|
||||
}
|
||||
|
||||
// RRule editor
|
||||
function rrule_preview() {
|
||||
var ruleset = new rrule.RRuleSet();
|
||||
|
||||
@@ -122,14 +121,7 @@ $(document).on("pretix:bind-forms", function () {
|
||||
});
|
||||
}
|
||||
}
|
||||
$("#rrule-formset").on("change keydown keyup keypress dp.change", "input, select", function () {
|
||||
rrule_preview();
|
||||
});
|
||||
rrule_preview();
|
||||
|
||||
$("#rrule-formset").on("formAdded", "div", function (event) {rrule_bind_form($(event.target)); });
|
||||
|
||||
// Timeslot editor
|
||||
$("#subevent_add_many_slots_go").on("click", function () {
|
||||
$("#time-formset [data-formset-form]").each(function () {
|
||||
var tf = $(this).find("[name$=time_from]").val()
|
||||
@@ -175,45 +167,13 @@ $(document).on("pretix:bind-forms", function () {
|
||||
$(this).addClass("hidden");
|
||||
});
|
||||
|
||||
// Hide config for products that are not for sale
|
||||
function quota_form_handlers(el) {
|
||||
// searchable_selection = True
|
||||
el.find('[id^="id_quotas-"]').on("select2:select select2:unselect", () => {
|
||||
update_item_visibility();
|
||||
});
|
||||
// searchable_selection = False
|
||||
el.find('input[id^="id_quotas-"][id*=itemvars_]').on("change", () => {
|
||||
update_item_visibility();
|
||||
});
|
||||
}
|
||||
function update_item_visibility() {
|
||||
const itemvars = [];
|
||||
$("#rrule-formset").on("change keydown keyup keypress dp.change", "input, select", function () {
|
||||
rrule_preview();
|
||||
});
|
||||
rrule_preview();
|
||||
|
||||
// searchable_selection = True
|
||||
$("select[id^=id_quotas-][id$=-itemvars]").filter((idx, el) => {
|
||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
||||
}).each((_, e) => itemvars.push(...$(e).val()));
|
||||
// searchable_selection = False
|
||||
$("input[id^=id_quotas-][id*=itemvars_]:checked").filter((idx, el) => {
|
||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
||||
}).each((_, e) => itemvars.push($(e).val()));
|
||||
$("#rrule-formset").on("formAdded", "div", function (event) { rrule_bind_form($(event.target)); });
|
||||
|
||||
$("div[data-itemvar]").each(function (idx, e) {
|
||||
const el = $(e);
|
||||
el.prop("hidden", !itemvars.includes(el.attr("data-itemvar")) && !el.find(".has-error, .alert-danger").length);
|
||||
});
|
||||
}
|
||||
|
||||
$('[data-formset-prefix="quotas"]').on("formDeleted", "div", () => {
|
||||
update_item_visibility();
|
||||
}).on("formAdded", "div", (event) => {
|
||||
quota_form_handlers($(event.target));
|
||||
update_item_visibility();
|
||||
})
|
||||
quota_form_handlers($("body"));
|
||||
update_item_visibility();
|
||||
|
||||
// Auto-set name of check-in list
|
||||
var $namef = $("input[id^=id_name]").first();
|
||||
var lastValue = $namef.val();
|
||||
$namef.change(function () {
|
||||
|
||||
@@ -63,10 +63,6 @@ td > .form-group > .checkbox {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], div[data-nested-formset-body], details[data-formset-form] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-plugins .panel-title {
|
||||
line-height: 34px;
|
||||
}
|
||||
@@ -95,12 +91,6 @@ div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], d
|
||||
}
|
||||
}
|
||||
|
||||
.submit-group-sticky {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.panel .form-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
2
src/pretix/static/rrule/rrule.js
vendored
2
src/pretix/static/rrule/rrule.js
vendored
@@ -1934,7 +1934,7 @@ function buildPoslist(bysetpos, timeset, start, end, ii, dayset) {
|
||||
function iter(iterResult, options) {
|
||||
var dtstart = options.dtstart, freq = options.freq, interval = options.interval, until = options.until, bysetpos = options.bysetpos;
|
||||
var count = options.count;
|
||||
if (count <= 0 || interval <= 0) {
|
||||
if (count === 0 || count < 0 || interval === 0 || interval < 0) {
|
||||
return emitResult(iterResult);
|
||||
}
|
||||
var counterDate = datetime_DateTime.fromDate(dtstart);
|
||||
|
||||
@@ -327,6 +327,26 @@ def test_enqueue_order_twice(event):
|
||||
SimpleOrderSync.enqueue_order(order, 'testcase_2nd')
|
||||
|
||||
|
||||
class DoNothingSync(SimpleOrderSync):
|
||||
|
||||
def should_sync_order(self, order):
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_should_not_sync(event):
|
||||
_register_with_fake_plugin_name(datasync_providers, DoNothingSync, 'testplugin')
|
||||
|
||||
DoNothingSync.fake_api_client = FakeSyncAPI()
|
||||
|
||||
for order in event.orders.order_by("code").all():
|
||||
DoNothingSync.enqueue_order(order, 'testcase')
|
||||
|
||||
sync_all()
|
||||
|
||||
assert DoNothingSync.fake_api_client.fake_database == {}
|
||||
|
||||
|
||||
StaticMappingWithAssociations = namedtuple('StaticMappingWithAssociations', (
|
||||
'id', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mappings', 'association_mappings'
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user