From 03f03328b81d53755a74555d56f14a729131360e Mon Sep 17 00:00:00 2001
From: Marc Aymerich
Date: Fri, 10 Jul 2015 13:00:51 +0000
Subject: [PATCH] Fixes on billing
---
TODO.md | 10 +--
orchestra/contrib/bills/actions.py | 34 ++++++++--
orchestra/contrib/bills/admin.py | 4 +-
orchestra/contrib/bills/models.py | 17 ++++-
.../bills/templates/admin/bills/report.html | 39 +++++++++--
orchestra/contrib/domains/models.py | 4 +-
orchestra/contrib/orchestration/methods.py | 4 +-
orchestra/contrib/orders/actions.py | 28 ++++++++
orchestra/contrib/orders/admin.py | 4 +-
.../templates/admin/orders/order/report.html | 66 +++++++++++++++++++
orchestra/contrib/payments/actions.py | 13 +++-
.../admin/payments/transaction/report.html | 31 ++++++++-
orchestra/templatetags/utils.py | 6 ++
13 files changed, 228 insertions(+), 32 deletions(-)
create mode 100644 orchestra/contrib/orders/templates/admin/orders/order/report.html
diff --git a/TODO.md b/TODO.md
index f4334e58..bf93ff5c 100644
--- a/TODO.md
+++ b/TODO.md
@@ -435,10 +435,6 @@ serailzer self.instance on create.
# process monitor data to represent state, or maybe create new resource datas when period expires?
-
-# Automatically mark as paid transactions with 0 or prevent its creation?
-
-
@register.filter
def comma(value):
value = str(value)
@@ -447,3 +443,9 @@ def comma(value):
return ','.join((left, right))
return value
+
+# FIX CLOSE SEND DOWNLOAD
+
+# payment/bill report allow to change template using a setting variable
+# Payment transaction stats
+# order stats: service, cost, top profit, etc
diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py
index d3f03862..945b696a 100644
--- a/orchestra/contrib/bills/actions.py
+++ b/orchestra/contrib/bills/actions.py
@@ -1,6 +1,7 @@
import io
import zipfile
from datetime import date
+from decimal import Decimal
from django.contrib import messages
from django.contrib.admin import helpers
@@ -35,7 +36,7 @@ view_bill.url_name = 'view'
@transaction.atomic
-def close_bills(modeladmin, request, queryset):
+def close_bills(modeladmin, request, queryset, action='close_bills'):
queryset = queryset.filter(is_open=True)
if not queryset:
messages.warning(request, _("Selected bills should be in open state"))
@@ -80,7 +81,7 @@ def close_bills(modeladmin, request, queryset):
'content_message': _("Once a bill is closed it can not be further modified.
"
"Please select a payment source for the selected bills"),
'action_name': 'Close bills',
- 'action_value': 'close_bills',
+ 'action_value': action,
'display_objects': [],
'queryset': queryset,
'opts': opts,
@@ -94,8 +95,11 @@ close_bills.verbose_name = _("Close")
close_bills.url_name = 'close'
-@action_with_confirmation()
-def send_bills(modeladmin, request, queryset):
+def send_bills_action(modeladmin, request, queryset):
+ """
+ raw function without confirmation
+ enables reuse on close_send_download_bills because of generic_confirmation.action_view
+ """
for bill in queryset:
if not validate_contact(request, bill):
return False
@@ -108,6 +112,11 @@ def send_bills(modeladmin, request, queryset):
_("One bill has been sent."),
_("%i bills have been sent.") % num,
num))
+
+
+@action_with_confirmation()
+def send_bills(modeladmin, request, queryset):
+ return send_bills_action(modeladmin, request, queryset)
send_bills.verbose_name = lambda bill: _("Resend" if getattr(bill, 'is_sent', False) else "Send")
send_bills.url_name = 'send'
@@ -131,9 +140,9 @@ download_bills.url_name = 'download'
def close_send_download_bills(modeladmin, request, queryset):
- response = close_bills(modeladmin, request, queryset)
+ response = close_bills(modeladmin, request, queryset, action='close_send_download_bills')
if request.POST.get('post') == 'generic_confirmation':
- response = send_bills(modeladmin, request, queryset)
+ response = send_bills_action(modeladmin, request, queryset)
if response is False:
return
return download_bills(modeladmin, request, queryset)
@@ -282,7 +291,20 @@ amend_bills.url_name = 'amend'
def report(modeladmin, request, queryset):
+ subtotals = {}
+ total = 0
+ for bill in queryset:
+ for tax, subtotal in bill.compute_subtotals().items():
+ try:
+ subtotals[tax][0] += subtotal[0]
+ except KeyError:
+ subtotals[tax] = subtotal
+ else:
+ subtotals[tax][1] += subtotal[1]
+ total += bill.get_total()
context = {
+ 'subtotals': subtotals,
+ 'total': total,
'bills': queryset,
'currency': settings.BILLS_CURRENCY,
}
diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py
index 1cabf881..7cc0096c 100644
--- a/orchestra/contrib/bills/admin.py
+++ b/orchestra/contrib/bills/admin.py
@@ -184,7 +184,7 @@ class BillLineManagerAdmin(BillLineAdmin):
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
list_display = (
- 'number', 'type_link', 'account_link', 'created_on_display',
+ 'number', 'type_link', 'account_link', 'updated_on_display',
'num_lines', 'display_total', 'display_payment_state', 'is_open', 'is_sent'
)
list_filter = (
@@ -218,7 +218,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
inlines = [BillLineInline, ClosedBillLineInline]
date_hierarchy = 'closed_on'
- created_on_display = admin_date('created_on', short_description=_("Created"))
+ updated_on_display = admin_date('updated_on', short_description=_("Updated"))
amend_of_link = admin_link('amend_of')
def amend_links(self, bill):
diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py
index aabb3305..78c15b20 100644
--- a/orchestra/contrib/bills/models.py
+++ b/orchestra/contrib/bills/models.py
@@ -14,6 +14,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.accounts.models import Account
from orchestra.contrib.contacts.models import Contact
from orchestra.core import validators
+from orchestra.utils.functional import cached
from orchestra.utils.html import html_to_pdf
from . import settings
@@ -205,7 +206,7 @@ class Bill(models.Model):
if not self.is_open:
return self.total
try:
- return round(self.computed_total, 2)
+ return round(self.computed_total or 0, 2)
except AttributeError:
self.computed_total = self.compute_total()
return self.computed_total
@@ -328,6 +329,7 @@ class Bill(models.Model):
self.number = self.get_number()
super(Bill, self).save(*args, **kwargs)
+ @cached
def compute_subtotals(self):
subtotals = {}
lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)))
@@ -337,15 +339,24 @@ class Bill(models.Model):
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
return subtotals
+ @cached
def compute_base(self):
bases = self.lines.annotate(
- bases=F('subtotal') + Coalesce(F('sublines__total'), 0)
+ bases=Sum(F('subtotal') + Coalesce(F('sublines__total'), 0))
)
return round(bases.aggregate(Sum('bases'))['bases__sum'] or 0, 2)
+ @cached
+ def compute_tax(self):
+ taxes = self.lines.annotate(
+ taxes=Sum((F('subtotal') + Coalesce(F('sublines__total'), 0)) * (F('tax')/100))
+ )
+ return round(taxes.aggregate(Sum('taxes'))['taxes__sum'] or 0, 2)
+
+ @cached
def compute_total(self):
totals = self.lines.annotate(
- totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100)
+ totals=Sum((F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100))
)
return round(totals.aggregate(Sum('totals'))['totals__sum'] or 0, 2)
diff --git a/orchestra/contrib/bills/templates/admin/bills/report.html b/orchestra/contrib/bills/templates/admin/bills/report.html
index 61d74426..4a26ec78 100644
--- a/orchestra/contrib/bills/templates/admin/bills/report.html
+++ b/orchestra/contrib/bills/templates/admin/bills/report.html
@@ -6,12 +6,16 @@
-
-
+
+
+{% for tax, subtotal in subtotals.items %}
+
+ {% trans "subtotal" %} {{ tax }}% {% trans "VAT" %} |
+ {{ subtotal|first}} |
+
+
+ {% trans "taxes" %} {{ tax }}% {% trans "VAT" %} |
+ {{ subtotal|last}} |
+
+{% endfor %}
+
+ {% trans "TOTAL" %} |
+ {{ total }} |
+
+
+
+
+