From b3afad5d5d723efcaab5e5b7d25bf66ea967661b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Sat, 18 Apr 2020 11:43:55 +0200 Subject: [PATCH] Compute VAT rate and amount on bill generation --- .../uncloud/uncloud_pay/models.py | 67 +++++++++++-------- .../uncloud/uncloud_pay/serializers.py | 9 ++- .../uncloud/uncloud_pay/tests.py | 16 ++--- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/uncloud_django_based/uncloud/uncloud_pay/models.py b/uncloud_django_based/uncloud/uncloud_pay/models.py index f10f813..bcce598 100644 --- a/uncloud_django_based/uncloud/uncloud_pay/models.py +++ b/uncloud_django_based/uncloud/uncloud_pay/models.py @@ -469,6 +469,28 @@ class BillingAddress(models.Model): self.name, self.street, self.postal_code, self.city, self.country) +# Populated with the import-vat-numbers django command. +class VATRate(models.Model): + start_date = models.DateField(blank=True, null=True) + stop_date = models.DateField(blank=True, null=True) + territory_codes = models.TextField(blank=True, default='') + currency_code = models.CharField(max_length=10) + rate = models.FloatField() + rate_type = models.TextField(blank=True, default='') + description = models.TextField(blank=True, default='') + + @staticmethod + def get_for_country(country_code): + vat_rate = None + try: + vat_rate = VATRate.objects.get( + territory_codes=country_code, start_date__isnull=False, stop_date=None + ) + return vat_rate.rate + except VATRate.DoesNotExist as dne: + logger.debug(str(dne)) + logger.debug("Did not find VAT rate for %s, returning 0" % country_code) + return 0 class Bill(models.Model): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -506,9 +528,17 @@ class Bill(models.Model): return bill_records @property - def total(self): + def amount(self): return reduce(lambda acc, record: acc + record.amount, self.records, 0) + @property + def vat_amount(self): + return reduce(lambda acc, record: acc + record.vat_amount, self.records, 0) + + @property + def total(self): + return self.amount + self.vat_amount + @property def final(self): # A bill is final when its ending date is passed. @@ -752,37 +782,20 @@ class BillRecord(): format(record.recurring_period)) @property - def vat(self): - return 0 + def vat_rate(self): + return Decimal(VATRate.get_for_country(self.bill.billing_address.country)) + + @property + def vat_amount(self): + return self.amount * self.vat_rate @property def amount(self): return Decimal(float(self.recurring_price) * self.recurring_count) + self.one_time_price -# Populated with the import-vat-numbers django command. -class VATRate(models.Model): - start_date = models.DateField(blank=True, null=True) - stop_date = models.DateField(blank=True, null=True) - territory_codes = models.TextField(blank=True, default='') - currency_code = models.CharField(max_length=10) - rate = models.FloatField() - rate_type = models.TextField(blank=True, default='') - description = models.TextField(blank=True, default='') - - @staticmethod - def get_for_country(country_code): - vat_rate = None - try: - vat_rate = VATRates.objects.get( - territory_codes=country, start_date__isnull=False, stop_date=None - ) - logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate)) - return vat_rate.rate - except VATRates.DoesNotExist as dne: - logger.debug(str(dne)) - logger.debug("Did not find VAT rate for %s, returning 0" % country) - return 0 - + @property + def total(self): + return self.amount + self.vat_amount ### # Orders. diff --git a/uncloud_django_based/uncloud/uncloud_pay/serializers.py b/uncloud_django_based/uncloud/uncloud_pay/serializers.py index 1f6eb62..1b5db24 100644 --- a/uncloud_django_based/uncloud/uncloud_pay/serializers.py +++ b/uncloud_django_based/uncloud/uncloud_pay/serializers.py @@ -61,8 +61,10 @@ class BillRecordSerializer(serializers.Serializer): recurring_price = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS) recurring_period = serializers.ChoiceField(choices=RecurringPeriod.choices) recurring_count = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS) - vat = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS) + vat_rate = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS) + vat_amount = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS) amount = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS) + total = serializers.DecimalField(AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS) class BillingAddressSerializer(serializers.ModelSerializer): class Meta: @@ -75,8 +77,9 @@ class BillSerializer(serializers.ModelSerializer): class Meta: model = Bill - fields = ['reference', 'owner', 'total', 'due_date', 'creation_date', - 'starting_date', 'ending_date', 'records', 'final', 'billing_address'] + fields = ['reference', 'owner', 'amount', 'vat_amount', 'total', + 'due_date', 'creation_date', 'starting_date', 'ending_date', + 'records', 'final', 'billing_address'] # We do not want users to mutate the country / VAT number of an address, as it # will change VAT on existing bills. diff --git a/uncloud_django_based/uncloud/uncloud_pay/tests.py b/uncloud_django_based/uncloud/uncloud_pay/tests.py index 9b23c68..64f0442 100644 --- a/uncloud_django_based/uncloud/uncloud_pay/tests.py +++ b/uncloud_django_based/uncloud/uncloud_pay/tests.py @@ -37,18 +37,18 @@ class BillingTestCase(TestCase): # Generate & check bill for first month: full recurring_price + setup. first_month_bills = order.bills # Initial bill generated at order creation. self.assertEqual(len(first_month_bills), 1) - self.assertEqual(first_month_bills[0].total, one_time_price + recurring_price) + self.assertEqual(first_month_bills[0].amount, one_time_price + recurring_price) # Generate & check bill for second month: full recurring_price. second_month_bills = Bill.generate_for(2020, 4, self.user) self.assertEqual(len(second_month_bills), 1) - self.assertEqual(second_month_bills[0].total, recurring_price) + self.assertEqual(second_month_bills[0].amount, recurring_price) # Generate & check bill for third and last month: partial recurring_price. third_month_bills = Bill.generate_for(2020, 5, self.user) self.assertEqual(len(third_month_bills), 1) # 31 days in May. - self.assertEqual(float(third_month_bills[0].total), + self.assertEqual(float(third_month_bills[0].amount), round((7/31) * recurring_price, AMOUNT_DECIMALS)) # Check that running Bill.generate_for() twice does not create duplicates. @@ -76,7 +76,7 @@ class BillingTestCase(TestCase): date.fromisoformat('2020-03-31')) self.assertEqual(first_year_bills[0].ending_date.date(), date.fromisoformat('2021-03-30')) - self.assertEqual(first_year_bills[0].total, + self.assertEqual(first_year_bills[0].amount, recurring_price + one_time_price) # Generate & check bill for second year: recurring_price. @@ -86,7 +86,7 @@ class BillingTestCase(TestCase): date.fromisoformat('2021-03-31')) self.assertEqual(second_year_bills[0].ending_date.date(), date.fromisoformat('2022-03-30')) - self.assertEqual(second_year_bills[0].total, recurring_price) + self.assertEqual(second_year_bills[0].amount, recurring_price) # Check that running Bill.generate_for() twice does not create duplicates. self.assertEqual(len(Bill.generate_for(2020, 3, self.user)), 0) @@ -114,13 +114,13 @@ class BillingTestCase(TestCase): # Generate & check bill for first month: recurring_price + setup. first_month_bills = order.bills self.assertEqual(len(first_month_bills), 1) - self.assertEqual(float(first_month_bills[0].total), + self.assertEqual(float(first_month_bills[0].amount), round(16 * recurring_price, AMOUNT_DECIMALS) + one_time_price) # Generate & check bill for first month: recurring_price. second_month_bills = Bill.generate_for(2020, 4, self.user) self.assertEqual(len(second_month_bills), 1) - self.assertEqual(float(second_month_bills[0].total), + self.assertEqual(float(second_month_bills[0].amount), round(12 * recurring_price, AMOUNT_DECIMALS)) class ProductActivationTestCase(TestCase): @@ -159,7 +159,7 @@ class ProductActivationTestCase(TestCase): self.assertEqual(product.status, UncloudStatus.AWAITING_PAYMENT) # Pay initial bill, check that product is activated. - amount = product.order.bills[0].total + amount = product.order.bills[0].amount payment = Payment(owner=self.user, amount=amount) payment.save() self.assertEqual(