models: add months returning supporter had lapsed

A new public method, months_expired(), which is similar to the
existing status() in layout, but instead of returning whether the
supporter is new/active/lapsed/etc., it checks to see whether the
supporter is returning after having been lapsed, and if so, returns
the month range corresponding to how long they had been lapsed before
returning as a supporter in the current month.
This commit is contained in:
Denver Gingerich 2018-01-25 10:49:29 -05:00 committed by Brett Smith
parent 9350167d29
commit 0d3d9db4ed

View file

@ -75,6 +75,13 @@ class Supporter:
STATUS_LAPSED = 'Lapsed' STATUS_LAPSED = 'Lapsed'
STATUS_LOST = 'Lost' STATUS_LOST = 'Lost'
RETURNING_0MO = 'ReturningAfter0-3monthLapse'
RETURNING_3MO = 'ReturningAfter3-6monthLapse'
RETURNING_6MO = 'ReturningAfter6-9monthLapse'
RETURNING_9MO = 'ReturningAfter9-12monthLapse'
RETURNING_12MO = 'ReturningAfter>12monthLapse'
RETURNING_NOT = 'NotReturning'
LOST_THRESHOLD = datetime.timedelta(days=365) LOST_THRESHOLD = datetime.timedelta(days=365)
LAPSED_THRESHOLD = datetime.timedelta() LAPSED_THRESHOLD = datetime.timedelta()
@ -131,6 +138,11 @@ class Supporter:
self._supporter_type(payments)) self._supporter_type(payments))
lapse_date = _expose(_lapse_date) lapse_date = _expose(_lapse_date)
def _second_last_lapse_date(self, payments):
# TODO: find a way without listification - needed due to indexing
return self._calculate_lapse_date(list(payments)[-2].date,
self._supporter_type(payments))
def status(self, as_of_date=None): def status(self, as_of_date=None):
if as_of_date is None: if as_of_date is None:
as_of_date = Date.today() as_of_date = Date.today()
@ -148,3 +160,47 @@ class Supporter:
return self.STATUS_NEW return self.STATUS_NEW
else: else:
return self.STATUS_ACTIVE return self.STATUS_ACTIVE
def months_expired(self, as_of_date=None):
if as_of_date is None:
as_of_date = Date.today()
payments = self.payments(as_of_date)
payments_count = payments.count()
if payments_count == 0:
return None
lapse_date = self._lapse_date(payments)
days_past_due = as_of_date - lapse_date
if as_of_date.adjust_month(-1, 1) < payments.first().date <= as_of_date:
return self.RETURNING_NOT # started paying this month so not "returning"
elif as_of_date.adjust_month(-1, 1) < payments.last().date <= as_of_date:
# (there are at least 2 payments because first().date != last().date)
if payments.last().date <= self._second_last_lapse_date(payments):
# the most recent payment was this month, and it was before or on
# the lapse date for the last payment (i.e. it was "on-time") so
# this is a normal active subscriber, not a "re-"subscriber
return self.RETURNING_NOT
else:
# the most recent payment was this month, and it was after the lapse
# date for the last payment (so this is a "re-"subscriber)
# let's see how far past due the payment was, and return accordingly
last_past_due = (payments.last().date
- self._second_last_lapse_date(payments))
# TODO: use real month boundaries (approximating ok, but not great)
if last_past_due < datetime.timedelta(days=91):
return self.RETURNING_0MO
elif last_past_due < datetime.timedelta(days=183):
return self.RETURNING_3MO
elif last_past_due < datetime.timedelta(days=274):
return self.RETURNING_6MO
elif last_past_due < datetime.timedelta(days=365):
return self.RETURNING_9MO
else:
return self.RETURNING_12MO
else:
# supporter lapsed/lost or an annual supporter who paid 2-12 months ago
return self.RETURNING_NOT