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,
|
||||
default=OPEN)
|
||||
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)
|
||||
#base = 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;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -90,28 +87,28 @@ hr {
|
|||
</div>
|
||||
|
||||
<div id="buyer-details">
|
||||
<span class="name">Aadults</span><br>
|
||||
ES01939933<br>
|
||||
Carrer nnoseque, 0<br>
|
||||
08034 - Barcelona<br>
|
||||
Spain<br>
|
||||
<span class="name">{{ buyer.name }}</span><br>
|
||||
{{ buyer.vat }}<br>
|
||||
{{ buyer.address }}<br>
|
||||
{{ buyer.zipcode }} - {{ buyer.city }}<br>
|
||||
{{ buyer.country }}<br>
|
||||
</div>
|
||||
|
||||
<div id="number" class="column-1">
|
||||
<span id="number-title">Membership Fee</span><br>
|
||||
<span id="number-value">Q20110232</span><br>
|
||||
<span id="number-date">Nov, 2011</span><br>
|
||||
<span id="number-value">{{ bill.number }}</span><br>
|
||||
<span id="number-date">{{ bill.created_on | date }}</span><br>
|
||||
</div>
|
||||
|
||||
<div id="amount" class="column-2">
|
||||
<span id="amount-value">1232,00 €</span><br>
|
||||
<span id="amount-note">To pay before Oct 20, 2011<br>
|
||||
<span id="amount-value">{{ bill.get_total }} €</span><br>
|
||||
<span id="amount-note">To pay before {{ bill.due_date }}<br>
|
||||
on 213.232.322.232.332<br>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="date" class="column-2">
|
||||
from Apr 1, 2010 to Apr 1, 2011
|
||||
From {{ bill.lines.get.description }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.db import models
|
|||
from django.contrib import admin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
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.apps.accounts.admin import AccountAdminMixin
|
||||
from orchestra.core import services
|
||||
from orchestra.utils.humanize import naturaldate
|
||||
|
||||
from .actions import BillSelectedOrders
|
||||
from .filters import ActiveOrderListFilter
|
||||
from .filters import ActiveOrderListFilter, BilledOrderListFilter
|
||||
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'
|
||||
)
|
||||
list_display_links = ('id', 'service')
|
||||
list_filter = (ActiveOrderListFilter, 'service',)
|
||||
list_filter = (ActiveOrderListFilter, BilledOrderListFilter, 'service',)
|
||||
actions = (BillSelectedOrders(),)
|
||||
date_hierarchy = 'registered_on'
|
||||
default_changelist_filters = (
|
||||
|
@ -90,9 +92,20 @@ class OrderAdmin(AccountAdminMixin, ChangeListDefaultFilter, admin.ModelAdmin):
|
|||
|
||||
content_object_link = admin_link('content_object', order=False)
|
||||
display_registered_on = admin_date('registered_on')
|
||||
display_billed_until = admin_date('billed_until')
|
||||
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):
|
||||
qs = super(OrderAdmin, self).get_queryset(request)
|
||||
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):
|
||||
def create_bills(self, account, lines):
|
||||
invoice = None
|
||||
fees = []
|
||||
bills = []
|
||||
for order, nominal_price, size, ini, end, discounts in lines:
|
||||
service = order.service
|
||||
if service.is_fee:
|
||||
fee = Fee.objects.get_or_create(account=account, status=Fee.OPEN)
|
||||
line = fee.lines.create(rate=service.nominal_price, amount=size,
|
||||
total=nominal_price, tax=0)
|
||||
fee, __ = Fee.objects.get_or_create(account=account, status=Fee.OPEN)
|
||||
line = fee.lines.create(
|
||||
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)
|
||||
fees.append(fee)
|
||||
bills.append(fee)
|
||||
else:
|
||||
if invoice is None:
|
||||
invoice, __ = Invoice.objects.get_or_create(account=account,
|
||||
status=Invoice.OPEN)
|
||||
bills.append(invoice)
|
||||
description = order.description
|
||||
if service.billing_period != service.NEVER:
|
||||
description += " {ini} to {end}".format(
|
||||
|
@ -32,7 +39,7 @@ class BillsBackend(object):
|
|||
tax=service.tax,
|
||||
)
|
||||
self.create_sublines(line, discounts)
|
||||
return [invoice] + fees
|
||||
return bills
|
||||
|
||||
def create_sublines(self, line, discounts):
|
||||
for name, value in discounts:
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
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 _
|
||||
|
||||
|
||||
class ActiveOrderListFilter(SimpleListFilter):
|
||||
""" Filter tickets by created_by according to request.user """
|
||||
title = _("Orders")
|
||||
title = _("is active")
|
||||
parameter_name = 'is_active'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
|
@ -26,3 +28,29 @@ class ActiveOrderListFilter(SimpleListFilter):
|
|||
choices = iter(super(ActiveOrderListFilter, self).choices(cl))
|
||||
choices.next()
|
||||
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)")),
|
||||
),
|
||||
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"),
|
||||
choices=(
|
||||
(NEVER, _("No trial")),
|
||||
|
@ -161,7 +161,7 @@ class Service(models.Model):
|
|||
(ONE_MONTH, _("One month")),
|
||||
(ALWAYS, _("Always refound")),
|
||||
),
|
||||
default=NEVER)
|
||||
default=NEVER, blank=True)
|
||||
|
||||
@property
|
||||
def nominal_price(self):
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.db import models
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.core import accounts
|
||||
from orchestra.core import accounts, services
|
||||
|
||||
from . import settings
|
||||
|
||||
|
@ -33,3 +33,4 @@ class Rate(models.Model):
|
|||
|
||||
|
||||
accounts.register(Pack)
|
||||
services.register(Pack, menu=False)
|
||||
|
|
|
@ -6,26 +6,26 @@ from django.utils.translation import ungettext, ugettext as _
|
|||
|
||||
def pluralize_year(n):
|
||||
return ungettext(
|
||||
_('{ahead}{num:.1f} year{ago}'),
|
||||
_('{ahead}{num:.1f} years{ago}'), n)
|
||||
_('{num:.1f} year{ago}'),
|
||||
_('{num:.1f} years{ago}'), n)
|
||||
|
||||
|
||||
def pluralize_month(n):
|
||||
return ungettext(
|
||||
_('{ahead}{num:.1f} month{ago}'),
|
||||
_('{ahead}{num:.1f} months{ago}'), n)
|
||||
_('{num:.1f} month{ago}'),
|
||||
_('{num:.1f} months{ago}'), n)
|
||||
|
||||
|
||||
def pluralize_week(n):
|
||||
return ungettext(
|
||||
_('{ahead}{num:.1f} week{ago}'),
|
||||
_('{ahead}{num:.1f} weeks {ago}'), n)
|
||||
_('{num:.1f} week{ago}'),
|
||||
_('{num:.1f} weeks {ago}'), n)
|
||||
|
||||
|
||||
def pluralize_day(n):
|
||||
return ungettext(
|
||||
_('{ahead}{num:.1f} day{ago}'),
|
||||
_('{ahead}{num:.1f} days{ago}'), n)
|
||||
_('{num:.1f} day{ago}'),
|
||||
_('{num:.1f} days{ago}'), n)
|
||||
|
||||
|
||||
OLDER_CHUNKS = (
|
||||
|
@ -57,10 +57,8 @@ def naturaldate(date, include_seconds=False):
|
|||
seconds = delta.seconds
|
||||
|
||||
ago = ' ago'
|
||||
ahead = ''
|
||||
if days < 0:
|
||||
ago = ''
|
||||
ahead = 'in '
|
||||
days = abs(days)
|
||||
|
||||
if days == 0:
|
||||
|
@ -68,22 +66,22 @@ def naturaldate(date, include_seconds=False):
|
|||
if minutes > 0:
|
||||
minutes += float(seconds)/60
|
||||
return ungettext(
|
||||
_('{ahead}{minutes:.1f} minute{ago}'),
|
||||
_('{ahead}{minutes:.1f} minutes{ago}'), minutes
|
||||
).format(minutes=minutes, ago=ago, ahead=ahead)
|
||||
_('{minutes:.1f} minute{ago}'),
|
||||
_('{minutes:.1f} minutes{ago}'), minutes
|
||||
).format(minutes=minutes, ago=ago)
|
||||
else:
|
||||
if include_seconds and seconds:
|
||||
return ungettext(
|
||||
_('{ahead}{seconds} second{ago}'),
|
||||
_('{ahead}{seconds} seconds{ago}'), seconds
|
||||
).format(seconds=seconds, ago=ago, ahead=ahead)
|
||||
_('{seconds} second{ago}'),
|
||||
_('{seconds} seconds{ago}'), seconds
|
||||
).format(seconds=seconds, ago=ago)
|
||||
return _('just now')
|
||||
else:
|
||||
hours += float(minutes)/60
|
||||
return ungettext(
|
||||
_('{ahead}{hours:.1f} hour{ago}'),
|
||||
_('{ahead}{hours:.1f} hours{ago}'), hours
|
||||
).format(hours=hours, ago=ago, ahead=ahead)
|
||||
_('{hours:.1f} hour{ago}'),
|
||||
_('{hours:.1f} hours{ago}'), hours
|
||||
).format(hours=hours, ago=ago)
|
||||
|
||||
if delta_midnight.days == 0:
|
||||
return _('yesterday at {time}').format(time=date.strftime('%H:%M'))
|
||||
|
@ -93,9 +91,9 @@ def naturaldate(date, include_seconds=False):
|
|||
if days < 7.0:
|
||||
count = days + float(hours)/24
|
||||
fmt = pluralize_day(count)
|
||||
return fmt.format(num=count, ago=ago, ahead=ahead)
|
||||
return fmt.format(num=count, ago=ago)
|
||||
if days >= chunk:
|
||||
count = (delta_midnight.days + 1) / chunk
|
||||
count = abs(count)
|
||||
fmt = pluralizefun(count)
|
||||
return fmt.format(num=count, ago=ago, ahead=ahead)
|
||||
return fmt.format(num=count, ago=ago)
|
||||
|
|
Loading…
Reference in a new issue