TrafficPrepayBillingTest tests passing
This commit is contained in:
parent
0de534f4e2
commit
a3696b859a
|
@ -46,6 +46,7 @@ class BillsBackend(object):
|
||||||
def format_period(self, ini, end):
|
def format_period(self, ini, end):
|
||||||
ini = ini.strftime("%b, %Y")
|
ini = ini.strftime("%b, %Y")
|
||||||
end = (end-datetime.timedelta(seconds=1)).strftime("%b, %Y")
|
end = (end-datetime.timedelta(seconds=1)).strftime("%b, %Y")
|
||||||
|
# TODO if diff is less than a month: write the month only
|
||||||
if ini == end:
|
if ini == end:
|
||||||
return ini
|
return ini
|
||||||
return _("{ini} to {end}").format(ini=ini, end=end)
|
return _("{ini} to {end}").format(ini=ini, end=end)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
@ -167,8 +168,9 @@ class Order(models.Model):
|
||||||
metrics = self.metrics.filter(updated_on__lt=end, updated_on__gte=ini)
|
metrics = self.metrics.filter(updated_on__lt=end, updated_on__gte=ini)
|
||||||
elif len(args) == 1:
|
elif len(args) == 1:
|
||||||
date = args[0]
|
date = args[0]
|
||||||
metrics = self.metrics.filter(updated_on__year=date.year,
|
date = datetime.date(year=date.year, month=date.month, day=date.day)
|
||||||
updated_on__month=date.month, updated_on__day=date.day)
|
date += datetime.timedelta(days=1)
|
||||||
|
metrics = self.metrics.filter(updated_on__lt=date)
|
||||||
elif not args:
|
elif not args:
|
||||||
return self.metrics.latest('updated_on').value
|
return self.metrics.latest('updated_on').value
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -330,23 +330,24 @@ class TrafficBillingTest(BaseTrafficBillingTest):
|
||||||
|
|
||||||
|
|
||||||
class TrafficPrepayBillingTest(BaseTrafficBillingTest):
|
class TrafficPrepayBillingTest(BaseTrafficBillingTest):
|
||||||
METRIC = "max((account.resources.traffic.used or 0) - getattr(account.miscellaneous.filter(service__name='traffic prepay').last(), 'amount', 0), 0)"
|
METRIC = "max((account.resources.traffic.used or 0) - getattr(account.miscellaneous.filter(is_active=True, service__name='traffic prepay').last(), 'amount', 0), 0)"
|
||||||
|
|
||||||
def create_prepay_service(self):
|
def create_prepay_service(self):
|
||||||
service = Service.objects.create(
|
service = Service.objects.create(
|
||||||
description="Traffic prepay",
|
description="Traffic prepay",
|
||||||
content_type=ContentType.objects.get_for_model(Miscellaneous),
|
content_type=ContentType.objects.get_for_model(Miscellaneous),
|
||||||
match="miscellaneous.is_active and miscellaneous.service.name.lower() == 'traffic prepay'",
|
match="miscellaneous.is_active and miscellaneous.service.name.lower() == 'traffic prepay'",
|
||||||
billing_period=Service.ANUAL,
|
billing_period=Service.MONTHLY,
|
||||||
billing_point=Service.FIXED_DATE,
|
# make sure full months are always paid
|
||||||
|
billing_point=Service.ON_REGISTER,
|
||||||
is_fee=False,
|
is_fee=False,
|
||||||
metric="miscellaneous.amount",
|
metric="miscellaneous.amount",
|
||||||
pricing_period=Service.BILLING_PERIOD,
|
pricing_period=Service.NEVER,
|
||||||
rate_algorithm=Service.STEP_PRICE,
|
rate_algorithm=Service.STEP_PRICE,
|
||||||
on_cancel=Service.NOTHING, # TODO on_register == NOTHING or make on_cancel generic
|
on_cancel=Service.NOTHING,
|
||||||
payment_style=Service.PREPAY,
|
payment_style=Service.PREPAY,
|
||||||
tax=0,
|
tax=0,
|
||||||
nominal_price=5
|
nominal_price=50
|
||||||
)
|
)
|
||||||
return service
|
return service
|
||||||
|
|
||||||
|
@ -361,37 +362,43 @@ class TrafficPrepayBillingTest(BaseTrafficBillingTest):
|
||||||
service = self.create_traffic_service()
|
service = self.create_traffic_service()
|
||||||
prepay_service = self.create_prepay_service()
|
prepay_service = self.create_prepay_service()
|
||||||
account = self.create_account()
|
account = self.create_account()
|
||||||
|
|
||||||
self.create_traffic_resource()
|
self.create_traffic_resource()
|
||||||
prepay = self.create_prepay(10, account=account)
|
now = timezone.now()
|
||||||
self.report_traffic(account, timezone.now(), 10**9)
|
|
||||||
|
prepay = self.create_prepay(10, account=account)
|
||||||
|
bill = account.orders.bill(proforma=True)[0]
|
||||||
|
self.assertEqual(10*50, bill.get_total())
|
||||||
|
|
||||||
|
self.report_traffic(account, timezone.now(), 10**10)
|
||||||
|
with freeze_time(now+relativedelta(months=1)):
|
||||||
|
bill = account.orders.bill(proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(2*10*50 + 0*10, bill.get_total())
|
||||||
|
|
||||||
|
# TODO dateutils.relativedelta is buggy with fakedatetime
|
||||||
|
# TODO RuntimeWarning: DateTimeField MetricStorage.updated_on received a naive
|
||||||
|
self.report_traffic(account, timezone.now(), 10**10)
|
||||||
|
with freeze_time(now+relativedelta(months=1)):
|
||||||
|
bill = account.orders.bill(proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(2*10*50 + 0*10, bill.get_total())
|
||||||
|
|
||||||
|
self.report_traffic(account, timezone.now(), 10**10)
|
||||||
|
with freeze_time(now+relativedelta(months=1)):
|
||||||
|
bill = account.orders.bill(proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(2*10*50 + (30-10-10)*10, bill.get_total())
|
||||||
|
|
||||||
|
with freeze_time(now+relativedelta(months=2)):
|
||||||
|
self.report_traffic(account, timezone.now(), 10**11)
|
||||||
|
with freeze_time(now+relativedelta(months=1)):
|
||||||
|
bill = account.orders.bill(proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(2*10*50 + (30-10-10)*10, bill.get_total())
|
||||||
|
|
||||||
|
with freeze_time(now+relativedelta(months=3)):
|
||||||
|
bill = account.orders.bill(proforma=True, new_open=True)[0]
|
||||||
|
self.assertEqual(4*10*50 + (30-10-10)*10 + (100-10-10)*10, bill.get_total())
|
||||||
|
|
||||||
print prepay_service.orders.all()
|
|
||||||
# TODO metric on the current day! how to solve it consistently?
|
# TODO metric on the current day! how to solve it consistently?
|
||||||
# TODO prepay doesnt allow for discount
|
# TODO prepay doesnt allow for discount
|
||||||
|
|
||||||
# move into the past
|
|
||||||
# TODO with patch.object(timezone, 'now', return_value=now+relativedelta(years=1)):
|
|
||||||
delta = datetime.timedelta(days=60)
|
|
||||||
date = (timezone.now()-delta).date()
|
|
||||||
order = service.orders.get()
|
|
||||||
order.registered_on = date
|
|
||||||
order.save()
|
|
||||||
|
|
||||||
metric = order.metrics.latest()
|
|
||||||
metric.updated_on -= delta
|
|
||||||
metric.save()
|
|
||||||
|
|
||||||
bills = service.orders.bill(proforma=True)
|
|
||||||
self.assertEqual(0, bills[0].get_total())
|
|
||||||
|
|
||||||
self.report_traffic(account, date, 10**10*9)
|
|
||||||
metric = order.metrics.latest()
|
|
||||||
metric.updated_on -= delta
|
|
||||||
metric.save()
|
|
||||||
|
|
||||||
bills = service.orders.bill(proforma=True)
|
|
||||||
self.assertEqual((90-10-10)*10, bills[0].get_total())
|
|
||||||
|
|
||||||
|
|
||||||
class MailboxBillingTest(BaseBillingTest):
|
class MailboxBillingTest(BaseBillingTest):
|
||||||
|
@ -583,3 +590,6 @@ class PlanBillingTest(BaseBillingTest):
|
||||||
|
|
||||||
def test_plan(self):
|
def test_plan(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# TODO web disk size
|
||||||
|
|
|
@ -73,8 +73,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
day = 1
|
day = 1
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
bp = datetime.datetime(year=date.year, month=date.month, day=day,
|
bp = datetime.date(year=date.year, month=date.month, day=day)
|
||||||
tzinfo=timezone.get_current_timezone()).date()
|
|
||||||
elif self.billing_period == self.ANUAL:
|
elif self.billing_period == self.ANUAL:
|
||||||
if self.billing_point == self.ON_REGISTER:
|
if self.billing_point == self.ON_REGISTER:
|
||||||
month = order.registered_on.month
|
month = order.registered_on.month
|
||||||
|
@ -89,16 +88,24 @@ class ServiceHandler(plugins.Plugin):
|
||||||
year = bp.year - relativedelta.relativedelta(years=1)
|
year = bp.year - relativedelta.relativedelta(years=1)
|
||||||
if bp.month >= month:
|
if bp.month >= month:
|
||||||
year = bp.year + 1
|
year = bp.year + 1
|
||||||
bp = datetime.datetime(year=year, month=month, day=day,
|
bp = datetime.date(year=year, month=month, day=day)
|
||||||
tzinfo=timezone.get_current_timezone()).date()
|
|
||||||
elif self.billing_period == self.NEVER:
|
elif self.billing_period == self.NEVER:
|
||||||
bp = order.registered_on
|
bp = order.registered_on
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
if self.on_cancel != self.NOTHING and order.cancelled_on and order.cancelled_on < bp:
|
if self.on_cancel != self.NOTHING and order.cancelled_on and order.cancelled_on < bp:
|
||||||
return order.cancelled_on
|
bp = order.cancelled_on
|
||||||
return bp
|
return bp
|
||||||
|
|
||||||
|
# def aligned(self, date):
|
||||||
|
# if self.granularity == self.DAILY:
|
||||||
|
# return date
|
||||||
|
# elif self.granularity == self.MONTHLY:
|
||||||
|
# return datetime.date(year=date.year, month=date.month, day=1)
|
||||||
|
# elif self.granularity == self.ANUAL:
|
||||||
|
# return datetime.date(year=date.year, month=1, day=1)
|
||||||
|
# raise NotImplementedError
|
||||||
|
|
||||||
def get_price_size(self, ini, end):
|
def get_price_size(self, ini, end):
|
||||||
rdelta = relativedelta.relativedelta(end, ini)
|
rdelta = relativedelta.relativedelta(end, ini)
|
||||||
if self.billing_period == self.MONTHLY:
|
if self.billing_period == self.MONTHLY:
|
||||||
|
@ -126,11 +133,9 @@ class ServiceHandler(plugins.Plugin):
|
||||||
period = self.get_pricing_period()
|
period = self.get_pricing_period()
|
||||||
rdelta = self.get_pricing_rdelta()
|
rdelta = self.get_pricing_rdelta()
|
||||||
if period == self.MONTHLY:
|
if period == self.MONTHLY:
|
||||||
ini = datetime.datetime(year=ini.year, month=ini.month, day=day,
|
ini = datetime.date(year=ini.year, month=ini.month, day=day)
|
||||||
tzinfo=timezone.get_current_timezone()).date()
|
|
||||||
elif period == self.ANUAL:
|
elif period == self.ANUAL:
|
||||||
ini = datetime.datetime(year=ini.year, month=month, day=day,
|
ini = datetime.date(year=ini.year, month=month, day=day)
|
||||||
tzinfo=timezone.get_current_timezone()).date()
|
|
||||||
elif period == self.NEVER:
|
elif period == self.NEVER:
|
||||||
yield ini, end
|
yield ini, end
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
@ -246,12 +251,13 @@ class ServiceHandler(plugins.Plugin):
|
||||||
for order in porders:
|
for order in porders:
|
||||||
bu = getattr(order, 'new_billed_until', order.billed_until)
|
bu = getattr(order, 'new_billed_until', order.billed_until)
|
||||||
if bu:
|
if bu:
|
||||||
if order.registered_on > ini and order.registered_on <= end:
|
registered = order.registered_on
|
||||||
|
if registered > ini and registered <= end:
|
||||||
counter += 1
|
counter += 1
|
||||||
if order.registered_on != bu and bu > ini and bu <= end:
|
if registered != bu and bu > ini and bu <= end:
|
||||||
counter += 1
|
counter += 1
|
||||||
if order.billed_until and order.billed_until != bu:
|
if order.billed_until and order.billed_until != bu:
|
||||||
if order.registered_on != order.billed_until and order.billed_until > ini and order.billed_until <= end:
|
if registered != order.billed_until and order.billed_until > ini and order.billed_until <= end:
|
||||||
counter += 1
|
counter += 1
|
||||||
return counter
|
return counter
|
||||||
|
|
||||||
|
@ -331,6 +337,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
# In most cases:
|
# In most cases:
|
||||||
# ini >= registered_date, end < registered_date
|
# ini >= registered_date, end < registered_date
|
||||||
# boundary lookup and exclude cancelled and billed
|
# boundary lookup and exclude cancelled and billed
|
||||||
|
# TODO service.payment_style == self.POSTPAY no discounts no shit on_cancel
|
||||||
orders_ = []
|
orders_ = []
|
||||||
bp = None
|
bp = None
|
||||||
ini = datetime.date.max
|
ini = datetime.date.max
|
||||||
|
@ -339,7 +346,7 @@ class ServiceHandler(plugins.Plugin):
|
||||||
cini = order.registered_on
|
cini = order.registered_on
|
||||||
if order.billed_until:
|
if order.billed_until:
|
||||||
# exclude cancelled and billed
|
# exclude cancelled and billed
|
||||||
if self.on_cancel != self.REFOUND:
|
if self.on_cancel != self.REFUND:
|
||||||
if order.cancelled_on and order.billed_until > order.cancelled_on:
|
if order.cancelled_on and order.billed_until > order.cancelled_on:
|
||||||
continue
|
continue
|
||||||
cini = order.billed_until
|
cini = order.billed_until
|
||||||
|
@ -413,20 +420,22 @@ class ServiceHandler(plugins.Plugin):
|
||||||
order.new_billed_until = bp
|
order.new_billed_until = bp
|
||||||
if self.get_pricing_period() == self.NEVER:
|
if self.get_pricing_period() == self.NEVER:
|
||||||
# Changes (Mailbox disk-like)
|
# Changes (Mailbox disk-like)
|
||||||
for ini, end, metric in order.get_metric(ini, bp, changes=True):
|
for cini, cend, metric in order.get_metric(ini, bp, changes=True):
|
||||||
price = self.get_price(order, metric)
|
price = self.get_price(order, metric)
|
||||||
lines.append(self.generate_line(order, price, ini, end, metric=metric))
|
lines.append(self.generate_line(order, price, cini, cend, metric=metric))
|
||||||
else:
|
elif self.get_pricing_period() == self.billing_period:
|
||||||
# pricing_slots (Traffic-like)
|
# pricing_slots (Traffic-like)
|
||||||
for ini, end in self.get_pricing_slots(ini, bp):
|
for cini, cend in self.get_pricing_slots(ini, bp):
|
||||||
metric = order.get_metric(ini, end)
|
metric = order.get_metric(cini, cend)
|
||||||
price = self.get_price(order, metric)
|
price = self.get_price(order, metric)
|
||||||
lines.append(self.generate_line(order, price, ini, end, metric=metric))
|
lines.append(self.generate_line(order, price, cini, cend, metric=metric))
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
else:
|
else:
|
||||||
# One-time billing
|
# One-time billing
|
||||||
if order.billed_until:
|
if order.billed_until:
|
||||||
continue
|
continue
|
||||||
date = order.registered_on
|
date = timezone.now().date()
|
||||||
order.new_billed_until = date
|
order.new_billed_until = date
|
||||||
if self.get_pricing_period() == self.NEVER:
|
if self.get_pricing_period() == self.NEVER:
|
||||||
# get metric (Job-like)
|
# get metric (Job-like)
|
||||||
|
|
|
@ -77,6 +77,7 @@ autodiscover('handlers')
|
||||||
|
|
||||||
class Service(models.Model):
|
class Service(models.Model):
|
||||||
NEVER = ''
|
NEVER = ''
|
||||||
|
# DAILY = 'DAILY'
|
||||||
MONTHLY = 'MONTHLY'
|
MONTHLY = 'MONTHLY'
|
||||||
ANUAL = 'ANUAL'
|
ANUAL = 'ANUAL'
|
||||||
TEN_DAYS = 'TEN_DAYS'
|
TEN_DAYS = 'TEN_DAYS'
|
||||||
|
@ -90,7 +91,7 @@ class Service(models.Model):
|
||||||
NOTHING = 'NOTHING'
|
NOTHING = 'NOTHING'
|
||||||
DISCOUNT = 'DISCOUNT'
|
DISCOUNT = 'DISCOUNT'
|
||||||
COMPENSATE = 'COMPENSATE'
|
COMPENSATE = 'COMPENSATE'
|
||||||
REFOUND = 'REFOUND'
|
REFUND = 'REFUND'
|
||||||
PREPAY = 'PREPAY'
|
PREPAY = 'PREPAY'
|
||||||
POSTPAY = 'POSTPAY'
|
POSTPAY = 'POSTPAY'
|
||||||
STEP_PRICE = 'STEP_PRICE'
|
STEP_PRICE = 'STEP_PRICE'
|
||||||
|
@ -175,9 +176,27 @@ class Service(models.Model):
|
||||||
(NOTHING, _("Nothing")),
|
(NOTHING, _("Nothing")),
|
||||||
(DISCOUNT, _("Discount")),
|
(DISCOUNT, _("Discount")),
|
||||||
(COMPENSATE, _("Compensat")),
|
(COMPENSATE, _("Compensat")),
|
||||||
(REFOUND, _("Refound")),
|
(REFUND, _("Refund")),
|
||||||
),
|
),
|
||||||
default=DISCOUNT)
|
default=DISCOUNT)
|
||||||
|
# on_broken_period = models.CharField(_("on broken period", max_length=16,
|
||||||
|
# help_text=_("Defines the billing behaviour when periods are incomplete on register and on cancel"),
|
||||||
|
# choices=(
|
||||||
|
# (NOTHING, _("Nothing, period is atomic")),
|
||||||
|
# (DISCOUNT, _("Bill partially")),
|
||||||
|
# (COMPENSATE, _("Compensate on cancel")),
|
||||||
|
# (REFUND, _("Refund on cancel")),
|
||||||
|
# ),
|
||||||
|
# default=DISCOUNT)
|
||||||
|
# granularity = models.CharField(_("granularity"), max_length=16,
|
||||||
|
# help_text=_("Defines the minimum size a period can be broken into"),
|
||||||
|
# choices=(
|
||||||
|
# (DAILY, _("One day")),
|
||||||
|
# (MONTHLY, _("One month")),
|
||||||
|
# (ANUAL, _("One year")),
|
||||||
|
# ),
|
||||||
|
# default=DAILY,
|
||||||
|
# )
|
||||||
payment_style = models.CharField(_("payment style"), max_length=16,
|
payment_style = models.CharField(_("payment style"), max_length=16,
|
||||||
help_text=_("Designates whether this service should be paid after "
|
help_text=_("Designates whether this service should be paid after "
|
||||||
"consumtion (postpay/on demand) or prepaid"),
|
"consumtion (postpay/on demand) or prepaid"),
|
||||||
|
@ -194,14 +213,14 @@ class Service(models.Model):
|
||||||
# (ONE_MONTH, _("One month")),
|
# (ONE_MONTH, _("One month")),
|
||||||
# ),
|
# ),
|
||||||
# default=NEVER)
|
# default=NEVER)
|
||||||
# refound_period = models.CharField(_("refound period"), max_length=16,
|
# refund_period = models.CharField(_("refund period"), max_length=16,
|
||||||
# help_text=_("Period in which automatic refound will be performed on "
|
# help_text=_("Period in which automatic refund will be performed on "
|
||||||
# "service cancellation"),
|
# "service cancellation"),
|
||||||
# choices=(
|
# choices=(
|
||||||
# (NEVER, _("Never refound")),
|
# (NEVER, _("Never refund")),
|
||||||
# (TEN_DAYS, _("Ten days")),
|
# (TEN_DAYS, _("Ten days")),
|
||||||
# (ONE_MONTH, _("One month")),
|
# (ONE_MONTH, _("One month")),
|
||||||
# (ALWAYS, _("Always refound")),
|
# (ALWAYS, _("Always refund")),
|
||||||
# ),
|
# ),
|
||||||
# default=NEVER, blank=True)
|
# default=NEVER, blank=True)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue