TrafficPrepayBillingTest tests passing

This commit is contained in:
Marc 2014-09-25 16:28:47 +00:00
parent 0de534f4e2
commit a3696b859a
5 changed files with 102 additions and 61 deletions

View File

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

View File

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

View File

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

View File

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

View File

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