Fixes on fees

This commit is contained in:
Marc 2014-09-03 14:51:07 +00:00
parent f4c8ca06ca
commit 5cfb48f8df
8 changed files with 92 additions and 48 deletions

View File

@ -54,7 +54,7 @@ class Bill(models.Model):
status = models.CharField(_("status"), max_length=16, choices=STATUSES, status = models.CharField(_("status"), max_length=16, choices=STATUSES,
default=OPEN) default=OPEN)
created_on = models.DateTimeField(_("created on"), auto_now_add=True) created_on = models.DateTimeField(_("created on"), auto_now_add=True)
due_on = models.DateTimeField(_("due on"), null=True, blank=True) due_on = models.DateField(_("due on"), null=True, blank=True)
last_modified_on = models.DateTimeField(_("last modified on"), auto_now=True) last_modified_on = models.DateTimeField(_("last modified on"), auto_now=True)
#base = models.DecimalField(max_digits=12, decimal_places=2) #base = models.DecimalField(max_digits=12, decimal_places=2)
#tax = models.DecimalField(max_digits=12, decimal_places=2) #tax = models.DecimalField(max_digits=12, decimal_places=2)

View File

@ -78,9 +78,6 @@ hr {
border: 2px solid #809708; border: 2px solid #809708;
clear: left; clear: left;
} }
</style> </style>
{% endblock %} {% endblock %}
@ -90,28 +87,28 @@ hr {
</div> </div>
<div id="buyer-details"> <div id="buyer-details">
<span class="name">Aadults</span><br> <span class="name">{{ buyer.name }}</span><br>
ES01939933<br> {{ buyer.vat }}<br>
Carrer nnoseque, 0<br> {{ buyer.address }}<br>
08034 - Barcelona<br> {{ buyer.zipcode }} - {{ buyer.city }}<br>
Spain<br> {{ buyer.country }}<br>
</div> </div>
<div id="number" class="column-1"> <div id="number" class="column-1">
<span id="number-title">Membership Fee</span><br> <span id="number-title">Membership Fee</span><br>
<span id="number-value">Q20110232</span><br> <span id="number-value">{{ bill.number }}</span><br>
<span id="number-date">Nov, 2011</span><br> <span id="number-date">{{ bill.created_on | date }}</span><br>
</div> </div>
<div id="amount" class="column-2"> <div id="amount" class="column-2">
<span id="amount-value">1232,00 &euro;</span><br> <span id="amount-value">{{ bill.get_total }} &euro;</span><br>
<span id="amount-note">To pay before Oct 20, 2011<br> <span id="amount-note">To pay before {{ bill.due_date }}<br>
on 213.232.322.232.332<br> on 213.232.322.232.332<br>
</span> </span>
</div> </div>
<div id="date" class="column-2"> <div id="date" class="column-2">
from Apr 1, 2010 to Apr 1, 2011 From {{ bill.lines.get.description }}
</div> </div>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -3,6 +3,7 @@ from django.db import models
from django.contrib import admin from django.contrib import admin
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeListDefaultFilter from orchestra.admin import ChangeListDefaultFilter
@ -10,9 +11,10 @@ from orchestra.admin.filters import UsedContentTypeFilter
from orchestra.admin.utils import admin_link, admin_date from orchestra.admin.utils import admin_link, admin_date
from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin
from orchestra.core import services from orchestra.core import services
from orchestra.utils.humanize import naturaldate
from .actions import BillSelectedOrders from .actions import BillSelectedOrders
from .filters import ActiveOrderListFilter from .filters import ActiveOrderListFilter, BilledOrderListFilter
from .models import Service, Order, MetricStorage from .models import Service, Order, MetricStorage
@ -81,7 +83,7 @@ class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
'display_registered_on', 'display_billed_until', 'display_cancelled_on' 'display_registered_on', 'display_billed_until', 'display_cancelled_on'
) )
list_display_links = ('id', 'service') list_display_links = ('id', 'service')
list_filter = (ActiveOrderListFilter, 'service',) list_filter = (ActiveOrderListFilter, BilledOrderListFilter, 'service',)
actions = (BillSelectedOrders(),) actions = (BillSelectedOrders(),)
date_hierarchy = 'registered_on' date_hierarchy = 'registered_on'
default_changelist_filters = ( default_changelist_filters = (
@ -90,9 +92,20 @@ class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
content_object_link = admin_link('content_object', order=False) content_object_link = admin_link('content_object', order=False)
display_registered_on = admin_date('registered_on') display_registered_on = admin_date('registered_on')
display_billed_until = admin_date('billed_until')
display_cancelled_on = admin_date('cancelled_on') display_cancelled_on = admin_date('cancelled_on')
def display_billed_until(self, order):
value = order.billed_until
color = ''
if value and value < timezone.now():
color = 'style="color:red;"'
return '<span title="{raw}" {color}>{human}</span>'.format(
raw=escape(str(value)), color=color, human=escape(naturaldate(value)),
)
display_billed_until.short_description = _("billed until")
display_billed_until.allow_tags = True
display_billed_until.admin_order_field = 'billed_until'
def get_queryset(self, request): def get_queryset(self, request):
qs = super(OrderAdmin, self).get_queryset(request) qs = super(OrderAdmin, self).get_queryset(request)
return qs.select_related('service').prefetch_related('content_object') return qs.select_related('service').prefetch_related('content_object')

View File

@ -6,19 +6,26 @@ from orchestra.apps.bills.models import Invoice, Fee, BillLine, BillSubline
class BillsBackend(object): class BillsBackend(object):
def create_bills(self, account, lines): def create_bills(self, account, lines):
invoice = None invoice = None
fees = [] bills = []
for order, nominal_price, size, ini, end, discounts in lines: for order, nominal_price, size, ini, end, discounts in lines:
service = order.service service = order.service
if service.is_fee: if service.is_fee:
fee = Fee.objects.get_or_create(account=account, status=Fee.OPEN) fee, __ = Fee.objects.get_or_create(account=account, status=Fee.OPEN)
line = fee.lines.create(rate=service.nominal_price, amount=size, line = fee.lines.create(
total=nominal_price, tax=0) rate=service.nominal_price,
amount=size,
total=nominal_price, tax=0,
description="{ini} to {end}".format(
ini=ini.strftime("%b, %Y"),
end=(end-datetime.timedelta(seconds=1)).strftime("%b, %Y")),
)
self.create_sublines(line, discounts) self.create_sublines(line, discounts)
fees.append(fee) bills.append(fee)
else: else:
if invoice is None: if invoice is None:
invoice, __ = Invoice.objects.get_or_create(account=account, invoice, __ = Invoice.objects.get_or_create(account=account,
status=Invoice.OPEN) status=Invoice.OPEN)
bills.append(invoice)
description = order.description description = order.description
if service.billing_period != service.NEVER: if service.billing_period != service.NEVER:
description += " {ini} to {end}".format( description += " {ini} to {end}".format(
@ -32,7 +39,7 @@ class BillsBackend(object):
tax=service.tax, tax=service.tax,
) )
self.create_sublines(line, discounts) self.create_sublines(line, discounts)
return [invoice] + fees return bills
def create_sublines(self, line, discounts): def create_sublines(self, line, discounts):
for name, value in discounts: for name, value in discounts:

View File

@ -1,10 +1,12 @@
from django.contrib.admin import SimpleListFilter from django.contrib.admin import SimpleListFilter
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class ActiveOrderListFilter(SimpleListFilter): class ActiveOrderListFilter(SimpleListFilter):
""" Filter tickets by created_by according to request.user """ """ Filter tickets by created_by according to request.user """
title = _("Orders") title = _("is active")
parameter_name = 'is_active' parameter_name = 'is_active'
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
@ -26,3 +28,29 @@ class ActiveOrderListFilter(SimpleListFilter):
choices = iter(super(ActiveOrderListFilter, self).choices(cl)) choices = iter(super(ActiveOrderListFilter, self).choices(cl))
choices.next() choices.next()
return choices return choices
class BilledOrderListFilter(SimpleListFilter):
""" Filter tickets by created_by according to request.user """
title = _("billed")
parameter_name = 'pending'
def lookups(self, request, model_admin):
return (
('to_date', _("To date")),
('full', _("Full period")),
('not', _("Not billed")),
)
def queryset(self, request, queryset):
if self.value() == 'to_date':
return queryset.filter(billed_until__isnull=False,
billed_until__gte=timezone.now())
elif self.value() == 'full':
raise NotImplementedError
elif self.value() == 'not':
return queryset.filter(
Q(billed_until__isnull=True) |
Q(billed_until__lt=timezone.now())
)
return queryset

View File

@ -144,7 +144,7 @@ class Service(models.Model):
(POSTPAY, _("Postpay (on demand)")), (POSTPAY, _("Postpay (on demand)")),
), ),
default=PREPAY) default=PREPAY)
trial_period = models.CharField(_("trial period"), max_length=16, trial_period = models.CharField(_("trial period"), max_length=16, blank=True,
help_text=_("Period in which no charge will be issued"), help_text=_("Period in which no charge will be issued"),
choices=( choices=(
(NEVER, _("No trial")), (NEVER, _("No trial")),
@ -161,7 +161,7 @@ class Service(models.Model):
(ONE_MONTH, _("One month")), (ONE_MONTH, _("One month")),
(ALWAYS, _("Always refound")), (ALWAYS, _("Always refound")),
), ),
default=NEVER) default=NEVER, blank=True)
@property @property
def nominal_price(self): def nominal_price(self):

View File

@ -2,7 +2,7 @@ from django.db import models
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.core import accounts from orchestra.core import accounts, services
from . import settings from . import settings
@ -33,3 +33,4 @@ class Rate(models.Model):
accounts.register(Pack) accounts.register(Pack)
services.register(Pack, menu=False)

View File

@ -6,26 +6,26 @@ from django.utils.translation import ungettext, ugettext as _
def pluralize_year(n): def pluralize_year(n):
return ungettext( return ungettext(
_('{ahead}{num:.1f} year{ago}'), _('{num:.1f} year{ago}'),
_('{ahead}{num:.1f} years{ago}'), n) _('{num:.1f} years{ago}'), n)
def pluralize_month(n): def pluralize_month(n):
return ungettext( return ungettext(
_('{ahead}{num:.1f} month{ago}'), _('{num:.1f} month{ago}'),
_('{ahead}{num:.1f} months{ago}'), n) _('{num:.1f} months{ago}'), n)
def pluralize_week(n): def pluralize_week(n):
return ungettext( return ungettext(
_('{ahead}{num:.1f} week{ago}'), _('{num:.1f} week{ago}'),
_('{ahead}{num:.1f} weeks {ago}'), n) _('{num:.1f} weeks {ago}'), n)
def pluralize_day(n): def pluralize_day(n):
return ungettext( return ungettext(
_('{ahead}{num:.1f} day{ago}'), _('{num:.1f} day{ago}'),
_('{ahead}{num:.1f} days{ago}'), n) _('{num:.1f} days{ago}'), n)
OLDER_CHUNKS = ( OLDER_CHUNKS = (
@ -57,10 +57,8 @@ def naturaldate(date, include_seconds=False):
seconds = delta.seconds seconds = delta.seconds
ago = ' ago' ago = ' ago'
ahead = ''
if days < 0: if days < 0:
ago = '' ago = ''
ahead = 'in '
days = abs(days) days = abs(days)
if days == 0: if days == 0:
@ -68,22 +66,22 @@ def naturaldate(date, include_seconds=False):
if minutes > 0: if minutes > 0:
minutes += float(seconds)/60 minutes += float(seconds)/60
return ungettext( return ungettext(
_('{ahead}{minutes:.1f} minute{ago}'), _('{minutes:.1f} minute{ago}'),
_('{ahead}{minutes:.1f} minutes{ago}'), minutes _('{minutes:.1f} minutes{ago}'), minutes
).format(minutes=minutes, ago=ago, ahead=ahead) ).format(minutes=minutes, ago=ago)
else: else:
if include_seconds and seconds: if include_seconds and seconds:
return ungettext( return ungettext(
_('{ahead}{seconds} second{ago}'), _('{seconds} second{ago}'),
_('{ahead}{seconds} seconds{ago}'), seconds _('{seconds} seconds{ago}'), seconds
).format(seconds=seconds, ago=ago, ahead=ahead) ).format(seconds=seconds, ago=ago)
return _('just now') return _('just now')
else: else:
hours += float(minutes)/60 hours += float(minutes)/60
return ungettext( return ungettext(
_('{ahead}{hours:.1f} hour{ago}'), _('{hours:.1f} hour{ago}'),
_('{ahead}{hours:.1f} hours{ago}'), hours _('{hours:.1f} hours{ago}'), hours
).format(hours=hours, ago=ago, ahead=ahead) ).format(hours=hours, ago=ago)
if delta_midnight.days == 0: if delta_midnight.days == 0:
return _('yesterday at {time}').format(time=date.strftime('%H:%M')) return _('yesterday at {time}').format(time=date.strftime('%H:%M'))
@ -93,9 +91,9 @@ def naturaldate(date, include_seconds=False):
if days < 7.0: if days < 7.0:
count = days + float(hours)/24 count = days + float(hours)/24
fmt = pluralize_day(count) fmt = pluralize_day(count)
return fmt.format(num=count, ago=ago, ahead=ahead) return fmt.format(num=count, ago=ago)
if days >= chunk: if days >= chunk:
count = (delta_midnight.days + 1) / chunk count = (delta_midnight.days + 1) / chunk
count = abs(count) count = abs(count)
fmt = pluralizefun(count) fmt = pluralizefun(count)
return fmt.format(num=count, ago=ago, ahead=ahead) return fmt.format(num=count, ago=ago)