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):
|
||||
ini = ini.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:
|
||||
return ini
|
||||
return _("{ini} to {end}").format(ini=ini, end=end)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import datetime
|
||||
import decimal
|
||||
import logging
|
||||
import sys
|
||||
|
@ -167,8 +168,9 @@ class Order(models.Model):
|
|||
metrics = self.metrics.filter(updated_on__lt=end, updated_on__gte=ini)
|
||||
elif len(args) == 1:
|
||||
date = args[0]
|
||||
metrics = self.metrics.filter(updated_on__year=date.year,
|
||||
updated_on__month=date.month, updated_on__day=date.day)
|
||||
date = datetime.date(year=date.year, month=date.month, day=date.day)
|
||||
date += datetime.timedelta(days=1)
|
||||
metrics = self.metrics.filter(updated_on__lt=date)
|
||||
elif not args:
|
||||
return self.metrics.latest('updated_on').value
|
||||
else:
|
||||
|
|
|
@ -330,23 +330,24 @@ class TrafficBillingTest(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):
|
||||
service = Service.objects.create(
|
||||
description="Traffic prepay",
|
||||
content_type=ContentType.objects.get_for_model(Miscellaneous),
|
||||
match="miscellaneous.is_active and miscellaneous.service.name.lower() == 'traffic prepay'",
|
||||
billing_period=Service.ANUAL,
|
||||
billing_point=Service.FIXED_DATE,
|
||||
billing_period=Service.MONTHLY,
|
||||
# make sure full months are always paid
|
||||
billing_point=Service.ON_REGISTER,
|
||||
is_fee=False,
|
||||
metric="miscellaneous.amount",
|
||||
pricing_period=Service.BILLING_PERIOD,
|
||||
pricing_period=Service.NEVER,
|
||||
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,
|
||||
tax=0,
|
||||
nominal_price=5
|
||||
nominal_price=50
|
||||
)
|
||||
return service
|
||||
|
||||
|
@ -361,37 +362,43 @@ class TrafficPrepayBillingTest(BaseTrafficBillingTest):
|
|||
service = self.create_traffic_service()
|
||||
prepay_service = self.create_prepay_service()
|
||||
account = self.create_account()
|
||||
|
||||
self.create_traffic_resource()
|
||||
prepay = self.create_prepay(10, account=account)
|
||||
self.report_traffic(account, timezone.now(), 10**9)
|
||||
now = timezone.now()
|
||||
|
||||
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 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):
|
||||
|
@ -583,3 +590,6 @@ class PlanBillingTest(BaseBillingTest):
|
|||
|
||||
def test_plan(self):
|
||||
pass
|
||||
|
||||
|
||||
# TODO web disk size
|
||||
|
|
|
@ -73,8 +73,7 @@ class ServiceHandler(plugins.Plugin):
|
|||
day = 1
|
||||
else:
|
||||
raise NotImplementedError(msg)
|
||||
bp = datetime.datetime(year=date.year, month=date.month, day=day,
|
||||
tzinfo=timezone.get_current_timezone()).date()
|
||||
bp = datetime.date(year=date.year, month=date.month, day=day)
|
||||
elif self.billing_period == self.ANUAL:
|
||||
if self.billing_point == self.ON_REGISTER:
|
||||
month = order.registered_on.month
|
||||
|
@ -89,16 +88,24 @@ class ServiceHandler(plugins.Plugin):
|
|||
year = bp.year - relativedelta.relativedelta(years=1)
|
||||
if bp.month >= month:
|
||||
year = bp.year + 1
|
||||
bp = datetime.datetime(year=year, month=month, day=day,
|
||||
tzinfo=timezone.get_current_timezone()).date()
|
||||
bp = datetime.date(year=year, month=month, day=day)
|
||||
elif self.billing_period == self.NEVER:
|
||||
bp = order.registered_on
|
||||
else:
|
||||
raise NotImplementedError(msg)
|
||||
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
|
||||
|
||||
# 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):
|
||||
rdelta = relativedelta.relativedelta(end, ini)
|
||||
if self.billing_period == self.MONTHLY:
|
||||
|
@ -126,11 +133,9 @@ class ServiceHandler(plugins.Plugin):
|
|||
period = self.get_pricing_period()
|
||||
rdelta = self.get_pricing_rdelta()
|
||||
if period == self.MONTHLY:
|
||||
ini = datetime.datetime(year=ini.year, month=ini.month, day=day,
|
||||
tzinfo=timezone.get_current_timezone()).date()
|
||||
ini = datetime.date(year=ini.year, month=ini.month, day=day)
|
||||
elif period == self.ANUAL:
|
||||
ini = datetime.datetime(year=ini.year, month=month, day=day,
|
||||
tzinfo=timezone.get_current_timezone()).date()
|
||||
ini = datetime.date(year=ini.year, month=month, day=day)
|
||||
elif period == self.NEVER:
|
||||
yield ini, end
|
||||
raise StopIteration
|
||||
|
@ -246,12 +251,13 @@ class ServiceHandler(plugins.Plugin):
|
|||
for order in porders:
|
||||
bu = getattr(order, 'new_billed_until', order.billed_until)
|
||||
if bu:
|
||||
if order.registered_on > ini and order.registered_on <= end:
|
||||
registered = order.registered_on
|
||||
if registered > ini and registered <= end:
|
||||
counter += 1
|
||||
if order.registered_on != bu and bu > ini and bu <= end:
|
||||
if registered != bu and bu > ini and bu <= end:
|
||||
counter += 1
|
||||
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
|
||||
return counter
|
||||
|
||||
|
@ -331,6 +337,7 @@ class ServiceHandler(plugins.Plugin):
|
|||
# In most cases:
|
||||
# ini >= registered_date, end < registered_date
|
||||
# boundary lookup and exclude cancelled and billed
|
||||
# TODO service.payment_style == self.POSTPAY no discounts no shit on_cancel
|
||||
orders_ = []
|
||||
bp = None
|
||||
ini = datetime.date.max
|
||||
|
@ -339,7 +346,7 @@ class ServiceHandler(plugins.Plugin):
|
|||
cini = order.registered_on
|
||||
if order.billed_until:
|
||||
# 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:
|
||||
continue
|
||||
cini = order.billed_until
|
||||
|
@ -413,20 +420,22 @@ class ServiceHandler(plugins.Plugin):
|
|||
order.new_billed_until = bp
|
||||
if self.get_pricing_period() == self.NEVER:
|
||||
# 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)
|
||||
lines.append(self.generate_line(order, price, ini, end, metric=metric))
|
||||
else:
|
||||
lines.append(self.generate_line(order, price, cini, cend, metric=metric))
|
||||
elif self.get_pricing_period() == self.billing_period:
|
||||
# pricing_slots (Traffic-like)
|
||||
for ini, end in self.get_pricing_slots(ini, bp):
|
||||
metric = order.get_metric(ini, end)
|
||||
for cini, cend in self.get_pricing_slots(ini, bp):
|
||||
metric = order.get_metric(cini, cend)
|
||||
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:
|
||||
# One-time billing
|
||||
if order.billed_until:
|
||||
continue
|
||||
date = order.registered_on
|
||||
date = timezone.now().date()
|
||||
order.new_billed_until = date
|
||||
if self.get_pricing_period() == self.NEVER:
|
||||
# get metric (Job-like)
|
||||
|
|
|
@ -77,6 +77,7 @@ autodiscover('handlers')
|
|||
|
||||
class Service(models.Model):
|
||||
NEVER = ''
|
||||
# DAILY = 'DAILY'
|
||||
MONTHLY = 'MONTHLY'
|
||||
ANUAL = 'ANUAL'
|
||||
TEN_DAYS = 'TEN_DAYS'
|
||||
|
@ -90,7 +91,7 @@ class Service(models.Model):
|
|||
NOTHING = 'NOTHING'
|
||||
DISCOUNT = 'DISCOUNT'
|
||||
COMPENSATE = 'COMPENSATE'
|
||||
REFOUND = 'REFOUND'
|
||||
REFUND = 'REFUND'
|
||||
PREPAY = 'PREPAY'
|
||||
POSTPAY = 'POSTPAY'
|
||||
STEP_PRICE = 'STEP_PRICE'
|
||||
|
@ -175,9 +176,27 @@ class Service(models.Model):
|
|||
(NOTHING, _("Nothing")),
|
||||
(DISCOUNT, _("Discount")),
|
||||
(COMPENSATE, _("Compensat")),
|
||||
(REFOUND, _("Refound")),
|
||||
(REFUND, _("Refund")),
|
||||
),
|
||||
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,
|
||||
help_text=_("Designates whether this service should be paid after "
|
||||
"consumtion (postpay/on demand) or prepaid"),
|
||||
|
@ -194,14 +213,14 @@ class Service(models.Model):
|
|||
# (ONE_MONTH, _("One month")),
|
||||
# ),
|
||||
# default=NEVER)
|
||||
# refound_period = models.CharField(_("refound period"), max_length=16,
|
||||
# help_text=_("Period in which automatic refound will be performed on "
|
||||
# refund_period = models.CharField(_("refund period"), max_length=16,
|
||||
# help_text=_("Period in which automatic refund will be performed on "
|
||||
# "service cancellation"),
|
||||
# choices=(
|
||||
# (NEVER, _("Never refound")),
|
||||
# (NEVER, _("Never refund")),
|
||||
# (TEN_DAYS, _("Ten days")),
|
||||
# (ONE_MONTH, _("One month")),
|
||||
# (ALWAYS, _("Always refound")),
|
||||
# (ALWAYS, _("Always refund")),
|
||||
# ),
|
||||
# default=NEVER, blank=True)
|
||||
|
||||
|
|
Loading…
Reference in New Issue