diff --git a/TODO.md b/TODO.md
index a5e52152..da145c6b 100644
--- a/TODO.md
+++ b/TODO.md
@@ -430,7 +430,6 @@ mkhomedir_helper or create ssh homes with bash.rc and such
# Automatically re-run backends until success? only timedout executions?
# TODO save serialized versions ob backendoperation.instance in order to allow backend reexecution of deleted objects
-# upgrade to django 1.9 and make margins wider
# lets encrypt: DNS vs HTTP challange
# lets enctypt: autorenew
@@ -459,8 +458,7 @@ with open(file) as handler:
os.unlink(file)
-# change filter By PHP version: by detail
-
# Mark transaction process as executed should not override higher transaction states
+# Bill amend and related transaction, what to do? allow edit transaction ammount of amends when their are pending execution
-# mailbox.addresses get_Queryset SQL contact @ with mailboxes and forwards
+# DASHBOARD: Show owned tickets, scheduled actions, maintenance operations (diff domains)
diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py
index deee5194..f49be37d 100644
--- a/orchestra/admin/options.py
+++ b/orchestra/admin/options.py
@@ -142,6 +142,7 @@ class ChangeViewActionsMixin(object):
view.tool_description = tool_description
view.css_class = getattr(action, 'css_class', 'historylink')
view.help_text = getattr(action, 'help_text', '')
+ view.hidden = getattr(action, 'hidden', False)
views.append(view)
return views
@@ -150,7 +151,7 @@ class ChangeViewActionsMixin(object):
kwargs['extra_context'] = {}
obj = self.get_object(request, unquote(object_id))
kwargs['extra_context']['object_tools_items'] = [
- action.__dict__ for action in self.get_change_view_actions(obj)
+ action.__dict__ for action in self.get_change_view_actions(obj) if not action.hidden
]
return super().change_view(request, object_id, **kwargs)
diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py
index f288b4b0..058d66fa 100644
--- a/orchestra/contrib/bills/actions.py
+++ b/orchestra/contrib/bills/actions.py
@@ -32,6 +32,7 @@ def view_bill(modeladmin, request, queryset):
return HttpResponse(html)
view_bill.tool_description = _("View")
view_bill.url_name = 'view'
+view_bill.hidden = True
@transaction.atomic
diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py
index d4604b39..55bd3569 100644
--- a/orchestra/contrib/bills/admin.py
+++ b/orchestra/contrib/bills/admin.py
@@ -102,6 +102,7 @@ class ClosedBillLineInline(BillLineInline):
'display_subtotal', 'display_total'
)
readonly_fields = fields
+ can_delete = False
def display_description(self, line):
descriptions = [line.description]
@@ -127,9 +128,6 @@ class ClosedBillLineInline(BillLineInline):
def has_add_permission(self, request):
return False
-
- def has_delete_permission(self, request, obj=None):
- return False
class BillLineAdmin(admin.ModelAdmin):
@@ -243,7 +241,77 @@ class BillLineManagerAdmin(BillLineAdmin):
return super().changelist_view(request, context)
-class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
+class BillAdminMixin(AccountAdminMixin):
+ def display_total_with_subtotals(self, bill):
+ if bill.pk:
+ currency = settings.BILLS_CURRENCY.lower()
+ subtotals = []
+ for tax, subtotal in bill.compute_subtotals().items():
+ subtotals.append(_("Subtotal %s%% VAT %s &%s;") % (tax, subtotal[0], currency))
+ subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency))
+ subtotals = '\n'.join(subtotals)
+ return '%s &%s;' % (subtotals, bill.compute_total(), currency)
+ display_total_with_subtotals.allow_tags = True
+ display_total_with_subtotals.short_description = _("total")
+ display_total_with_subtotals.admin_order_field = 'approx_total'
+
+ def display_payment_state(self, bill):
+ if bill.pk:
+ t_opts = bill.transactions.model._meta
+ if bill.get_type() == bill.PROFORMA:
+ return '---'
+ transactions = bill.transactions.all()
+ if len(transactions) == 1:
+ args = (transactions[0].pk,)
+ view = 'admin:%s_%s_change' % (t_opts.app_label, t_opts.model_name)
+ url = reverse(view, args=args)
+ else:
+ url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.model_name))
+ url += '?bill=%i' % bill.pk
+ state = bill.get_payment_state_display().upper()
+ title = ''
+ if bill.closed_amends:
+ state = '%s*' % state
+ title = _("This bill has been amended, this value may not be valid.")
+ color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
+ return '{name}'.format(
+ url=url, color=color, name=state, title=title)
+ display_payment_state.allow_tags = True
+ display_payment_state.short_description = _("Payment")
+
+ def get_queryset(self, request):
+ qs = super().get_queryset(request)
+ qs = qs.annotate(
+ models.Count('lines'),
+ # FIXME https://code.djangoproject.com/ticket/10060
+ approx_total=Coalesce(Sum(
+ (F('lines__subtotal') + Coalesce('lines__sublines__total', 0)) * (1+F('lines__tax')/100),
+ ), 0),
+ )
+ qs = qs.prefetch_related(
+ Prefetch('amends', queryset=Bill.objects.filter(is_open=False), to_attr='closed_amends')
+ )
+ return qs.defer('html')
+
+
+class AmendInline(BillAdminMixin, admin.TabularInline):
+ model = Bill
+ fields = (
+ 'self_link', 'type', 'display_total_with_subtotals', 'display_payment_state', 'is_open',
+ 'is_sent'
+ )
+ readonly_fields = fields
+ verbose_name_plural = _("Amends")
+ can_delete = False
+ extra = 0
+
+ self_link = admin_link('__str__')
+
+ def has_add_permission(self, *args, **kwargs):
+ return False
+
+
+class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
list_display = (
'number', 'type_link', 'account_link', 'closed_on_display', 'updated_on_display',
'num_lines', 'display_total', 'display_payment_state', 'is_sent'
@@ -256,9 +324,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
change_list_template = 'admin/bills/bill/change_list.html'
fieldsets = (
(None, {
- 'fields': ['number', 'type', 'amend_of_link', 'account_link',
- 'display_total_with_subtotals', 'display_payment_state',
- 'is_sent', 'comments'],
+ 'fields': ['number', 'type', (), 'account_link', 'display_total_with_subtotals',
+ 'display_payment_state', 'is_sent', 'comments'],
}),
(_("Dates"), {
'classes': ('collapse',),
@@ -281,31 +348,26 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
actions.amend_bills, actions.bill_report, actions.service_report,
actions.close_send_download_bills, list_accounts,
]
- change_readonly_fields = (
- 'account_link', 'type', 'is_open', 'amend_of_link', 'amend_links'
- )
+ change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link')
readonly_fields = (
'number', 'display_total', 'is_sent', 'display_payment_state', 'created_on_display',
'closed_on_display', 'updated_on_display', 'display_total_with_subtotals',
)
- inlines = [BillLineInline, ClosedBillLineInline]
date_hierarchy = 'closed_on'
- # TODO when merged https://github.com/django/django/pull/5213
- #approximate_date_hierarchy = admin.ApproximateWith.MONTHS
created_on_display = admin_date('created_on', short_description=_("Created"))
closed_on_display = admin_date('closed_on', short_description=_("Closed"))
updated_on_display = admin_date('updated_on', short_description=_("Updated"))
amend_of_link = admin_link('amend_of')
- def amend_links(self, bill):
- links = []
- for amend in bill.amends.all():
- url = reverse('admin:bills_bill_change', args=(amend.id,))
- links.append('{num}'.format(url=url, num=amend.number))
- return '
'.join(links)
- amend_links.short_description = _("Amends")
- amend_links.allow_tags = True
+# def amend_links(self, bill):
+# links = []
+# for amend in bill.amends.all():
+# url = reverse('admin:bills_bill_change', args=(amend.id,))
+# links.append('{num}'.format(url=url, num=amend.number))
+# return '
'.join(links)
+# amend_links.short_description = _("Amends")
+# amend_links.allow_tags = True
def num_lines(self, bill):
return bill.lines__count
@@ -319,18 +381,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
display_total.short_description = _("total")
display_total.admin_order_field = 'approx_total'
- def display_total_with_subtotals(self, bill):
- currency = settings.BILLS_CURRENCY.lower()
- subtotals = []
- for tax, subtotal in bill.compute_subtotals().items():
- subtotals.append(_("Subtotal %s%% VAT %s &%s;") % (tax, subtotal[0], currency))
- subtotals.append(_("Taxes %s%% VAT %s &%s;") % (tax, subtotal[1], currency))
- subtotals = '\n'.join(subtotals)
- return '%s &%s;' % (subtotals, bill.compute_total(), currency)
- display_total_with_subtotals.allow_tags = True
- display_total_with_subtotals.short_description = _("total")
- display_total_with_subtotals.admin_order_field = 'approx_total'
-
def type_link(self, bill):
bill_type = bill.type.lower()
url = reverse('admin:bills_%s_changelist' % bill_type)
@@ -339,27 +389,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
type_link.short_description = _("type")
type_link.admin_order_field = 'type'
- def display_payment_state(self, bill):
- t_opts = bill.transactions.model._meta
- transactions = bill.transactions.all()
- if len(transactions) == 1:
- args = (transactions[0].pk,)
- view = 'admin:%s_%s_change' % (t_opts.app_label, t_opts.model_name)
- url = reverse(view, args=args)
- else:
- url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.model_name))
- url += '?bill=%i' % bill.pk
- state = bill.get_payment_state_display().upper()
- title = ''
- if bill.closed_amends:
- state = '%s*' % state
- title = _("This bill has been amended, this value may not be valid.")
- color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
- return '{name}'.format(
- url=url, color=color, name=state, title=title)
- display_payment_state.allow_tags = True
- display_payment_state.short_description = _("Payment")
-
def get_urls(self):
""" Hook bill lines management URLs on bill admin """
urls = super().get_urls()
@@ -381,10 +410,11 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
fieldsets = super().get_fieldsets(request, obj)
if obj:
# Switches between amend_of_link and amend_links fields
+ fields = fieldsets[0][1]['fields']
if obj.amend_of_id:
- fieldsets[0][1]['fields'][2] = 'amend_of_link'
+ fields[2] = 'amend_of_link'
else:
- fieldsets[0][1]['fields'][2] = 'amend_links'
+ fields[2] = ()
if obj.is_open:
fieldsets = fieldsets[0:-1]
return fieldsets
@@ -400,10 +430,15 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
return [action for action in actions if action.__name__ not in exclude]
def get_inline_instances(self, request, obj=None):
- inlines = super().get_inline_instances(request, obj)
+ cls = type(self)
if obj and not obj.is_open:
- return [inline for inline in inlines if type(inline) != BillLineInline]
- return [inline for inline in inlines if type(inline) != ClosedBillLineInline]
+ if obj.amends.all():
+ cls.inlines = [AmendInline, ClosedBillLineInline]
+ else:
+ cls.inlines = [ClosedBillLineInline]
+ else:
+ cls.inlines = [BillLineInline]
+ return super().get_inline_instances(request, obj)
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
@@ -416,20 +451,6 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
formfield.queryset = formfield.queryset.filter(is_open=False)
return formfield
- def get_queryset(self, request):
- qs = super().get_queryset(request)
- qs = qs.annotate(
- models.Count('lines'),
- # FIXME https://code.djangoproject.com/ticket/10060
- approx_total=Coalesce(Sum(
- (F('lines__subtotal') + Coalesce('lines__sublines__total', 0)) * (1+F('lines__tax')/100),
- ), 0),
- )
- qs = qs.prefetch_related(
- Prefetch('amends', queryset=Bill.objects.filter(is_open=False), to_attr='closed_amends')
- )
- return qs.defer('html')
-
def change_view(self, request, object_id, **kwargs):
# TODO raise404, here and everywhere
bill = self.get_object(request, unquote(object_id))
diff --git a/orchestra/contrib/bills/filters.py b/orchestra/contrib/bills/filters.py
index 091c49c8..27a4b239 100644
--- a/orchestra/contrib/bills/filters.py
+++ b/orchestra/contrib/bills/filters.py
@@ -140,20 +140,21 @@ class AmendedListFilter(SimpleListFilter):
def lookups(self, request, model_admin):
return (
- ('1', _("Closed amends")),
- ('-1', _("Open or closed amends")),
- ('0', _("No closed amends")),
- ('-0', _("No amends")),
+ ('1', _("Amended")),
+ ('2', _("Open amends")),
+ ('3', _("Closed amends")),
+ ('0', _("No amends")),
)
def queryset(self, request, queryset):
if self.value() is None:
return queryset
- amended = queryset.filter(type__in=Bill.AMEND_MAP.values(), amend_of__isnull=False)
- if not self.value().startswith('-'):
- amended = amended.filter(is_open=False)
- amended_ids = amended.distinct().values_list('amend_of_id', flat=True)
- if self.value().endswith('1'):
- return queryset.filter(id__in=amended_ids)
- else:
- return queryset.exclude(id__in=amended_ids)
+ amended = queryset.filter(amends__isnull=False)
+ if self.value() == '1':
+ return amended.distinct()
+ elif self.value() == '2':
+ return amended.filter(amends__is_open=True).distinct()
+ elif self.value() == '3':
+ return amended.filter(amends__is_open=False).distinct()
+ elif self.value() == '0':
+ return queryset.filter(amends__isnull=True).distinct()
diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py
index 3b29356d..5a6453ed 100644
--- a/orchestra/contrib/bills/models.py
+++ b/orchestra/contrib/bills/models.py
@@ -1,6 +1,7 @@
import datetime
from dateutil.relativedelta import relativedelta
+from django.core.urlresolvers import reverse
from django.core.validators import ValidationError, RegexValidator
from django.db import models
from django.db.models import F, Sum
@@ -254,6 +255,9 @@ class Bill(models.Model):
return now + payment.get_due_delta()
return now + relativedelta(months=1)
+ def get_absolute_url(self):
+ return reverse('admin:bills_bill_view', args=(self.pk,))
+
def close(self, payment=False):
if not self.is_open:
raise TypeError("Bill not in Open state.")
diff --git a/orchestra/contrib/bills/templates/bills/microspective-proforma.html b/orchestra/contrib/bills/templates/bills/microspective-proforma.html
index 8efbfbd3..92400244 100644
--- a/orchestra/contrib/bills/templates/bills/microspective-proforma.html
+++ b/orchestra/contrib/bills/templates/bills/microspective-proforma.html
@@ -7,3 +7,7 @@
{% endwith %}
{% endblock %}
+
+
+{% block payment %}
+{% endblock %}
diff --git a/orchestra/contrib/bills/templates/bills/microspective.html b/orchestra/contrib/bills/templates/bills/microspective.html
index 52091115..6992d456 100644
--- a/orchestra/contrib/bills/templates/bills/microspective.html
+++ b/orchestra/contrib/bills/templates/bills/microspective.html
@@ -140,6 +140,7 @@
Action |
- ID |
- Service |
- Account |
- Content object |
- Registered on |
- Billed until |
- Cancelled on
- |
+ Action |
+ ID |
+ Service |
+ Account |
+ Content object |
+ Registered on |
+ Billed until |
+ Cancelled on |
+ Ignored |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
{{ action }} | +{{ action.capitalize }} | {% if order.pk %}{{ order.id }}{% endif %} | {{ order.service }} | {{ order.account }} | @@ -32,6 +32,7 @@{{ order.registered_on }} | {{ order.billed_unitl }} | {{ order.canncelled_on }} | +