forked from uncloud/uncloud
Phase in admin, remove uuid from bills
This commit is contained in:
parent
662845128f
commit
8decfe1b16
6 changed files with 246 additions and 117 deletions
|
@ -86,4 +86,5 @@ urlpatterns = [
|
||||||
description="uncloud API",
|
description="uncloud API",
|
||||||
version="1.0.0"
|
version="1.0.0"
|
||||||
), name='openapi-schema'),
|
), name='openapi-schema'),
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress
|
||||||
|
|
||||||
|
class BillAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
admin.site.register(Bill, BillAdmin)
|
||||||
|
|
||||||
|
admin.site.register(Order)
|
||||||
|
admin.site.register(BillRecord)
|
||||||
|
admin.site.register(BillingAddress)
|
||||||
|
|
55
uncloud_pay/migrations/0017_auto_20200621_1110.py
Normal file
55
uncloud_pay/migrations/0017_auto_20200621_1110.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Generated by Django 3.0.6 on 2020-06-21 11:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_pay', '0016_auto_20200523_2138'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BillRecord',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('usage_count', models.IntegerField(default=1)),
|
||||||
|
('creation_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('starting_date', models.DateTimeField()),
|
||||||
|
('ending_date', models.DateTimeField()),
|
||||||
|
('bill', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Bill')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ordertimothee',
|
||||||
|
name='bill',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ordertimothee',
|
||||||
|
name='billing_address',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ordertimothee',
|
||||||
|
name='owner',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='order',
|
||||||
|
name='bill',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='order',
|
||||||
|
name='one_time_price',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='order',
|
||||||
|
name='recurring_price',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='BillNico',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='OrderTimothee',
|
||||||
|
),
|
||||||
|
]
|
25
uncloud_pay/migrations/0018_auto_20200621_1140.py
Normal file
25
uncloud_pay/migrations/0018_auto_20200621_1140.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.0.6 on 2020-06-21 11:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_pay', '0017_auto_20200621_1110'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='bill',
|
||||||
|
name='bill_records',
|
||||||
|
field=models.ManyToManyField(through='uncloud_pay.BillRecord', to='uncloud_pay.Order'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='billrecord',
|
||||||
|
name='order',
|
||||||
|
field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
23
uncloud_pay/migrations/0019_auto_20200621_1144.py
Normal file
23
uncloud_pay/migrations/0019_auto_20200621_1144.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.0.6 on 2020-06-21 11:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_pay', '0018_auto_20200621_1140'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='bill',
|
||||||
|
name='uuid',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='bill',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -223,11 +223,14 @@ class BillingAddress(models.Model):
|
||||||
return addresses[0]
|
return addresses[0]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}, {}, {} {}, {}".format(
|
return "{} - {}, {}, {} {}, {}".format(
|
||||||
self.name, self.street, self.postal_code, self.city,
|
self.owner,
|
||||||
self.country)
|
self.name, self.street, self.postal_code, self.city,
|
||||||
|
self.country)
|
||||||
|
|
||||||
|
###
|
||||||
|
# VAT
|
||||||
|
|
||||||
# Populated with the import-vat-numbers django command.
|
|
||||||
class VATRate(models.Model):
|
class VATRate(models.Model):
|
||||||
start_date = models.DateField(blank=True, null=True)
|
start_date = models.DateField(blank=True, null=True)
|
||||||
stop_date = models.DateField(blank=True, null=True)
|
stop_date = models.DateField(blank=True, null=True)
|
||||||
|
@ -250,115 +253,6 @@ class VATRate(models.Model):
|
||||||
logger.debug("Did not find VAT rate for %s, returning 0" % country_code)
|
logger.debug("Did not find VAT rate for %s, returning 0" % country_code)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
class BillRecord(models.Model):
|
|
||||||
"""
|
|
||||||
Entry of a bill, dynamically generated from an order.
|
|
||||||
"""
|
|
||||||
|
|
||||||
bill = models.ForeignKey(Bill, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
# How many times the order has been used in this record
|
|
||||||
usage_count = models.IntegerField(default=1)
|
|
||||||
|
|
||||||
# The timeframe the bill record is for can (and probably often will) differ
|
|
||||||
# from the bill time
|
|
||||||
creation_date = models.DateTimeField(auto_now_add=True)
|
|
||||||
starting_date = models.DateTimeField()
|
|
||||||
ending_date = models.DateTimeField()
|
|
||||||
|
|
||||||
class Bill(models.Model):
|
|
||||||
""" FIXME:
|
|
||||||
Bill needs to be unique in the triple (owner, year, month)
|
|
||||||
"""
|
|
||||||
|
|
||||||
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
owner = models.ForeignKey(get_user_model(),
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
creation_date = models.DateTimeField(auto_now_add=True)
|
|
||||||
starting_date = models.DateTimeField()
|
|
||||||
ending_date = models.DateTimeField()
|
|
||||||
due_date = models.DateField()
|
|
||||||
|
|
||||||
valid = models.BooleanField(default=True)
|
|
||||||
|
|
||||||
# billing address and vat rate is the same for the whole bill
|
|
||||||
# @property
|
|
||||||
# def vat_rate(self):
|
|
||||||
# return Decimal(VATRate.get_for_country(self.bill.billing_address.country))
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create_all_bills(cls):
|
|
||||||
for owner in get_user_model().objects.all():
|
|
||||||
# mintime = time of first order
|
|
||||||
# maxtime = time of last order
|
|
||||||
# iterate month based through it
|
|
||||||
|
|
||||||
cls.assign_orders_to_bill(owner, year, month)
|
|
||||||
pass
|
|
||||||
|
|
||||||
def assign_orders_to_bill(self, owner, year, month):
|
|
||||||
"""
|
|
||||||
Generate a bill for the specific month of a user.
|
|
||||||
|
|
||||||
First handle all one time orders
|
|
||||||
|
|
||||||
FIXME:
|
|
||||||
|
|
||||||
- limit this to active users in the future! (2020-05-23)
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
Find all one time orders that have a starting date that falls into this month
|
|
||||||
recurring_period=RecurringPeriod.ONE_TIME,
|
|
||||||
|
|
||||||
Can we do this even for recurring / all of them
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# FIXME: add something to check whether the order should be billed at all - i.e. a marker that
|
|
||||||
# disables searching -> optimization for later
|
|
||||||
# Create the initial bill record
|
|
||||||
# FIXME: maybe limit not even to starting/ending date, but to empty_bill record -- to be fixed in the future
|
|
||||||
# for order in Order.objects.filter(Q(starting_date__gte=self.starting_date),
|
|
||||||
# Q(starting_date__lte=self.ending_date),
|
|
||||||
|
|
||||||
# FIXME below: only check for active orders
|
|
||||||
|
|
||||||
# Ensure all orders of that owner have at least one bill record
|
|
||||||
for order in Order.objects.filter(owner=owner,
|
|
||||||
bill_records=None):
|
|
||||||
|
|
||||||
bill_record = BillRecord.objects.create(bill=self,
|
|
||||||
usage_count=1,
|
|
||||||
starting_date=order.starting_date,
|
|
||||||
ending_date=order.starting_date + timedelta(seconds=order.recurring_period))
|
|
||||||
|
|
||||||
|
|
||||||
# For each recurring order get the usage and bill it
|
|
||||||
for order in Order.objects.filter(~Q(recurring_period=RecurringPeriod.ONE_TIME),
|
|
||||||
Q(starting_date__lt=self.starting_date),
|
|
||||||
owner=owner):
|
|
||||||
|
|
||||||
if order.recurring_period > 0: # avoid div/0 - these are one time payments
|
|
||||||
|
|
||||||
# How much time will have passed by the end of the billing cycle
|
|
||||||
td = self.ending_date - order.starting_date
|
|
||||||
|
|
||||||
# How MANY times it will have been used by then
|
|
||||||
used_times = ceil(td / timedelta(seconds=order.recurring_period))
|
|
||||||
|
|
||||||
billed_times = len(order.bills)
|
|
||||||
|
|
||||||
# How many times it WAS billed -- can also be inferred from the bills that link to it!
|
|
||||||
if used_times > billed_times:
|
|
||||||
billing_times = used_times - billed_times
|
|
||||||
|
|
||||||
# ALSO REGISTER THE TIME PERIOD!
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Orders.
|
# Orders.
|
||||||
|
|
||||||
|
@ -382,9 +276,9 @@ class Order(models.Model):
|
||||||
ending_date = models.DateTimeField(blank=True,
|
ending_date = models.DateTimeField(blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
|
|
||||||
bill_records = models.ManyToManyField(BillRecord,
|
# bill_records = models.ManyToManyField(BillRecord,
|
||||||
editable=False,
|
# editable=False,
|
||||||
blank=True)
|
# blank=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def count_billed(self):
|
def count_billed(self):
|
||||||
|
@ -512,6 +406,127 @@ class Order(models.Model):
|
||||||
self.recurring_price)
|
self.recurring_price)
|
||||||
|
|
||||||
|
|
||||||
|
class Bill(models.Model):
|
||||||
|
""" FIXME:
|
||||||
|
Bill needs to be unique in the triple (owner, year, month)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
owner = models.ForeignKey(get_user_model(),
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
creation_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
starting_date = models.DateTimeField()
|
||||||
|
ending_date = models.DateTimeField()
|
||||||
|
due_date = models.DateField()
|
||||||
|
|
||||||
|
valid = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
# Mapping to BillRecords
|
||||||
|
# https://stackoverflow.com/questions/4443190/djangos-manytomany-relationship-with-additional-fields
|
||||||
|
|
||||||
|
bill_records = models.ManyToManyField(Order, through="BillRecord")
|
||||||
|
|
||||||
|
# billing address and vat rate is the same for the whole bill
|
||||||
|
# @property
|
||||||
|
# def vat_rate(self):
|
||||||
|
# return Decimal(VATRate.get_for_country(self.bill.billing_address.country))
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"uc-{self.id}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_all_bills(cls):
|
||||||
|
for owner in get_user_model().objects.all():
|
||||||
|
# mintime = time of first order
|
||||||
|
# maxtime = time of last order
|
||||||
|
# iterate month based through it
|
||||||
|
|
||||||
|
cls.assign_orders_to_bill(owner, year, month)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def assign_orders_to_bill(self, owner, year, month):
|
||||||
|
"""
|
||||||
|
Generate a bill for the specific month of a user.
|
||||||
|
|
||||||
|
First handle all one time orders
|
||||||
|
|
||||||
|
FIXME:
|
||||||
|
|
||||||
|
- limit this to active users in the future! (2020-05-23)
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
Find all one time orders that have a starting date that falls into this month
|
||||||
|
recurring_period=RecurringPeriod.ONE_TIME,
|
||||||
|
|
||||||
|
Can we do this even for recurring / all of them
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# FIXME: add something to check whether the order should be billed at all - i.e. a marker that
|
||||||
|
# disables searching -> optimization for later
|
||||||
|
# Create the initial bill record
|
||||||
|
# FIXME: maybe limit not even to starting/ending date, but to empty_bill record -- to be fixed in the future
|
||||||
|
# for order in Order.objects.filter(Q(starting_date__gte=self.starting_date),
|
||||||
|
# Q(starting_date__lte=self.ending_date),
|
||||||
|
|
||||||
|
# FIXME below: only check for active orders
|
||||||
|
|
||||||
|
# Ensure all orders of that owner have at least one bill record
|
||||||
|
for order in Order.objects.filter(owner=owner,
|
||||||
|
bill_records=None):
|
||||||
|
|
||||||
|
bill_record = BillRecord.objects.create(bill=self,
|
||||||
|
usage_count=1,
|
||||||
|
starting_date=order.starting_date,
|
||||||
|
ending_date=order.starting_date + timedelta(seconds=order.recurring_period))
|
||||||
|
|
||||||
|
|
||||||
|
# For each recurring order get the usage and bill it
|
||||||
|
for order in Order.objects.filter(~Q(recurring_period=RecurringPeriod.ONE_TIME),
|
||||||
|
Q(starting_date__lt=self.starting_date),
|
||||||
|
owner=owner):
|
||||||
|
|
||||||
|
if order.recurring_period > 0: # avoid div/0 - these are one time payments
|
||||||
|
|
||||||
|
# How much time will have passed by the end of the billing cycle
|
||||||
|
td = self.ending_date - order.starting_date
|
||||||
|
|
||||||
|
# How MANY times it will have been used by then
|
||||||
|
used_times = ceil(td / timedelta(seconds=order.recurring_period))
|
||||||
|
|
||||||
|
billed_times = len(order.bills)
|
||||||
|
|
||||||
|
# How many times it WAS billed -- can also be inferred from the bills that link to it!
|
||||||
|
if used_times > billed_times:
|
||||||
|
billing_times = used_times - billed_times
|
||||||
|
|
||||||
|
# ALSO REGISTER THE TIME PERIOD!
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BillRecord(models.Model):
|
||||||
|
"""
|
||||||
|
Entry of a bill, dynamically generated from an order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bill = models.ForeignKey(Bill, on_delete=models.CASCADE)
|
||||||
|
order = models.ForeignKey(Order, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
# How many times the order has been used in this record
|
||||||
|
usage_count = models.IntegerField(default=1)
|
||||||
|
|
||||||
|
# The timeframe the bill record is for can (and probably often will) differ
|
||||||
|
# from the bill time
|
||||||
|
creation_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
starting_date = models.DateTimeField()
|
||||||
|
ending_date = models.DateTimeField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{order.owner}"
|
||||||
|
|
||||||
|
|
||||||
class OrderRecord(models.Model):
|
class OrderRecord(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -523,6 +538,7 @@ class OrderRecord(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
order = models.ForeignKey(Order, on_delete=models.CASCADE)
|
order = models.ForeignKey(Order, on_delete=models.CASCADE)
|
||||||
|
|
||||||
one_time_price = models.DecimalField(default=0.0,
|
one_time_price = models.DecimalField(default=0.0,
|
||||||
max_digits=AMOUNT_MAX_DIGITS,
|
max_digits=AMOUNT_MAX_DIGITS,
|
||||||
decimal_places=AMOUNT_DECIMALS,
|
decimal_places=AMOUNT_DECIMALS,
|
||||||
|
|
Loading…
Reference in a new issue