forked from uncloud/uncloud
Initial product activation implementation
This commit is contained in:
parent
839bd5f8a9
commit
5d5bf486b5
3 changed files with 43 additions and 24 deletions
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue