JourneyJoker/payment/models.py

219 lines
7.2 KiB
Python
Raw Normal View History

from django.db import models
from django.contrib.auth import get_user_model
from django.core.files.base import ContentFile
from django.utils import timezone
2021-04-18 14:46:57 +00:00
from django.conf import settings
from dateutil.relativedelta import relativedelta
from pyinvoice.models import InvoiceInfo, ServiceProviderInfo, ClientInfo, Item, Transaction, PDFInfo
from pyinvoice.templates import SimpleInvoice
from polymorphic.models import PolymorphicModel
from django_countries.fields import CountryField
from io import BytesIO
2021-04-18 14:46:57 +00:00
import uuid
import urllib.request
from dbsettings.functions import getValue
from .functions import invoice_upload_path
2021-04-18 14:46:57 +00:00
from .signals import initiate_payment
from auction.models import Inquiry
2021-04-18 14:46:57 +00:00
from localauth.models import PersonMixin, AddressMixin
2021-04-18 14:46:57 +00:00
class BillingAddress(PersonMixin, AddressMixin):
user = models.ForeignKey(get_user_model(), models.CASCADE)
2021-04-18 14:46:57 +00:00
@classmethod
def from_profile(cls, profile):
return cls.objects.create(
company = profile.company,
vat_id = profile.vat_id,
first_name = profile.first_name,
last_name = profile.last_name,
street = profile.street,
city = profile.city,
zip = profile.zip,
state = profile.state,
country = profile.country,
user = profile.user
)
class Invoice(models.Model):
2021-04-18 14:46:57 +00:00
uuid = models.UUIDField(default=uuid.uuid4)
user = models.ForeignKey(get_user_model(), models.PROTECT)
billing_address = models.ForeignKey(BillingAddress, models.PROTECT)
currency = models.CharField(max_length=3)
tax_rate = models.DecimalField(max_digits=4, decimal_places=2)
invoice = models.FileField(null=True, blank=True, upload_to=invoice_upload_path)
inquiry = models.OneToOneField(Inquiry, null=True, blank=True, on_delete=models.SET_NULL)
@property
def price_net(self):
price = 0
2021-04-18 14:46:57 +00:00
for item in self.invoiceitem_set.all():
price += item.net_total
return price
@property
def tax(self):
return round(self.price_net * self.tax_rate / 100, 2)
@property
def price_gross(self):
return self.price_net + self.tax
@property
def payment_instructions(self):
return "Alle Preise in %s." % self.currency
@property
def url(self):
return False
@property
2021-04-18 14:46:57 +00:00
def balance(self):
paid_amount = 0
2021-04-18 14:46:57 +00:00
for payment in self.invoicepayment_set.all():
paid_amount += payment.amount
2021-04-18 14:46:57 +00:00
return paid_amount - self.price_gross
2021-04-18 14:46:57 +00:00
@property
def is_paid(self):
return self.balance >= 0
def generate_invoice(self):
output = BytesIO()
pdfinfo = PDFInfo(title='Rechnung', author='Kumi Media Ltd.', subject='Rechnung')
doc = SimpleInvoice(output, pdfinfo)
doc.is_paid = self.is_paid
doc.invoice_info = InvoiceInfo("%s%i" % (getValue("billing.prefix", ""), self.id), timezone.now().date(), timezone.now().date())
provider_data = { key: value for key, value in {
"name": getValue("billing.provider.name", False),
"street": getValue("billing.provider.street", False),
"city": getValue("billing.provider.city", False),
"state": getValue("billing.provider.state", False),
"country": getValue("billing.provider.country", False),
"post_code": getValue("billing.provider.zip", False),
"vat_tax_number": getValue("billing.provider.vat_id", False)
}.items() if value}
doc.service_provider_info = ServiceProviderInfo(**provider_data)
logo_url = getValue("billing.logo_url", False)
if logo_url:
try:
logo = BytesIO(urllib.request.urlopen(logo_url).read())
doc.logo(logo)
except:
pass
profile = self.user.clientprofile if self.inquiry else self.user.partnerprofile
client_data = { key: value for key, value in {
"name": profile.full_name,
"street": profile.street,
"city": profile.city,
"state": profile.state,
"country": profile.country,
"post_code": profile.zip,
"email": self.user.email,
}.items() if value}
doc.client_info = ClientInfo(**client_data)
2021-04-18 14:46:57 +00:00
for item in self.invoiceitem_set.all():
doc.add_item(Item(item.name, item.description, item.count, item.net_each))
doc.set_item_tax_rate(self.tax_rate)
2021-04-18 14:46:57 +00:00
for payment in self.invoicepayment_set.all():
doc.add_transaction(Transaction(payment.gateway, payment.gateway_id, payment.timestamp, payment.amount))
2021-04-18 14:46:57 +00:00
bottom_tip = ("Kunden-UID: %s<br /><br />" % profile.vat_id) if profile.vat_id else ""
bottom_tip += (f"{ self.payment_instructions }<br /><br />" if self.payment_instructions else "") + getValue("billing.bottom_tip", "")
bottom_tip += "<br /><br />" if bottom_tip else ""
bottom_tip += "Rechnung erstellt: %s" % str(timezone.now())
doc.set_bottom_tip(bottom_tip)
doc.finish()
self.invoice = ContentFile(output.getvalue(), "invoice.pdf")
self.save()
2021-04-18 14:46:57 +00:00
@classmethod
def from_inquiry(cls, inquiry):
invoice = cls.objects.create(
user = inquiry.client.user,
billing_address = BillingAddress.from_profile(inquiry.client),
currency = settings.CURRENCY_CODE,
tax_rate = 0,
inquiry = inquiry
)
InvoiceItem.objects.create(
invoice = invoice,
name = "Sicherheitsleistung",
description = "Rückzahlbare Sicherheitsleistung zu JourneyJoker-Anfrage #%i" % inquiry.id,
count = 1,
net_each = inquiry.budget
)
return invoice
class InvoiceItem(models.Model):
invoice = models.ForeignKey(Invoice, models.CASCADE)
name = models.CharField(max_length=64)
description = models.CharField(max_length=256, null=True, blank=True)
count = models.IntegerField()
net_each = models.DecimalField(max_digits=11, decimal_places=2)
2021-04-18 14:46:57 +00:00
@property
def net_total(self):
return self.net_each * self.count
class InvoicePayment(PolymorphicModel):
2021-04-18 14:46:57 +00:00
uuid = models.UUIDField(default=uuid.uuid4)
invoice = models.ForeignKey(Invoice, models.PROTECT)
amount = models.DecimalField(max_digits=9, decimal_places=2)
gateway_id = models.CharField(max_length=256)
timestamp = models.DateTimeField(default=timezone.now)
@property
def gateway(self):
raise NotImplementedError("%s does not implement gateway" % type(self))
2021-04-18 14:46:57 +00:00
@property
def status(self):
raise NotImplementedError("%s does not implement status" % type(self))
2021-03-22 17:42:07 +00:00
@classmethod
def initiate(cls, invoice):
raise NotImplementedError("%s does not implement initiate()" % cls.__name__)
2021-04-18 14:46:57 +00:00
@classmethod
def from_invoice(cls, invoice, gateway):
if invoice.balance < 0:
responses = initiate_payment.send_robust(sender=cls, invoice=invoice, gateway=gateway)
for handler, response in responses:
try:
return response["redirect"]
except:
continue
return False