Add utils and tests for calculating membership status

This commit is contained in:
Tanner Collin 2020-01-19 03:00:05 +00:00
parent f9c21b7001
commit 0fe999ca97
5 changed files with 442 additions and 5 deletions

View File

@ -36,6 +36,7 @@ class Member(models.Model):
current_start_date = models.DateField(default=date.today, null=True) current_start_date = models.DateField(default=date.today, null=True)
application_date = models.DateField(default=date.today, null=True) application_date = models.DateField(default=date.today, null=True)
vetted_date = models.DateField(blank=True, null=True) vetted_date = models.DateField(blank=True, null=True)
paused_date = models.DateField(blank=True, null=True)
monthly_fees = models.IntegerField(default=55, blank=True, null=True) monthly_fees = models.IntegerField(default=55, blank=True, null=True)
class Transaction(models.Model): class Transaction(models.Model):

View File

@ -1,3 +1,343 @@
from django.test import TestCase import django, sys, os
os.environ['DJANGO_SETTINGS_MODULE'] = 'apiserver.settings'
django.setup()
# Create your tests here. from django.test import TestCase
import datetime
from dateutil import relativedelta
from apiserver.api import utils, models
testing_member, _ = models.Member.objects.get_or_create(
first_name='unittest',
preferred_name='unittest',
last_name='tester',
)
class TestMonthsSpanned(TestCase):
def test_num_months_spanned_one_month(self):
date2 = datetime.date(2020, 1, 10)
date1 = datetime.date(2020, 2, 10)
spanned = utils.num_months_spanned(date1, date2)
self.assertEqual(spanned, 1)
def test_num_months_spanned_one_week(self):
date1 = datetime.date(2020, 2, 5)
date2 = datetime.date(2020, 1, 28)
spanned = utils.num_months_spanned(date1, date2)
self.assertEqual(spanned, 1)
def test_num_months_spanned_two_days(self):
date1 = datetime.date(2020, 2, 1)
date2 = datetime.date(2020, 1, 31)
spanned = utils.num_months_spanned(date1, date2)
self.assertEqual(spanned, 1)
def test_num_months_spanned_two_years(self):
date1 = datetime.date(2022, 1, 18)
date2 = datetime.date(2020, 1, 18)
spanned = utils.num_months_spanned(date1, date2)
self.assertEqual(spanned, 24)
def test_num_months_spanned_same_month(self):
date1 = datetime.date(2020, 1, 31)
date2 = datetime.date(2020, 1, 1)
spanned = utils.num_months_spanned(date1, date2)
self.assertEqual(spanned, 0)
class TestMonthsDifference(TestCase):
def test_num_months_difference_one_month(self):
date2 = datetime.date(2020, 1, 10)
date1 = datetime.date(2020, 2, 10)
difference = utils.num_months_difference(date1, date2)
self.assertEqual(difference, 1)
def test_num_months_difference_one_week(self):
date1 = datetime.date(2020, 2, 5)
date2 = datetime.date(2020, 1, 28)
difference = utils.num_months_difference(date1, date2)
self.assertEqual(difference, 0)
def test_num_months_difference_two_days(self):
date1 = datetime.date(2020, 2, 1)
date2 = datetime.date(2020, 1, 31)
difference = utils.num_months_difference(date1, date2)
self.assertEqual(difference, 0)
def test_num_months_difference_two_years(self):
date1 = datetime.date(2022, 1, 18)
date2 = datetime.date(2020, 1, 18)
difference = utils.num_months_difference(date1, date2)
self.assertEqual(difference, 24)
def test_num_months_difference_same_month(self):
date1 = datetime.date(2020, 1, 31)
date2 = datetime.date(2020, 1, 1)
difference = utils.num_months_difference(date1, date2)
self.assertEqual(difference, 0)
class TestAddMonths(TestCase):
def test_add_months_one_month(self):
date = datetime.date(2020, 1, 18)
num_months = 1
new_date = utils.add_months(date, num_months)
self.assertEqual(new_date, datetime.date(2020, 2, 18))
def test_add_months_february(self):
date = datetime.date(2020, 1, 31)
num_months = 1
new_date = utils.add_months(date, num_months)
self.assertEqual(new_date, datetime.date(2020, 2, 29))
def test_add_months_february_leap(self):
date = datetime.date(2020, 2, 29)
num_months = 12
new_date = utils.add_months(date, num_months)
self.assertEqual(new_date, datetime.date(2021, 2, 28))
def test_add_months_hundred_years(self):
date = datetime.date(2020, 1, 31)
num_months = 1200
new_date = utils.add_months(date, num_months)
self.assertEqual(new_date, datetime.date(2120, 1, 31))
class TestCalcStatus(TestCase):
def test_calc_member_status_14_days(self):
expire_date = datetime.date.today() + datetime.timedelta(days=14)
status = utils.calc_member_status(expire_date)
self.assertEqual(status, 'Current')
def test_calc_member_status_90_days(self):
expire_date = datetime.date.today() + datetime.timedelta(days=90)
status = utils.calc_member_status(expire_date)
self.assertEqual(status, 'Prepaid')
def test_calc_member_status_tomorrow(self):
expire_date = datetime.date.today() + datetime.timedelta(days=1)
status = utils.calc_member_status(expire_date)
self.assertEqual(status, 'Current')
def test_calc_member_status_today(self):
expire_date = datetime.date.today()
status = utils.calc_member_status(expire_date)
self.assertEqual(status, 'Current')
def test_calc_member_status_yesterday(self):
expire_date = datetime.date.today() - datetime.timedelta(days=1)
status = utils.calc_member_status(expire_date)
self.assertEqual(status, 'Due')
def test_calc_member_status_85_days_ago(self):
expire_date = datetime.date.today() - datetime.timedelta(days=85)
status = utils.calc_member_status(expire_date)
self.assertEqual(status, 'Overdue')
def test_calc_member_status_95_days_ago(self):
expire_date = datetime.date.today() - datetime.timedelta(days=95)
status = utils.calc_member_status(expire_date)
self.assertEqual(status, 'Former Member')
class TestFakeMonths(TestCase):
def test_fake_missing_membership_months_one_month(self):
testing_member.current_start_date = datetime.date(2018, 6, 6)
testing_member.expire_date = datetime.date(2018, 7, 6)
tx = utils.fake_missing_membership_months(testing_member)
self.assertEqual(tx.number_of_membership_months, 1)
def test_fake_missing_membership_months_one_and_half_month(self):
testing_member.current_start_date = datetime.date(2018, 6, 1)
testing_member.expire_date = datetime.date(2018, 7, 15)
tx = utils.fake_missing_membership_months(testing_member)
self.assertEqual(tx.number_of_membership_months, 1)
def test_fake_missing_membership_months_one_year(self):
testing_member.current_start_date = datetime.date(2018, 6, 6)
testing_member.expire_date = datetime.date(2019, 6, 6)
tx = utils.fake_missing_membership_months(testing_member)
self.assertEqual(tx.number_of_membership_months, 12)
def test_fake_missing_membership_months_same_month(self):
testing_member.current_start_date = datetime.date(2018, 6, 6)
testing_member.expire_date = datetime.date(2018, 6, 16)
tx = utils.fake_missing_membership_months(testing_member)
self.assertEqual(tx.number_of_membership_months, 0)
class TestTallyMembership(TestCase):
def get_member_clear_transactions(self):
member = testing_member
member.paused_date = None
member.expire_date = None
return member
def test_tally_membership_months_prepaid(self):
member = self.get_member_clear_transactions()
test_num_months = 8
start_date = datetime.date.today() - relativedelta.relativedelta(months=6, days=14)
end_date = start_date + relativedelta.relativedelta(months=test_num_months)
member.current_start_date = start_date
member.save()
for i in range(test_num_months):
models.Transaction.objects.create(
amount=0,
member_id=member.id,
number_of_membership_months=1,
)
result = utils.tally_membership_months(member)
self.assertEqual(member.expire_date, end_date)
self.assertEqual(member.status, 'Prepaid')
def test_tally_membership_months_current(self):
member = self.get_member_clear_transactions()
test_num_months = 7
start_date = datetime.date.today() - relativedelta.relativedelta(months=6, days=14)
end_date = start_date + relativedelta.relativedelta(months=test_num_months)
member.current_start_date = start_date
member.save()
for i in range(test_num_months):
models.Transaction.objects.create(
amount=0,
member_id=member.id,
number_of_membership_months=1,
)
result = utils.tally_membership_months(member)
self.assertEqual(member.expire_date, end_date)
self.assertEqual(member.status, 'Current')
def test_tally_membership_months_due(self):
member = self.get_member_clear_transactions()
test_num_months = 6
start_date = datetime.date.today() - relativedelta.relativedelta(months=6, days=14)
end_date = start_date + relativedelta.relativedelta(months=test_num_months)
member.current_start_date = start_date
member.save()
for i in range(test_num_months):
models.Transaction.objects.create(
amount=0,
member_id=member.id,
number_of_membership_months=1,
)
result = utils.tally_membership_months(member)
self.assertEqual(member.expire_date, end_date)
self.assertEqual(member.status, 'Due')
def test_tally_membership_months_overdue(self):
member = self.get_member_clear_transactions()
test_num_months = 5
start_date = datetime.date.today() - relativedelta.relativedelta(months=6, days=14)
end_date = start_date + relativedelta.relativedelta(months=test_num_months)
member.current_start_date = start_date
member.save()
for i in range(test_num_months):
models.Transaction.objects.create(
amount=0,
member_id=member.id,
number_of_membership_months=1,
)
result = utils.tally_membership_months(member)
self.assertEqual(member.expire_date, end_date)
self.assertEqual(member.status, 'Overdue')
def test_tally_membership_months_overdue(self):
member = self.get_member_clear_transactions()
test_num_months = 1
start_date = datetime.date.today() - relativedelta.relativedelta(months=6, days=14)
end_date = start_date + relativedelta.relativedelta(months=test_num_months)
member.current_start_date = start_date
member.save()
for i in range(test_num_months):
models.Transaction.objects.create(
amount=0,
member_id=member.id,
number_of_membership_months=1,
)
result = utils.tally_membership_months(member)
self.assertEqual(member.expire_date, end_date)
self.assertEqual(member.paused_date, end_date)
self.assertEqual(member.status, 'Former Member')
def test_tally_membership_months_dont_run(self):
member = self.get_member_clear_transactions()
start_date = datetime.date.today()
member.current_start_date = start_date
member.paused_date = start_date
member.save()
result = utils.tally_membership_months(member)
self.assertEqual(result, False)

View File

@ -0,0 +1,96 @@
import datetime
from dateutil import relativedelta
from django.db.models import Sum
from . import models, old_models
def num_months_spanned(d1, d2):
'''
Return number of months thresholds two dates span.
Order of arguments is same as subtraction
'''
return (d1.year - d2.year) * 12 + d1.month - d2.month
def num_months_difference(d1, d2):
'''
Return number of whole months between two dates.
Order of arguments is same as subtraction
'''
r = relativedelta.relativedelta(d1, d2)
return r.months + 12 * r.years
def calc_member_status(expire_date):
today = datetime.date.today()
difference = num_months_difference(expire_date, today)
if difference >= 1:
return 'Prepaid'
elif difference <= -3:
return 'Former Member'
elif difference <= -1:
return 'Overdue'
elif today <= expire_date:
return 'Current'
elif today > expire_date:
return 'Due'
else:
raise()
def add_months(date, num_months):
return date + relativedelta.relativedelta(months=num_months)
def fake_missing_membership_months(member):
'''
Return a transaction adding fake months on importing the member so the
length of their membership resolves to their imported expiry date
'''
start_date = member.current_start_date
expire_date = member.expire_date
missing_months = num_months_spanned(expire_date, start_date)
user = member.user if member.user else None
memo = '{} mth membership dues accounting old portal import, {} to {}'.format(
str(missing_months), start_date, expire_date
)
tx = models.Transaction.objects.create(
amount=0,
user=user,
memo=memo,
member_id=member.id,
reference_number='',
info_source='System',
payment_method='N/A',
category='Membership',
account_type='Clearing',
number_of_membership_months=missing_months,
)
return tx
def tally_membership_months(member):
'''
Sum together member's dues and calculate their new expire date and status
Doesn't work if member is paused.
'''
if member.paused_date: return False
start_date = member.current_start_date
txs = models.Transaction.objects.filter(member_id=member.id)
total_months_agg = txs.aggregate(Sum('number_of_membership_months'))
total_months = total_months_agg['number_of_membership_months__sum']
expire_date = add_months(start_date, total_months)
status = calc_member_status(expire_date)
member.expire_date = expire_date
member.status = status
if status == 'Former Member':
member.paused_date = expire_date
member.save()
return True

View File

@ -29,7 +29,7 @@ export function LoginForm(props) {
return ( return (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<Header size='medium'>Login to Spaceport</Header> <Header size='medium'>Log In to Spaceport</Header>
<Form.Input <Form.Input
label='Username' label='Username'
name='username' name='username'
@ -45,7 +45,7 @@ export function LoginForm(props) {
error={error.password} error={error.password}
/> />
<Form.Button loading={loading} error={error.non_field_errors}> <Form.Button loading={loading} error={error.non_field_errors}>
Login Log In
</Form.Button> </Form.Button>
</Form> </Form>
); );

View File

@ -32,7 +32,7 @@ export function TransactionEditor(props) {
]; ];
const sourceOptions = [ const sourceOptions = [
{ key: '0', text: 'Web', value: 'Web' }, { key: '0', text: 'Web (Spaceport)', value: 'Web' },
{ key: '1', text: 'Database Edit', value: 'DB Edit' }, { key: '1', text: 'Database Edit', value: 'DB Edit' },
{ key: '2', text: 'System', value: 'System' }, { key: '2', text: 'System', value: 'System' },
{ key: '3', text: 'Receipt or Statement', value: 'Receipt or Stmt' }, { key: '3', text: 'Receipt or Statement', value: 'Receipt or Stmt' },