diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py index 82005c9..1d16eab 100644 --- a/uncloud_pay/models.py +++ b/uncloud_pay/models.py @@ -553,8 +553,14 @@ class Bill(models.Model): @property def final(self): - # A bill is final when its ending date is passed. - return self.ending_date < timezone.now() + # A bill is final when its ending date is passed, or when all of its + # orders have been terminated. + every_order_terminated = True + billing_period_is_over = self.ending_date < timezone.now() + for order in self.order_set.all(): + every_order_terminated = every_order_terminated and order.is_terminated + + return billing_period_is_over or every_order_terminated def activate_products(self): for order in self.order_set.all(): @@ -850,6 +856,14 @@ class Order(models.Model): on_delete=models.PROTECT, blank=True, null=True) + @property + def is_terminated(self): + return self.ending_date != None and self.ending_date < timezone.now() + + def terminate(self): + if not self.is_terminated: + self.ending_date = timezone.now() + self.save() # Trigger initial bill generation at order creation. def save(self, *args, **kwargs): diff --git a/uncloud_pay/views.py b/uncloud_pay/views.py index 55f1940..18b76e4 100644 --- a/uncloud_pay/views.py +++ b/uncloud_pay/views.py @@ -313,3 +313,15 @@ class AdminOrderViewSet(mixins.ListModelMixin, def get_queryset(self): return Order.objects.all() + + @action(detail=True, methods=['post']) + def terminate(self, request, pk): + order = self.get_object() + if order.is_terminated: + return Response( + {'error': 'Order is already terminated.'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + else: + order.terminate() + return Response({}, status=status.HTTP_200_OK)