Start implementation of automatic invoice generation

This commit is contained in:
Kumi 2020-06-05 09:38:55 +02:00
parent 96cae766ed
commit 1bc81e7126
5 changed files with 70 additions and 31 deletions

21
core/helpers/billable.py Normal file
View 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

View file

@ -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

View file

@ -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,

View file

@ -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()

View file

@ -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