diff --git a/TODO.md b/TODO.md index d2170e32..8b75515b 100644 --- a/TODO.md +++ b/TODO.md @@ -161,3 +161,5 @@ textwrap.dedent( \\) * Disable menu on tests, fucking overlapping * service.name / verbose_name instead of .description ? + +* Bills can have sublines? diff --git a/orchestra/apps/bills/admin.py b/orchestra/apps/bills/admin.py index d440c801..cbdcddb9 100644 --- a/orchestra/apps/bills/admin.py +++ b/orchestra/apps/bills/admin.py @@ -3,6 +3,7 @@ from django.contrib import admin from django.contrib.admin.utils import unquote from django.core.urlresolvers import reverse from django.db import models +from django.templatetags.static import static from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ @@ -26,23 +27,19 @@ PAYMENT_STATE_COLORS = { class BillLineInline(admin.TabularInline): model = BillLine - fields = ('description', 'rate', 'quantity', 'tax', 'subtotal', 'get_total') - readonly_fields = ('get_total',) + fields = ('description', 'rate', 'quantity', 'tax', 'subtotal', 'display_total') + readonly_fields = ('display_total',) - def get_readonly_fields(self, request, obj=None): - if obj and not obj.is_open: - return self.fields - return super(BillLineInline, self).get_readonly_fields(request, obj=obj) - - def has_add_permission(self, request): - if request.__bill__ and not request.__bill__.is_open: - return False - return super(BillLineInline, self).has_add_permission(request) - - def has_delete_permission(self, request, obj=None): - if obj and not obj.is_open: - return False - return super(BillLineInline, self).has_delete_permission(request, obj=obj) + def display_total(self, line): + total = line.get_total() + sublines = line.sublines.all() + if sublines: + content = '\n'.join(['%s: %s' % (sub.description, sub.total) for sub in sublines]) + img = static('admin/img/icon_alert.gif') + return '%s' % (content, str(total), img) + return total + display_total.short_description = _("Total") + display_total.allow_tags = True def formfield_for_dbfield(self, db_field, **kwargs): """ Make value input widget bigger """ @@ -51,6 +48,40 @@ class BillLineInline(admin.TabularInline): else: kwargs['widget'] = forms.TextInput(attrs={'size':'13'}) return super(BillLineInline, self).formfield_for_dbfield(db_field, **kwargs) + + def get_queryset(self, request): + qs = super(BillLineInline, self).get_queryset(request) + return qs.prefetch_related('sublines') + + +class ClosedBillLineInline(BillLineInline): + # TODO reimplement as nested inlines when upstream + # https://code.djangoproject.com/ticket/9025 + + fields = ('display_description', 'rate', 'quantity', 'tax', 'display_subtotal', 'display_total') + readonly_fields = fields + + def display_description(self, line): + descriptions = [line.description] + for subline in line.sublines.all(): + descriptions.append(' '*4+subline.description) + return '
'.join(descriptions) + display_description.short_description = _("Description") + display_description.allow_tags = True + + def display_subtotal(self, line): + subtotals = ['  ' + str(line.subtotal)] + for subline in line.sublines.all(): + subtotals.append(str(subline.total)) + return '
'.join(subtotals) + display_subtotal.short_description = _("Subtotal") + display_subtotal.allow_tags = True + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): @@ -62,7 +93,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): add_fields = ('account', 'type', 'is_open', 'due_on', 'comments') fieldsets = ( (None, { - 'fields': ('number', 'display_total', 'account_link', 'type', + 'fields': ('number', 'type', 'account_link', 'display_total', 'display_payment_state', 'is_sent', 'due_on', 'comments'), }), (_("Raw"), { @@ -74,7 +105,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): change_view_actions = [view_bill, download_bills, send_bills, close_bills] change_readonly_fields = ('account_link', 'type', 'is_open') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') - inlines = [BillLineInline] + inlines = [BillLineInline, ClosedBillLineInline] created_on_display = admin_date('created_on') @@ -134,9 +165,10 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): return [action for action in actions if action.__name__ not in exclude] def get_inline_instances(self, request, obj=None): - # Make parent object available for inline.has_add_permission() - request.__bill__ = obj - return super(BillAdmin, self).get_inline_instances(request, obj=obj) + inlines = super(BillAdmin, self).get_inline_instances(request, obj=obj) + if obj and not obj.is_open: + return [inline for inline in inlines if not type(inline) == BillLineInline] + return [inline for inline in inlines if not type(inline) == ClosedBillLineInline] def formfield_for_dbfield(self, db_field, **kwargs): """ Make value input widget bigger """ diff --git a/orchestra/apps/bills/models.py b/orchestra/apps/bills/models.py index 3de1bab1..60d6302d 100644 --- a/orchestra/apps/bills/models.py +++ b/orchestra/apps/bills/models.py @@ -316,6 +316,7 @@ class BillSubline(models.Model): # TODO: order info for undoing line = models.ForeignKey(BillLine, verbose_name=_("bill line"), related_name='sublines') description = models.CharField(_("description"), max_length=256) + # TODO rename to subtotal total = models.DecimalField(max_digits=12, decimal_places=2) type = models.CharField(_("type"), max_length=16, choices=TYPES, default=OTHER)