diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py index 5f550530..33a902bf 100644 --- a/orchestra/apps/bills/models.py +++ b/orchestra/apps/bills/models.py @@ -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) diff --git a/orchestra/apps/bills/templates/bills/microspective-fee.html b/orchestra/apps/bills/templates/bills/microspective-fee.html index 7df25b5a..90ba630d 100644 --- a/orchestra/apps/bills/templates/bills/microspective-fee.html +++ b/orchestra/apps/bills/templates/bills/microspective-fee.html @@ -78,9 +78,6 @@ hr { border: 2px solid #809708; clear: left; } - - - {% endblock %} @@ -90,28 +87,28 @@ hr {
- Aadults
- ES01939933
- Carrer nnoseque, 0
- 08034 - Barcelona
- Spain
+ {{ buyer.name }}
+ {{ buyer.vat }}
+ {{ buyer.address }}
+ {{ buyer.zipcode }} - {{ buyer.city }}
+ {{ buyer.country }}
Membership Fee
- Q20110232
- Nov, 2011
+ {{ bill.number }}
+ {{ bill.created_on | date }}
- 1232,00 €
- To pay before Oct 20, 2011
+ {{ bill.get_total }} €
+ To pay before {{ bill.due_date }}
on 213.232.322.232.332
-from Apr 1, 2010 to Apr 1, 2011 +From {{ bill.lines.get.description }}
{% endblock %} {% block content %} diff --git a/orchestra/apps/orders/admin.py b/orchestra/apps/orders/admin.py index 8923da9d..8288405b 100644 --- a/orchestra/apps/orders/admin.py +++ b/orchestra/apps/orders/admin.py @@ -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 '{human}'.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') diff --git a/orchestra/apps/orders/backends.py b/orchestra/apps/orders/backends.py index 93bd7c9d..4d7606e8 100644 --- a/orchestra/apps/orders/backends.py +++ b/orchestra/apps/orders/backends.py @@ -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: diff --git a/orchestra/apps/orders/filters.py b/orchestra/apps/orders/filters.py index b02f51f5..87d1c7a7 100644 --- a/orchestra/apps/orders/filters.py +++ b/orchestra/apps/orders/filters.py @@ -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 diff --git a/orchestra/apps/orders/models.py b/orchestra/apps/orders/models.py index 89de5229..d8dc0b7c 100644 --- a/orchestra/apps/orders/models.py +++ b/orchestra/apps/orders/models.py @@ -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): diff --git a/orchestra/apps/prices/models.py b/orchestra/apps/prices/models.py index ae39206d..0c4c297b 100644 --- a/orchestra/apps/prices/models.py +++ b/orchestra/apps/prices/models.py @@ -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) diff --git a/orchestra/utils/humanize.py b/orchestra/utils/humanize.py index dc821394..6d8e37b5 100644 --- a/orchestra/utils/humanize.py +++ b/orchestra/utils/humanize.py @@ -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)