Fixes on fees
This commit is contained in:
parent
f4c8ca06ca
commit
5cfb48f8df
|
@ -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)
|
||||||
|
|
|
@ -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 €</span><br>
|
<span id="amount-value">{{ bill.get_total }} €</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 %}
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue