Compute VAT rate and amount on bill generation
This commit is contained in:
parent
3a03717b12
commit
b3afad5d5d
3 changed files with 54 additions and 38 deletions
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue