From 8a17ee6de56bbc55c9479a5185b3850e5d980ddf Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sun, 21 Jun 2020 16:08:00 +0200 Subject: [PATCH] Include BillRecords in the admin --- uncloud_pay/admin.py | 8 ++- .../management/commands/bootstrap-user.py | 40 +++++++++++ .../migrations/0002_auto_20200621_1335.py | 33 +++++++++ uncloud_pay/models.py | 68 ++++++++++++++----- 4 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 uncloud_pay/management/commands/bootstrap-user.py create mode 100644 uncloud_pay/migrations/0002_auto_20200621_1335.py diff --git a/uncloud_pay/admin.py b/uncloud_pay/admin.py index 0a851b9..4135432 100644 --- a/uncloud_pay/admin.py +++ b/uncloud_pay/admin.py @@ -2,11 +2,13 @@ from django.contrib import admin from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress +class BillRecordInline(admin.TabularInline): + model = Bill.bill_records.through + class BillAdmin(admin.ModelAdmin): - pass - + inlines = [ BillRecordInline ] +; admin.site.register(Bill, BillAdmin) - admin.site.register(Order) admin.site.register(BillRecord) admin.site.register(BillingAddress) diff --git a/uncloud_pay/management/commands/bootstrap-user.py b/uncloud_pay/management/commands/bootstrap-user.py new file mode 100644 index 0000000..b6525bc --- /dev/null +++ b/uncloud_pay/management/commands/bootstrap-user.py @@ -0,0 +1,40 @@ +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +import datetime + +from uncloud_pay.models import * + +class Command(BaseCommand): + help = 'Bootstrap user (for testing)' + + def add_arguments(self, parser): + parser.add_argument('username', type=str) + + def handle(self, *args, **options): + user = get_user_model().objects.get(username=options['username']) + + addr = BillingAddress.objects.get_or_create( + owner=user, + active=True, + defaults={'organization': 'ungleich', + 'name': 'Nico Schottelius', + 'street': 'Hauptstrasse 14', + 'city': 'Luchsingen', + 'postal_code': '8775', + 'country': 'CH' } + ) + + + bills = Bill.objects.filter(owner=user) + + # not even one bill? create! + if bills: + bill = bills[0] + else: + bill = Bill.objects.create(owner=user) + + # find any order that is associated to this bill + orders = Order.objects.filter(owner=user) + ) + print(f"Addr: {addr}") + print(f"Bill: {bill}") diff --git a/uncloud_pay/migrations/0002_auto_20200621_1335.py b/uncloud_pay/migrations/0002_auto_20200621_1335.py new file mode 100644 index 0000000..c4a6e64 --- /dev/null +++ b/uncloud_pay/migrations/0002_auto_20200621_1335.py @@ -0,0 +1,33 @@ +# Generated by Django 3.0.6 on 2020-06-21 13:35 + +from django.db import migrations, models +import uncloud_pay.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='bill', + name='due_date', + field=models.DateField(default=uncloud_pay.models.default_payment_delay), + ), + migrations.AlterField( + model_name='bill', + name='ending_date', + field=models.DateTimeField(default=uncloud_pay.models.end_of_this_month), + ), + migrations.AlterField( + model_name='bill', + name='starting_date', + field=models.DateTimeField(default=uncloud_pay.models.start_of_this_month), + ), + migrations.AddConstraint( + model_name='bill', + constraint=models.UniqueConstraint(fields=('owner', 'starting_date', 'ending_date'), name='one_bill_per_month_per_user'), + ), + ] diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py index 9ad005b..0afd135 100644 --- a/uncloud_pay/models.py +++ b/uncloud_pay/models.py @@ -10,7 +10,7 @@ import logging from functools import reduce import itertools from math import ceil -from datetime import timedelta +import datetime from calendar import monthrange from decimal import Decimal @@ -23,11 +23,36 @@ from decimal import Decimal import decimal # Used to generate bill due dates. -BILL_PAYMENT_DELAY=timedelta(days=10) +BILL_PAYMENT_DELAY=datetime.timedelta(days=10) # Initialize logger. logger = logging.getLogger(__name__) +def start_of_month(a_day): + """ Returns first of the month of a given datetime object""" + return a_day.replace(day=1,hour=0,minute=0,second=0, microsecond=0) + +def end_of_month(a_day): + """ Returns first of the month of a given datetime object""" + + _, last_day = monthrange(a_day.year, a_day.month) + return a_day.replace(day=last_day,hour=23,minute=59,second=59, microsecond=0) + +def start_of_this_month(): + """ Returns first of this month""" + a_day = timezone.now() + return a_day.replace(day=1,hour=0,minute=0,second=0, microsecond=0) + +def end_of_this_month(): + """ Returns first of this month""" + a_day = timezone.now() + + _, last_day = monthrange(a_day.year, a_day.month) + return a_day.replace(day=last_day,hour=23,minute=59,second=59, microsecond=0) + +def default_payment_delay(): + return timezone.now() + BILL_PAYMENT_DELAY + # See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types class RecurringPeriod(models.IntegerChoices): """ @@ -390,34 +415,41 @@ class Order(models.Model): description=description) def __str__(self): - return "{} created at {}, {}->{}, recurring period {}. One time price {}, recurring price {}".format( - self.id, self.creation_date, - self.starting_date, self.ending_date, - self.recurring_period, - self.one_time_price, - self.recurring_price) + return f"Order {self.owner}-{self.id}" + + # return "{} created at {}, {}->{}, recurring period {}. One time price {}, recurring price {}".format( + # self.id, self.creation_date, + # self.starting_date, self.ending_date, + # self.recurring_period, + # self.one_time_price, + # self.recurring_price) class Bill(models.Model): - """ FIXME: - Bill needs to be unique in the triple (owner, year, month) - """ - 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() + # FIXME: this is a race condition, if ending_date is evaluated + # in the next month the bill spawns two months! + starting_date = models.DateTimeField(default=start_of_this_month) + ending_date = models.DateTimeField(default=end_of_this_month) + due_date = models.DateField(default=default_payment_delay) 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") + class Meta: + constraints = [ + models.UniqueConstraint(fields=['owner', + 'starting_date', + 'ending_date' ], + name='one_bill_per_month_per_user') + ] + # billing address and vat rate is the same for the whole bill # @property # def vat_rate(self): @@ -425,7 +457,7 @@ class Bill(models.Model): def __str__(self): - return f"uc-{self.id}" + return f"Bill {self.owner}-{self.id}" @classmethod def create_all_bills(cls): @@ -516,7 +548,7 @@ class BillRecord(models.Model): ending_date = models.DateTimeField() def __str__(self): - return f"{order.owner}" + return f"{self.bill}: {self.usage_count} x {self.order}" class OrderRecord(models.Model):