Fixes on billing
This commit is contained in:
parent
47098ae398
commit
03f03328b8
10
TODO.md
10
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?
|
# 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
|
@register.filter
|
||||||
def comma(value):
|
def comma(value):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
@ -447,3 +443,9 @@ def comma(value):
|
||||||
return ','.join((left, right))
|
return ','.join((left, right))
|
||||||
return value
|
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
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import io
|
import io
|
||||||
import zipfile
|
import zipfile
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.admin import helpers
|
from django.contrib.admin import helpers
|
||||||
|
@ -35,7 +36,7 @@ view_bill.url_name = 'view'
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def close_bills(modeladmin, request, queryset):
|
def close_bills(modeladmin, request, queryset, action='close_bills'):
|
||||||
queryset = queryset.filter(is_open=True)
|
queryset = queryset.filter(is_open=True)
|
||||||
if not queryset:
|
if not queryset:
|
||||||
messages.warning(request, _("Selected bills should be in open state"))
|
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.</p>"
|
'content_message': _("Once a bill is closed it can not be further modified.</p>"
|
||||||
"<p>Please select a payment source for the selected bills"),
|
"<p>Please select a payment source for the selected bills"),
|
||||||
'action_name': 'Close bills',
|
'action_name': 'Close bills',
|
||||||
'action_value': 'close_bills',
|
'action_value': action,
|
||||||
'display_objects': [],
|
'display_objects': [],
|
||||||
'queryset': queryset,
|
'queryset': queryset,
|
||||||
'opts': opts,
|
'opts': opts,
|
||||||
|
@ -94,8 +95,11 @@ close_bills.verbose_name = _("Close")
|
||||||
close_bills.url_name = 'close'
|
close_bills.url_name = 'close'
|
||||||
|
|
||||||
|
|
||||||
@action_with_confirmation()
|
def send_bills_action(modeladmin, request, queryset):
|
||||||
def send_bills(modeladmin, request, queryset):
|
"""
|
||||||
|
raw function without confirmation
|
||||||
|
enables reuse on close_send_download_bills because of generic_confirmation.action_view
|
||||||
|
"""
|
||||||
for bill in queryset:
|
for bill in queryset:
|
||||||
if not validate_contact(request, bill):
|
if not validate_contact(request, bill):
|
||||||
return False
|
return False
|
||||||
|
@ -108,6 +112,11 @@ def send_bills(modeladmin, request, queryset):
|
||||||
_("One bill has been sent."),
|
_("One bill has been sent."),
|
||||||
_("%i bills have been sent.") % num,
|
_("%i bills have been sent.") % num,
|
||||||
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.verbose_name = lambda bill: _("Resend" if getattr(bill, 'is_sent', False) else "Send")
|
||||||
send_bills.url_name = 'send'
|
send_bills.url_name = 'send'
|
||||||
|
|
||||||
|
@ -131,9 +140,9 @@ download_bills.url_name = 'download'
|
||||||
|
|
||||||
|
|
||||||
def close_send_download_bills(modeladmin, request, queryset):
|
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':
|
if request.POST.get('post') == 'generic_confirmation':
|
||||||
response = send_bills(modeladmin, request, queryset)
|
response = send_bills_action(modeladmin, request, queryset)
|
||||||
if response is False:
|
if response is False:
|
||||||
return
|
return
|
||||||
return download_bills(modeladmin, request, queryset)
|
return download_bills(modeladmin, request, queryset)
|
||||||
|
@ -282,7 +291,20 @@ amend_bills.url_name = 'amend'
|
||||||
|
|
||||||
|
|
||||||
def report(modeladmin, request, queryset):
|
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 = {
|
context = {
|
||||||
|
'subtotals': subtotals,
|
||||||
|
'total': total,
|
||||||
'bills': queryset,
|
'bills': queryset,
|
||||||
'currency': settings.BILLS_CURRENCY,
|
'currency': settings.BILLS_CURRENCY,
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,7 +184,7 @@ class BillLineManagerAdmin(BillLineAdmin):
|
||||||
|
|
||||||
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = (
|
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'
|
'num_lines', 'display_total', 'display_payment_state', 'is_open', 'is_sent'
|
||||||
)
|
)
|
||||||
list_filter = (
|
list_filter = (
|
||||||
|
@ -218,7 +218,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
inlines = [BillLineInline, ClosedBillLineInline]
|
inlines = [BillLineInline, ClosedBillLineInline]
|
||||||
date_hierarchy = 'closed_on'
|
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')
|
amend_of_link = admin_link('amend_of')
|
||||||
|
|
||||||
def amend_links(self, bill):
|
def amend_links(self, bill):
|
||||||
|
|
|
@ -14,6 +14,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra.contrib.accounts.models import Account
|
from orchestra.contrib.accounts.models import Account
|
||||||
from orchestra.contrib.contacts.models import Contact
|
from orchestra.contrib.contacts.models import Contact
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
|
from orchestra.utils.functional import cached
|
||||||
from orchestra.utils.html import html_to_pdf
|
from orchestra.utils.html import html_to_pdf
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
@ -205,7 +206,7 @@ class Bill(models.Model):
|
||||||
if not self.is_open:
|
if not self.is_open:
|
||||||
return self.total
|
return self.total
|
||||||
try:
|
try:
|
||||||
return round(self.computed_total, 2)
|
return round(self.computed_total or 0, 2)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.computed_total = self.compute_total()
|
self.computed_total = self.compute_total()
|
||||||
return self.computed_total
|
return self.computed_total
|
||||||
|
@ -328,6 +329,7 @@ class Bill(models.Model):
|
||||||
self.number = self.get_number()
|
self.number = self.get_number()
|
||||||
super(Bill, self).save(*args, **kwargs)
|
super(Bill, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
@cached
|
||||||
def compute_subtotals(self):
|
def compute_subtotals(self):
|
||||||
subtotals = {}
|
subtotals = {}
|
||||||
lines = self.lines.annotate(totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)))
|
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))
|
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
|
||||||
return subtotals
|
return subtotals
|
||||||
|
|
||||||
|
@cached
|
||||||
def compute_base(self):
|
def compute_base(self):
|
||||||
bases = self.lines.annotate(
|
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)
|
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):
|
def compute_total(self):
|
||||||
totals = self.lines.annotate(
|
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)
|
return round(totals.aggregate(Sum('totals'))['totals__sum'] or 0, 2)
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,16 @@
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@page {
|
@page {
|
||||||
size: 11.69in 8.27in;
|
size: 11.69in 8.27in;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
font-family: sans;
|
font-family: sans;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
max-width: 10in;
|
max-width: 10in;
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
.item.column-name {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
table tr:nth-child(even) {
|
table tr:nth-child(even) {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
|
@ -23,6 +27,7 @@
|
||||||
color: white;
|
color: white;
|
||||||
background-color: grey;
|
background-color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item.column-base, .item.column-vat, .item.column-total, .item.column-number {
|
.item.column-base, .item.column-vat, .item.column-total, .item.column-number {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
@ -32,8 +37,30 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<table>
|
<table id="summary">
|
||||||
<tr id="transaction">
|
<tr class="header">
|
||||||
|
<th class="title column-name">{% trans "Summary" %}</th>
|
||||||
|
<th class="title column-total">{% trans "Total" %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for tax, subtotal in subtotals.items %}
|
||||||
|
<tr>
|
||||||
|
<td class="item column-name">{% trans "subtotal" %} {{ tax }}% {% trans "VAT" %}</td>
|
||||||
|
<td class="item column-total">{{ subtotal|first}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="item column-name">{% trans "taxes" %} {{ tax }}% {% trans "VAT" %}</td>
|
||||||
|
<td class="item column-total">{{ subtotal|last}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td class="item column-name"><b>{% trans "TOTAL" %}</b></td>
|
||||||
|
<td class="item column-total"><b>{{ total }}</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<table id="main">
|
||||||
|
<tr class="header">
|
||||||
<th class="title column-number">{% trans "Number" %}</th>
|
<th class="title column-number">{% trans "Number" %}</th>
|
||||||
<th class="title column-vat-number">{% trans "VAT number" %}</th>
|
<th class="title column-vat-number">{% trans "VAT number" %}</th>
|
||||||
<th class="title column-billcontant">{% trans "Contact" %}</th>
|
<th class="title column-billcontant">{% trans "Contact" %}</th>
|
||||||
|
|
|
@ -172,14 +172,14 @@ class Domain(models.Model):
|
||||||
type=Record.MX,
|
type=Record.MX,
|
||||||
value=mx
|
value=mx
|
||||||
))
|
))
|
||||||
if not has_a:
|
# A and AAAA point to the same default host
|
||||||
|
if not has_a and not has_aaaa:
|
||||||
default_a = settings.DOMAINS_DEFAULT_A
|
default_a = settings.DOMAINS_DEFAULT_A
|
||||||
if default_a:
|
if default_a:
|
||||||
records.append(AttrDict(
|
records.append(AttrDict(
|
||||||
type=Record.A,
|
type=Record.A,
|
||||||
value=default_a
|
value=default_a
|
||||||
))
|
))
|
||||||
if not has_aaaa:
|
|
||||||
default_aaaa = settings.DOMAINS_DEFAULT_AAAA
|
default_aaaa = settings.DOMAINS_DEFAULT_AAAA
|
||||||
if default_aaaa:
|
if default_aaaa:
|
||||||
records.append(AttrDict(
|
records.append(AttrDict(
|
||||||
|
|
|
@ -15,10 +15,8 @@ from . import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
paramiko_connections = {}
|
|
||||||
|
|
||||||
|
def Paramiko(backend, log, server, cmds, async=False, paramiko_connections={}):
|
||||||
def Paramiko(backend, log, server, cmds, async=False):
|
|
||||||
"""
|
"""
|
||||||
Executes cmds to remote server using Pramaiko
|
Executes cmds to remote server using Pramaiko
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
@ -144,3 +145,30 @@ def mark_as_not_ignored(modeladmin, request, queryset):
|
||||||
_("%i selected orders have been marked as not ignored.") % num,
|
_("%i selected orders have been marked as not ignored.") % num,
|
||||||
num)
|
num)
|
||||||
modeladmin.message_user(request, msg)
|
modeladmin.message_user(request, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def report(modeladmin, request, queryset):
|
||||||
|
services = {}
|
||||||
|
totals = [0, 0, None, 0]
|
||||||
|
now = timezone.now().date()
|
||||||
|
for order in queryset.select_related('service'):
|
||||||
|
name = order.service.description
|
||||||
|
active, cancelled = (1, 0) if not order.cancelled_on or order.cancelled_on > now else (0, 1)
|
||||||
|
try:
|
||||||
|
info = services[name]
|
||||||
|
except KeyError:
|
||||||
|
nominal_price = order.service.nominal_price
|
||||||
|
info = [active, cancelled, nominal_price, 1]
|
||||||
|
services[name] = info
|
||||||
|
else:
|
||||||
|
info[0] += active
|
||||||
|
info[1] += cancelled
|
||||||
|
info[3] += 1
|
||||||
|
totals[0] += active
|
||||||
|
totals[1] += cancelled
|
||||||
|
totals[3] += 1
|
||||||
|
context = {
|
||||||
|
'services': sorted(services.items(), key=lambda n: -n[1][0]),
|
||||||
|
'totals': totals,
|
||||||
|
}
|
||||||
|
return render(request, 'admin/orders/order/report.html', context)
|
||||||
|
|
|
@ -11,7 +11,7 @@ from orchestra.admin.utils import admin_link, admin_date
|
||||||
from orchestra.contrib.accounts.admin import AccountAdminMixin
|
from orchestra.contrib.accounts.admin import AccountAdminMixin
|
||||||
from orchestra.utils.humanize import naturaldate
|
from orchestra.utils.humanize import naturaldate
|
||||||
|
|
||||||
from .actions import BillSelectedOrders, mark_as_ignored, mark_as_not_ignored
|
from .actions import BillSelectedOrders, mark_as_ignored, mark_as_not_ignored, report
|
||||||
from .filters import IgnoreOrderListFilter, ActiveOrderListFilter, BilledOrderListFilter
|
from .filters import IgnoreOrderListFilter, ActiveOrderListFilter, BilledOrderListFilter
|
||||||
from .models import Order, MetricStorage
|
from .models import Order, MetricStorage
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
default_changelist_filters = (
|
default_changelist_filters = (
|
||||||
('ignore', '0'),
|
('ignore', '0'),
|
||||||
)
|
)
|
||||||
actions = (BillSelectedOrders(), mark_as_ignored, mark_as_not_ignored)
|
actions = (BillSelectedOrders(), mark_as_ignored, mark_as_not_ignored, report)
|
||||||
change_view_actions = (BillSelectedOrders(), mark_as_ignored, mark_as_not_ignored)
|
change_view_actions = (BillSelectedOrders(), mark_as_ignored, mark_as_not_ignored)
|
||||||
date_hierarchy = 'registered_on'
|
date_hierarchy = 'registered_on'
|
||||||
inlines = (MetricStorageInline,)
|
inlines = (MetricStorageInline,)
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
{% load i18n utils %}
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Transaction Report</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<style type="text/css">
|
||||||
|
@page {
|
||||||
|
size: 11.69in 8.27in;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
max-width: 10in;
|
||||||
|
font-family: sans;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
table tr:nth-child(even) {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
table tr:nth-child(odd) {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
table th {
|
||||||
|
color: white;
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
.item.column-created, .item.column-updated {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.item.column-amount {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table id="summary">
|
||||||
|
<tr class="header">
|
||||||
|
<th class="title column-name">{% trans "Services" %}</th>
|
||||||
|
<th class="title column-active">{% trans "Active" %}</th>
|
||||||
|
<th class="title column-cancelled">{% trans "Cancelled" %}</th>
|
||||||
|
<th class="title column-nominal-price">{% trans "Nominal price" %}</th>
|
||||||
|
<th class="title column-number">{% trans "Number" %}</th>
|
||||||
|
<th class="title column-number">{% trans "Profit" %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for service, info in services %}
|
||||||
|
<tr>
|
||||||
|
<td class="item column-name">{{ service }}</td>
|
||||||
|
<td class="item column-amount">{{ info.0 }}</td>
|
||||||
|
<td class="item column-amount">{{ info.1 }}</td>
|
||||||
|
<td class="item column-amount">{{ info.2 }}</td>
|
||||||
|
<td class="item column-amount">{{ info.3 }}</td>
|
||||||
|
<td class="item column-amount">{{ info.2|mul:info.3 }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td class="item column-name"><b>{% trans "TOTAL" %}</b></td>
|
||||||
|
<td class="item column-amount"><b>{{ totals.0 }}</b></td>
|
||||||
|
<td class="item column-amount">{{ totals.1 }}</td>
|
||||||
|
<td class="item column-amount">{{ totals.2 }}</td>
|
||||||
|
<td class="item column-amount">{{ totals.3 }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
# TODO calculate profit better: order.get_price() for everyperiod / metric, etc
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -190,7 +190,18 @@ def report(modeladmin, request, queryset):
|
||||||
else:
|
else:
|
||||||
transactions = queryset.values_list('transactions__id', flat=True).distinct()
|
transactions = queryset.values_list('transactions__id', flat=True).distinct()
|
||||||
transactions = Transaction.objects.filter(id__in=transactions)
|
transactions = Transaction.objects.filter(id__in=transactions)
|
||||||
|
states = {}
|
||||||
|
total = 0
|
||||||
|
for transaction in transactions:
|
||||||
|
state = transaction.get_state_display()
|
||||||
|
try:
|
||||||
|
states[state] += transaction.amount
|
||||||
|
except KeyError:
|
||||||
|
states[state] = transaction.amount
|
||||||
|
total += transaction.amount
|
||||||
context = {
|
context = {
|
||||||
'transactions': transactions
|
'states': states,
|
||||||
|
'total': total,
|
||||||
|
'transactions': transactions,
|
||||||
}
|
}
|
||||||
return render(request, 'admin/payments/transaction/report.html', context)
|
return render(request, 'admin/payments/transaction/report.html', context)
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
size: 11.69in 8.27in;
|
size: 11.69in 8.27in;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
|
max-width: 10in;
|
||||||
font-family: sans;
|
font-family: sans;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
max-width: 10in;
|
|
||||||
}
|
}
|
||||||
table tr:nth-child(even) {
|
table tr:nth-child(even) {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
|
@ -23,9 +23,34 @@
|
||||||
color: white;
|
color: white;
|
||||||
background-color: grey;
|
background-color: grey;
|
||||||
}
|
}
|
||||||
|
.item.column-created, .item.column-updated {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.item.column-amount {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<table id="summary">
|
||||||
|
<tr class="header">
|
||||||
|
<th class="title column-name">{% trans "Summary" %}</th>
|
||||||
|
<th class="title column-amount">{% trans "Amount" %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for state, amount in states.items %}
|
||||||
|
<tr>
|
||||||
|
<td class="item column-name">{{ state }}</td>
|
||||||
|
<td class="item column-amount">{{ amount }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr>
|
||||||
|
<td class="item column-name"><b>{% trans "TOTAL" %}</b></td>
|
||||||
|
<td class="item column-amount"><b>{{ total }}</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr id="transaction">
|
<tr id="transaction">
|
||||||
<th class="title column-id">ID</th>
|
<th class="title column-id">ID</th>
|
||||||
|
@ -47,8 +72,8 @@
|
||||||
<td class="item column-iban">{{ transaction.source.data.iban }}</td>
|
<td class="item column-iban">{{ transaction.source.data.iban }}</td>
|
||||||
<td class="item column-amount">{{ transaction.amount }}</td>
|
<td class="item column-amount">{{ transaction.amount }}</td>
|
||||||
<td class="item column-state">{{ transaction.get_state_display }}</td>
|
<td class="item column-state">{{ transaction.get_state_display }}</td>
|
||||||
<td class="item column-state">{{ transaction.created_at|date }}</td>
|
<td class="item column-created">{{ transaction.created_at|date }}</td>
|
||||||
<td class="item column-state">{{ transaction.modified_at|date }}</td>
|
<td class="item column-updated">{% if transaction.created_at|date != transaction.modified_at|date %}{{ transaction.modified_at|date }}{% else %} --- {% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -100,3 +100,9 @@ def isactive(obj):
|
||||||
@register.filter
|
@register.filter
|
||||||
def sub(value, arg):
|
def sub(value, arg):
|
||||||
return value - arg
|
return value - arg
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def mul(value, arg):
|
||||||
|
return value * arg
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue