From 059791e2f216c95b82bb115b84541520c702e688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Fri, 28 Feb 2020 08:59:32 +0100 Subject: [PATCH] Add initial generate-bills and charge-negative-balance uncloud-pay commands --- uncloud/uncloud_pay/helpers.py | 12 +++ .../commands/charge-negative-balance.py | 23 +++++ .../management/commands/generate-bills.py | 48 +++++++++++ .../migrations/0005_auto_20200228_0737.py | 42 ++++++++++ .../migrations/0006_auto_20200228_0741.py | 18 ++++ .../migrations/0007_remove_order_bill.py | 17 ++++ .../uncloud_pay/migrations/0008_order_bill.py | 18 ++++ uncloud/uncloud_pay/models.py | 16 ++-- uncloud/uncloud_pay/serializers.py | 9 +- uncloud/uncloud_vm/views.py.orig | 84 +++++++++++++++++++ 10 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 uncloud/uncloud_pay/helpers.py create mode 100644 uncloud/uncloud_pay/management/commands/charge-negative-balance.py create mode 100644 uncloud/uncloud_pay/management/commands/generate-bills.py create mode 100644 uncloud/uncloud_pay/migrations/0005_auto_20200228_0737.py create mode 100644 uncloud/uncloud_pay/migrations/0006_auto_20200228_0741.py create mode 100644 uncloud/uncloud_pay/migrations/0007_remove_order_bill.py create mode 100644 uncloud/uncloud_pay/migrations/0008_order_bill.py create mode 100644 uncloud/uncloud_vm/views.py.orig diff --git a/uncloud/uncloud_pay/helpers.py b/uncloud/uncloud_pay/helpers.py new file mode 100644 index 0000000..9dc39cd --- /dev/null +++ b/uncloud/uncloud_pay/helpers.py @@ -0,0 +1,12 @@ +from functools import reduce +from .models import Bill, Payment + +def sum_amounts(entries): + return reduce(lambda acc, entry: acc + entry.amount, entries, 0) + + +def get_balance_for(user): + bills = sum_amounts(Bill.objects.filter(owner=user)) + payments = sum_amounts(Payment.objects.filter(owner=user)) + return payments - bills + diff --git a/uncloud/uncloud_pay/management/commands/charge-negative-balance.py b/uncloud/uncloud_pay/management/commands/charge-negative-balance.py new file mode 100644 index 0000000..ae4c8dc --- /dev/null +++ b/uncloud/uncloud_pay/management/commands/charge-negative-balance.py @@ -0,0 +1,23 @@ +from django.core.management.base import BaseCommand +from uncloud_auth.models import User +from uncloud_pay.models import Order, Bill +from uncloud_pay.helpers import get_balance_for + +from datetime import timedelta +from django.utils import timezone + +class Command(BaseCommand): + help = 'Generate bills and charge customers if necessary.' + + def add_arguments(self, parser): + pass + + def handle(self, *args, **options): + users = User.objects.all() + print("Processing {} users.".format(users.count())) + for user in users: + balance = get_balance_for(user) + if balance < 0: + print("User {} has negative balance ({}), charging.".format(user.username, balance)) + # TODO: charge + print("=> Done.") diff --git a/uncloud/uncloud_pay/management/commands/generate-bills.py b/uncloud/uncloud_pay/management/commands/generate-bills.py new file mode 100644 index 0000000..92075ce --- /dev/null +++ b/uncloud/uncloud_pay/management/commands/generate-bills.py @@ -0,0 +1,48 @@ +from django.core.management.base import BaseCommand +from uncloud_auth.models import User +from uncloud_pay.models import Order, Bill + +from datetime import timedelta +from django.utils import timezone + +class Command(BaseCommand): + help = 'Generate bills and charge customers if necessary.' + + def add_arguments(self, parser): + pass + + # TODO: check for existing bills + def handle(self, *args, **options): + customers = User.objects.all() + print("Processing {} users.".format(customers.count())) + for customer in customers: + orders = Order.objects.filter(owner=customer) + + # Pay all non-billed usage untill now. + bill_starting_date = timezone.now() + bill_ending_date = timezone.now() + + billed_orders = [] + for order in orders: + print(order) + if True: # FIXME + billed_orders.append(order) + + # Update starting date if need be. + if order.starting_date < bill_starting_date: + bill_starting_date = order.starting_date + + if len(billed_orders) > 0: + bill = Bill(owner=customer, + starting_date=bill_starting_date, + ending_date=bill_starting_date, + due_date=timezone.now() + timedelta(days=10)) + bill.save() + + for order in billed_orders: + print(order) + order.bill.add(bill) + + print("Created bill {} for user {}".format(bill.uuid, customer.username)) + + print("=> Done.") diff --git a/uncloud/uncloud_pay/migrations/0005_auto_20200228_0737.py b/uncloud/uncloud_pay/migrations/0005_auto_20200228_0737.py new file mode 100644 index 0000000..c646724 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0005_auto_20200228_0737.py @@ -0,0 +1,42 @@ +# Generated by Django 3.0.3 on 2020-02-28 07:37 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0004_auto_20200227_1532'), + ] + + operations = [ + migrations.RemoveField( + model_name='bill', + name='id', + ), + migrations.RemoveField( + model_name='bill', + name='paid', + ), + migrations.AddField( + model_name='bill', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='bill', + name='creation_date', + field=models.DateTimeField(auto_now_add=True), + ), + migrations.AlterField( + model_name='order', + name='creation_date', + field=models.DateTimeField(auto_now_add=True), + ), + migrations.AlterField( + model_name='order', + name='starting_date', + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/uncloud/uncloud_pay/migrations/0006_auto_20200228_0741.py b/uncloud/uncloud_pay/migrations/0006_auto_20200228_0741.py new file mode 100644 index 0000000..ef03bda --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0006_auto_20200228_0741.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.3 on 2020-02-28 07:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0005_auto_20200228_0737'), + ] + + operations = [ + migrations.AlterField( + model_name='order', + name='bill', + field=models.ManyToManyField(blank=True, editable=False, to='uncloud_pay.Bill'), + ), + ] diff --git a/uncloud/uncloud_pay/migrations/0007_remove_order_bill.py b/uncloud/uncloud_pay/migrations/0007_remove_order_bill.py new file mode 100644 index 0000000..ea79416 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0007_remove_order_bill.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.3 on 2020-02-28 07:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0006_auto_20200228_0741'), + ] + + operations = [ + migrations.RemoveField( + model_name='order', + name='bill', + ), + ] diff --git a/uncloud/uncloud_pay/migrations/0008_order_bill.py b/uncloud/uncloud_pay/migrations/0008_order_bill.py new file mode 100644 index 0000000..315ac60 --- /dev/null +++ b/uncloud/uncloud_pay/migrations/0008_order_bill.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.3 on 2020-02-28 07:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0007_remove_order_bill'), + ] + + operations = [ + migrations.AddField( + model_name='order', + name='bill', + field=models.ManyToManyField(blank=True, editable=False, to='uncloud_pay.Bill'), + ), + ] diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py index 6077963..f3de8c4 100644 --- a/uncloud/uncloud_pay/models.py +++ b/uncloud/uncloud_pay/models.py @@ -19,21 +19,26 @@ class RecurringPeriod(models.TextChoices): PER_SECOND = 'SECOND', _('Per Second') class Bill(models.Model): + 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() + creation_date = models.DateTimeField(auto_now_add=True) starting_date = models.DateTimeField() ending_date = models.DateTimeField() due_date = models.DateField() - paid = models.BooleanField(default=False) valid = models.BooleanField(default=True) @property def amount(self): - # iterate over all related orders - return 20 + orders = Order.objects.filter(bill=self) + amount = 0 + for order in orders: + amount += order.recurring_price + + return amount + class Order(models.Model): @@ -49,8 +54,7 @@ class Order(models.Model): bill = models.ManyToManyField(Bill, editable=False, - blank=True, - null=True) + blank=True) recurring_price = models.FloatField(editable=False) diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py index 9449ee6..a4a1f1b 100644 --- a/uncloud/uncloud_pay/serializers.py +++ b/uncloud/uncloud_pay/serializers.py @@ -1,6 +1,7 @@ from django.contrib.auth import get_user_model from rest_framework import serializers from .models import * +from .helpers import get_balance_for from functools import reduce from uncloud_vm.serializers import VMProductSerializer @@ -10,7 +11,7 @@ class BillSerializer(serializers.ModelSerializer): class Meta: model = Bill fields = ['owner', 'amount', 'due_date', 'creation_date', - 'starting_date', 'ending_date', 'paid'] + 'starting_date', 'ending_date'] class PaymentSerializer(serializers.ModelSerializer): class Meta: @@ -41,8 +42,4 @@ class UserSerializer(serializers.ModelSerializer): return reduce(lambda acc, entry: acc + entry.amount, entries, 0) def get_balance(self, user): - bills = self.__sum_balance(Bill.objects.filter(owner=user)) - payments = self.__sum_balance(Payment.objects.filter(owner=user)) - balance = payments - bills - - return balance + return get_balance_for(user) diff --git a/uncloud/uncloud_vm/views.py.orig b/uncloud/uncloud_vm/views.py.orig new file mode 100644 index 0000000..a311320 --- /dev/null +++ b/uncloud/uncloud_vm/views.py.orig @@ -0,0 +1,84 @@ +from django.shortcuts import render + +from django.contrib.auth.models import User +from django.shortcuts import get_object_or_404 + +from rest_framework import viewsets, permissions +from rest_framework.response import Response + +<<<<<<< HEAD +from .models import VMHost, VMProduct, VMSnapshotProduct +from uncloud_pay.models import Order + +======= +from uncloud_pay.models import Order, RecurringPeriod + +from .models import VMHost, VMProduct +>>>>>>> Quickly wire vm creation to orders +from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer + +import datetime + +class VMHostViewSet(viewsets.ModelViewSet): + serializer_class = VMHostSerializer + queryset = VMHost.objects.all() + permission_classes = [permissions.IsAdminUser] + + +class VMProductViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = VMProductSerializer + + def get_queryset(self): + return VMProduct.objects.filter(owner=self.request.user) + + def create(self, request): + # Create base order. + order = Order.objects.create( + recurring_period=RecurringPeriod.PER_MONTH, + recurring_price=0, + one_time_price=0, + owner=request.user + ) + + # Create VM. + serializer = VMProductSerializer(data=request.data, context={'request': request}) + serializer.is_valid(raise_exception=True) + vm = serializer.save(owner=request.user, order=order) + + # FIXME: commit everything (VM + order) at once. + order.recurring_price = vm.recurring_price(order.recurring_period) + order.one_time_price = 0 + order.save() + + return Response(serializer.data) + + +class VMSnapshotProductViewSet(viewsets.ModelViewSet): + permission_classes = [permissions.IsAuthenticated] + serializer_class = VMSnapshotProductSerializer + + def get_queryset(self): + return VMSnapshotProduct.objects.filter(owner=self.request.user) + + def create(self, request): + serializer = VMSnapshotProductSerializer(data=request.data, context={'request': request}) + serializer.is_valid(raise_exception=True) + + # Create order + now = datetime.datetime.now() + order = Order(owner=request.user, + creation_date=now, + starting_date=now, + recurring_price=20, + one_time_price=0, + recurring_period="per_month") + order.save() + + # FIXME: calculate the gb_* values + serializer.save(owner=request.user, + order=order, + gb_ssd=12, + gb_hdd=20) + + return Response(serializer.data)