Allow attaching invoices to emails

This commit is contained in:
Raphael Michel
2017-11-07 17:52:09 +01:00
parent 7649fa11d3
commit f3221e6e76
7 changed files with 76 additions and 19 deletions

View File

@@ -368,7 +368,7 @@ class Order(LoggedModel):
def send_mail(self, subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent',
user: User=None, headers: dict=None, sender: str=None):
user: User=None, headers: dict=None, sender: str=None, invoices: list=None):
"""
Sends an email to the user that placed this order. Basically, this method does two things:
@@ -393,7 +393,8 @@ class Order(LoggedModel):
email_content = render_mail(template, context)[0]
mail(
recipient, subject, template, context,
self.event, self.locale, self, headers, sender
self.event, self.locale, self, headers, sender,
invoices=invoices
)
except SendMailException:
raise
@@ -404,7 +405,8 @@ class Order(LoggedModel):
data={
'subject': subject,
'message': email_content,
'recipient': recipient
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else []
}
)

View File

@@ -158,7 +158,7 @@ def build_cancellation(invoice: Invoice):
return invoice
def generate_cancellation(invoice: Invoice):
def generate_cancellation(invoice: Invoice, trigger_pdf=True):
cancellation = copy.copy(invoice)
cancellation.pk = None
cancellation.invoice_no = None
@@ -170,7 +170,8 @@ def generate_cancellation(invoice: Invoice):
cancellation.save()
cancellation = build_cancellation(cancellation)
invoice_pdf(cancellation.pk)
if trigger_pdf:
invoice_pdf(cancellation.pk)
return cancellation
@@ -183,7 +184,7 @@ def regenerate_invoice(invoice: Invoice):
return invoice
def generate_invoice(order: Order):
def generate_invoice(order: Order, trigger_pdf=True):
locale = order.event.settings.get('invoice_language')
if locale:
if locale == '__user__':
@@ -197,10 +198,11 @@ def generate_invoice(order: Order):
locale=locale
)
invoice = build_invoice(invoice)
invoice_pdf(invoice.pk)
if trigger_pdf:
invoice_pdf(invoice.pk)
if order.status in (Order.STATUS_CANCELED, Order.STATUS_REFUNDED):
generate_cancellation(invoice)
generate_cancellation(invoice, trigger_pdf)
return invoice

View File

@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Union
import bleach
import cssutils
import markdown
from celery import chain
from django.conf import settings
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import get_template
@@ -12,7 +13,8 @@ from i18nfield.strings import LazyI18nString
from inlinestyler.utils import inline_css
from pretix.base.i18n import language
from pretix.base.models import Event, InvoiceAddress, Order
from pretix.base.models import Event, Invoice, InvoiceAddress, Order
from pretix.base.services.invoices import invoice_pdf_task
from pretix.celery_app import app
from pretix.multidomain.urlreverse import build_absolute_uri
@@ -33,7 +35,7 @@ class SendMailException(Exception):
def mail(email: str, subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, event: Event=None, locale: str=None,
order: Order=None, headers: dict=None, sender: str=None):
order: Order=None, headers: dict=None, sender: str=None, invoices: list=None):
"""
Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.
@@ -61,6 +63,8 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
:param sender: Set the sender email address. If not set and ``event`` is set, the event's default will be used,
otherwise the system default.
:param invoices: A list of invoices to attach to this email.
:raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean
that the email has been sent, just that it has been queued by the email backend.
"""
@@ -137,15 +141,42 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
tpl = get_template('pretixbase/email/plainwrapper.html')
body_html = tpl.render(htmlctx)
return mail_send([email], subject, body_plain, body_html, sender, event.id if event else None, headers)
send_task = mail_send_task.si(
to=[email],
subject=subject,
body=body_plain,
html=body_html,
sender=sender,
event=event.id if event else None,
headers=headers,
invoices=[i.pk for i in invoices]
)
if invoices:
task_chain = [invoice_pdf_task.si(i.pk).on_error(send_task) for i in invoices if not i.file]
else:
task_chain = []
task_chain.append(send_task)
chain(*task_chain).apply_async()
@app.task
def mail_send_task(to: List[str], subject: str, body: str, html: str, sender: str,
event: int=None, headers: dict=None, bcc: List[str]=None) -> bool:
def mail_send_task(*args, to: List[str], subject: str, body: str, html: str, sender: str,
event: int=None, headers: dict=None, bcc: List[str]=None, invoices: List[int]=None) -> bool:
email = EmailMultiAlternatives(subject, body, sender, to=to, bcc=bcc, headers=headers)
if html is not None:
email.attach_alternative(inline_css(html), "text/html")
if invoices:
invoices = Invoice.objects.filter(pk__in=invoices)
for inv in invoices:
if inv.file:
email.attach(
'{}.pdf'.format(inv.number),
inv.file.file.read(),
'application/pdf'
)
if event:
event = Event.objects.get(id=event)
backend = event.get_mail_backend()

View File

@@ -127,9 +127,13 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
}, user=user, api_token=api_token)
order_paid.send(order.event, order=order)
invoice = None
if order.event.settings.get('invoice_generate') in ('True', 'paid') and invoice_qualified(order):
if not order.invoices.exists():
generate_invoice(order)
invoice = generate_invoice(
order,
trigger_pdf=not send_mail or not order.event.settings.invoice_email_attachment
)
if send_mail:
with language(order.locale):
@@ -155,7 +159,8 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date
try:
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_paid', user
'pretix.event.order.email.order_paid', user,
invoices=[invoice] if invoice and order.event.settings.invoice_email_attachment else []
)
except SendMailException:
logger.exception('Order paid email could not be sent')
@@ -502,9 +507,11 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
order = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info)
invoice = order.invoices.last() # Might be generated by plugin already
if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
if not order.invoices.exists():
generate_invoice(order)
if not invoice:
invoice = generate_invoice(order, trigger_pdf=not event.settings.invoice_email_attachment)
# send_mail will trigger PDF generation later
if order.total == Decimal('0.00'):
email_template = event.settings.mail_text_order_free
@@ -536,7 +543,8 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
try:
order.send_mail(
email_subject, email_template, email_context,
log_entry
log_entry,
invoices=[invoice] if invoice and event.settings.invoice_email_attachment else []
)
except SendMailException:
logger.exception('Order received email could not be sent')

View File

@@ -129,6 +129,10 @@ DEFAULTS = {
'default': '__user__',
'type': str
},
'invoice_email_attachment': {
'default': 'False',
'type': bool
},
'show_items_outside_presale_period': {
'default': 'True',
'type': bool

View File

@@ -544,7 +544,16 @@ class InvoiceSettingsForm(SettingsForm):
('user', _('Automatically on user request')),
('True', _('Automatically for all created orders')),
('paid', _('Automatically on payment')),
)
),
help_text=_("Invoices will never be automatically generated for free orders.")
)
invoice_email_attachment = forms.BooleanField(
label=_("Attach invoices to emails"),
help_text=_("If invocies are automatically generated for all orders, they will be attached to the order "
"confirmation mail. If they are automatically generated on payment, they will be attached to the "
"payment confirmation mail. If they are not automatically generated, they will not be attached "
"to emails."),
required=False
)
invoice_renderer = forms.ChoiceField(
label=_("Invoice style"),

View File

@@ -11,6 +11,7 @@
{% bootstrap_field form.invoice_address_required layout="control" %}
{% bootstrap_field form.invoice_name_required layout="control" %}
{% bootstrap_field form.invoice_generate layout="control" %}
{% bootstrap_field form.invoice_email_attachment layout="control" %}
{% bootstrap_field form.invoice_address_vatid layout="control" %}
{% bootstrap_field form.invoice_numbers_consecutive layout="control" %}
{% bootstrap_field form.invoice_numbers_prefix layout="control" %}