Changed Services and Billables into BaseBillables

Minor code improvements
This commit is contained in:
Kumi 2020-06-06 13:25:54 +02:00
parent 6b645d07ed
commit f2ff66dccb
9 changed files with 63 additions and 55 deletions

39
core/mixins/billable.py Normal file
View file

@ -0,0 +1,39 @@
from django.db.models import PositiveIntegerField, DateField, IntegerChoices
class ActionChoices(IntegerChoices):
WAIT = 0, "Do not invoice for now"
NEXT = 1, "Add to client's next invoice"
CRON = 2, "Invoice at next cron run"
DATE = 3, "Invoice at date"
class CycleChoices(IntegerChoices):
DAYS = 0, "Days"
WEEKS = 1, "Weeks"
MONTHS = 2, "Months"
YEARS = 3, "Years"
class RecurMixin:
date = DateField()
recur_action = PositiveIntegerField(choices=ActionChoices.choices)
recur_cycle = PositiveIntegerField(choices=CycleChoices.choices)
recur_count = PositiveIntegerField()
def next_invoicing(self):
from core.models.invoices import InvoiceItem, Invoice
if not self.recur_action == ActionChoices.DATE:
return False
try:
invoiceitems = InvoiceItem.objects.filter(billable=self)
invoice = Invoice.objects.filter(invoiceitem_set__contains=invoiceitems).latest("created")
delta = relativedelta(days=self.recur_count if self.recur_cycle == CycleChoices.DAYS else 0,
weeks=self.recur_count if self.recur_cycle == CycleChoices.WEEKS else 0,
months=self.recur_count if self.recur_cycle == CycleChoices.MONTHS else 0,
years=self.recur_count if self.recur_cycle == CycleChoices.YEARS else 0)
return invoice.created + delta
except:
return self.date

View file

@ -4,7 +4,7 @@ from core.models.auth import LoginSession, PWResetToken, LoginLog, IPLimit
from core.models.local import Currency, TaxPolicy, TaxRule from core.models.local import Currency, TaxPolicy, TaxRule
from core.models.cron import CronLog from core.models.cron import CronLog
from core.models.products import ProductGroup, Product, ProductPlan, ProductPlanItem from core.models.products import ProductGroup, Product, ProductPlan, ProductPlanItem
from core.models.billable import CycleChoices, Billable from core.models.billable import BaseBillable
from core.models.services import Service, ServicePlan, ServicePlanItem from core.models.services import Service, ServicePlan
from core.models.invoices import Invoice, InvoiceItem from core.models.invoices import Invoice, InvoiceItem
from core.models.api import APIKey from core.models.api import APIKey

View file

@ -1,4 +1,4 @@
from django.db.models import IntegerChoices, Model, ForeignKey, CASCADE, TextField, PositiveIntegerField, BooleanField, DateField from django.db.models import Model, ForeignKey, CASCADE, TextField, PositiveIntegerField, BooleanField, DateField
from django.utils import timezone from django.utils import timezone
from core.models.profiles import ClientProfile from core.models.profiles import ClientProfile
@ -6,52 +6,28 @@ from core.models.brands import Brand
from core.fields.base import LongCharField from core.fields.base import LongCharField
from core.fields.numbers import CostField from core.fields.numbers import CostField
from core.models.local import Currency from core.models.local import Currency
from core.mixins.billable import RecurMixin
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
class ActionChoices(IntegerChoices): class BaseBillable(PolymorphicModel):
WAIT = 0, "Do not invoice for now"
NEXT = 1, "Add to client's next invoice"
CRON = 2, "Invoice at next cron run"
DATE = 3, "Invoice at date"
class CycleChoices(IntegerChoices):
DAYS = 0, "Days"
WEEKS = 1, "Weeks"
MONTHS = 2, "Months"
YEARS = 3, "Years"
class Billable(Model):
client = ForeignKey(ClientProfile, on_delete=CASCADE)
brand = ForeignKey(Brand, on_delete=CASCADE)
name = LongCharField() name = LongCharField()
description = TextField() description = TextField(blank=True, null=True)
individual_cost = CostField() amount = CostField()
amount = PositiveIntegerField() count = PositiveIntegerField(default=1)
currency = ForeignKey(Currency, on_delete=CASCADE) currency = ForeignKey(Currency, on_delete=CASCADE)
taxable = BooleanField() taxable = BooleanField()
action = PositiveIntegerField(choices=ActionChoices.choices) client = ForeignKey(ClientProfile, on_delete=CASCADE)
date = DateField(null=True) brand = ForeignKey(Brand, on_delete=CASCADE)
recur_cycle = PositiveIntegerField(choices=CycleChoices.choices, null=True)
recur_count = PositiveIntegerField(null=True)
@property
def next_invoicing(self): def next_invoicing(self):
from core.models.invoices import InvoiceItem, Invoice raise NotImplementedError(f"{type(self)} does not implement property next_invoicing!")
if not self.recur_cycle == ActionChoices.DATE: @property
return False def can_invoice(self):
raise NotImplementedError(f"{type(self)} does not implement property can_invoice!")
try: class Billable(RecurMixin, BaseBillable):
invoiceitems = InvoiceItem.objects.filter(billable=self) pass
invoice = Invoice.objects.filter(invoiceitem_set__contains=invoiceitems).latest("created")
delta = relativedelta(days=self.recur_count if self.recur_cycle == CycleChoices.DAYS else 0,
weeks=self.recur_count if self.recur_cycle == CycleChoices.WEEKS else 0,
months=self.recur_count if self.recur_cycle == CycleChoices.MONTHS else 0,
years=self.recur_count if self.recur_cycle == CycleChoices.YEARS else 0)
return invoice.created + delta
except:
return self.date

View file

@ -5,6 +5,7 @@ from core.fields.numbers import CostField
from core.models.profiles import ClientProfile from core.models.profiles import ClientProfile
from core.models.local import Currency from core.models.local import Currency
from core.models.brands import Brand from core.models.brands import Brand
from core.models.billable import BaseBillable
class Invoice(Model): class Invoice(Model):
client = ForeignKey(ClientProfile, on_delete=PROTECT) client = ForeignKey(ClientProfile, on_delete=PROTECT)
@ -16,9 +17,6 @@ class Invoice(Model):
currency = ForeignKey(Currency, on_delete=PROTECT) currency = ForeignKey(Currency, on_delete=PROTECT)
class InvoiceItem(Model): class InvoiceItem(Model):
from core.models.services import Service
from core.models.billable import Billable
invoice = ForeignKey(Invoice, on_delete=CASCADE) invoice = ForeignKey(Invoice, on_delete=CASCADE)
sort = PositiveIntegerField() sort = PositiveIntegerField()
name = LongCharField() name = LongCharField()
@ -26,5 +24,4 @@ class InvoiceItem(Model):
price = CostField() price = CostField()
discount = CostField() discount = CostField()
taxable = BooleanField() taxable = BooleanField()
service = ForeignKey(Service, on_delete=SET_NULL, null=True) billable = ForeignKey(BaseBillable, on_delete=SET_NULL, null=True)
billable = ForeignKey(Billable, on_delete=SET_NULL, null=True)

View file

@ -38,7 +38,7 @@ class ProductPlan(Model):
currency = ForeignKey(Currency, on_delete=CASCADE) currency = ForeignKey(Currency, on_delete=CASCADE)
class ProductPlanItem(Model): class ProductPlanItem(Model):
from core.models.billable import CycleChoices from core.mixins.billable import CycleChoices
plan = ForeignKey(ProductPlan, on_delete=CASCADE) plan = ForeignKey(ProductPlan, on_delete=CASCADE)
cycle = PositiveIntegerField(choices=CycleChoices.choices) cycle = PositiveIntegerField(choices=CycleChoices.choices)

View file

@ -5,6 +5,7 @@ from django_countries.fields import CountryField
from django.db.models import OneToOneField, CASCADE, ImageField, Model, ForeignKey, SET_DEFAULT, ManyToManyField, DateTimeField, TextField from django.db.models import OneToOneField, CASCADE, ImageField, Model, ForeignKey, SET_DEFAULT, ManyToManyField, DateTimeField, TextField
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils.safestring import mark_safe
from core.helpers.files import generate_storage_filename from core.helpers.files import generate_storage_filename
from core.models.local import Currency from core.models.local import Currency

View file

@ -1,6 +1,7 @@
from core.fields.numbers import CostField from core.fields.numbers import CostField
from core.models.local import Currency from core.models.local import Currency
from core.models.profiles import ClientProfile from core.models.profiles import ClientProfile
from core.models.billable import BaseBillable
from django.db.models import Model, TextField, CharField, ManyToManyField, ForeignKey, CASCADE, PositiveIntegerField, OneToOneField, BooleanField, SET_NULL from django.db.models import Model, TextField, CharField, ManyToManyField, ForeignKey, CASCADE, PositiveIntegerField, OneToOneField, BooleanField, SET_NULL
@ -37,10 +38,5 @@ class ServicePlan(Model):
for item in productplan.serviceplanitem_set: for item in productplan.serviceplanitem_set:
ServicePlanItem.objects.create(plan=plan, cycle=item.cycle, count=item.count, cost=item.cost) ServicePlanItem.objects.create(plan=plan, cycle=item.cycle, count=item.count, cost=item.cost)
class ServicePlanItem(Model): class ServicePlanItem(BaseBillable):
from core.models.billable import CycleChoices
plan = ForeignKey(ServicePlan, on_delete=CASCADE) plan = ForeignKey(ServicePlan, on_delete=CASCADE)
cycle = PositiveIntegerField(choices=CycleChoices.choices)
count = PositiveIntegerField()
cost = CostField()

View file

@ -1,6 +1,4 @@
from django.forms import TextInput from django.forms import TextInput
from django.conf import settings
from django.utils.safestring import mark_safe
class ColorPickerWidget(TextInput): class ColorPickerWidget(TextInput):
def __init__(self, attrs={}): def __init__(self, attrs={}):

View file

@ -2,6 +2,7 @@
{% load navigation %} {% load navigation %}
{% load dbsetting %} {% load dbsetting %}
{% load permissions %} {% load permissions %}
{% load bootstrap4 %}
{% dbsetting "core.title" as sitetitle %} {% dbsetting "core.title" as sitetitle %}
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">