Start implementation of automatic invoice generation
This commit is contained in:
parent
96cae766ed
commit
1bc81e7126
5 changed files with 70 additions and 31 deletions
21
core/helpers/billable.py
Normal file
21
core/helpers/billable.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from core.models.billable import Billable, ActionChoices
|
||||
from core.models.services import Service
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
def generate_invoices():
|
||||
todo = {}
|
||||
|
||||
for billable in Billable.objects.filter(recur_cycle=ActionChoices.DATE):
|
||||
date = billable.next_invoicing()
|
||||
if date and date <= timezone.now():
|
||||
if not billable.brand in todo.keys():
|
||||
todo[billable.brand] = {}
|
||||
if not billable.currency in todo[billable.brand].keys():
|
||||
todo[billable.brand][billable.currency] = {}
|
||||
if not billable.client in todo[billable.brand][billable.currency].keys():
|
||||
todo[billable.brand][billable.currency][billable.client] = {"billable": []}
|
||||
todo[billable.brand][billable.currency][billable.client]["billable"].append(billable)
|
||||
|
||||
for service in Service.objects.all():
|
||||
pass
|
|
@ -5,6 +5,6 @@ from core.models.local import Currency, TaxPolicy, TaxRule
|
|||
from core.models.cron import CronLog
|
||||
from core.models.products import ProductGroup, Product, ProductPlan, ProductPlanItem
|
||||
from core.models.billable import CycleChoices, Billable
|
||||
from core.models.services import Service, ServicePlan, ServicePlanItem
|
||||
from core.models.services import Service
|
||||
from core.models.invoices import Invoice, InvoiceItem
|
||||
from core.models.api import APIKey
|
|
@ -5,6 +5,7 @@ from core.models.profiles import ClientProfile
|
|||
from core.models.brands import Brand
|
||||
from core.fields.base import LongCharField
|
||||
from core.fields.numbers import CostField
|
||||
from core.models.local import Currency
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
@ -30,6 +31,7 @@ class Billable(Model):
|
|||
description = TextField()
|
||||
individual_cost = CostField()
|
||||
amount = PositiveIntegerField()
|
||||
currency = ForeignKey(Currency, on_delete=CASCADE)
|
||||
taxable = BooleanField()
|
||||
action = PositiveIntegerField(choices=ActionChoices.choices)
|
||||
date = DateField(null=True)
|
||||
|
@ -39,12 +41,11 @@ class Billable(Model):
|
|||
def next_invoicing(self):
|
||||
from core.models.invoices import InvoiceItem, Invoice
|
||||
|
||||
try:
|
||||
invoiceitems = InvoiceItem.objects.filter(billable=self)
|
||||
|
||||
if not recur_cycle:
|
||||
if not self.recur_cycle == 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,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from core.fields.base import LongCharField
|
||||
from core.fields.color import ColorField
|
||||
from core.fields.numbers import CostField
|
||||
from core.models.local import Currency
|
||||
from core.models.brands import Brand
|
||||
|
||||
from django.db.models import Model, PositiveIntegerField, ForeignKey, CASCADE, CharField, TextField, ManyToManyField
|
||||
from django.db.models import Model, PositiveIntegerField, ForeignKey, CASCADE, CharField, TextField, ManyToManyField, BooleanField
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
|
@ -19,19 +21,21 @@ class Product(Model):
|
|||
description = TextField(null=True, blank=True)
|
||||
product_type = LongCharField(null=True, blank=True)
|
||||
product_groups = ManyToManyField(ProductGroup)
|
||||
brand = ForeignKey(Brand, on_delete=CASCADE)
|
||||
|
||||
@property
|
||||
def handler(self):
|
||||
if self.product_type:
|
||||
try:
|
||||
product_type = import_module(self.product_type)
|
||||
return product_type.ProductRouter(self.id)
|
||||
return product_type.ProductRouter(self)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not load product type {self.product_type} for product {self.id}: {e}")
|
||||
return None
|
||||
|
||||
class ProductPlan(Model):
|
||||
product = ForeignKey(Product, on_delete=CASCADE)
|
||||
currency = ForeignKey(Currency, on_delete=CASCADE)
|
||||
|
||||
class ProductPlanItem(Model):
|
||||
from core.models.billable import CycleChoices
|
||||
|
@ -40,3 +44,4 @@ class ProductPlanItem(Model):
|
|||
cycle = PositiveIntegerField(choices=CycleChoices.choices)
|
||||
count = PositiveIntegerField()
|
||||
cost = CostField()
|
||||
taxable = BooleanField()
|
|
@ -1,44 +1,56 @@
|
|||
from core.fields.numbers import CostField
|
||||
from core.models.local import Currency
|
||||
from core.models.profiles import ClientProfile
|
||||
|
||||
from django.db.models import Model, TextField, CharField, ManyToManyField, ForeignKey, CASCADE, PositiveIntegerField, OneToOneField
|
||||
from django.db.models import Model, TextField, CharField, ManyToManyField, ForeignKey, CASCADE, PositiveIntegerField, OneToOneField, BooleanField, SET_NULL
|
||||
|
||||
from importlib import import_module
|
||||
from logging import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class Service(Model):
|
||||
from core.models.products import ProductGroup
|
||||
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(null=True, blank=True)
|
||||
handler_module = CharField(max_length=255, null=True, blank=True)
|
||||
product_groups = ManyToManyField(ProductGroup)
|
||||
|
||||
@property
|
||||
def handler(self):
|
||||
if self.handler_module:
|
||||
try:
|
||||
handler_module = import_module(self.handler_module)
|
||||
return handler_module.ProductRouter(self.id)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not load product handler {self.handler_module} for product {self.id}: {e}")
|
||||
return None
|
||||
|
||||
class ServicePlan(Model):
|
||||
service = OneToOneField(Service, on_delete=CASCADE)
|
||||
|
||||
@classmethod
|
||||
def from_productplan(cls, productplan, service):
|
||||
plan = cls.objects.create(service=service)
|
||||
|
||||
for item in productplan.serviceplanitem_set:
|
||||
ServicePlanItem.objects.create(plan=plan, cycle=item.cycle, count=item.count, cost=item.cost)
|
||||
|
||||
class ServicePlanItem(Model):
|
||||
from core.models.products import ProductGroup, Product
|
||||
from core.models.billable import CycleChoices
|
||||
|
||||
plan = ForeignKey(ServicePlan, on_delete=CASCADE)
|
||||
client = ForeignKey(ClientProfile, on_delete=CASCADE)
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(null=True, blank=True)
|
||||
service_type = CharField(max_length=255, null=True, blank=True)
|
||||
product = ForeignKey(Product, on_delete=SET_NULL, null=True)
|
||||
product_groups = ManyToManyField(ProductGroup)
|
||||
currency = ForeignKey(Currency, on_delete=CASCADE)
|
||||
cycle = PositiveIntegerField(choices=CycleChoices.choices)
|
||||
count = PositiveIntegerField()
|
||||
cost = CostField()
|
||||
taxable=BooleanField()
|
||||
|
||||
@property
|
||||
def handler(self):
|
||||
if self.service_type and self.product:
|
||||
try:
|
||||
service_type = import_module(self.service_type)
|
||||
return service_type.ProductRouter(self.product, self)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Could not load product handler {self.service_type} for product {self.id}: {e}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_productplanitem(cls, productplanitem, client):
|
||||
plan = cls.objects.create(client=client,
|
||||
name=productplanitem.plan.product.name,
|
||||
description=productplanitem.plan.product.description,
|
||||
service_type=productplanitem.plan.product.product_type,
|
||||
product=productplanitem.plan.product,
|
||||
product_groups=productplanitem.plan.product.product_groups,
|
||||
currency=productplanitem.plan.currency,
|
||||
cycle=productplanitem.cycle,
|
||||
count=productplanitem.count,
|
||||
cost=productplanitem.cost,
|
||||
taxable=productplanitem.taxable)
|
||||
|
||||
@property
|
||||
def invoicable(self):
|
||||
pass
|
||||
|
|
Loading…
Reference in a new issue