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

Merge branch 'master' into mailing

parents a3dfabd0 b311bcb6
Branches mailing
No related tags found
1 merge request!5Mailing
Pipeline #59562 failed
from django.apps import apps
from django.db import OperationalError
from aleksis.core.util.apps import AppConfig
from aleksis.core.util.core_helpers import get_site_preferences
......@@ -13,15 +14,23 @@ class DefaultConfig(AppConfig):
"Repository": "https://edugit.org/AlekSIS/onboarding//AlekSIS-App-Tezor",
}
licence = "EUPL-1.2+"
copyright_info = (([2022], "Dominik George", "dominik.george@teckids.org"), ([2022], "Tom Teichler", "tom.teichler@teckids.org"),)
copyright_info = (
([2022], "Dominik George", "dominik.george@teckids.org"),
([2022], "Tom Teichler", "tom.teichler@teckids.org"),
)
def ready(self):
from django.conf import settings # noqa
settings.PAYMENT_VARIANTS = {}
for app_config in apps.app_configs.values():
if hasattr(app_config, "get_payment_variants"):
variants = app_config.get_payment_variants()
try:
variants = app_config.get_payment_variants()
except OperationalError:
# Non-fatal, database is not yet ready
continue
for name, config in variants.items():
if name not in settings.PAYMENT_VARIANTS:
settings.PAYMENT_VARIANTS[name] = config
......@@ -31,30 +40,37 @@ class DefaultConfig(AppConfig):
variants = {}
if prefs["payments__sofort_api_id"]:
variants["sofort"] = ("payments.sofort.SofortProvider", {
"id": prefs["payments__sofort_api_id"],
"key": prefs["payments__sofort_api_key"],
"project_id": prefs["payments__sofort_project_id"],
"endpoint": "https://api.sofort.com/api/xml",
})
variants["sofort"] = (
"payments.sofort.SofortProvider",
{
"id": prefs["payments__sofort_api_id"],
"key": prefs["payments__sofort_api_key"],
"project_id": prefs["payments__sofort_project_id"],
"endpoint": "https://api.sofort.com/api/xml",
},
)
if prefs["payments__paypal_client_id"]:
variants["paypal"] = ("payments.paypal.PaypalProvider", {
"client_id": prefs["payments__paypal_client_id"],
"secret": prefs["payments__paypal_secret"],
"capture": not prefs["payments__paypal_capture"],
"endpoint": "https://api.paypal.com",
})
variants["paypal"] = (
"payments.paypal.PaypalProvider",
{
"client_id": prefs["payments__paypal_client_id"],
"secret": prefs["payments__paypal_secret"],
"capture": not prefs["payments__paypal_capture"],
"endpoint": "https://api.paypal.com",
},
)
if prefs["payments__pledge_enabled"]:
variants["pledge"] = ("djp_sepa.providers.PaymentPledgeProvider", {
})
variants["pledge"] = ("djp_sepa.providers.PaymentPledgeProvider", {})
if prefs["payments__sdd_creditor_identifier"]:
variants["sdd"] = ("djp_sepa.providers.DirectDebitProvider", {
"creditor": prefs["payments__sdd_creditor"],
"creditor_identifier": prefs["payments__sdd_creditor_identifier"],
})
variants["sdd"] = (
"djp_sepa.providers.DirectDebitProvider",
{
"creditor": prefs["payments__sdd_creditor"],
"creditor_identifier": prefs["payments__sdd_creditor_identifier"],
},
)
return variants
......@@ -5,6 +5,7 @@ from aleksis.core.mixins import ExtensibleForm
from .models.base import Client
from .models.invoice import InvoiceGroup
class EditClientForm(ExtensibleForm):
"""Form to create or edit clients."""
......@@ -17,9 +18,7 @@ class EditClientForm(ExtensibleForm):
class EditInvoiceGroupForm(ExtensibleForm):
layout = Layout(
Row("name", "template_name")
)
layout = Layout(Row("name", "template_name"))
class Meta:
model = InvoiceGroup
......
......@@ -19,15 +19,20 @@ from .base import Client
class InvoiceGroup(ExtensibleModel):
name = models.CharField(verbose_name=_("Invoice group name"), max_length=255)
client = models.ForeignKey(
Client, verbose_name=_("Linked client"), related_name="invoice_groups", on_delete=models.SET_NULL, null=True
Client,
verbose_name=_("Linked client"),
related_name="invoice_groups",
on_delete=models.SET_NULL,
null=True,
)
template_name = models.CharField(verbose_name=_("Template to render invoices with as PDF"), blank=True, max_length=255)
template_name = models.CharField(
verbose_name=_("Template to render invoices with as PDF"), blank=True, max_length=255
)
def __str__(self) -> str:
return self.name
class Meta:
constraints = [
models.UniqueConstraint(fields=["client", "name"], name="group_uniq_per_client")
......@@ -52,7 +57,11 @@ class Invoice(BasePayment, PureDjangoModel):
}
group = models.ForeignKey(
InvoiceGroup, verbose_name=_("Invoice group"), related_name="invoices", on_delete=models.SET_NULL, null=True
InvoiceGroup,
verbose_name=_("Invoice group"),
related_name="invoices",
on_delete=models.SET_NULL,
null=True,
)
number = models.CharField(verbose_name=_("Invoice number"), max_length=255)
......@@ -63,7 +72,13 @@ class Invoice(BasePayment, PureDjangoModel):
for_object = GenericForeignKey("for_content_type", "for_object_id")
# For manual invoicing
person = models.ForeignKey(Person, on_delete=models.SET_NULL, verbose_name=_("Invoice recipient (person)"), blank=True, null=True)
person = models.ForeignKey(
Person,
on_delete=models.SET_NULL,
verbose_name=_("Invoice recipient (person)"),
blank=True,
null=True,
)
items = models.ManyToManyField("InvoiceItem", verbose_name=_("Invoice items"))
@classmethod
......@@ -101,7 +116,10 @@ class Invoice(BasePayment, PureDjangoModel):
class Meta:
constraints = [
models.UniqueConstraint(fields=["number", "group"], name="number_uniq_per_group"),
models.CheckConstraint(check=(Q(for_object_id__isnull=True) | Q(person__isnull=True)), name="object_or_person"),
models.CheckConstraint(
check=(Q(for_object_id__isnull=True) | Q(person__isnull=True)),
name="object_or_person",
),
]
def get_billing_email_recipients(self):
......@@ -124,17 +142,21 @@ class Invoice(BasePayment, PureDjangoModel):
values = []
for tax_rate, total in tax_amounts.items():
values.append({
"name": _("Included VAT {} %").format(tax_rate),
"value": total,
values.append(
{
"name": _("Included VAT {} %").format(tax_rate),
"value": total,
"currency": self.currency,
}
)
values.append(
{
"name": _("Gross total"),
"value": self.total,
"currency": self.currency,
})
values.append({
"name": _("Gross total"),
"value": self.total,
"currency": self.currency,
})
}
)
return TotalsTable(values)
......@@ -148,9 +170,20 @@ class Invoice(BasePayment, PureDjangoModel):
class InvoiceItem(ExtensibleModel):
sku = models.CharField(max_length=255, verbose_name=_("Article no."), blank=True)
description = models.CharField(max_length=255, verbose_name=_("Purchased item"))
price = models.DecimalField(verbose_name=_("Item gross price"), max_digits=9, decimal_places=2, default="0.0")
price = models.DecimalField(
verbose_name=_("Item gross price"), max_digits=9, decimal_places=2, default="0.0"
)
currency = models.CharField(max_length=10, verbose_name=_("Currency"))
tax_rate = models.DecimalField(verbose_name=_("Tax rate"), max_digits=4, decimal_places=1, default="0.0")
tax_rate = models.DecimalField(
verbose_name=_("Tax rate"), max_digits=4, decimal_places=1, default="0.0"
)
def as_purchased_item(self):
yield PurchasedItem(name=self.description, quantity=1, price=self.price, currency=self.currency, sku=self.sku, tax_rate=self.tax_rate)
yield PurchasedItem(
name=self.description,
quantity=1,
price=self.price,
currency=self.currency,
sku=self.sku,
tax_rate=self.tax_rate,
)
......@@ -6,16 +6,19 @@ from .models.invoice import Invoice
User = get_user_model()
@predicate
def is_own_invoice(user: User, obj: Invoice):
"""Predicate which checks if the invoice is linked to the current user."""
return obj.get_person() == user.person
@predicate
def has_no_payment_variant(user: User, obj: Invoice):
"""Predicate which checks that the invoice has no payment variant."""
return not obj.variant
def is_in_payment_status(status: str):
"""Predicate which checks whether the invoice is in a specific state."""
......
......@@ -15,7 +15,9 @@ class EnablePledge(BooleanPreference):
section = payments
name = "public_payments"
verbose_name = _("Public payments")
help_text = _("Allow anyone (including guests) to make payments. Basic invoice information will be visible to anyone who knows the invoice token.")
help_text = _(
"Allow anyone (including guests) to make payments. Basic invoice information will be visible to anyone who knows the invoice token."
)
default = True
required = False
......
import rules
from payments import PaymentStatus
from aleksis.core.util.predicates import has_person, has_global_perm, has_any_object, has_object_perm, is_site_preference_set
from aleksis.core.util.predicates import (
has_any_object,
has_global_perm,
has_object_perm,
has_person,
is_site_preference_set,
)
from .models.base import Client
from .models.invoice import Invoice, InvoiceGroup
from .predicates import has_no_payment_variant, is_own_invoice, is_in_payment_status
from .predicates import has_no_payment_variant, is_in_payment_status, is_own_invoice
# View clients
view_clients_predicate = has_person & (
......@@ -39,7 +45,8 @@ rules.add_perm("tezor.delete_client_rule", delete_client_predicate)
# View invoice groups
view_invoice_groups_predicate = has_person & (
has_global_perm("tezor.view_invoice_group") | has_any_object("tezor.view_invoice_group", InvoiceGroup)
has_global_perm("tezor.view_invoice_group")
| has_any_object("tezor.view_invoice_group", InvoiceGroup)
)
rules.add_perm("tezor.view_invoice_groups_rule", view_invoice_groups_predicate)
......@@ -57,35 +64,73 @@ rules.add_perm("tezor.edit_invoice_group_rule", edit_invoice_group_predicate)
# Create invoice groups
create_invoice_groups_predicate = has_person & (
has_global_perm("tezor.create_invoice_group") | has_any_object("tezor.create_invoice_group", InvoiceGroup)
has_global_perm("tezor.create_invoice_group")
| has_any_object("tezor.create_invoice_group", InvoiceGroup)
)
rules.add_perm("tezor.create_invoice_groups_rule", create_invoice_groups_predicate)
# Delete invoice groups
delete_invoice_groups_predicate = has_person & (
has_global_perm("tezor.delete_invoice_group") | has_any_object("tezor.delete_invoice_group", InvoiceGroup)
has_global_perm("tezor.delete_invoice_group")
| has_any_object("tezor.delete_invoice_group", InvoiceGroup)
)
rules.add_perm("tezor.delete_invoice_groups_rule", delete_invoice_groups_predicate)
# Display invoice billing information
display_billing_predicate = has_person & (is_own_invoice | has_global_perm("tezor.display_billing") | has_object_perm("tezor.display_billing"))
display_billing_predicate = has_person & (
is_own_invoice
| has_global_perm("tezor.display_billing")
| has_object_perm("tezor.display_billing")
)
rules.add_perm("tezor.display_billing_rule", display_billing_predicate)
# Display invoice purchased items
display_purchased_items_predicate = has_person & (is_own_invoice | has_global_perm("tezor.display_purchased_items") | has_object_perm("tezor.display_purchased_items"))
display_purchased_items_predicate = has_person & (
is_own_invoice
| has_global_perm("tezor.display_purchased_items")
| has_object_perm("tezor.display_purchased_items")
)
rules.add_perm("tezor.display_purchased_items_rule", display_purchased_items_predicate)
# Change payment variant
change_payment_variant_predicate = has_person & is_in_payment_status(PaymentStatus.WAITING) & ((is_own_invoice & has_no_payment_variant) | has_global_perm("tezor.change_payment_variant") | has_object_perm("tezor.change_payment_variant"))
change_payment_variant_predicate = (
has_person
& is_in_payment_status(PaymentStatus.WAITING)
& (
(is_own_invoice & has_no_payment_variant)
| has_global_perm("tezor.change_payment_variant")
| has_object_perm("tezor.change_payment_variant")
)
)
rules.add_perm("tezor.change_payment_variant", change_payment_variant_predicate)
# Start payment
do_payment_predicate = has_person & (is_in_payment_status(PaymentStatus.WAITING) | is_in_payment_status(PaymentStatus.INPUT) | is_in_payment_status(PaymentStatus.ERROR) | is_in_payment_status(PaymentStatus.REJECTED)) & ((is_own_invoice | is_site_preference_set("payments", "public_payments")) | has_global_perm("tezor.do_payment") | has_object_perm("tezor.do_payment"))
do_payment_predicate = (
has_person
& (
is_in_payment_status(PaymentStatus.WAITING)
| is_in_payment_status(PaymentStatus.INPUT)
| is_in_payment_status(PaymentStatus.ERROR)
| is_in_payment_status(PaymentStatus.REJECTED)
)
& (
(is_own_invoice | is_site_preference_set("payments", "public_payments"))
| has_global_perm("tezor.do_payment")
| has_object_perm("tezor.do_payment")
)
)
rules.add_perm("tezor.do_payment", do_payment_predicate)
# View invoice
view_invoice_predicate = has_person & is_own_invoice | is_site_preference_set("payments", "public_payments") | has_global_perm("tezor.view_invoice") | has_object_perm("tezor.view_invoice")
view_invoice_predicate = (
has_person & is_own_invoice
| is_site_preference_set("payments", "public_payments")
| has_global_perm("tezor.view_invoice")
| has_object_perm("tezor.view_invoice")
)
rules.add_perm("tezor.view_invoice_rule", view_invoice_predicate)
print_invoice_predicate = (view_invoice_predicate & display_billing_predicate & display_purchased_items_predicate)
print_invoice_predicate = (
view_invoice_predicate & display_billing_predicate & display_purchased_items_predicate
)
rules.add_perm("tezor.print_invoice_rule", print_invoice_predicate)
......@@ -60,6 +60,7 @@ class ClientsTable(tables.Table):
text=_("Delete"),
)
class InvoiceGroupsTable(tables.Table):
name = tables.Column()
......@@ -83,6 +84,7 @@ class InvoiceGroupsTable(tables.Table):
text=_("Delete"),
)
class InvoicesTable(tables.Table):
number = tables.Column()
......
from django.shortcuts import redirect, render, get_object_or_404
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, TemplateView, View
from django.views.generic.detail import DetailView
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, TemplateView, View
from django.views.generic.detail import DetailView
from payments import get_payment_model, PaymentStatus, RedirectNeeded
from django_tables2.views import RequestConfig, SingleTableView
from payments import PaymentStatus, RedirectNeeded, get_payment_model
from rules.contrib.views import PermissionRequiredMixin
from django_tables2.views import SingleTableView, RequestConfig
from templated_email import InlineImage, send_templated_mail
from aleksis.core.views import RenderPDFView
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 .tables import ClientsTable, InvoiceGroupsTable, InvoicesTable
from .forms import EditClientForm, EditInvoiceGroupForm
from .models.base import Client
from .models.invoice import Invoice, InvoiceGroup
from .tables import ClientsTable, InvoiceGroupsTable, InvoicesTable
class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView):
......@@ -44,7 +45,11 @@ class DoPaymentView(PermissionRequiredMixin, View):
def dispatch(self, request, token):
self.object = get_object_or_404(self.model, token=token)
if self.object.status not in [PaymentStatus.WAITING, PaymentStatus.INPUT, PaymentStatus.REJECTED]:
if self.object.status not in [
PaymentStatus.WAITING,
PaymentStatus.INPUT,
PaymentStatus.REJECTED,
]:
return redirect(self.object.get_success_url())
try:
......@@ -119,6 +124,7 @@ class ClientDetailView(PermissionRequiredMixin, DetailView):
return context
class InvoiceGroupDetailView(PermissionRequiredMixin, DetailView):
model = InvoiceGroup
......@@ -147,13 +153,17 @@ class InvoiceGroupCreateView(PermissionRequiredMixin, AdvancedCreateView):
success_url = reverse_lazy("clients")
success_message = _("The invoice_group has been created.")
def form_valid(self, form):
client = Client.objects.get(id=self.kwargs["pk"])
InvoiceGroup.objects.create(client=client, name=form.cleaned_data["name"], template_name=form.cleaned_data["template_name"])
InvoiceGroup.objects.create(
client=client,
name=form.cleaned_data["name"],
template_name=form.cleaned_data["template_name"],
)
return super().form_valid(form)
@method_decorator(never_cache, name="dispatch")
class InvoiceGroupEditView(PermissionRequiredMixin, AdvancedEditView):
"""Edit view for invoice_groups."""
......
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