diff --git a/src/pretix/base/datasync/datasync.py b/src/pretix/base/datasync/datasync.py index 99b280b01a..3124485795 100644 --- a/src/pretix/base/datasync/datasync.py +++ b/src/pretix/base/datasync/datasync.py @@ -66,7 +66,7 @@ def sync_all(): with scopes_disabled(): queue = ( OrderSyncQueue.objects - .filter(not_before__lt=now()) + .filter(not_before__lt=now(), need_manual_retry__isnull=True) .order_by(Window( expression=RowNumber(), partition_by=[F("event_id")], @@ -231,7 +231,8 @@ class OutboundSyncProvider: "error": e.messages, "full_message": e.full_message, }) - sq.delete() + sq.need_manual_retry = "unrecoverable" + sq.save() except RecoverableSyncError as e: sq.failed_attempts += 1 sq.not_before = self.next_retry_date(sq) @@ -246,7 +247,8 @@ class OutboundSyncProvider: "error": e.messages, "full_message": e.full_message, }) - sq.delete() + sq.need_manual_retry = "recoverable" + sq.save() else: sq.save() except Exception as e: @@ -259,7 +261,8 @@ class OutboundSyncProvider: "error": [], "full_message": str(e), }) - sq.delete() + sq.need_manual_retry = "unhandled" + sq.save() else: if not all(res.get("action", "") == "nothing_to_do" for res in mapped_objects.values()): sq.order.log_action("pretix.event.order.data_sync.success", { diff --git a/src/pretix/base/migrations/0282_datasync.py b/src/pretix/base/migrations/0282_datasync.py deleted file mode 100644 index 5b5bc1d331..0000000000 --- a/src/pretix/base/migrations/0282_datasync.py +++ /dev/null @@ -1,96 +0,0 @@ -# Generated by Django 4.2.16 on 2025-05-07 13:01 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("pretixbase", "0281_event_is_remote"), - ] - - operations = [ - migrations.CreateModel( - name="OrderSyncResult", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False - ), - ), - ("sync_provider", models.CharField(max_length=128)), - ("external_object_type", models.CharField(max_length=128)), - ("external_id_field", models.CharField(max_length=128)), - ("id_value", models.CharField(max_length=128)), - ("external_link_href", models.CharField(max_length=255, null=True)), - ( - "external_link_display_name", - models.CharField(max_length=255, null=True), - ), - ("transmitted", models.DateTimeField(auto_now_add=True)), - ( - "order", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="sync_results", - to="pretixbase.order", - ), - ), - ( - "order_position", - models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="sync_results", - to="pretixbase.orderposition", - ), - ), - ], - options={ - "indexes": [ - models.Index( - fields=["order", "sync_provider"], - name="pretixbase__order_i_3e3c84_idx", - ) - ], - }, - ), - migrations.CreateModel( - name="OrderSyncQueue", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, primary_key=True, serialize=False - ), - ), - ("sync_provider", models.CharField(max_length=128)), - ("triggered_by", models.CharField(max_length=128)), - ("triggered", models.DateTimeField(auto_now_add=True)), - ("failed_attempts", models.PositiveIntegerField(default=0)), - ("not_before", models.DateTimeField(db_index=True)), - ( - "event", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="queued_sync_jobs", - to="pretixbase.event", - ), - ), - ( - "order", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="queued_sync_jobs", - to="pretixbase.order", - ), - ), - ], - options={ - "ordering": ("triggered",), - "unique_together": {("order", "sync_provider")}, - }, - ), - ] diff --git a/src/pretix/base/migrations/0282_ordersyncresult_ordersyncqueue.py b/src/pretix/base/migrations/0282_ordersyncresult_ordersyncqueue.py new file mode 100644 index 0000000000..94af364c56 --- /dev/null +++ b/src/pretix/base/migrations/0282_ordersyncresult_ordersyncqueue.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2.21 on 2025-06-26 16:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0281_event_is_remote'), + ] + + operations = [ + migrations.CreateModel( + name='OrderSyncResult', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('sync_provider', models.CharField(max_length=128)), + ('external_object_type', models.CharField(max_length=128)), + ('external_id_field', models.CharField(max_length=128)), + ('id_value', models.CharField(max_length=128)), + ('external_link_href', models.CharField(max_length=255, null=True)), + ('external_link_display_name', models.CharField(max_length=255, null=True)), + ('transmitted', models.DateTimeField(auto_now_add=True)), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sync_results', to='pretixbase.order')), + ('order_position', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sync_results', to='pretixbase.orderposition')), + ], + options={ + 'indexes': [models.Index(fields=['order', 'sync_provider'], name='pretixbase__order_i_3e3c84_idx')], + }, + ), + migrations.CreateModel( + name='OrderSyncQueue', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), + ('sync_provider', models.CharField(max_length=128)), + ('triggered_by', models.CharField(max_length=128)), + ('triggered', models.DateTimeField(auto_now_add=True)), + ('failed_attempts', models.PositiveIntegerField(default=0)), + ('not_before', models.DateTimeField(db_index=True)), + ('need_manual_retry', models.CharField(null=True)), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='queued_sync_jobs', to='pretixbase.event')), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='queued_sync_jobs', to='pretixbase.order')), + ], + options={ + 'ordering': ('triggered',), + 'unique_together': {('order', 'sync_provider')}, + }, + ), + ] diff --git a/src/pretix/base/models/datasync.py b/src/pretix/base/models/datasync.py index 5e8af263d0..43d0429d35 100644 --- a/src/pretix/base/models/datasync.py +++ b/src/pretix/base/models/datasync.py @@ -24,6 +24,9 @@ import logging from functools import cached_property from django.db import models +from django.utils.translation import ( + gettext as _, gettext_lazy, ngettext_lazy, pgettext_lazy, +) from pretix.base.models import Event, Order, OrderPosition @@ -48,6 +51,11 @@ class OrderSyncQueue(models.Model): triggered = models.DateTimeField(blank=False, null=False, auto_now_add=True) failed_attempts = models.PositiveIntegerField(default=0) not_before = models.DateTimeField(blank=False, null=False, db_index=True) + need_manual_retry = models.CharField(blank=True, null=True, choices=[ + ('recoverable', _('Temporary error, retry exceeded')), + ('unrecoverable', _('Misconfiguration')), + ('unhandled', _('Unhandled exception')) + ]) class Meta: unique_together = (("order", "sync_provider"),)