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 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 import uuid import urllib.request from dbsettings.functions import getValue from .functions import invoice_upload_path from .signals import initiate_payment from auction.models import Inquiry from localauth.models import PersonMixin, AddressMixin class BillingAddress(PersonMixin, AddressMixin): user = models.ForeignKey(get_user_model(), models.CASCADE) @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): 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) created = models.DateTimeField(auto_now_add=True) @property def price_net(self): price = 0 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 def balance(self): paid_amount = 0 for payment in self.invoicepayment_set.all(): paid_amount += payment.amount return paid_amount - self.price_gross @property def is_paid(self): return self.balance >= 0 def finalize(self, *args, **kwargs): if self.is_paid: try: self.inquiry.process_payment(*args, **kwargs) except Inquiry.DoesNotExist: pass self.generate_invoice() 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) 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) for payment in self.invoicepayment_set.all(): doc.add_transaction(Transaction(payment.gateway, payment.gateway_id, payment.timestamp, payment.amount)) bottom_tip = ("Kunden-UID: %s

" % profile.vat_id) if profile.vat_id else "" bottom_tip += (f"{ self.payment_instructions }

" if self.payment_instructions else "") + getValue("billing.bottom_tip", "") bottom_tip += "

" 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() @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) @property def net_total(self): return self.net_each * self.count class InvoicePayment(PolymorphicModel): 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)) @property def status(self): raise NotImplementedError("%s does not implement status" % type(self)) @classmethod def initiate(cls, invoice): raise NotImplementedError("%s does not implement initiate()" % cls.__name__) def finalize(self, *args, **kwargs): return self.invoice.finalize(*args, **kwargs) @classmethod def from_invoice(cls, invoice, gateway): if not invoice.is_paid: responses = initiate_payment.send_robust(sender=cls, invoice=invoice, gateway=gateway) for handler, response in responses: try: return response["redirect"] except: continue return False