Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/onboarding/AlekSIS-App-Tezor
  • sunweaver/AlekSIS-App-Tezor
  • 3lisvequii/AlekSIS-App-Tezor
3 results
Show changes
Showing
with 1225 additions and 166 deletions
from django.utils.translation import gettext_lazy as _
MENUS = {
"NAV_MENU_CORE": [
{
"name": _("Payments"),
"url": "#",
"root": True,
"icon": "price_check",
"validators": [
"menu_generator.validators.is_authenticated",
"aleksis.core.util.core_helpers.has_person",
],
"submenu": [
{
"name": _("Clients"),
"url": "clients",
"icon": "account_balance",
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"tezor.can_view_clients",
)
],
},
],
}
]
}
# Generated by Django 3.2.12 on 2022-03-06 21:33
import aleksis.core.mixins
import django.contrib.sites.managers
import aleksis.core.managers
from django.db import migrations, models
import django.db.models.deletion
......@@ -11,7 +11,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('sites', '0002_alter_domain_unique'),
('contenttypes', '0002_remove_content_type_name'),
]
......@@ -22,10 +21,9 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('extended_data', models.JSONField(default=dict, editable=False)),
('name', models.CharField(max_length=255, verbose_name='Name')),
('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
],
managers=[
('objects', django.contrib.sites.managers.CurrentSiteManager()),
('objects', aleksis.core.managers.AlekSISBaseManager()),
],
),
migrations.CreateModel(
......@@ -35,11 +33,10 @@ class Migration(migrations.Migration):
('extended_data', models.JSONField(default=dict, editable=False)),
('name', models.CharField(max_length=255, verbose_name='Invoice group name')),
('template_name', models.CharField(blank=True, max_length=255, verbose_name='Template to render invoices with as PDF')),
('client', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_groups', to='tezor.client', verbose_name='Linked client')),
('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invoice_groups', to='tezor.client', verbose_name='Linked client')),
],
managers=[
('objects', django.contrib.sites.managers.CurrentSiteManager()),
('objects', aleksis.core.managers.AlekSISBaseManager()),
],
),
migrations.CreateModel(
......@@ -75,7 +72,7 @@ class Migration(migrations.Migration):
('number', models.CharField(max_length=255, verbose_name='Invoice number')),
('for_object_id', models.PositiveIntegerField()),
('for_content_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.contenttype')),
('group', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoices', to='tezor.invoicegroup', verbose_name='Invoice group')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invoices', to='tezor.invoicegroup', verbose_name='Invoice group')),
],
bases=(models.Model, aleksis.core.mixins.PureDjangoModel),
),
......@@ -85,10 +82,10 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='invoice',
constraint=models.UniqueConstraint(fields=('transaction_id', 'group'), name='number_uniq_per_group'),
constraint=models.UniqueConstraint(fields=('number', 'group'), name='number_uniq_per_group'),
),
migrations.AddConstraint(
model_name='client',
constraint=models.UniqueConstraint(fields=('name', 'site'), name='uniq_client_per_site'),
constraint=models.UniqueConstraint(fields=('name',), name='uniq_client_per_site'),
),
]
# Generated by Django 3.2.12 on 2022-03-12 21:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tezor', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='due_date',
field=models.DateField(null=True, verbose_name='Payment due date'),
),
]
# Generated by Django 3.2.12 on 2022-03-12 21:41
import aleksis.core.managers
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0038_notification_send_at'),
('tezor', '0002_invoice_due_date'),
]
operations = [
migrations.CreateModel(
name='InvoiceItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('extended_data', models.JSONField(default=dict, editable=False)),
('sku', models.CharField(blank=True, max_length=255, verbose_name='Article no.')),
('description', models.CharField(max_length=255, verbose_name='Purchased item')),
('price', models.DecimalField(decimal_places=2, default='0.0', max_digits=9, verbose_name='Item net price')),
('currency', models.CharField(max_length=10, verbose_name='Currency')),
('tax_rate', models.DecimalField(decimal_places=1, default='0.0', max_digits=4, verbose_name='Tax rate')),
],
options={
'abstract': False,
},
managers=[
('objects', aleksis.core.managers.AlekSISBaseManager()),
],
),
migrations.AddField(
model_name='invoice',
name='person',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.person', verbose_name='Invoice recipient (person)'),
),
migrations.AddConstraint(
model_name='invoice',
constraint=models.CheckConstraint(check=models.Q(('for_object_id__isnull', True), ('person__isnull', True), _connector='OR'), name='object_or_person'),
),
migrations.AddField(
model_name='invoice',
name='items',
field=models.ManyToManyField(to='tezor.InvoiceItem', verbose_name='Invoice items'),
),
]
# 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,
),
migrations.AlterModelOptions(
name='invoice',
options={'permissions': (('send_invoice_email', 'Can send invoice by email'),)},
),
]
# Generated by Django 3.2.12 on 2022-03-14 14:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('tezor', '0004_client_email'),
]
operations = [
migrations.AlterField(
model_name='invoice',
name='for_content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.contenttype'),
),
migrations.AlterField(
model_name='invoice',
name='for_object_id',
field=models.PositiveIntegerField(blank=True, null=True),
),
]
# Generated by Django 3.2.12 on 2022-03-14 14:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('tezor', '0005_alter_fields_invoice'),
]
operations = [
migrations.RemoveConstraint(
model_name='invoice',
name='object_or_person',
),
migrations.AddConstraint(
model_name='invoice',
constraint=models.CheckConstraint(check=models.Q(models.Q(('for_object_id__isnull', True), ('person__isnull', False)), models.Q(('for_object_id__isnull', False), ('person__isnull', True)), _connector='OR'), name='object_or_person'),
),
]
# Generated by Django 3.2.12 on 2022-03-19 22:21
import warnings
import django.core.validators
from django.db import migrations, models
import localflavor.generic.models
def configure_clients(apps, schema_editor):
db_alias = schema_editor.connection.alias
Client = apps.get_model("tezor", "Client")
GlobalPreferenceModel = apps.get_model("dynamic_preferences", "GlobalPreferenceModel")
fields = ["sofort_api_id", "sofort_api_key", "sofort_project_id", "paypal_client_id", "paypal_secret", "paypal_capture", "pledge_enabled", "sdd_creditor", "sdd_creditor_identifier"]
values = {}
for field in fields:
try:
pref = GlobalPreferenceModel.objects.using(db_alias).get(section="payments", name=field)
except GlobalPreferenceModel.DoesNotExist:
continue
value = pref.raw_value
if value == "True":
value = True
elif value == "False":
value = False
values[field] = value
if not "enabled" in field:
provider = field.split("_")[0]
values[f"{provider}_enabled"] = True
for variant in ("sdd", "pledge", "paypal", "sofort"):
if values.get(f"{variant}_enabled", False):
for field in Client._meta.fields:
if field.name.startswith(f"{variant}_") and values.get(field.name, "") == "":
values[f"{variant}_enabled"] = False
warnings.warn(f"Payment variant {variant} enabled but {field.name} not configured!")
Client._base_manager.update(**values)
class Migration(migrations.Migration):
dependencies = [
('tezor', '0006_invoice_constraints'),
]
operations = [
migrations.AddField(
model_name='client',
name='paypal_capture',
field=models.BooleanField(default=False, verbose_name='Use PayPal Authorize & Capture'),
),
migrations.AddField(
model_name='client',
name='paypal_client_id',
field=models.CharField(blank=True, max_length=255, verbose_name='PayPal client ID'),
),
migrations.AddField(
model_name='client',
name='paypal_enabled',
field=models.BooleanField(default=False, verbose_name='PayPal enabled'),
),
migrations.AddField(
model_name='client',
name='paypal_secret',
field=models.CharField(blank=True, max_length=255, verbose_name='PayPal secret'),
),
migrations.AddField(
model_name='client',
name='pledge_enabled',
field=models.BooleanField(default=False, verbose_name='Pledge enabled'),
),
migrations.AddField(
model_name='client',
name='sdd_bic',
field=localflavor.generic.models.BICField(blank=True, max_length=11, verbose_name='BIC/SWIFT code of bank'),
),
migrations.AddField(
model_name='client',
name='sdd_creditor',
field=models.CharField(blank=True, max_length=255, verbose_name='SEPA Direct Debit - Creditor name'),
),
migrations.AddField(
model_name='client',
name='sdd_creditor_identifier',
field=models.CharField(blank=True, max_length=35, validators=[django.core.validators.RegexValidator('^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,31}$')], verbose_name='SEPA Direct Debit - Creditor identifier'),
),
migrations.AddField(
model_name='client',
name='sdd_enabled',
field=models.BooleanField(default=False, verbose_name='Debit enabled'),
),
migrations.AddField(
model_name='client',
name='sdd_iban',
field=localflavor.generic.models.IBANField(blank=True, include_countries=None, max_length=34, use_nordea_extensions=False, verbose_name='IBAN of bank account'),
),
migrations.AddField(
model_name='client',
name='sofort_api_id',
field=models.CharField(blank=True, max_length=255, verbose_name='Sofort / Klarna API ID'),
),
migrations.AddField(
model_name='client',
name='sofort_api_key',
field=models.CharField(blank=True, max_length=255, verbose_name='Sofort / Klarna API key'),
),
migrations.AddField(
model_name='client',
name='sofort_enabled',
field=models.BooleanField(default=False, verbose_name='Sofort / Klarna enabled'),
),
migrations.AddField(
model_name='client',
name='sofort_project_id',
field=models.CharField(blank=True, max_length=255, verbose_name='Sofort / Klarna Project ID'),
),
migrations.AddConstraint(
model_name='client',
constraint=models.CheckConstraint(check=models.Q(models.Q(('sofort_enabled', True), models.Q(('sofort_api_id', ''), _negated=True), models.Q(('sofort_api_key', ''), _negated=True), models.Q(('sofort_project_id', ''), _negated=True)), ('sofort_enabled', False), _connector='OR'), name='sofort_enabled_configured'),
),
migrations.AddConstraint(
model_name='client',
constraint=models.CheckConstraint(check=models.Q(models.Q(('sdd_enabled', True), models.Q(('sdd_creditor', ''), _negated=True), models.Q(('sdd_creditor_identifier', ''), _negated=True), models.Q(('sdd_iban', ''), _negated=True), models.Q(('sdd_bic', ''), _negated=True)), ('sdd_enabled', False), _connector='OR'), name='sdd_enabled_configured'),
),
migrations.AddConstraint(
model_name='client',
constraint=models.CheckConstraint(check=models.Q(models.Q(('paypal_enabled', True), models.Q(('paypal_client_id', ''), _negated=True), models.Q(('paypal_secret', ''), _negated=True)), ('paypal_enabled', False), _connector='OR'), name='paypal_enabled_configured'),
),
migrations.RunPython(configure_clients),
]
# Generated by Django 3.2.12 on 2022-03-20 19:29
from django.db import migrations
def set_person_on_invoices(apps, schema_editor):
Invoice = apps.get_model("tezor", "Invoice")
for invoice in Invoice.objects.filter(person__isnull=True, for_object_id__isnull=False):
person = invoice.for_object.get_person()
invoice.person=person
invoice.save()
class Migration(migrations.Migration):
dependencies = [
('tezor', '0007_client_payment_variants'),
]
operations = [
migrations.RemoveConstraint(
model_name='invoice',
name='object_or_person',
),
migrations.RunPython(set_person_on_invoices),
]
# Generated by Django 3.2.12 on 2022-03-20 21:51
from django.db import migrations
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
('tezor', '0008_always_set_person'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='billing_phone',
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None),
),
]
# Generated by Django 4.2.3 on 2023-07-22 15:41
import aleksis.core.managers
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tezor', '0009_invoice_billing_phone'),
]
operations = [
migrations.AddField(
model_name='client',
name='managed_by_app_label',
field=models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance'),
),
migrations.AddField(
model_name='invoicegroup',
name='managed_by_app_label',
field=models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance'),
),
migrations.AddField(
model_name='invoiceitem',
name='managed_by_app_label',
field=models.CharField(blank=True, editable=False, max_length=255, verbose_name='App label of app responsible for managing this instance'),
),
]
# Generated by Django 4.2.10 on 2024-03-01 11:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("tezor", "0010_alter_client_options_alter_invoice_options_and_more"),
]
operations = [
migrations.AlterModelManagers(
name="client",
managers=[],
),
migrations.AlterModelManagers(
name="invoicegroup",
managers=[],
),
migrations.AlterModelManagers(
name="invoiceitem",
managers=[],
),
migrations.RemoveConstraint(
model_name="client",
name="uniq_client_per_site",
),
] + [
migrations.RunSQL(
f"ALTER TABLE tezor_{model_name} drop column if exists site_id;"
) for model_name in
[
"client",
"invoicegroup",
"invoiceitem",
]
] + [
migrations.AlterField(
model_name="client",
name="name",
field=models.CharField(max_length=255, unique=True, verbose_name="Name"),
),
]
This diff is collapsed.
# Generated by Django 5.1.7 on 2025-03-10 10:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tezor', '0012_paymentvariant'),
]
operations = [
migrations.AddField(
model_name='invoiceitem',
name='quantity',
field=models.PositiveIntegerField(default=1, verbose_name='Quantity'),
),
]
......@@ -5,12 +5,15 @@ from aleksis.core.mixins import ExtensibleModel
class Client(ExtensibleModel):
name = models.CharField(verbose_name=_("Name"), max_length=255)
name = models.CharField(verbose_name=_("Name"), max_length=255, unique=True)
email = models.EmailField(verbose_name=_("Email"))
class Meta:
constraints = [
models.UniqueConstraint(fields=["name", "site"], name="uniq_client_per_site")
]
def get_payment_variant_choices(self):
return [(v._class_name, v.verbose_name) for v in self.payment_variants.all()]
def __str__(self) -> str:
def __str__(self):
return self.name
class Meta:
verbose_name = _("Client")
verbose_name_plural = _("Clients")
This diff is collapsed.
from typing import ClassVar
from django.core.validators import RegexValidator
from django.db import models
from django.utils.functional import classproperty
from django.utils.translation import gettext_lazy as _
from localflavor.generic.models import BICField, IBANField
from payments.core import BasicProvider
from aleksis.core.mixins import ExtensiblePolymorphicModel, RegistryObject
from .base import Client
class PaymentVariant(RegistryObject, ExtensiblePolymorphicModel):
"""A single, configured payment variant."""
icon: ClassVar[str] = None
verbose_name: ClassVar[str] = None
description = models.CharField(max_length=255, verbose_name=_("Description"))
client = models.ForeignKey(
Client, on_delete=models.CASCADE, verbose_name=_("Client"), related_name="payment_variants"
)
def __str__(self) -> str:
return self.description
def get_provider(self) -> BasicProvider:
"""Get django-payments provider for this payment variant."""
raise NotImplementedError()
@classproperty
def as_choices(cls) -> list[tuple[str, str]]:
"""Get payment variants as choices."""
return [
(variant._class_name, variant.verbose_name) for variant in cls.registered_objects_list
]
@classproperty
def variant_name(cls):
return cls._class_name
class Meta:
verbose_name = _("Payment variant")
verbose_name_plural = _("Payment variants")
class SofortPaymentVariant(PaymentVariant):
icon = "simple-icons:klarna"
verbose_name = _("Klarna/Sofort")
_class_name = "sofort"
api_id = models.CharField(verbose_name=_("API ID"), max_length=255)
api_key = models.CharField(verbose_name=_("API key"), max_length=255)
project_id = models.CharField(verbose_name=_("Project ID"), max_length=255)
def get_provider(self):
from payments.sofort import SofortProvider # noqa
provider = SofortProvider(key=self.api_key, id=self.api_sid, project_id=self.project_id)
return provider
class Meta:
verbose_name = _("Sofort/Klarna payment variant")
verbose_name_plural = _("Sofort/Klarna payment variants")
class PaypalPaymentVariant(PaymentVariant):
icon = "logos:paypal"
verbose_name = _("PayPal")
_class_name = "paypal"
api_client_id = models.CharField(verbose_name=_("Client ID"), max_length=255)
secret = models.CharField(verbose_name=_("Secret"), max_length=255)
capture = models.BooleanField(verbose_name=_("Use PayPal Authorize & Capture"), default=False)
endpoint = models.URLField(verbose_name=_("Endpoint"), default="https://api.paypal.com")
def get_provider(self):
from payments.paypal import PaypalProvider # noqa
provider = PaypalProvider(
client_id=self.client_id,
secret=self.secret,
capture=self.capture,
endpoint=self.endpoint,
)
return provider
class Meta:
verbose_name = _("PayPal payment variant")
verbose_name_plural = _("PayPal payment variants")
class SEPADirectDebitPaymentVariant(PaymentVariant):
icon = "mdi:bank-transfer"
verbose_name = _("SEPA Direct Debit")
_class_name = "sdd"
creditor = models.CharField(verbose_name=_("Creditor name"), max_length=255)
creditor_identifier = models.CharField(
verbose_name=_("Creditor identifier"),
max_length=35,
validators=[RegexValidator("^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,31}$")],
)
iban = IBANField(verbose_name=_("IBAN of bank account"))
bic = BICField(verbose_name=_("BIC/SWIFT code of bank"))
def get_provider(self):
from djp_sepa.providers import DirectDebitProvider # noqa
provider = DirectDebitProvider(
creditor=self.creditor,
creditor_identifier=self.creditor_identifier,
iban=self.iban,
bic=self.bic,
)
return provider
class Meta:
verbose_name = _("SEPA Direct Debit payment variant")
verbose_name_plural = _("SEPA Direct Debit payment variants")
class PledgePaymentVariant(PaymentVariant):
icon = "mdi:hand-coin"
verbose_name = _("Payment pledge/Manual payment")
_class_name = "pledge"
def get_provider(self):
from djp_sepa.providers import PaymentPledgeProvider # noqa
provider = PaymentPledgeProvider()
return provider
class Meta:
verbose_name = _("Pledge payment variant")
verbose_name_plural = _("Pledge payment variants")
......@@ -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."""
......@@ -24,3 +27,13 @@ def is_in_payment_status(status: str):
return obj.status == status
return _predicate
def has_payment_variant(variant: str):
"""Predicate which checks whether the invoice has a specific variant."""
@predicate
def _predicate(user: User, obj: Invoice):
return obj.variant == variant
return _predicate
This diff is collapsed.
This diff is collapsed.