forked from CGM_Public/pretix_original
Allow to send all invoices to a specific email address (#2072)
Co-authored-by: Martin Gross <gross@rami.io>
This commit is contained in:
@@ -719,6 +719,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
|||||||
'invoice_include_expire_date',
|
'invoice_include_expire_date',
|
||||||
'invoice_address_explanation_text',
|
'invoice_address_explanation_text',
|
||||||
'invoice_email_attachment',
|
'invoice_email_attachment',
|
||||||
|
'invoice_email_organizer',
|
||||||
'invoice_address_from_name',
|
'invoice_address_from_name',
|
||||||
'invoice_address_from',
|
'invoice_address_from',
|
||||||
'invoice_address_from_zipcode',
|
'invoice_address_from_zipcode',
|
||||||
|
|||||||
19
src/pretix/base/migrations/0186_invoice_sent_to_organizer.py
Normal file
19
src/pretix/base/migrations/0186_invoice_sent_to_organizer.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.2 on 2021-05-09 14:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0185_memberships'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='sent_to_organizer',
|
||||||
|
field=models.BooleanField(null=True, default=False),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -109,11 +109,14 @@ class Invoice(models.Model):
|
|||||||
order = models.ForeignKey('Order', related_name='invoices', db_index=True, on_delete=models.CASCADE)
|
order = models.ForeignKey('Order', related_name='invoices', db_index=True, on_delete=models.CASCADE)
|
||||||
organizer = models.ForeignKey('Organizer', related_name='invoices', db_index=True, on_delete=models.PROTECT)
|
organizer = models.ForeignKey('Organizer', related_name='invoices', db_index=True, on_delete=models.PROTECT)
|
||||||
event = models.ForeignKey('Event', related_name='invoices', db_index=True, on_delete=models.CASCADE)
|
event = models.ForeignKey('Event', related_name='invoices', db_index=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
prefix = models.CharField(max_length=160, db_index=True)
|
prefix = models.CharField(max_length=160, db_index=True)
|
||||||
invoice_no = models.CharField(max_length=19, db_index=True)
|
invoice_no = models.CharField(max_length=19, db_index=True)
|
||||||
full_invoice_no = models.CharField(max_length=190, db_index=True)
|
full_invoice_no = models.CharField(max_length=190, db_index=True)
|
||||||
|
|
||||||
is_cancellation = models.BooleanField(default=False)
|
is_cancellation = models.BooleanField(default=False)
|
||||||
refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True, on_delete=models.CASCADE)
|
refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
invoice_from = models.TextField()
|
invoice_from = models.TextField()
|
||||||
invoice_from_name = models.CharField(max_length=190, null=True)
|
invoice_from_name = models.CharField(max_length=190, null=True)
|
||||||
invoice_from_zipcode = models.CharField(max_length=190, null=True)
|
invoice_from_zipcode = models.CharField(max_length=190, null=True)
|
||||||
@@ -121,6 +124,7 @@ class Invoice(models.Model):
|
|||||||
invoice_from_country = FastCountryField(null=True)
|
invoice_from_country = FastCountryField(null=True)
|
||||||
invoice_from_tax_id = models.CharField(max_length=190, null=True)
|
invoice_from_tax_id = models.CharField(max_length=190, null=True)
|
||||||
invoice_from_vat_id = models.CharField(max_length=190, null=True)
|
invoice_from_vat_id = models.CharField(max_length=190, null=True)
|
||||||
|
|
||||||
invoice_to = models.TextField()
|
invoice_to = models.TextField()
|
||||||
invoice_to_company = models.TextField(null=True)
|
invoice_to_company = models.TextField(null=True)
|
||||||
invoice_to_name = models.TextField(null=True)
|
invoice_to_name = models.TextField(null=True)
|
||||||
@@ -131,6 +135,9 @@ class Invoice(models.Model):
|
|||||||
invoice_to_country = FastCountryField(null=True)
|
invoice_to_country = FastCountryField(null=True)
|
||||||
invoice_to_vat_id = models.TextField(null=True)
|
invoice_to_vat_id = models.TextField(null=True)
|
||||||
invoice_to_beneficiary = models.TextField(null=True)
|
invoice_to_beneficiary = models.TextField(null=True)
|
||||||
|
internal_reference = models.TextField(blank=True)
|
||||||
|
custom_field = models.CharField(max_length=255, null=True)
|
||||||
|
|
||||||
date = models.DateField(default=today)
|
date = models.DateField(default=today)
|
||||||
locale = models.CharField(max_length=50, default='en')
|
locale = models.CharField(max_length=50, default='en')
|
||||||
introductory_text = models.TextField(blank=True)
|
introductory_text = models.TextField(blank=True)
|
||||||
@@ -138,14 +145,21 @@ class Invoice(models.Model):
|
|||||||
reverse_charge = models.BooleanField(default=False)
|
reverse_charge = models.BooleanField(default=False)
|
||||||
payment_provider_text = models.TextField(blank=True)
|
payment_provider_text = models.TextField(blank=True)
|
||||||
footer_text = models.TextField(blank=True)
|
footer_text = models.TextField(blank=True)
|
||||||
|
|
||||||
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
|
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
|
||||||
foreign_currency_rate = models.DecimalField(decimal_places=4, max_digits=10, null=True, blank=True)
|
foreign_currency_rate = models.DecimalField(decimal_places=4, max_digits=10, null=True, blank=True)
|
||||||
foreign_currency_rate_date = models.DateField(null=True, blank=True)
|
foreign_currency_rate_date = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
shredded = models.BooleanField(default=False)
|
shredded = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
# The field sent_to_organizer records whether this invocie was already sent to the organizer by a configured
|
||||||
|
# mechanism such as email.
|
||||||
|
# NULL: The cronjob that handles sending did not yet run.
|
||||||
|
# True: The invoice was sent.
|
||||||
|
# False: The invoice wasn't sent and never will, because sending was not configured at the time of the check.
|
||||||
|
sent_to_organizer = models.BooleanField(null=True, blank=True)
|
||||||
|
|
||||||
file = models.FileField(null=True, blank=True, upload_to=invoice_filename, max_length=255)
|
file = models.FileField(null=True, blank=True, upload_to=invoice_filename, max_length=255)
|
||||||
internal_reference = models.TextField(blank=True)
|
|
||||||
custom_field = models.CharField(max_length=255, null=True)
|
|
||||||
|
|
||||||
objects = ScopedManager(organizer='event__organizer')
|
objects = ScopedManager(organizer='event__organizer')
|
||||||
|
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
|||||||
cancellation.date = timezone.now().date()
|
cancellation.date = timezone.now().date()
|
||||||
cancellation.payment_provider_text = ''
|
cancellation.payment_provider_text = ''
|
||||||
cancellation.file = None
|
cancellation.file = None
|
||||||
|
cancellation.sent_to_organizer = None
|
||||||
with language(invoice.locale, invoice.event.settings.region):
|
with language(invoice.locale, invoice.event.settings.region):
|
||||||
cancellation.invoice_from = invoice.event.settings.get('invoice_address_from')
|
cancellation.invoice_from = invoice.event.settings.get('invoice_address_from')
|
||||||
cancellation.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
|
cancellation.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
|
||||||
@@ -442,3 +443,44 @@ def fetch_ecb_rates(sender, **kwargs):
|
|||||||
gs.settings.ecb_rates_dict = json.dumps(rates, cls=DjangoJSONEncoder)
|
gs.settings.ecb_rates_dict = json.dumps(rates, cls=DjangoJSONEncoder)
|
||||||
except urllib.error.URLError:
|
except urllib.error.URLError:
|
||||||
logger.exception('Could not retrieve rates from ECB')
|
logger.exception('Could not retrieve rates from ECB')
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(signal=periodic_task)
|
||||||
|
@scopes_disabled()
|
||||||
|
def send_invoices_to_organizer(sender, **kwargs):
|
||||||
|
from pretix.base.services.mail import mail
|
||||||
|
|
||||||
|
batch_size = 50
|
||||||
|
# this adds some rate limiting on the number of invoices to send at the same time. If there's more, the next
|
||||||
|
# cronjob will handle them
|
||||||
|
max_number_of_batches = 10
|
||||||
|
|
||||||
|
for i in range(max_number_of_batches):
|
||||||
|
with transaction.atomic():
|
||||||
|
qs = Invoice.objects.filter(
|
||||||
|
sent_to_organizer__isnull=True
|
||||||
|
).prefetch_related('event').select_for_update(skip_locked=True)
|
||||||
|
for i in qs[:batch_size]:
|
||||||
|
if i.event.settings.invoice_email_organizer:
|
||||||
|
with language(i.event.settings.locale):
|
||||||
|
mail(
|
||||||
|
email=i.event.settings.invoice_email_organizer,
|
||||||
|
subject=_('New invoice: {number}').format(number=i.number),
|
||||||
|
template=LazyI18nString.from_gettext(_(
|
||||||
|
'Hello,\n\n'
|
||||||
|
'a new invoice for {event} has been created, see attached.\n\n'
|
||||||
|
'We are sending this email because you configured us to do so in your event settings.'
|
||||||
|
)),
|
||||||
|
context={
|
||||||
|
'event': str(i.event),
|
||||||
|
},
|
||||||
|
locale=i.event.settings.locale,
|
||||||
|
event=i.event,
|
||||||
|
invoices=[i],
|
||||||
|
auto_email=True,
|
||||||
|
)
|
||||||
|
i.sent_to_organizer = True
|
||||||
|
i.save(update_fields=['sent_to_organizer'])
|
||||||
|
else:
|
||||||
|
i.sent_to_organizer = False
|
||||||
|
i.save(update_fields=['sent_to_organizer'])
|
||||||
|
|||||||
@@ -854,6 +854,18 @@ DEFAULTS = {
|
|||||||
"to emails."),
|
"to emails."),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
'invoice_email_organizer': {
|
||||||
|
'default': '',
|
||||||
|
'type': str,
|
||||||
|
'form_class': forms.EmailField,
|
||||||
|
'serializer_class': serializers.EmailField,
|
||||||
|
'form_kwargs': dict(
|
||||||
|
label=_("Email address to receive a copy of each invoice"),
|
||||||
|
help_text=_("Each newly created invoice will be sent to this email address shortly after creation. You can "
|
||||||
|
"use this for an automated import of invoices to your accounting system. The invoice will be "
|
||||||
|
"the only attachment of the email."),
|
||||||
|
)
|
||||||
|
},
|
||||||
'show_items_outside_presale_period': {
|
'show_items_outside_presale_period': {
|
||||||
'default': 'True',
|
'default': 'True',
|
||||||
'type': bool,
|
'type': bool,
|
||||||
|
|||||||
@@ -756,6 +756,7 @@ class InvoiceSettingsForm(SettingsForm):
|
|||||||
'invoice_numbers_counter_length',
|
'invoice_numbers_counter_length',
|
||||||
'invoice_address_explanation_text',
|
'invoice_address_explanation_text',
|
||||||
'invoice_email_attachment',
|
'invoice_email_attachment',
|
||||||
|
'invoice_email_organizer',
|
||||||
'invoice_address_from_name',
|
'invoice_address_from_name',
|
||||||
'invoice_address_from',
|
'invoice_address_from',
|
||||||
'invoice_address_from_zipcode',
|
'invoice_address_from_zipcode',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
{% bootstrap_field form.invoice_generate layout="control" %}
|
{% bootstrap_field form.invoice_generate layout="control" %}
|
||||||
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
|
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
|
||||||
{% bootstrap_field form.invoice_email_attachment layout="control" %}
|
{% bootstrap_field form.invoice_email_attachment layout="control" %}
|
||||||
|
{% bootstrap_field form.invoice_email_organizer layout="control" %}
|
||||||
{% bootstrap_field form.invoice_language layout="control" %}
|
{% bootstrap_field form.invoice_language layout="control" %}
|
||||||
{% bootstrap_field form.invoice_include_free layout="control" %}
|
{% bootstrap_field form.invoice_include_free layout="control" %}
|
||||||
{% bootstrap_field form.invoice_show_payments layout="control" %}
|
{% bootstrap_field form.invoice_show_payments layout="control" %}
|
||||||
|
|||||||
Reference in New Issue
Block a user