Changed Services and Billables into BaseBillables
Minor code improvements
This commit is contained in:
parent
6b645d07ed
commit
f2ff66dccb
9 changed files with 63 additions and 55 deletions
39
core/mixins/billable.py
Normal file
39
core/mixins/billable.py
Normal 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
|
|
@ -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
|
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
|
||||||
|
|
|
@ -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={}):
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue