Fixes on bills
This commit is contained in:
parent
03f03328b8
commit
e1eda7a7d5
15
TODO.md
15
TODO.md
|
@ -444,8 +444,19 @@ def comma(value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
# FIX CLOSE SEND DOWNLOAD
|
|
||||||
|
|
||||||
# payment/bill report allow to change template using a setting variable
|
# payment/bill report allow to change template using a setting variable
|
||||||
# Payment transaction stats
|
# Payment transaction stats, graps over time
|
||||||
# order stats: service, cost, top profit, etc
|
# order stats: service, cost, top profit, etc
|
||||||
|
# TODO remove bill.total
|
||||||
|
|
||||||
|
|
||||||
|
reporter.stories_filed = F('stories_filed') + 1
|
||||||
|
reporter.save()
|
||||||
|
In order to access the new value that has been saved in this way, the object will need to be reloaded:
|
||||||
|
https://docs.djangoproject.com/en/dev/ref/models/conditional-expressions/
|
||||||
|
Greatest
|
||||||
|
Colaesce('total', 'computed_total')
|
||||||
|
Case
|
||||||
|
|
||||||
|
# case on payment transaction state ? case when trans.amount >
|
||||||
|
|
|
@ -163,7 +163,7 @@ function install_requirements () {
|
||||||
|
|
||||||
# Install a more recent version of wkhtmltopdf (0.12.2) (PDF page number support)
|
# Install a more recent version of wkhtmltopdf (0.12.2) (PDF page number support)
|
||||||
wkhtmltox=$(mktemp)
|
wkhtmltox=$(mktemp)
|
||||||
wget http://downloads.sourceforge.net/wkhtmltopdf/wkhtmltox-0.12.2.1_linux-jessie-amd64.deb -O ${wkhtmltox}
|
wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2.1/wkhtmltox-0.12.2.1_linux-jessie-amd64.deb -O ${wkhtmltox}
|
||||||
dpkg -i ${wkhtmltox}
|
dpkg -i ${wkhtmltox}
|
||||||
|
|
||||||
# Make sure locales are in place before installing postgres
|
# Make sure locales are in place before installing postgres
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.core.urlresolvers import reverse
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.utils import translation
|
from django.utils import translation, 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 _
|
||||||
|
|
||||||
|
@ -134,7 +134,9 @@ def download_bills(modeladmin, request, queryset):
|
||||||
return response
|
return response
|
||||||
bill = queryset.get()
|
bill = queryset.get()
|
||||||
pdf = bill.as_pdf()
|
pdf = bill.as_pdf()
|
||||||
return HttpResponse(pdf, content_type='application/pdf')
|
response = HttpResponse(pdf, content_type='application/pdf')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % bill.number
|
||||||
|
return response
|
||||||
download_bills.verbose_name = _("Download")
|
download_bills.verbose_name = _("Download")
|
||||||
download_bills.url_name = 'download'
|
download_bills.url_name = 'download'
|
||||||
|
|
||||||
|
@ -290,7 +292,7 @@ amend_bills.verbose_name = _("Amend")
|
||||||
amend_bills.url_name = 'amend'
|
amend_bills.url_name = 'amend'
|
||||||
|
|
||||||
|
|
||||||
def report(modeladmin, request, queryset):
|
def bill_report(modeladmin, request, queryset):
|
||||||
subtotals = {}
|
subtotals = {}
|
||||||
total = 0
|
total = 0
|
||||||
for bill in queryset:
|
for bill in queryset:
|
||||||
|
@ -301,11 +303,54 @@ def report(modeladmin, request, queryset):
|
||||||
subtotals[tax] = subtotal
|
subtotals[tax] = subtotal
|
||||||
else:
|
else:
|
||||||
subtotals[tax][1] += subtotal[1]
|
subtotals[tax][1] += subtotal[1]
|
||||||
total += bill.get_total()
|
total += bill.compute_total()
|
||||||
context = {
|
context = {
|
||||||
'subtotals': subtotals,
|
'subtotals': subtotals,
|
||||||
'total': total,
|
'total': total,
|
||||||
'bills': queryset,
|
'bills': queryset,
|
||||||
'currency': settings.BILLS_CURRENCY,
|
'currency': settings.BILLS_CURRENCY,
|
||||||
}
|
}
|
||||||
return render(request, 'admin/bills/report.html', context)
|
return render(request, 'admin/bills/bill/report.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def service_report(modeladmin, request, queryset):
|
||||||
|
services = {}
|
||||||
|
totals = [0, 0, 0, 0, 0]
|
||||||
|
now = timezone.now().date()
|
||||||
|
if queryset.model == Bill:
|
||||||
|
queryset = BillLine.objects.filter(bill_id__in=queryset.values_list('id', flat=True))
|
||||||
|
# Filter amends
|
||||||
|
queryset = queryset.filter(bill__amend_of__isnull=True)
|
||||||
|
for line in queryset.select_related('order__service').prefetch_related('sublines'):
|
||||||
|
order, service = None, None
|
||||||
|
if line.order_id:
|
||||||
|
order = line.order
|
||||||
|
service = order.service
|
||||||
|
name = service.description
|
||||||
|
active, cancelled = (1, 0) if not order.cancelled_on or order.cancelled_on > now else (0, 1)
|
||||||
|
nominal_price = order.service.nominal_price
|
||||||
|
else:
|
||||||
|
name = '*%s' % line.description
|
||||||
|
active = 1
|
||||||
|
cancelled = 0
|
||||||
|
nominal_price = 0
|
||||||
|
try:
|
||||||
|
info = services[name]
|
||||||
|
except KeyError:
|
||||||
|
info = [active, cancelled, nominal_price, line.quantity or 1, line.compute_total()]
|
||||||
|
services[name] = info
|
||||||
|
else:
|
||||||
|
info[0] += active
|
||||||
|
info[1] += cancelled
|
||||||
|
info[3] += line.quantity or 1
|
||||||
|
info[4] += line.compute_total()
|
||||||
|
totals[0] += active
|
||||||
|
totals[1] += cancelled
|
||||||
|
totals[2] += nominal_price
|
||||||
|
totals[3] += line.quantity or 1
|
||||||
|
totals[4] += line.compute_total()
|
||||||
|
context = {
|
||||||
|
'services': sorted(services.items(), key=lambda n: -n[1][4]),
|
||||||
|
'totals': totals,
|
||||||
|
}
|
||||||
|
return render(request, 'admin/bills/billline/report.html', context)
|
||||||
|
|
|
@ -88,7 +88,7 @@ class ClosedBillLineInline(BillLineInline):
|
||||||
display_description.allow_tags = True
|
display_description.allow_tags = True
|
||||||
|
|
||||||
def display_subtotal(self, line):
|
def display_subtotal(self, line):
|
||||||
subtotals = [' ' + str(line.subtotal)]
|
subtotals = [' ' + str(line.subtotal)]
|
||||||
for subline in line.sublines.all():
|
for subline in line.sublines.all():
|
||||||
subtotals.append(str(subline.total))
|
subtotals.append(str(subline.total))
|
||||||
return '<br>'.join(subtotals)
|
return '<br>'.join(subtotals)
|
||||||
|
@ -112,9 +112,11 @@ class BillLineAdmin(admin.ModelAdmin):
|
||||||
'description', 'bill_link', 'display_is_open', 'account_link', 'rate', 'quantity', 'tax',
|
'description', 'bill_link', 'display_is_open', 'account_link', 'rate', 'quantity', 'tax',
|
||||||
'subtotal', 'display_sublinetotal', 'display_total'
|
'subtotal', 'display_sublinetotal', 'display_total'
|
||||||
)
|
)
|
||||||
actions = (actions.undo_billing, actions.move_lines, actions.copy_lines,)
|
actions = (
|
||||||
list_filter = ('tax', ('bill', admin.RelatedOnlyFieldListFilter), 'bill__is_open')
|
actions.undo_billing, actions.move_lines, actions.copy_lines, actions.service_report
|
||||||
list_select_related = ('bill',)
|
)
|
||||||
|
list_filter = ('tax', 'bill__is_open', 'order__service')
|
||||||
|
list_select_related = ('bill', 'bill__account')
|
||||||
search_fields = ('description', 'bill__number')
|
search_fields = ('description', 'bill__number')
|
||||||
|
|
||||||
account_link = admin_link('bill__account')
|
account_link = admin_link('bill__account')
|
||||||
|
@ -139,9 +141,7 @@ class BillLineAdmin(admin.ModelAdmin):
|
||||||
qs = super(BillLineAdmin, self).get_queryset(request)
|
qs = super(BillLineAdmin, self).get_queryset(request)
|
||||||
qs = qs.annotate(
|
qs = qs.annotate(
|
||||||
subline_total=Sum('sublines__total'),
|
subline_total=Sum('sublines__total'),
|
||||||
computed_total=Sum(
|
computed_total=(F('subtotal') + Sum(Coalesce('sublines__total', 0))) * (1+F('tax')/100),
|
||||||
(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
'fields': ('html',),
|
'fields': ('html',),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
list_prefetch_related = ('transactions',)
|
list_prefetch_related = ('transactions', 'lines__sublines')
|
||||||
search_fields = ('number', 'account__username', 'comments')
|
search_fields = ('number', 'account__username', 'comments')
|
||||||
change_view_actions = [
|
change_view_actions = [
|
||||||
actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills,
|
actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills,
|
||||||
|
@ -211,7 +211,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
]
|
]
|
||||||
actions = [
|
actions = [
|
||||||
actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills,
|
actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills,
|
||||||
actions.amend_bills, actions.report, actions.close_send_download_bills,
|
actions.amend_bills, actions.bill_report, actions.service_report,
|
||||||
|
actions.close_send_download_bills,
|
||||||
]
|
]
|
||||||
change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link', 'amend_links')
|
change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link', 'amend_links')
|
||||||
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')
|
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')
|
||||||
|
@ -236,10 +237,10 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
num_lines.short_description = _("lines")
|
num_lines.short_description = _("lines")
|
||||||
|
|
||||||
def display_total(self, bill):
|
def display_total(self, bill):
|
||||||
return "%s &%s;" % (round(bill.computed_total or 0, 2), settings.BILLS_CURRENCY.lower())
|
return "%s &%s;" % (bill.compute_total(), settings.BILLS_CURRENCY.lower())
|
||||||
display_total.allow_tags = True
|
display_total.allow_tags = True
|
||||||
display_total.short_description = _("total")
|
display_total.short_description = _("total")
|
||||||
display_total.admin_order_field = 'computed_total'
|
display_total.admin_order_field = 'approx_total'
|
||||||
|
|
||||||
def type_link(self, bill):
|
def type_link(self, bill):
|
||||||
bill_type = bill.type.lower()
|
bill_type = bill.type.lower()
|
||||||
|
@ -309,8 +310,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
def get_inline_instances(self, request, obj=None):
|
def get_inline_instances(self, request, obj=None):
|
||||||
inlines = super(BillAdmin, self).get_inline_instances(request, obj)
|
inlines = super(BillAdmin, self).get_inline_instances(request, obj)
|
||||||
if obj and not obj.is_open:
|
if obj and not obj.is_open:
|
||||||
return [inline for inline in inlines if not isinstance(inline, BillLineInline)]
|
return [inline for inline in inlines if type(inline) != BillLineInline]
|
||||||
return [inline for inline in inlines if not isinstance(inline, ClosedBillLineInline)]
|
return [inline for inline in inlines if type(inline) != ClosedBillLineInline]
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Make value input widget bigger """
|
""" Make value input widget bigger """
|
||||||
|
@ -327,9 +328,10 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
qs = super(BillAdmin, self).get_queryset(request)
|
qs = super(BillAdmin, self).get_queryset(request)
|
||||||
qs = qs.annotate(
|
qs = qs.annotate(
|
||||||
models.Count('lines'),
|
models.Count('lines'),
|
||||||
computed_total=Sum(
|
# FIXME https://code.djangoproject.com/ticket/10060
|
||||||
(F('lines__subtotal') + Coalesce(F('lines__sublines__total'), 0)) * (1+F('lines__tax')/100)
|
approx_total=Coalesce(Sum(
|
||||||
),
|
(F('lines__subtotal') + Coalesce('lines__sublines__total', 0)) * (1+F('lines__tax')/100),
|
||||||
|
), 0),
|
||||||
)
|
)
|
||||||
qs = qs.prefetch_related(
|
qs = qs.prefetch_related(
|
||||||
Prefetch('amends', queryset=Bill.objects.filter(is_open=False), to_attr='closed_amends')
|
Prefetch('amends', queryset=Bill.objects.filter(is_open=False), to_attr='closed_amends')
|
||||||
|
|
|
@ -22,7 +22,7 @@ class SelectSourceForm(forms.ModelForm):
|
||||||
super(SelectSourceForm, self).__init__(*args, **kwargs)
|
super(SelectSourceForm, self).__init__(*args, **kwargs)
|
||||||
bill = kwargs.get('instance')
|
bill = kwargs.get('instance')
|
||||||
if bill:
|
if bill:
|
||||||
total = bill.get_total()
|
total = bill.compute_total()
|
||||||
sources = bill.account.paymentsources.filter(is_active=True)
|
sources = bill.account.paymentsources.filter(is_active=True)
|
||||||
recharge = bool(total < 0)
|
recharge = bool(total < 0)
|
||||||
choices = [(None, '-----------')]
|
choices = [(None, '-----------')]
|
||||||
|
|
Binary file not shown.
|
@ -183,7 +183,7 @@ msgstr "Factura"
|
||||||
|
|
||||||
#: filters.py:21 models.py:88
|
#: filters.py:21 models.py:88
|
||||||
msgid "Amendment invoice"
|
msgid "Amendment invoice"
|
||||||
msgstr "Factura rectificative"
|
msgstr "Factura rectificativa"
|
||||||
|
|
||||||
#: filters.py:22 models.py:89
|
#: filters.py:22 models.py:89
|
||||||
msgid "Fee"
|
msgid "Fee"
|
||||||
|
|
|
@ -165,7 +165,7 @@ class Bill(models.Model):
|
||||||
else:
|
else:
|
||||||
raise TypeError("Unknown state")
|
raise TypeError("Unknown state")
|
||||||
ongoing = bool(secured != 0 or created or processed or executed)
|
ongoing = bool(secured != 0 or created or processed or executed)
|
||||||
total = self.get_total()
|
total = self.compute_total()
|
||||||
if total >= 0:
|
if total >= 0:
|
||||||
if secured >= total:
|
if secured >= total:
|
||||||
return self.PAID
|
return self.PAID
|
||||||
|
@ -202,15 +202,6 @@ class Bill(models.Model):
|
||||||
'amend_of': _("Type %s requires an amend of link.") % self.get_type_display()
|
'amend_of': _("Type %s requires an amend of link.") % self.get_type_display()
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_total(self):
|
|
||||||
if not self.is_open:
|
|
||||||
return self.total
|
|
||||||
try:
|
|
||||||
return round(self.computed_total or 0, 2)
|
|
||||||
except AttributeError:
|
|
||||||
self.computed_total = self.compute_total()
|
|
||||||
return self.computed_total
|
|
||||||
|
|
||||||
def get_payment_state_display(self):
|
def get_payment_state_display(self):
|
||||||
value = self.payment_state
|
value = self.payment_state
|
||||||
return force_text(dict(self.PAYMENT_STATES).get(value, value))
|
return force_text(dict(self.PAYMENT_STATES).get(value, value))
|
||||||
|
@ -332,31 +323,42 @@ class Bill(models.Model):
|
||||||
@cached
|
@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') + Sum(Coalesce('sublines__total', 0)))
|
||||||
for tax, total in lines.values_list('tax', 'totals'):
|
for tax, total in lines.values_list('tax', 'totals'):
|
||||||
subtotal, taxes = subtotals.get(tax) or (0, 0)
|
try:
|
||||||
subtotal += total
|
subtotals[tax] += total
|
||||||
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
|
except KeyError:
|
||||||
return subtotals
|
subtotals[tax] = total
|
||||||
|
result = {}
|
||||||
|
for tax, subtotal in subtotals.items():
|
||||||
|
result[tax] = (subtotal, round(tax/100*subtotal, 2))
|
||||||
|
return result
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def compute_base(self):
|
def compute_base(self):
|
||||||
bases = self.lines.annotate(
|
bases = self.lines.annotate(
|
||||||
bases=Sum(F('subtotal') + Coalesce(F('sublines__total'), 0))
|
bases=F('subtotal') + Sum(Coalesce('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
|
@cached
|
||||||
def compute_tax(self):
|
def compute_tax(self):
|
||||||
taxes = self.lines.annotate(
|
taxes = self.lines.annotate(
|
||||||
taxes=Sum((F('subtotal') + Coalesce(F('sublines__total'), 0)) * (F('tax')/100))
|
taxes=(F('subtotal') + Coalesce(Sum('sublines__total'), 0)) * (F('tax')/100)
|
||||||
)
|
)
|
||||||
return round(taxes.aggregate(Sum('taxes'))['taxes__sum'] or 0, 2)
|
return round(taxes.aggregate(Sum('taxes'))['taxes__sum'] or 0, 2)
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def compute_total(self):
|
def compute_total(self):
|
||||||
|
if 'lines' in getattr(self, '_prefetched_objects_cache', ()):
|
||||||
|
total = 0
|
||||||
|
for line in self.lines.all():
|
||||||
|
line_total = line.compute_total()
|
||||||
|
total += line_total * (1+line.tax/100)
|
||||||
|
return round(total, 2)
|
||||||
|
else:
|
||||||
totals = self.lines.annotate(
|
totals = self.lines.annotate(
|
||||||
totals=Sum((F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100))
|
totals=(F('subtotal') + Sum(Coalesce('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)
|
||||||
|
|
||||||
|
@ -410,11 +412,6 @@ class BillLine(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "#%i" % self.pk
|
return "#%i" % self.pk
|
||||||
|
|
||||||
def compute_total(self):
|
|
||||||
""" Computes subline discounts """
|
|
||||||
if self.pk:
|
|
||||||
return self.subtotal + sum([sub.total for sub in self.sublines.all()])
|
|
||||||
|
|
||||||
def get_verbose_quantity(self):
|
def get_verbose_quantity(self):
|
||||||
return self.verbose_quantity or self.quantity
|
return self.verbose_quantity or self.quantity
|
||||||
|
|
||||||
|
@ -434,6 +431,17 @@ class BillLine(models.Model):
|
||||||
return ini
|
return ini
|
||||||
return "{ini} / {end}".format(ini=ini, end=end)
|
return "{ini} / {end}".format(ini=ini, end=end)
|
||||||
|
|
||||||
|
@cached
|
||||||
|
def compute_total(self):
|
||||||
|
total = self.subtotal or 0
|
||||||
|
if hasattr(self, 'subline_total'):
|
||||||
|
total += self.subline_total or 0
|
||||||
|
elif 'sublines' in getattr(self, '_prefetched_objects_cache', ()):
|
||||||
|
total += sum(subline.total for subline in self.sublines.all())
|
||||||
|
else:
|
||||||
|
total += self.sublines.aggregate(sub_total=Sum('total'))['sub_total'] or 0
|
||||||
|
return round(total, 2)
|
||||||
|
|
||||||
# def save(self, *args, **kwargs):
|
# def save(self, *args, **kwargs):
|
||||||
# super(BillLine, self).save(*args, **kwargs)
|
# super(BillLine, self).save(*args, **kwargs)
|
||||||
# if self.bill.is_open:
|
# if self.bill.is_open:
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
{% 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;
|
||||||
|
}
|
||||||
|
.footnote {
|
||||||
|
font-family: sans;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
</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 "Quantity" %}</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.4 }}</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"><b>{{ totals.1 }}<b></td>
|
||||||
|
<td class="item column-amount"><b>{{ totals.2 }}<b></td>
|
||||||
|
<td class="item column-amount"><b>{{ totals.3 }}<b></td>
|
||||||
|
<td class="item column-amount"><b>{{ totals.4 }}<b></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="footnote">
|
||||||
|
* Custom lines
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -51,7 +51,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="total">
|
<div id="total">
|
||||||
<span class="title">{% trans "TOTAL" %}</span><br>
|
<span class="title">{% trans "TOTAL" %}</span><br>
|
||||||
<psan class="value">{{ bill.get_total }} &{{ currency.lower }};</span>
|
<psan class="value">{{ bill.compute_total }} &{{ currency.lower }};</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="bill-date">
|
<div id="bill-date">
|
||||||
<span class="title">{% blocktrans with bill_type=bill.get_type_display.upper %}{{ bill_type }} DATE{% endblocktrans %}</span><br>
|
<span class="title">{% blocktrans with bill_type=bill.get_type_display.upper %}{{ bill_type }} DATE{% endblocktrans %}</span><br>
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
<br>
|
<br>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<span class="total column-title">{% trans "total" %}</span>
|
<span class="total column-title">{% trans "total" %}</span>
|
||||||
<span class="total column-value">{{ bill.get_total }} &{{ currency.lower }};</span>
|
<span class="total column-value">{{ bill.compute_total }} &{{ currency.lower }};</span>
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
<th class="title column-cancelled">{% trans "Cancelled" %}</th>
|
<th class="title column-cancelled">{% trans "Cancelled" %}</th>
|
||||||
<th class="title column-nominal-price">{% trans "Nominal price" %}</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 "Number" %}</th>
|
||||||
<th class="title column-number">{% trans "Profit" %}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% for service, info in services %}
|
{% for service, info in services %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -49,7 +48,6 @@
|
||||||
<td class="item column-amount">{{ info.1 }}</td>
|
<td class="item column-amount">{{ info.1 }}</td>
|
||||||
<td class="item column-amount">{{ info.2 }}</td>
|
<td class="item column-amount">{{ info.2 }}</td>
|
||||||
<td class="item column-amount">{{ info.3 }}</td>
|
<td class="item column-amount">{{ info.3 }}</td>
|
||||||
<td class="item column-amount">{{ info.2|mul:info.3 }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -57,10 +55,8 @@
|
||||||
<td class="item column-amount"><b>{{ totals.0 }}</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.1 }}</td>
|
||||||
<td class="item column-amount">{{ totals.2 }}</td>
|
<td class="item column-amount">{{ totals.2 }}</td>
|
||||||
<td class="item column-amount">{{ totals.3 }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
# TODO calculate profit better: order.get_price() for everyperiod / metric, etc
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue