Compute VAT rate and amount on bill generation

This commit is contained in:
fnux 2020-04-18 11:43:55 +02:00
parent 3a03717b12
commit b3afad5d5d
3 changed files with 54 additions and 38 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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(