diff --git a/uncloud_django_based/uncloud/uncloud_pay/management/commands/charge-negative-balance.py b/uncloud_django_based/uncloud/uncloud_pay/management/commands/charge-negative-balance.py index 24d53bf..8ee8736 100644 --- a/uncloud_django_based/uncloud/uncloud_pay/management/commands/charge-negative-balance.py +++ b/uncloud_django_based/uncloud/uncloud_pay/management/commands/charge-negative-balance.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand 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 django.utils import timezone @@ -15,7 +15,7 @@ class Command(BaseCommand): users = User.objects.all() print("Processing {} users.".format(users.count())) for user in users: - balance = get_balance_for(user) + balance = get_balance_for_user(user) if balance < 0: print("User {} has negative balance ({}), charging.".format(user.username, balance)) payment_method = PaymentMethod.get_primary_for(user) diff --git a/uncloud_django_based/uncloud/uncloud_pay/models.py b/uncloud_django_based/uncloud/uncloud_pay/models.py index 59a149c..b26621c 100644 --- a/uncloud_django_based/uncloud/uncloud_pay/models.py +++ b/uncloud_django_based/uncloud/uncloud_pay/models.py @@ -92,19 +92,19 @@ class Payment(models.Model): default='unknown') 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. - #def save(self, *args, **kwargs): - # # TODO: only run activation logic on creation, not on update. - # 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) + # We override save() in order to active products awaiting payment. + def save(self, *args, **kwargs): + # _state.adding is switched to false after super(...) call. + being_created = self._state.adding - # newly_paid_bills = list( - # set(unpaid_bills_before_payment) - set(unpaid_bills_after_payment)) - # for bill in newly_paid_bills: - # bill.activate_orders() + 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( + set(unpaid_bills_before_payment) - set(unpaid_bills_after_payment)) + for bill in newly_paid_bills: + bill.activate_products() class PaymentMethod(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -201,6 +201,12 @@ class Bill(models.Model): 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 def reference(self): return "{}-{}".format( @@ -227,6 +233,15 @@ class Bill(models.Model): # A bill is final when its ending date is passed. 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 def generate_for(year, month, user): # /!\ 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. # * For yearly bill: if previous_bill.ending_date is on working # month, generate new bill. - unpaid_orders = { 'monthly_or_less': [], 'yearly': {}} + unpaid_orders = { 'monthly_or_less': [], 'yearly': {} } for order in orders: try: previous_bill = order.bill.latest('ending_date') @@ -276,7 +291,7 @@ class Bill(models.Model): else: unpaid_orders['yearly'][next_yearly_bill_start_on] = bucket + [order] 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) # Handle working month's billing. @@ -335,25 +350,24 @@ class Bill(models.Model): @staticmethod def get_unpaid_for(user): - balance = get_balance_for(user) + balance = get_balance_for_user(user) unpaid_bills = [] # No unpaid bill if balance is positive. if balance >= 0: - return [] + return unpaid_bills else: bills = Bill.objects.filter( owner=user, - due_date__lt=timezone.now() ).order_by('-creation_date') # Amount to be paid by the customer. unpaid_balance = abs(balance) for bill in bills: - if unpaid_balance < 0: + if unpaid_balance <= 0: break - unpaid_balance -= bill.amount - unpaid_bills.append(bill) + unpaid_balance -= bill.total + unpaid_bills.append(bill) return unpaid_bills @@ -464,6 +478,11 @@ class Order(models.Model): choices = RecurringPeriod.choices, 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 def records(self): return OrderRecord.objects.filter(order=self) @@ -531,11 +550,11 @@ class Product(UncloudModel): on_delete=models.CASCADE, editable=False) - description = "" + description = "Generic Product" status = models.CharField(max_length=32, choices=UncloudStatus.choices, - default=UncloudStatus.PENDING) + default=UncloudStatus.AWAITING_PAYMENT) order = models.ForeignKey(Order, on_delete=models.CASCADE, diff --git a/uncloud_django_based/uncloud/uncloud_vm/views.py b/uncloud_django_based/uncloud/uncloud_vm/views.py index 50e2e66..71ffe6d 100644 --- a/uncloud_django_based/uncloud/uncloud_vm/views.py +++ b/uncloud_django_based/uncloud/uncloud_vm/views.py @@ -116,7 +116,7 @@ class VMProductViewSet(ProductViewSet): order_recurring_period = serializer.validated_data.pop("recurring_period") # Create base order. - order = Order.objects.create( + order = Order( recurring_period=order_recurring_period, owner=request.user, starting_date=timezone.now()