Initial product activation implementation

This commit is contained in:
fnux 2020-03-10 09:14:52 +01:00
parent 839bd5f8a9
commit 5d5bf486b5
3 changed files with 43 additions and 24 deletions

View file

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from uncloud_auth.models import User from uncloud_auth.models import User
from uncloud_pay.models import Order, Bill, PaymentMethod, get_balance_for from uncloud_pay.models import Order, Bill, PaymentMethod, get_balance_for_user
from datetime import timedelta from datetime import timedelta
from django.utils import timezone from django.utils import timezone
@ -15,7 +15,7 @@ class Command(BaseCommand):
users = User.objects.all() users = User.objects.all()
print("Processing {} users.".format(users.count())) print("Processing {} users.".format(users.count()))
for user in users: for user in users:
balance = get_balance_for(user) balance = get_balance_for_user(user)
if balance < 0: if balance < 0:
print("User {} has negative balance ({}), charging.".format(user.username, balance)) print("User {} has negative balance ({}), charging.".format(user.username, balance))
payment_method = PaymentMethod.get_primary_for(user) payment_method = PaymentMethod.get_primary_for(user)

View file

@ -92,19 +92,19 @@ class Payment(models.Model):
default='unknown') default='unknown')
timestamp = models.DateTimeField(editable=False, auto_now_add=True) timestamp = models.DateTimeField(editable=False, auto_now_add=True)
# WIP prepaid and service activation logic by fnux. # We override save() in order to active products awaiting payment.
## We override save() in order to active products awaiting payment. def save(self, *args, **kwargs):
#def save(self, *args, **kwargs): # _state.adding is switched to false after super(...) call.
# # TODO: only run activation logic on creation, not on update. being_created = self._state.adding
# unpaid_bills_before_payment = Bill.get_unpaid_for(self.owner)
# super(Payment, self).save(*args, **kwargs) # Save payment in DB.
# unpaid_bills_after_payment = Bill.get_unpaid_for(self.owner)
# newly_paid_bills = list( unpaid_bills_before_payment = Bill.get_unpaid_for(self.owner)
# set(unpaid_bills_before_payment) - set(unpaid_bills_after_payment)) super(Payment, self).save(*args, **kwargs) # Save payment in DB.
# for bill in newly_paid_bills: unpaid_bills_after_payment = Bill.get_unpaid_for(self.owner)
# bill.activate_orders()
newly_paid_bills = list(
set(unpaid_bills_before_payment) - set(unpaid_bills_after_payment))
for bill in newly_paid_bills:
bill.activate_products()
class PaymentMethod(models.Model): class PaymentMethod(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
@ -201,6 +201,12 @@ class Bill(models.Model):
valid = models.BooleanField(default=True) valid = models.BooleanField(default=True)
# Trigger product activation if bill paid at creation (from balance).
def save(self, *args, **kwargs):
super(Bill, self).save(*args, **kwargs)
if not self in Bill.get_unpaid_for(self.owner):
self.activate_products()
@property @property
def reference(self): def reference(self):
return "{}-{}".format( return "{}-{}".format(
@ -227,6 +233,15 @@ class Bill(models.Model):
# A bill is final when its ending date is passed. # A bill is final when its ending date is passed.
return self.ending_date < timezone.now() return self.ending_date < timezone.now()
def activate_products(self):
for order in self.order_set.all():
# FIXME: using __something might not be a good idea.
for product_class in Product.__subclasses__():
for product in product_class.objects.filter(order=order):
if product.status == UncloudStatus.AWAITING_PAYMENT:
product.status = UncloudStatus.PENDING
product.save()
@staticmethod @staticmethod
def generate_for(year, month, user): def generate_for(year, month, user):
# /!\ We exclusively work on the specified year and month. # /!\ We exclusively work on the specified year and month.
@ -248,7 +263,7 @@ class Bill(models.Model):
# (next_bill) ending_date, a new bill has to be generated. # (next_bill) ending_date, a new bill has to be generated.
# * For yearly bill: if previous_bill.ending_date is on working # * For yearly bill: if previous_bill.ending_date is on working
# month, generate new bill. # month, generate new bill.
unpaid_orders = { 'monthly_or_less': [], 'yearly': {}} unpaid_orders = { 'monthly_or_less': [], 'yearly': {} }
for order in orders: for order in orders:
try: try:
previous_bill = order.bill.latest('ending_date') previous_bill = order.bill.latest('ending_date')
@ -276,7 +291,7 @@ class Bill(models.Model):
else: else:
unpaid_orders['yearly'][next_yearly_bill_start_on] = bucket + [order] unpaid_orders['yearly'][next_yearly_bill_start_on] = bucket + [order]
else: else:
if previous_bill == None or previous_bill.ending_date <= ending_date: if previous_bill == None or previous_bill.ending_date < ending_date:
unpaid_orders['monthly_or_less'].append(order) unpaid_orders['monthly_or_less'].append(order)
# Handle working month's billing. # Handle working month's billing.
@ -335,24 +350,23 @@ class Bill(models.Model):
@staticmethod @staticmethod
def get_unpaid_for(user): def get_unpaid_for(user):
balance = get_balance_for(user) balance = get_balance_for_user(user)
unpaid_bills = [] unpaid_bills = []
# No unpaid bill if balance is positive. # No unpaid bill if balance is positive.
if balance >= 0: if balance >= 0:
return [] return unpaid_bills
else: else:
bills = Bill.objects.filter( bills = Bill.objects.filter(
owner=user, owner=user,
due_date__lt=timezone.now()
).order_by('-creation_date') ).order_by('-creation_date')
# Amount to be paid by the customer. # Amount to be paid by the customer.
unpaid_balance = abs(balance) unpaid_balance = abs(balance)
for bill in bills: for bill in bills:
if unpaid_balance < 0: if unpaid_balance <= 0:
break break
unpaid_balance -= bill.amount unpaid_balance -= bill.total
unpaid_bills.append(bill) unpaid_bills.append(bill)
return unpaid_bills return unpaid_bills
@ -464,6 +478,11 @@ class Order(models.Model):
choices = RecurringPeriod.choices, choices = RecurringPeriod.choices,
default = RecurringPeriod.PER_MONTH) default = RecurringPeriod.PER_MONTH)
# Trigger initial bill generation at order creation.
def save(self, *args, **kwargs):
super(Order, self).save(*args, **kwargs)
Bill.generate_for(timezone.now().year, timezone.now().month, self.owner)
@property @property
def records(self): def records(self):
return OrderRecord.objects.filter(order=self) return OrderRecord.objects.filter(order=self)
@ -531,11 +550,11 @@ class Product(UncloudModel):
on_delete=models.CASCADE, on_delete=models.CASCADE,
editable=False) editable=False)
description = "" description = "Generic Product"
status = models.CharField(max_length=32, status = models.CharField(max_length=32,
choices=UncloudStatus.choices, choices=UncloudStatus.choices,
default=UncloudStatus.PENDING) default=UncloudStatus.AWAITING_PAYMENT)
order = models.ForeignKey(Order, order = models.ForeignKey(Order,
on_delete=models.CASCADE, on_delete=models.CASCADE,

View file

@ -116,7 +116,7 @@ class VMProductViewSet(ProductViewSet):
order_recurring_period = serializer.validated_data.pop("recurring_period") order_recurring_period = serializer.validated_data.pop("recurring_period")
# Create base order. # Create base order.
order = Order.objects.create( order = Order(
recurring_period=order_recurring_period, recurring_period=order_recurring_period,
owner=request.user, owner=request.user,
starting_date=timezone.now() starting_date=timezone.now()