Skip to content
Snippets Groups Projects
Commit 307033a7 authored by Tom Teichler's avatar Tom Teichler :beers:
Browse files

Merge branch 'mailing' into 'master'

Mailing

See merge request !5
parents b311bcb6 0dfd73bc
No related branches found
No related tags found
1 merge request!5Mailing
Pipeline #59582 failed
...@@ -6,7 +6,7 @@ MENUS = { ...@@ -6,7 +6,7 @@ MENUS = {
"name": _("Payments and Money"), "name": _("Payments and Money"),
"url": "#", "url": "#",
"root": True, "root": True,
"svg_icon": "mdi:piggy_bank", "svg_icon": "mdi:piggy-bank",
"validators": [ "validators": [
"menu_generator.validators.is_authenticated", "menu_generator.validators.is_authenticated",
"aleksis.core.util.core_helpers.has_person", "aleksis.core.util.core_helpers.has_person",
......
# Generated by Django 3.2.12 on 2022-03-09 20:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tezor', '0003_manual_invoicing'),
]
operations = [
migrations.AddField(
model_name='client',
name='email',
field=models.EmailField(default='', max_length=254, verbose_name='Email'),
preserve_default=False,
),
]
...@@ -6,6 +6,7 @@ from aleksis.core.mixins import ExtensibleModel ...@@ -6,6 +6,7 @@ from aleksis.core.mixins import ExtensibleModel
class Client(ExtensibleModel): class Client(ExtensibleModel):
name = models.CharField(verbose_name=_("Name"), max_length=255) name = models.CharField(verbose_name=_("Name"), max_length=255)
email = models.EmailField(verbose_name=_("Email"))
class Meta: class Meta:
constraints = [ constraints = [
......
...@@ -81,6 +81,11 @@ class Invoice(BasePayment, PureDjangoModel): ...@@ -81,6 +81,11 @@ class Invoice(BasePayment, PureDjangoModel):
) )
items = models.ManyToManyField("InvoiceItem", verbose_name=_("Invoice items")) items = models.ManyToManyField("InvoiceItem", verbose_name=_("Invoice items"))
class Meta:
permissions = (
("send_invoice_email", _("Can send invoice by email")),
)
@classmethod @classmethod
def get_variant_choices(cls): def get_variant_choices(cls):
choices = [] choices = []
...@@ -98,10 +103,12 @@ class Invoice(BasePayment, PureDjangoModel): ...@@ -98,10 +103,12 @@ class Invoice(BasePayment, PureDjangoModel):
return self.__class__.STATUS_ICONS[self.status] return self.__class__.STATUS_ICONS[self.status]
def get_purchased_items(self): def get_purchased_items(self):
for item in self.items.all(): if self.items.count():
yield item.as_purchased_item() for item in self.items.all():
yield item.as_purchased_item()
else: else:
return self.for_object.get_purchased_items() for item in self.for_object.get_purchased_items():
yield item
def get_person(self): def get_person(self):
if self.person: if self.person:
...@@ -122,6 +129,12 @@ class Invoice(BasePayment, PureDjangoModel): ...@@ -122,6 +129,12 @@ class Invoice(BasePayment, PureDjangoModel):
), ),
] ]
def get_billing_email_recipients(self):
if hasattr(self.for_object, "get_billing_email_recipients"):
return self.for_object.get_billing_email_recipients()
else:
return [self.billing_email]
@property @property
def purchased_items_table(self): def purchased_items_table(self):
items = [i._asdict() for i in self.get_purchased_items()] items = [i._asdict() for i in self.get_purchased_items()]
...@@ -154,11 +167,14 @@ class Invoice(BasePayment, PureDjangoModel): ...@@ -154,11 +167,14 @@ class Invoice(BasePayment, PureDjangoModel):
return TotalsTable(values) return TotalsTable(values)
def get_success_url(self): def get_absolute_url(self):
return reverse("invoice_by_token", kwargs={"slug": self.token}) return reverse("invoice_by_token", kwargs={"slug": self.token})
def get_success_url(self):
return self.get_absolute_url()
def get_failure_url(self): def get_failure_url(self):
return reverse("invoice_by_token", kwargs={"slug": self.token}) return self.get_absolute_url()
class InvoiceItem(ExtensibleModel): class InvoiceItem(ExtensibleModel):
......
...@@ -134,3 +134,11 @@ print_invoice_predicate = ( ...@@ -134,3 +134,11 @@ print_invoice_predicate = (
view_invoice_predicate & display_billing_predicate & display_purchased_items_predicate view_invoice_predicate & display_billing_predicate & display_purchased_items_predicate
) )
rules.add_perm("tezor.print_invoice_rule", print_invoice_predicate) rules.add_perm("tezor.print_invoice_rule", print_invoice_predicate)
# Send invoice email
send_invoice_email_predicate = (
has_person & is_own_invoice
| has_global_perm("tezor.send_invoice_email")
| has_object_perm("tezor.send_invoice_email")
)
rules.add_perm("tezor.send_invoice_email_rule", send_invoice_email_predicate)
from aleksis.core.celery import app
from aleksis.core.util.email import send_email
from aleksis.core.util.pdf import generate_pdf_from_template
from .models.invoice import Invoice
@app.task
def email_invoice(invoice_token):
context = {}
invoice = Invoice.objects.get(token=invoice_token)
context["invoice"] = invoice
invoice_pdf, result = generate_pdf_from_template(invoice.group.template_name, context)
result.wait(timeout=30, disable_sync_subtasks=False)
invoice_pdf.refresh_from_db()
send_email(
template_name="invoice",
from_email=invoice.group.client.email,
recipient_list=invoice.get_billing_email_recipients(),
context=context,
attachments=[(invoice_pdf.file.name, invoice_pdf.file.read(), "application/pdf")],
)
{% extends "templated_email/base.email" %}
{% load i18n %}
{% block subject_content %}{% trans "Invoice" %} {{ invoice.number }}{% endblock %}
{% block html_content %}
<p>
{% blocktrans with number=invoice.number description=invoice.description %}
Please find attached invoice number {{ number }} for {{ description }}.
Please carefully read the PDF file concerning all payment details.
{% endblocktrans %}
</p>
{% if invoice.status == "waiting" %}
<p>
{% blocktrans %}
Please visit the following link to view and make the payment:
{% endblocktrans %}
</p>
<a href="{{ BASE_URL }}{{ invoice.get_absolute_url }}">{{ BASE_URL }}{{ invoice.get_absolute_url}}</a>
{% endif %}
{% endblock %}
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
{% has_perm 'tezor.display_purchased_items_rule' user object as can_view_purchased_items %} {% has_perm 'tezor.display_purchased_items_rule' user object as can_view_purchased_items %}
{% has_perm 'tezor.display_billing_rule' user object as can_view_billing_information %} {% has_perm 'tezor.display_billing_rule' user object as can_view_billing_information %}
{% has_perm 'tezor.print_invoice_rule' user object as can_print_invoice %} {% has_perm 'tezor.print_invoice_rule' user object as can_print_invoice %}
{% has_perm 'tezor.send_invoice_email_rule' user object as can_send_invoice_email %}
<h1>{% trans "Invoice" %} {{ object.number }} — {{ object.created.date }}</h1> <h1>{% trans "Invoice" %} {{ object.number }} — {{ object.created.date }}</h1>
...@@ -21,6 +22,9 @@ ...@@ -21,6 +22,9 @@
{% if can_print_invoice %} {% if can_print_invoice %}
<a class="btn colour-primary waves-effect waves-light" href="{% url 'print_invoice' object.token %}">{% trans "Print" %}</a> <a class="btn colour-primary waves-effect waves-light" href="{% url 'print_invoice' object.token %}">{% trans "Print" %}</a>
{% endif %} {% endif %}
{% if can_send_invoice_email %}
<a class="btn colour-primary waves-effect waves-light" href="{% url 'send_invoice_by_token' object.token %}">{% trans "Send Email" %}</a>
{% endif %}
<div class="row"> <div class="row">
{% if can_view_billing_information %} {% if can_view_billing_information %}
......
...@@ -56,4 +56,9 @@ urlpatterns = [ ...@@ -56,4 +56,9 @@ urlpatterns = [
views.InvoiceDetailView.as_view(), views.InvoiceDetailView.as_view(),
name="invoice_by_token", name="invoice_by_token",
), ),
path(
"invoice/<str:token>/send/",
views.SendInvoiceEmail.as_view(),
name="send_invoice_by_token",
),
] ]
...@@ -9,14 +9,18 @@ from django.views.generic.detail import DetailView ...@@ -9,14 +9,18 @@ from django.views.generic.detail import DetailView
from django_tables2.views import RequestConfig, SingleTableView from django_tables2.views import RequestConfig, SingleTableView
from payments import PaymentStatus, RedirectNeeded, get_payment_model from payments import PaymentStatus, RedirectNeeded, get_payment_model
from rules.contrib.views import PermissionRequiredMixin from rules.contrib.views import PermissionRequiredMixin
from django_tables2.views import SingleTableView, RequestConfig
from templated_email import InlineImage, send_templated_mail
from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
from aleksis.core.util.pdf import generate_pdf_from_template
from aleksis.core.views import RenderPDFView from aleksis.core.views import RenderPDFView
from .forms import EditClientForm, EditInvoiceGroupForm from .forms import EditClientForm, EditInvoiceGroupForm
from .models.base import Client from .models.base import Client
from .models.invoice import Invoice, InvoiceGroup from .models.invoice import Invoice, InvoiceGroup
from .tables import ClientsTable, InvoiceGroupsTable, InvoicesTable from .tables import ClientsTable, InvoiceGroupsTable, InvoicesTable
from .tasks import email_invoice
class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView): class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView):
...@@ -29,7 +33,6 @@ class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView): ...@@ -29,7 +33,6 @@ class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView):
self.template_name = invoice.group.template_name self.template_name = invoice.group.template_name
context["invoice"] = invoice context["invoice"] = invoice
print(invoice.group.__dict__)
return context return context
...@@ -189,3 +192,17 @@ class InvoiceDetailView(PermissionRequiredMixin, DetailView): ...@@ -189,3 +192,17 @@ class InvoiceDetailView(PermissionRequiredMixin, DetailView):
slug_field = "token" slug_field = "token"
permission_required = "tezor.view_invoice_rule" permission_required = "tezor.view_invoice_rule"
template_name = "tezor/invoice/full.html" template_name = "tezor/invoice/full.html"
class SendInvoiceEmail(PermissionRequiredMixin, View):
permission_required = "tezor.send_invoice_email_rule"
def get(self, request, token):
email_invoice.delay(token)
url = request.META.get("HTTP_REFERRER")
if not url:
url = Invoice.objects.get(token=token).get_absolute_url()
return redirect(url)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment