diff --git a/uncloud_pay/admin.py b/uncloud_pay/admin.py index 464ff1b..9c1b809 100644 --- a/uncloud_pay/admin.py +++ b/uncloud_pay/admin.py @@ -27,8 +27,6 @@ class BillRecordInline(admin.TabularInline): class BillAdmin(admin.ModelAdmin): inlines = [ BillRecordInline ] -# change_list_template = "uncloud_pay/change_list.html" - def get_urls(self): """ Create URLs for PDF view @@ -43,15 +41,6 @@ class BillAdmin(admin.ModelAdmin): return url_patterns - # def changelist_view(self, request, extra_context=None): - # extra_context = extra_context or {} - - # print("view exec") - # return super().changelist_view( - # request, extra_context=extra_context, - # ) - - def my_view(self, request, object_id): bill = self.get_object(request, object_id=object_id) print(bill) @@ -61,7 +50,7 @@ class BillAdmin(admin.ModelAdmin): output_file = NamedTemporaryFile() bill_html = render_to_string("bill.html.j2", {'bill': bill, - 'bill_records': bill.bill_records.all() + 'bill_records': bill.billrecord_set.all() }) bytestring_to_pdf(bill_html.encode('utf-8'), output_file) diff --git a/uncloud_pay/management/commands/add-opennebula-vm-orders.py b/uncloud_pay/management/commands/add-opennebula-vm-orders.py index 8febc21..658404a 100644 --- a/uncloud_pay/management/commands/add-opennebula-vm-orders.py +++ b/uncloud_pay/management/commands/add-opennebula-vm-orders.py @@ -5,8 +5,8 @@ from django.utils import timezone from datetime import datetime, timedelta from uncloud_pay.models import * -#import opennebula.models as one from uncloud_vm.models import * + import sys def vm_price_2020(cpu=1, ram=2, v6only=False): @@ -35,6 +35,7 @@ class Command(BaseCommand): def handle(self, *args, **options): user = get_user_model().objects.get(username=options['username']) + addr, created = BillingAddress.objects.get_or_create( owner=user, active=True, @@ -72,6 +73,8 @@ class Command(BaseCommand): vm25206.save() vm25206.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,4,17))) + Bill.create_next_bill_for_user(user) + sys.exit(0) diff --git a/uncloud_pay/migrations/0009_auto_20200808_2113.py b/uncloud_pay/migrations/0009_auto_20200808_2113.py new file mode 100644 index 0000000..e5090ef --- /dev/null +++ b/uncloud_pay/migrations/0009_auto_20200808_2113.py @@ -0,0 +1,22 @@ +# Generated by Django 3.1 on 2020-08-08 21:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0008_delete_orderrecord'), + ] + + operations = [ + migrations.RemoveField( + model_name='bill', + name='valid', + ), + migrations.AddField( + model_name='bill', + name='is_final', + field=models.BooleanField(default=False), + ), + ] diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py index 57a3d07..6d35b17 100644 --- a/uncloud_pay/models.py +++ b/uncloud_pay/models.py @@ -382,9 +382,7 @@ class Bill(models.Model): starting_date = models.DateTimeField(default=start_of_this_month) ending_date = models.DateTimeField() due_date = models.DateField(default=default_payment_delay) - - # what is valid for? should this be "final"? - valid = models.BooleanField(default=True) + is_final = models.BooleanField(default=False) class Meta: constraints = [ @@ -409,29 +407,36 @@ class Bill(models.Model): all_orders = Order.objects.filter(owner=owner).order_by('id') first_order = all_orders.first() + bill = None + ending_date = None - # Calculate the start date + # Get date & bill from previous bill if last_bill: - # TODO: check that last bill is finished/closed, if not continue using it - starting_date = last_bill.end_date + datetime.timedelta(seconds=1) + if not last_bill.is_final: + bill = last_bill + starting_date = last_bill.starting_date + ending_date = bill.ending_date + else: + starting_date = last_bill.end_date + datetime.timedelta(seconds=1) else: if first_order: starting_date = first_order.starting_date else: starting_date = timezone.now() - ending_date = end_of_month(starting_date) - # FIXME above: maybe even use different date / active / open bill - bill, created = cls.objects.get_or_create( - owner=owner, - starting_date=starting_date, - ending_date=ending_date) + if not ending_date: + ending_date = end_of_month(starting_date) + + # create new bill, if previous is closed/does not exist + if not bill: + + bill = cls.objects.create( + owner=owner, + starting_date=starting_date, + ending_date=ending_date) for order in all_orders: - # check if order needs to be billed - # check if order has previous billing record - if order.is_one_time: if order.billrecord_set.count() == 0: br = BillRecord.objects.create(bill=bill, @@ -447,6 +452,10 @@ class Bill(models.Model): starting_date=starting_date, ending_date=ending_date) + # Filtering ideas: + # If order is replaced, it should not be added anymore if it has been billed "last time" + # If order has ended and finally charged, do not charge anymore + return bill @classmethod @@ -546,7 +555,7 @@ class BillRecord(models.Model): @property def sum(self): - return self.order.price * self.quantity + return self.order.price * Decimal(self.quantity) def __str__(self): return f"{self.bill}: {self.quantity} x {self.order}" @@ -575,34 +584,54 @@ class Product(UncloudModel): # Default period for all products default_recurring_period = RecurringPeriod.PER_30D - def create_order_at(self, when_to_start, *args, **kwargs): + def create_order_at(self, when_to_start=None, recurring_period=None): billing_address = BillingAddress.get_address_for(self.owner) - order = Order.objects.create(owner=self.owner, - billing_address=billing_address, - starting_date=when_to_start, - one_time_price=self.one_time_price, - recurring_period=self.default_recurring_period, - recurring_price=self.recurring_price, - description=str(self)) + if not billing_address: + raise ValidationError("Cannot order without a billing address") - def create_or_update_order(self, when_to_start=None): + if not when_to_start: + when_to_start = timezone.now() + + if not recurring_period: + recurring_period = self.default_recurring_period + + one_time_order = None + + if self.one_time_price > 0: + + one_time_order = Order.objects.create(owner=self.owner, + billing_address=billing_address, + starting_date=when_to_start, + price=self.one_time_price, + recurring_period=RecurringPeriod.ONE_TIME, + description=str(self)) + + + if recurring_period != RecurringPeriod.ONE_TIME: + if one_time_order: + recurring_order = Order.objects.create(owner=self.owner, + billing_address=billing_address, + starting_date=when_to_start, + price=self.recurring_price, + recurring_period=recurring_period, + depends_on=one_time_order, + description=str(self)) + else: + recurring_order = Order.objects.create(owner=self.owner, + billing_address=billing_address, + starting_date=when_to_start, + price=self.recurring_price, + recurring_period=recurring_period, + description=str(self)) + + + def create_or_update_order(self, when_to_start=None, recurring_period=None): if not when_to_start: when_to_start = timezone.now() if not self.order: - billing_address = BillingAddress.get_address_for(self.owner) - - if not billing_address: - raise ValidationError("Cannot order without a billing address") - - self.order = Order.objects.create(owner=self.owner, - billing_address=billing_address, - starting_date=when_to_start, - one_time_price=self.one_time_price, - recurring_period=self.default_recurring_period, - recurring_price=self.recurring_price, - description=str(self)) + self.create_order_at(when_to_start, recurring_period) else: previous_order = self.order @@ -611,9 +640,8 @@ class Product(UncloudModel): new_order = Order.objects.create(owner=self.owner, billing_address=self.order.billing_address, starting_date=when_to_start, - one_time_price=self.one_time_price, - recurring_period=self.default_recurring_period, - recurring_price=self.recurring_price, + price=self.recurring_price, + recurring_period=recurring_period, description=str(self), replaces=self.order) @@ -641,6 +669,14 @@ class Product(UncloudModel): """ return 0 + + @property + def is_recurring(self): + return self.recurring_price > 0 + + # on is_one_time as this should be has_one_time which is the same as > 0 again... + + @property def billing_address(self): return self.order.billing_address diff --git a/uncloud_pay/templates/bill.html.j2 b/uncloud_pay/templates/bill.html.j2 index 4f186c2..10ae1e2 100644 --- a/uncloud_pay/templates/bill.html.j2 +++ b/uncloud_pay/templates/bill.html.j2 @@ -673,7 +673,7 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7
{% if bill.billing_address.organization != "" %} - {{ bill.billing_address.organization }} + ORG{{ bill.billing_address.organization }}
{{ bill.billing_address.name }} {% else %} {{ bill.billing_address.name }} @@ -709,19 +709,24 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7 - - - + + {% for record in bill_records %} - - + + + {% endfor %} @@ -733,13 +738,13 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7

VAT - {{ bill.vat_amount }} + {{ bill.vat_amount|floatformat:2 }}

- Gesamtbetrag - {{ bill.total }} + Total + {{ bill.sum|floatformat:2 }}

Beschreibung DetailAmountVATQuantityPrice/Unit Total
A{{ record.quantity }} + {{ record.starting_date|date:"c" }} + {% if record.ending_date %} + - {{ record.ending_date|date:"c" }} + {% endif %} + {{ record.order.description }} {{ record.quantity|floatformat:2 }}{{ record.order.price|floatformat:2 }}{{ record.sum|floatformat:2 }}