Improvements on bills app
This commit is contained in:
parent
26ee8bdfab
commit
1403329d1b
|
@ -1,47 +1,11 @@
|
|||
from django import forms
|
||||
from django.conf.urls import patterns, url
|
||||
from django.contrib import admin
|
||||
from django.forms.models import BaseInlineFormSet
|
||||
|
||||
from .utils import set_url_query
|
||||
from orchestra.utils.functional import cached
|
||||
|
||||
|
||||
class ExtendedModelAdmin(admin.ModelAdmin):
|
||||
add_fields = ()
|
||||
add_fieldsets = ()
|
||||
add_form = None
|
||||
change_readonly_fields = ()
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
fields = super(ExtendedModelAdmin, self).get_readonly_fields(request, obj=obj)
|
||||
if obj:
|
||||
return fields + self.change_readonly_fields
|
||||
return fields
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
if not obj:
|
||||
if self.add_fieldsets:
|
||||
return self.add_fieldsets
|
||||
elif self.add_fields:
|
||||
return [(None, {'fields': self.add_fields})]
|
||||
return super(ExtendedModelAdmin, self).get_fieldsets(request, obj=obj)
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
""" add_inlines and inline.parent_object """
|
||||
self.inlines = getattr(self, 'add_inlines', self.inlines)
|
||||
if obj:
|
||||
self.inlines = type(self).inlines
|
||||
inlines = super(ExtendedModelAdmin, self).get_inline_instances(request, obj=obj)
|
||||
for inline in inlines:
|
||||
inline.parent_object = obj
|
||||
return inlines
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
""" Use special form during user creation """
|
||||
defaults = {}
|
||||
if obj is None and self.add_form:
|
||||
defaults['form'] = self.add_form
|
||||
defaults.update(kwargs)
|
||||
return super(ExtendedModelAdmin, self).get_form(request, obj, **defaults)
|
||||
from .utils import set_url_query, action_to_view
|
||||
|
||||
|
||||
class ChangeListDefaultFilter(object):
|
||||
|
@ -58,8 +22,10 @@ class ChangeListDefaultFilter(object):
|
|||
for key, value in self.default_changelist_filters:
|
||||
set_url_query(request, key, value)
|
||||
defaults.append(key)
|
||||
# hack response cl context in order to hook default filter awaearness into search_form.html template
|
||||
response = super(ChangeListDefaultFilter, self).changelist_view(request, extra_context=extra_context)
|
||||
# hack response cl context in order to hook default filter awaearness
|
||||
# into search_form.html template
|
||||
response = super(ChangeListDefaultFilter, self).changelist_view(request,
|
||||
extra_context=extra_context)
|
||||
if hasattr(response, 'context_data') and 'cl' in response.context_data:
|
||||
response.context_data['cl'].default_changelist_filters = defaults
|
||||
return response
|
||||
|
@ -74,3 +40,92 @@ class AtLeastOneRequiredInlineFormSet(BaseInlineFormSet):
|
|||
if not any(cleaned_data and not cleaned_data.get('DELETE', False)
|
||||
for cleaned_data in self.cleaned_data):
|
||||
raise forms.ValidationError('At least one item required.')
|
||||
|
||||
|
||||
class ChangeViewActionsMixin(object):
|
||||
""" Makes actions visible on the admin change view page. """
|
||||
|
||||
change_view_actions = ()
|
||||
change_form_template = 'orchestra/admin/change_form.html'
|
||||
|
||||
def get_urls(self):
|
||||
"""Returns the additional urls for the change view links"""
|
||||
urls = super(ChangeViewActionsMixin, self).get_urls()
|
||||
admin_site = self.admin_site
|
||||
opts = self.model._meta
|
||||
new_urls = patterns('')
|
||||
for action in self.get_change_view_actions():
|
||||
new_urls += patterns('',
|
||||
url('^(\d+)/%s/$' % action.url_name,
|
||||
admin_site.admin_view(action),
|
||||
name='%s_%s_%s' % (opts.app_label,
|
||||
opts.module_name,
|
||||
action.url_name)))
|
||||
return new_urls + urls
|
||||
|
||||
@cached
|
||||
def get_change_view_actions(self):
|
||||
views = []
|
||||
for action in self.change_view_actions:
|
||||
if isinstance(action, basestring):
|
||||
action = getattr(self, action)
|
||||
view = action_to_view(action, self)
|
||||
view.url_name = getattr(action, 'url_name', action.__name__)
|
||||
view.verbose_name = getattr(action, 'verbose_name',
|
||||
view.url_name.capitalize().replace('_', ' '))
|
||||
view.css_class = getattr(action, 'css_class', 'historylink')
|
||||
view.description = getattr(action, 'description', '')
|
||||
views.append(view)
|
||||
return views
|
||||
|
||||
def change_view(self, *args, **kwargs):
|
||||
if not 'extra_context' in kwargs:
|
||||
kwargs['extra_context'] = {}
|
||||
kwargs['extra_context']['object_tools_items'] = [
|
||||
action.__dict__ for action in self.get_change_view_actions()
|
||||
]
|
||||
return super(ChangeViewActionsMixin, self).change_view(*args, **kwargs)
|
||||
|
||||
|
||||
class ChangeAddFieldsMixin(object):
|
||||
""" Enables to specify different set of fields for change and add views """
|
||||
add_fields = ()
|
||||
add_fieldsets = ()
|
||||
add_form = None
|
||||
change_readonly_fields = ()
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj=obj)
|
||||
if obj:
|
||||
return fields + self.change_readonly_fields
|
||||
return fields
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
if not obj:
|
||||
if self.add_fieldsets:
|
||||
return self.add_fieldsets
|
||||
elif self.add_fields:
|
||||
return [(None, {'fields': self.add_fields})]
|
||||
return super(ChangeAddFieldsMixin, self).get_fieldsets(request, obj=obj)
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
""" add_inlines and inline.parent_object """
|
||||
self.inlines = getattr(self, 'add_inlines', self.inlines)
|
||||
if obj:
|
||||
self.inlines = type(self).inlines
|
||||
inlines = super(ChangeAddFieldsMixin, self).get_inline_instances(request, obj=obj)
|
||||
for inline in inlines:
|
||||
inline.parent_object = obj
|
||||
return inlines
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
""" Use special form during user creation """
|
||||
defaults = {}
|
||||
if obj is None and self.add_form:
|
||||
defaults['form'] = self.add_form
|
||||
defaults.update(kwargs)
|
||||
return super(ChangeAddFieldsMixin, self).get_form(request, obj, **defaults)
|
||||
|
||||
|
||||
class ExtendedModelAdmin(ChangeViewActionsMixin, ChangeAddFieldsMixin, admin.ModelAdmin):
|
||||
pass
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.conf import settings
|
|||
from django.contrib import admin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.shortcuts import redirect
|
||||
from django.utils import importlib
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
|
@ -74,6 +75,19 @@ def set_url_query(request, key, value):
|
|||
request.META['QUERY_STRING'] = request.GET.urlencode()
|
||||
|
||||
|
||||
def action_to_view(action, modeladmin):
|
||||
""" Converts modeladmin action to view function """
|
||||
def action_view(request, object_id=1, modeladmin=modeladmin, action=action):
|
||||
queryset = modeladmin.model.objects.filter(pk=object_id)
|
||||
response = action(modeladmin, request, queryset)
|
||||
if not response:
|
||||
opts = modeladmin.model._meta
|
||||
url = 'admin:%s_%s_change' % (opts.app_label, opts.module_name)
|
||||
return redirect(url, object_id)
|
||||
return response
|
||||
return action_view
|
||||
|
||||
|
||||
@admin_field
|
||||
def admin_link(*args, **kwargs):
|
||||
instance = args[-1]
|
||||
|
|
|
@ -26,6 +26,10 @@ class Account(models.Model):
|
|||
@cached_property
|
||||
def name(self):
|
||||
return self.user.username
|
||||
|
||||
@classmethod
|
||||
def get_main(cls):
|
||||
return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK)
|
||||
|
||||
|
||||
services.register(Account, menu=False)
|
||||
|
|
|
@ -19,3 +19,6 @@ ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
|
|||
|
||||
|
||||
ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'en')
|
||||
|
||||
|
||||
ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK', 1)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
{% extends "orchestra/admin/change_form.html" %}
|
||||
{% load i18n admin_urls admin_static admin_modify %}
|
||||
|
||||
|
||||
|
@ -43,9 +43,6 @@
|
|||
<li>
|
||||
<a href="disable/" class="historylink">{% trans "Disable" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
|
||||
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
|
||||
</li>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
|
|
3
orchestra/apps/bills/actions.py
Normal file
3
orchestra/apps/bills/actions.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
def generate_bill(modeladmin, request, queryset):
|
||||
for bill in queryset:
|
||||
bill.close()
|
|
@ -2,9 +2,11 @@ from django.contrib import admin
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.admin import ExtendedModelAdmin
|
||||
from orchestra.admin.utils import admin_link, admin_date
|
||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||
|
||||
from .actions import generate_bill
|
||||
from .filters import BillTypeListFilter
|
||||
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, Budget,
|
||||
BillLine, BudgetLine)
|
||||
|
@ -24,24 +26,26 @@ class BudgetLineInline(admin.TabularInline):
|
|||
)
|
||||
|
||||
|
||||
class BillAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||
list_display = (
|
||||
'ident', 'status', 'bill_type_link', 'account_link', 'created_on_display'
|
||||
'ident', 'status', 'type_link', 'account_link', 'created_on_display'
|
||||
)
|
||||
list_filter = (BillTypeListFilter, 'status',)
|
||||
change_view_actions = [generate_bill]
|
||||
change_readonly_fields = ('account', 'type', 'status')
|
||||
readonly_fields = ('ident',)
|
||||
inlines = [BillLineInline]
|
||||
|
||||
account_link = admin_link('account')
|
||||
created_on_display = admin_date('created_on')
|
||||
|
||||
def bill_type_link(self, bill):
|
||||
bill_type = bill.bill_type.lower()
|
||||
def type_link(self, bill):
|
||||
bill_type = bill.type.lower()
|
||||
url = reverse('admin:bills_%s_changelist' % bill_type)
|
||||
return '<a href="%s">%s</a>' % (url, bill.get_bill_type_display())
|
||||
bill_type_link.allow_tags = True
|
||||
bill_type_link.short_description = _("type")
|
||||
bill_type_link.admin_order_field = 'bill_type'
|
||||
return '<a href="%s">%s</a>' % (url, bill.get_type_display())
|
||||
type_link.allow_tags = True
|
||||
type_link.short_description = _("type")
|
||||
type_link.admin_order_field = 'type'
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
if self.model is Budget:
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from django.db import models
|
||||
from django.template import loader, Context
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orchestra.apps.accounts.models import Account
|
||||
from orchestra.core import accounts
|
||||
|
||||
from . import settings
|
||||
|
@ -12,7 +15,7 @@ class BillManager(models.Manager):
|
|||
queryset = super(BillManager, self).get_queryset()
|
||||
if self.model != Bill:
|
||||
bill_type = self.model.get_type()
|
||||
queryset = queryset.filter(bill_type=bill_type)
|
||||
queryset = queryset.filter(type=bill_type)
|
||||
return queryset
|
||||
|
||||
|
||||
|
@ -44,7 +47,7 @@ class Bill(models.Model):
|
|||
blank=True)
|
||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||
related_name='%(class)s')
|
||||
bill_type = models.CharField(_("type"), max_length=16, choices=TYPES)
|
||||
type = models.CharField(_("type"), max_length=16, choices=TYPES)
|
||||
status = models.CharField(_("status"), max_length=16, choices=STATUSES,
|
||||
default=OPEN)
|
||||
created_on = models.DateTimeField(_("created on"), auto_now_add=True)
|
||||
|
@ -61,13 +64,25 @@ class Bill(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.ident
|
||||
|
||||
@cached_property
|
||||
def seller(self):
|
||||
return Account.get_main().invoicecontact
|
||||
|
||||
@cached_property
|
||||
def buyer(self):
|
||||
return self.account.invoicecontact
|
||||
|
||||
@property
|
||||
def lines(self):
|
||||
return self.billlines
|
||||
|
||||
@classmethod
|
||||
def get_type(cls):
|
||||
return cls.__name__.upper()
|
||||
|
||||
def set_ident(self):
|
||||
cls = type(self)
|
||||
bill_type = self.bill_type or cls.get_type()
|
||||
bill_type = self.type or cls.get_type()
|
||||
if bill_type == 'BILL':
|
||||
raise TypeError("get_new_ident() can not be used on a Bill class")
|
||||
# Bill number resets every natural year
|
||||
|
@ -91,11 +106,31 @@ class Bill(models.Model):
|
|||
|
||||
def close(self):
|
||||
self.status = self.CLOSED
|
||||
self.html = self.render()
|
||||
self.save()
|
||||
|
||||
def render(self):
|
||||
context = Context({
|
||||
'bill': self,
|
||||
'lines': self.lines.all(),
|
||||
'seller': self.seller,
|
||||
'buyer': self.buyer,
|
||||
'seller_info': {
|
||||
'phone': settings.BILLS_SELLER_PHONE,
|
||||
'website': settings.BILLS_SELLER_WEBSITE,
|
||||
'email': settings.BILLS_SELLER_EMAIL,
|
||||
},
|
||||
'currency': settings.BILLS_CURRENCY,
|
||||
})
|
||||
template = getattr(settings, 'BILLS_%s_TEMPLATE' % self.get_type())
|
||||
bill_template = loader.get_template(template)
|
||||
html = bill_template.render(context)
|
||||
html = html.replace('-pageskip-', '<pdf:nextpage />')
|
||||
return html
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.bill_type:
|
||||
self.bill_type = type(self).get_type()
|
||||
if not self.type:
|
||||
self.type = type(self).get_type()
|
||||
if not self.ident or (self.ident.startswith('O') and self.status != self.OPEN):
|
||||
self.set_ident()
|
||||
super(Bill, self).save(*args, **kwargs)
|
||||
|
@ -124,6 +159,10 @@ class AmendmentFee(Bill):
|
|||
class Budget(Bill):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
@property
|
||||
def lines(self):
|
||||
return self.budgetlines
|
||||
|
||||
|
||||
class BaseBillLine(models.Model):
|
||||
|
@ -145,7 +184,7 @@ class BudgetLine(BaseBillLine):
|
|||
|
||||
|
||||
class BillLine(BaseBillLine):
|
||||
order_id = models.PositiveIntegerField(blank=True)
|
||||
order_id = models.PositiveIntegerField(blank=True, null=True)
|
||||
order_last_bill_date = models.DateTimeField(null=True)
|
||||
order_billed_until = models.DateTimeField(null=True)
|
||||
auto = models.BooleanField(default=False)
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
BILLS_IDENT_NUMBER_LENGTH = getattr(settings, 'BILLS_IDENT_NUMBER_LENGTH', 4)
|
||||
|
||||
BILLS_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_INVOICE_IDENT_PREFIX', 'I')
|
||||
|
||||
BILLS_AMENDMENT_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_IDENT_PREFIX', 'A')
|
||||
|
||||
BILLS_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_FEE_IDENT_PREFIX', 'F')
|
||||
|
||||
BILLS_AMENDMENT_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_IDENT_PREFIX', 'B')
|
||||
|
||||
BILLS_BUDGET_IDENT_PREFIX = getattr(settings, 'BILLS_BUDGET_IDENT_PREFIX', 'Q')
|
||||
|
||||
|
||||
BILLS_INVOICE_TEMPLATE = getattr(settings, 'BILLS_INVOICE_TEMPLATE', 'bills/invoice.html')
|
||||
|
||||
|
||||
BILLS_CURRENCY = getattr(settings, 'BILLS_CURRENCY', 'euro')
|
||||
|
||||
|
||||
BILLS_SELLER_PHONE = getattr(settings, 'BILLS_SELLER_PHONE', '111-112-11-222')
|
||||
|
||||
BILLS_SELLER_EMAIL = getattr(settings, 'BILLS_SELLER_EMAIL', 'sales@orchestra.lan')
|
||||
|
||||
BILLS_SELLER_WEBSITE = getattr(settings, 'BILLS_SELLER_WEBSITE', 'www.orchestra.lan')
|
||||
|
|
309
orchestra/apps/bills/templates/bills/altinvoice.html
Normal file
309
orchestra/apps/bills/templates/bills/altinvoice.html
Normal file
|
@ -0,0 +1,309 @@
|
|||
<style>
|
||||
|
||||
body {
|
||||
max-width: 1048px;
|
||||
margin: 60 auto !important;
|
||||
float: none !important;
|
||||
font-family: Arial, 'Liberation Sans', 'DejaVu Sans', sans-serif;
|
||||
}
|
||||
|
||||
#logo {
|
||||
float: left;
|
||||
font-size: 25;
|
||||
font-weight: bold;
|
||||
color: grey;
|
||||
border-bottom: 5px solid grey;
|
||||
margin: 38px;
|
||||
margin-left: 60px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#bill-number {
|
||||
float: right;
|
||||
margin-top: 80px;
|
||||
text-align: right;
|
||||
font-size: 25;
|
||||
font-weight: bold;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
#bill-number .value {
|
||||
font-size: 40;
|
||||
color: #B23;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 20px;
|
||||
color: #ccc;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
|
||||
#bill-summary > * {
|
||||
float: right;
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
border-color: grey;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
#bill-summary .title {
|
||||
color: black;
|
||||
font-size: small;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: -7px;
|
||||
}
|
||||
|
||||
#bill-summary #total, #total .title{
|
||||
background-color: #B23;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
#seller-details, #buyer-details {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#buyer-details {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
|
||||
#lines > * {
|
||||
float: left;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#lines .title {
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid grey;
|
||||
color: #B23;
|
||||
}
|
||||
|
||||
#lines .value {
|
||||
border-bottom: 1px solid grey;
|
||||
}
|
||||
|
||||
#lines .column-id {
|
||||
width: 5%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#lines .column-description {
|
||||
width: 55%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#lines .column-quantity {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
#lines .column-rate {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
#lines .column-subtotal {
|
||||
width: 10%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
#totals {
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
#totals > * {
|
||||
text-align: right;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
#totals .column-title {
|
||||
font-weight: bold;
|
||||
color: #B23;
|
||||
width: 85%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#totals .column-value {
|
||||
width: 15%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#totals .subtotal {
|
||||
border-bottom: 1px solid grey;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
#totals .tax {
|
||||
border-bottom: 2px solid grey;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pagination {
|
||||
color: #B23;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#footer .title {
|
||||
color: #B23;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#footer > * > * {
|
||||
margin: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#pagination {
|
||||
margin-left: 20px;
|
||||
clear: right;
|
||||
}
|
||||
|
||||
#footer-column-1 {
|
||||
float: left;
|
||||
width: 48%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#footer-column-2 {
|
||||
float: right;
|
||||
width: 48%;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<div id="bill-number">
|
||||
Invoice<br>
|
||||
<div class="value">F20110232</div><br>
|
||||
</div>
|
||||
<div id="logo">
|
||||
YOUR<br>
|
||||
LOGO<br>
|
||||
HERE<br>
|
||||
</div>
|
||||
<div id="seller-details">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>Associacio Pangea - Coordinadora Comunicacio per a la Cooperacio</strong><br>
|
||||
ES2323233<br>
|
||||
<address>
|
||||
Pl eusebi guell 6-7, planta 0<br>
|
||||
08034 - Barcelona<br>
|
||||
Spain<br>
|
||||
</address>
|
||||
<br>
|
||||
93-803-21-32<br>
|
||||
<a href="mailto:sales@pangea.org">sales@pangea.org</a><br>
|
||||
<a href="http://www.pangea.org">www.pangea.org</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
<div id="bill-summary">
|
||||
<div id="due-date">
|
||||
<span class="title">DUE DATE</span><br>
|
||||
<psan class="value">Nov 21, 2011</span>
|
||||
</div>
|
||||
<div id="total">
|
||||
<span class="title">TOTAL</span><br>
|
||||
<psan class="value">122,03 €</span>
|
||||
</div>
|
||||
<div id="bill-date">
|
||||
<span class="title">INVOICE DATE</span><br>
|
||||
<psan class="value">Oct 20, 2012</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="buyer-details">
|
||||
<address>
|
||||
<strong>Aadults</strong><br>
|
||||
ES01939933<br>
|
||||
Carrer nnoseque, 0<br>
|
||||
08034 - Barcelona<br>
|
||||
Spain<br>
|
||||
</address>
|
||||
</div>
|
||||
|
||||
<div id="lines">
|
||||
<span class="title column-id">id</span>
|
||||
<span class="title column-description">description</span>
|
||||
<span class="title column-quantity">hrs/qty</span>
|
||||
<span class="title column-rate">rate/price</span>
|
||||
<span class="title column-subtotal">subtotal</span>
|
||||
<br>
|
||||
<span class="value column-id">1</span>
|
||||
<span class="value column-description">Hola que passa</span>
|
||||
<span class="value column-quantity">1</span>
|
||||
<span class="value column-rate">1 €</span>
|
||||
<span class="value column-subtotal">111 €</span>
|
||||
<br>
|
||||
<span class="value column-id">1</span>
|
||||
<span class="value column-description">Merda pura</span>
|
||||
<span class="value column-quantity">1</span>
|
||||
<span class="value column-rate">1 €</span>
|
||||
<span class="value column-subtotal">111 €</span>
|
||||
<br>
|
||||
<span class="value column-id">1</span>
|
||||
<span class="value column-description">I tu que et passa</span>
|
||||
<span class="value column-quantity">1</span>
|
||||
<span class="value column-rate">1 €</span>
|
||||
<span class="value column-subtotal">111 €</span>
|
||||
<br>
|
||||
<span class="value column-id">1</span>
|
||||
<span class="value column-description">Joder hostia puta</span>
|
||||
<span class="value column-quantity">1</span>
|
||||
<span class="value column-rate">1 €</span>
|
||||
<span class="value column-subtotal">111 €</span>
|
||||
<br>
|
||||
</div>
|
||||
<div id="totals">
|
||||
<span class="subtotal column-title">subtotal</span>
|
||||
<span class="subtotal column-value">33,03 €</span>
|
||||
<br>
|
||||
<span class="tax column-title">tax</span>
|
||||
<span class="tax column-value">33,03 €</span>
|
||||
<br>
|
||||
<span class="total column-title">total</span>
|
||||
<span class="total column-value">33,03 €</span>
|
||||
<br>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div id="pagination">
|
||||
Page 1 of 1
|
||||
</div>
|
||||
<div id="footer-column-1">
|
||||
<div id="comments">
|
||||
<span class="title">COMMENTS</span> The comments should be here. The comments should be here. The comments should be here. The comments should be here.
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer-column-2">
|
||||
<div id="payment">
|
||||
<span class="title">PAYMENT</span> You can pay our invoice by bank transfer
|
||||
llkdskdlsdk The comments should be here. The comments should be here. The comments should be here. The comments should be here.
|
||||
</div>
|
||||
<div id="questions">
|
||||
<span class="title">QUESTIONS</span> If you have any question about your bill, please
|
||||
feel free to contact us at your convinience. We will reply as soon as we get
|
||||
your message.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
217
orchestra/apps/bills/templates/bills/invoice.html
Normal file
217
orchestra/apps/bills/templates/bills/invoice.html
Normal file
|
@ -0,0 +1,217 @@
|
|||
<html>
|
||||
<style>
|
||||
@page {
|
||||
margin: 1cm;
|
||||
margin-bottom: 0cm;
|
||||
margin-top: 3cm;
|
||||
size: a4 portrait;
|
||||
background-image: url('img/letter_head.png');
|
||||
@frame footer {
|
||||
-pdf-frame-content: footerContent;
|
||||
bottom: 0cm;
|
||||
margin-left: 1cm;
|
||||
margin-right: 1cm;
|
||||
height: 2cm;
|
||||
}
|
||||
@frame simple {
|
||||
-pdf-frame-content: simple;
|
||||
bottom: 2.0cm;
|
||||
height: 2.5cm;
|
||||
margin-left: 1cm;
|
||||
margin-right: 1cm;
|
||||
}
|
||||
}
|
||||
|
||||
div#buyer-details{
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
div#specification{
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
div#specification td{
|
||||
vertical-align: middle;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
|
||||
table td {
|
||||
vertical-align: top;
|
||||
padding: 2px 0;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
table td.amount{
|
||||
text-align: right;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
table td.total{
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
table th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
tr.uneven {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
div#footerContent {
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
div#footerContent a {
|
||||
color: #790000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 90%;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div#totals {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
div#simple td {
|
||||
margin-left: 10px;
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
div#simple tr {
|
||||
border-right: 1px solid #333;
|
||||
}
|
||||
|
||||
div#simple table{
|
||||
text-align: center;
|
||||
border-left: 1px solid #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<h1>{{ bill_type }}</h1>
|
||||
<div id="buyer-details">
|
||||
<table>
|
||||
<tr>
|
||||
<td width="60%">
|
||||
<strong>{{ buyer.name }}</strong><br>
|
||||
{{ buyer.address }}<br>
|
||||
{{ buyer.zipcode }} {{ buyer.city }}<br>
|
||||
{{ buyer.country }}<br>
|
||||
{{ buyer.vat_number }}<br>
|
||||
</td>
|
||||
<td width="20%">
|
||||
<strong>Invoice number</strong><br />
|
||||
<strong>Date</strong><br />
|
||||
<strong>Due date</strong>
|
||||
</td>
|
||||
<td width="20%">
|
||||
: {{ bill.ident }}<br />
|
||||
: {{ bill.date|date:"d F, Y" }}<br />
|
||||
: {{ bill.due_on|date:"d F, Y" }}<br />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="specification">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<th width="5%">ID</th>
|
||||
<th width="65%">Description</th>
|
||||
<th width="20%">Amount</th>
|
||||
<th width="10%">Price</th>
|
||||
</tr>
|
||||
{% for line in lines %}
|
||||
<tr class="{% cycle 'even' 'uneven' %}"{% if forloop.last %} style="border-bottom: 1px solid #000;"{% endif %}>
|
||||
<td class="ID">{{ line.order_id }}</td>
|
||||
<td style="padding-left: 2px;">{{ line.description }}
|
||||
<span class="date">({{ line.initial_date|date:"d-m-Y" }}{% if line.initial_date != line.final_date %} - {{ line.final_date|date:"d-m-Y" }}{% endif %})</span></td>
|
||||
<td class="quantity">{{ line.amount }}</td>
|
||||
<td class="amount total">&{{ currency }}; {{ line.price }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
<div id="totals">
|
||||
<table width="100%">
|
||||
<tr>
|
||||
{% for tax, base in bases.items %}
|
||||
<td width="60%"> </td>
|
||||
<td width="20%">Subtotal{% if bases.items|length > 1 %} (for {{ tax }}% taxes){% endif %}</td>
|
||||
<td width="20%" class="amount">&{{ currency }}; {{ base }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<tr>
|
||||
{% for tax, value in taxes.items %}
|
||||
<td width="60%"> </td>
|
||||
<td width="20%">Total {{ tax }}%</td>
|
||||
<td width="20%" class="amount" style="border-bottom: 1px solid #333;">&{{ currency }}; {{ value }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="60%"> </td>
|
||||
<td width="20%" class="total"><strong>Total</strong></td>
|
||||
<td width="20%" class="amount total">&{{ currency }}; {{ total }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="simple">
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%" style="padding-top: 5px;">IBAN</th>
|
||||
<td width="34%" style="padding-top: 5px;">Invoice ID</th>
|
||||
<td width="33%" style="padding-top: 5px;">Amount {{ currency.upper }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>NL28INGB0004954664</strong></td>
|
||||
<td><strong>{{ bill.ident }}</strong></td>
|
||||
<td><strong>{{ total }}</strong></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="text-align:center;">The invoice is to be paid before <strong>{{ invoice.exp_date|date:"F jS, Y" }}</strong> with the mention of the invoice id.</p>
|
||||
</div>
|
||||
<div id="footerContent">
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%">
|
||||
{{ seller.name }}<br />
|
||||
{{ seller.address }}<br />
|
||||
{{ seller.city }}<br />
|
||||
{{ seller.country }}<br />
|
||||
</td>
|
||||
<td width="5%">
|
||||
Tel<br />
|
||||
Web<br />
|
||||
Email<br />
|
||||
</td>
|
||||
<td width="29%">
|
||||
{{ seller_info.phone }}<br />
|
||||
<a href="http://{{ seller_info.website }}">{{ seller_info.website }}</a><br />
|
||||
{{ seller_info.email }}
|
||||
</td>
|
||||
<td width="8%">
|
||||
Bank ING<br />
|
||||
IBAN<br />
|
||||
BTW<br />
|
||||
KvK<br />
|
||||
</td>
|
||||
<td width="25%">
|
||||
4954664<br />
|
||||
NL28INGB0004954664<br />
|
||||
NL 8207.29.449.B01<br />
|
||||
27343027
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
Payment info
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
5
orchestra/apps/bills/templates/bills/plans/PL_EN.html
Normal file
5
orchestra/apps/bills/templates/bills/plans/PL_EN.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
{% extends 'bills/plans/invoice_base.html' %}
|
||||
{% block title %}{{ invoice.full_number }}{% endblock %}
|
||||
{% block body %}
|
||||
{% include 'bills/plans/PL_EN_layout.html' %}
|
||||
{% endblock %}
|
171
orchestra/apps/bills/templates/bills/plans/PL_EN_layout.html
Normal file
171
orchestra/apps/bills/templates/bills/plans/PL_EN_layout.html
Normal file
|
@ -0,0 +1,171 @@
|
|||
{% if logo_url %}
|
||||
<img src="{{ logo_url }}" alt="company logo">
|
||||
{% endif %}
|
||||
|
||||
<div style="float:right; text-align: right;">
|
||||
<h1>
|
||||
<label><span class="pl invoice_header">{% if invoice.type == invoice.INVOICE_TYPES.INVOICE %}Faktura VAT nr{% endif %}{% if invoice.type == invoice.INVOICE_TYPES.PROFORMA %}Faktura PROFORMA nr{% endif %}{% if invoice.type == invoice.INVOICE_TYPES.DUPLICATE %}Faktura VAT DUPLIKAT nr{% endif %}</span> <span class="en">{% if invoice.type == invoice.INVOICE_TYPES.INVOICE %}Invoice ID{% endif %}{% if invoice.type == invoice.INVOICE_TYPES.PROFORMA %}Order confirmation ID{% endif %}{% if invoice.type == invoice.INVOICE_TYPES.DUPLICATE %}Invoice (duplicate) ID{% endif %}</span></label> <span id="full_number">{{ invoice.full_number }}</span>
|
||||
</h1>
|
||||
<h2>{% if not copy %}ORYGINAŁ{% else %}KOPIA{% endif %}</h2>
|
||||
<p> <label><span class="pl">Data wystawienia:</span> <span class="en">Issued</span></label> {{ invoice.issued|date:"Y-m-d" }}</p>
|
||||
{% if invoice.type != invoice.INVOICE_TYPES.PROFORMA %}{# Not a PROFORMA #}
|
||||
<p> <label><span class="pl">Data sprzedaży:</span> <span class="en">Date of order</span></label> {{ invoice.selling_date|date:"Y-m-d" }}</p>
|
||||
{% else %}
|
||||
<p> </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<table style="width: 100%; margin-bottom: 40px; font-size: 12px;" >
|
||||
<tr>
|
||||
<td style="width: 50%;">
|
||||
</td>
|
||||
<td style="width: 50%; padding-right: 4em; font-weight: bold; font-size: 15px;" id="shipping">
|
||||
<strong> <label><span class="pl">Adres wysyłki:</span> <span class="en">Shipping address</span></label></strong><br><br>
|
||||
|
||||
{{ buyer.name }}<br>
|
||||
{{ buyer.address }}<br>
|
||||
{{ buyer.zipcode }}<br>
|
||||
{{ buyer.country }}
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 50%; vertical-align: top;">
|
||||
|
||||
<strong> <label><span class="pl">Sprzedawca:</span> <span class="en">Seller</span></label></strong><br><br>
|
||||
{{ seller.name }}<br>
|
||||
{{ seller.address }}<br>
|
||||
{{ seller.zipcode }}<br>
|
||||
{{ seller.country }}<p>
|
||||
<label><span class="pl">Numer NIP:</span><span class="en">VAT ID</span></label> {{ invoice.issuer_tax_number }}<br>
|
||||
</td>
|
||||
<td style="width: 50%; vertical-align: top;">
|
||||
|
||||
<strong> <label><span class="pl">Nabywca:</span> <span class="en">Buyer</span></label></strong><br><br>
|
||||
{{ buyer.name }}<br>
|
||||
{{ buyer.address }}<br>
|
||||
{{ buyer.zipcode }}<br>
|
||||
{{ buyer.country }}
|
||||
|
||||
{% if invoice.buyer_tax_number %}
|
||||
<p>
|
||||
|
||||
<label><span class="pl">Numer NIP:</span><span class="en">VAT ID</span></label> {{ invoice.buyer_tax_number }}
|
||||
|
||||
</p>
|
||||
{% endif %}
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table style="margin-bottom: 40px; width: 100%;" id="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="pl">L.p.</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="pl">Nazwa usługi</span><br>
|
||||
<span class="en">Description</span>
|
||||
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span class="pl">Cena j. netto</span><br>
|
||||
<span class="en">Unit price</span>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<span class="pl">Ilość</span><br>
|
||||
<span class="en">Qty.</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="pl">J.m.</span>
|
||||
</td>
|
||||
{% if invoice.rebate %}
|
||||
|
||||
<td>
|
||||
<span class="pl">Rabat</span><br><span class="en">Rebate</span>
|
||||
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<span class="pl">Wartość netto</span><br>
|
||||
<span class="en">Subtotal</span>
|
||||
</td>
|
||||
<td style="width: 3%;">
|
||||
<span class="pl">VAT</span><br> <span class="en">TAX</span>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<span class="pl">Kwota VAT</span><br><span class="en">TAX/VAT Amount</span>
|
||||
</td>
|
||||
<td style="width: 8%;">
|
||||
|
||||
<span class="pl">Wartość brutto</span><br>
|
||||
<span class="en">Subtotal with TAX/VAT</span>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
1
|
||||
</td>
|
||||
<td class="center">{{ invoice.item_description }}</td>
|
||||
<td class="number">{{ invoice.unit_price_net|floatformat:2 }} {{ invoice.currency }}</td>
|
||||
<td class="center">{{ invoice.quantity }}</td>
|
||||
<td class="center"><span class="pl">sztuk</span><br><span class="en">units</span></td>
|
||||
|
||||
{% if invoice.rebate %}
|
||||
<td class="number">{{ invoice.rebate|floatformat:2 }} %</td>
|
||||
{% endif %}
|
||||
<td class="number">{{ invoice.total_net|floatformat:2 }} {{ invoice.currency }}</td>
|
||||
<td class="number">{% if invoice.tax != None %}{{ invoice.tax|floatformat:2 }} %{% else %}<span class="pl">n.p.</span><br><span class="en">n/a</span>{% endif %}</td>
|
||||
<td class="number">{% if invoice.tax_total != None %}{{ invoice.tax_total|floatformat:2 }} {{ invoice.currency }}{% else %}<span class="pl">n.p.</span><br><span class="en">n/a</span>{% endif %}</td>
|
||||
<td class="number">{{ invoice.total|floatformat:2 }} {{ invoice.currency }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="{% if invoice.rebate %}6{% else %}5{% endif %}" style="background-color: #EEE;"><label><span class="pl">Razem:</span><span class="en">Total</span></label> </td>
|
||||
<td>{{ invoice.total_net|floatformat:2 }} {{ invoice.currency }}</td>
|
||||
<td>{% if invoice.tax != None %}{{ invoice.tax|floatformat:2 }} %{% else %}<span class="pl">n.p.</span><br><span class="en">n/a</span>{% endif %}</td>
|
||||
<td>{% if invoice.tax_total != None %}{{ invoice.tax_total|floatformat:2 }} {{ invoice.currency }}{% else %}<span class="pl">n.p.</span><br><span class="en">n/a</span>{% endif %}</td>
|
||||
<td>{{ invoice.total|floatformat:2 }} {{ invoice.currency }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<div style="width: 100%;">
|
||||
|
||||
{% if invoice.type != invoice.INVOICE_TYPES.PROFORMA %}
|
||||
<strong><label><span class="pl">Sposób zapłaty:</span><span class="en">Payment</span></label></strong> <label><span class="pl">płatność elektroniczna</span> <span class="en">electronic payment</span></label><br><br>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<strong><label><span class="pl">Termin zapłaty:</span><span class="en">Payment till</span></label></strong>
|
||||
|
||||
{% if invoice.type == invoice.INVOICE_TYPES.PROFORMA %}
|
||||
|
||||
|
||||
{% else %}
|
||||
<label> <span class="pl">zapłacono dnia</span> <span class="en"> paid</span></label>
|
||||
{% endif %}
|
||||
|
||||
{{ invoice.payment_date|date:"Y-m-d" }}
|
||||
<br><br>
|
||||
<hr>
|
||||
|
||||
{% if invoice.type == invoice.INVOICE_TYPES.PROFORMA %}<p><span class="pl">Ten dokument <strong>nie jest</strong> fakturą VAT (nie jest dokumentem księgowym).</span><span class="en">This document <strong>is not</strong> an invoice.</span></p> {% endif %}
|
||||
|
||||
{% if invoice.tax == None and invoice.is_UE_customer %}
|
||||
<p>
|
||||
<span class="pl">Odwrotne obciążenie.</span><span class="en">-Reverse charge.</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
83
orchestra/apps/bills/templates/bills/plans/invoice_base.html
Normal file
83
orchestra/apps/bills/templates/bills/plans/invoice_base.html
Normal file
|
@ -0,0 +1,83 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<style type="text/css">
|
||||
html, body{
|
||||
font-size: 12px;
|
||||
font-family: Helvetica;
|
||||
}
|
||||
span.en {
|
||||
font-size: 10px;
|
||||
color:gray;
|
||||
}
|
||||
span.pl {
|
||||
font-size: 14px;
|
||||
color: black;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
border: 1px solid black;
|
||||
padding: 1em;
|
||||
}
|
||||
table thead{
|
||||
text-align: center;
|
||||
}
|
||||
#shipping{
|
||||
text-align: right;
|
||||
}
|
||||
table#items thead {
|
||||
background-color: #EEE;
|
||||
}
|
||||
table#items tfoot td{
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
table td.number{
|
||||
text-align: right;
|
||||
|
||||
}
|
||||
table td.center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
strong{
|
||||
font-size: 14px;
|
||||
}
|
||||
#full_number , span.invoice_header{
|
||||
font-size: 26px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
label{
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin-bottom: .5em;
|
||||
|
||||
}
|
||||
|
||||
label span.pl {
|
||||
display: block;
|
||||
}
|
||||
|
||||
label span.en{
|
||||
position: absolute;
|
||||
right: .5em;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
|
||||
</head>
|
||||
<body style="width: 960px; margin: 0 auto;" {% if auto_print %}onload="window.print();"{% endif %}>
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -30,6 +30,10 @@ class TransactionAdmin(admin.ModelAdmin):
|
|||
bill_link = admin_link('bill')
|
||||
account_link = admin_link('bill__account')
|
||||
display_state = admin_colored('state', colors=STATE_COLORS)
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(TransactionAdmin, self).get_queryset(request)
|
||||
return qs.select_related('source', 'bill__account__user')
|
||||
|
||||
|
||||
class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
@ -54,7 +58,8 @@ class PaymentProcessAdmin(admin.ModelAdmin):
|
|||
ids = []
|
||||
lines = []
|
||||
counter = 0
|
||||
tx_ids = process.transactions.order_by('id').values_list('id', flat=True)
|
||||
# Because of values_list this query doesn't benefit from prefetch_related
|
||||
tx_ids = process.transactions.values_list('id', flat=True)
|
||||
for tx_id in tx_ids:
|
||||
ids.append(str(tx_id))
|
||||
counter += 1
|
||||
|
|
10
orchestra/templates/orchestra/admin/change_form.html
Normal file
10
orchestra/templates/orchestra/admin/change_form.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
{% load i18n admin_urls utils %}
|
||||
|
||||
|
||||
{% block object-tools-items %}
|
||||
{% for item in object_tools_items %}
|
||||
<li><a href="{{ item.url_name }}/" class="{{ item.css_class }}" title="{{ item.description }}">{{ item.verbose_name }}</a></li>
|
||||
{% endfor %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
|
@ -44,4 +44,3 @@ def size(value, length):
|
|||
@register.filter(name='is_checkbox')
|
||||
def is_checkbox(field):
|
||||
return isinstance(field.field.widget, CheckboxInput)
|
||||
|
||||
|
|
Loading…
Reference in a new issue