Improvements on bills app
This commit is contained in:
parent
26ee8bdfab
commit
1403329d1b
|
@ -1,47 +1,11 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf.urls import patterns, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.forms.models import BaseInlineFormSet
|
from django.forms.models import BaseInlineFormSet
|
||||||
|
|
||||||
from .utils import set_url_query
|
from orchestra.utils.functional import cached
|
||||||
|
|
||||||
|
from .utils import set_url_query, action_to_view
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeListDefaultFilter(object):
|
class ChangeListDefaultFilter(object):
|
||||||
|
@ -58,8 +22,10 @@ class ChangeListDefaultFilter(object):
|
||||||
for key, value in self.default_changelist_filters:
|
for key, value in self.default_changelist_filters:
|
||||||
set_url_query(request, key, value)
|
set_url_query(request, key, value)
|
||||||
defaults.append(key)
|
defaults.append(key)
|
||||||
# hack response cl context in order to hook default filter awaearness into search_form.html template
|
# hack response cl context in order to hook default filter awaearness
|
||||||
response = super(ChangeListDefaultFilter, self).changelist_view(request, extra_context=extra_context)
|
# 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:
|
if hasattr(response, 'context_data') and 'cl' in response.context_data:
|
||||||
response.context_data['cl'].default_changelist_filters = defaults
|
response.context_data['cl'].default_changelist_filters = defaults
|
||||||
return response
|
return response
|
||||||
|
@ -74,3 +40,92 @@ class AtLeastOneRequiredInlineFormSet(BaseInlineFormSet):
|
||||||
if not any(cleaned_data and not cleaned_data.get('DELETE', False)
|
if not any(cleaned_data and not cleaned_data.get('DELETE', False)
|
||||||
for cleaned_data in self.cleaned_data):
|
for cleaned_data in self.cleaned_data):
|
||||||
raise forms.ValidationError('At least one item required.')
|
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.contrib import admin
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.utils import importlib
|
from django.utils import importlib
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
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()
|
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
|
@admin_field
|
||||||
def admin_link(*args, **kwargs):
|
def admin_link(*args, **kwargs):
|
||||||
instance = args[-1]
|
instance = args[-1]
|
||||||
|
|
|
@ -27,5 +27,9 @@ class Account(models.Model):
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_main(cls):
|
||||||
|
return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK)
|
||||||
|
|
||||||
|
|
||||||
services.register(Account, menu=False)
|
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_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 %}
|
{% load i18n admin_urls admin_static admin_modify %}
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,9 +43,6 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="disable/" class="historylink">{% trans "Disable" %}</a>
|
<a href="disable/" class="historylink">{% trans "Disable" %}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
{{ block.super }}
|
||||||
{% 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>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -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.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
from orchestra.admin.utils import admin_link, admin_date
|
from orchestra.admin.utils import admin_link, admin_date
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
|
|
||||||
|
from .actions import generate_bill
|
||||||
from .filters import BillTypeListFilter
|
from .filters import BillTypeListFilter
|
||||||
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, Budget,
|
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, Budget,
|
||||||
BillLine, BudgetLine)
|
BillLine, BudgetLine)
|
||||||
|
@ -24,24 +26,26 @@ class BudgetLineInline(admin.TabularInline):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BillAdmin(AccountAdminMixin, admin.ModelAdmin):
|
class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = (
|
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',)
|
list_filter = (BillTypeListFilter, 'status',)
|
||||||
|
change_view_actions = [generate_bill]
|
||||||
|
change_readonly_fields = ('account', 'type', 'status')
|
||||||
readonly_fields = ('ident',)
|
readonly_fields = ('ident',)
|
||||||
inlines = [BillLineInline]
|
inlines = [BillLineInline]
|
||||||
|
|
||||||
account_link = admin_link('account')
|
account_link = admin_link('account')
|
||||||
created_on_display = admin_date('created_on')
|
created_on_display = admin_date('created_on')
|
||||||
|
|
||||||
def bill_type_link(self, bill):
|
def type_link(self, bill):
|
||||||
bill_type = bill.bill_type.lower()
|
bill_type = bill.type.lower()
|
||||||
url = reverse('admin:bills_%s_changelist' % bill_type)
|
url = reverse('admin:bills_%s_changelist' % bill_type)
|
||||||
return '<a href="%s">%s</a>' % (url, bill.get_bill_type_display())
|
return '<a href="%s">%s</a>' % (url, bill.get_type_display())
|
||||||
bill_type_link.allow_tags = True
|
type_link.allow_tags = True
|
||||||
bill_type_link.short_description = _("type")
|
type_link.short_description = _("type")
|
||||||
bill_type_link.admin_order_field = 'bill_type'
|
type_link.admin_order_field = 'type'
|
||||||
|
|
||||||
def get_inline_instances(self, request, obj=None):
|
def get_inline_instances(self, request, obj=None):
|
||||||
if self.model is Budget:
|
if self.model is Budget:
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.template import loader, Context
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.apps.accounts.models import Account
|
||||||
from orchestra.core import accounts
|
from orchestra.core import accounts
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
@ -12,7 +15,7 @@ class BillManager(models.Manager):
|
||||||
queryset = super(BillManager, self).get_queryset()
|
queryset = super(BillManager, self).get_queryset()
|
||||||
if self.model != Bill:
|
if self.model != Bill:
|
||||||
bill_type = self.model.get_type()
|
bill_type = self.model.get_type()
|
||||||
queryset = queryset.filter(bill_type=bill_type)
|
queryset = queryset.filter(type=bill_type)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +47,7 @@ class Bill(models.Model):
|
||||||
blank=True)
|
blank=True)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='%(class)s')
|
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,
|
status = models.CharField(_("status"), max_length=16, choices=STATUSES,
|
||||||
default=OPEN)
|
default=OPEN)
|
||||||
created_on = models.DateTimeField(_("created on"), auto_now_add=True)
|
created_on = models.DateTimeField(_("created on"), auto_now_add=True)
|
||||||
|
@ -61,13 +64,25 @@ class Bill(models.Model):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.ident
|
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
|
@classmethod
|
||||||
def get_type(cls):
|
def get_type(cls):
|
||||||
return cls.__name__.upper()
|
return cls.__name__.upper()
|
||||||
|
|
||||||
def set_ident(self):
|
def set_ident(self):
|
||||||
cls = type(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':
|
if bill_type == 'BILL':
|
||||||
raise TypeError("get_new_ident() can not be used on a Bill class")
|
raise TypeError("get_new_ident() can not be used on a Bill class")
|
||||||
# Bill number resets every natural year
|
# Bill number resets every natural year
|
||||||
|
@ -91,11 +106,31 @@ class Bill(models.Model):
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.status = self.CLOSED
|
self.status = self.CLOSED
|
||||||
|
self.html = self.render()
|
||||||
self.save()
|
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):
|
def save(self, *args, **kwargs):
|
||||||
if not self.bill_type:
|
if not self.type:
|
||||||
self.bill_type = type(self).get_type()
|
self.type = type(self).get_type()
|
||||||
if not self.ident or (self.ident.startswith('O') and self.status != self.OPEN):
|
if not self.ident or (self.ident.startswith('O') and self.status != self.OPEN):
|
||||||
self.set_ident()
|
self.set_ident()
|
||||||
super(Bill, self).save(*args, **kwargs)
|
super(Bill, self).save(*args, **kwargs)
|
||||||
|
@ -125,6 +160,10 @@ class Budget(Bill):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lines(self):
|
||||||
|
return self.budgetlines
|
||||||
|
|
||||||
|
|
||||||
class BaseBillLine(models.Model):
|
class BaseBillLine(models.Model):
|
||||||
bill = models.ForeignKey(Bill, verbose_name=_("bill"),
|
bill = models.ForeignKey(Bill, verbose_name=_("bill"),
|
||||||
|
@ -145,7 +184,7 @@ class BudgetLine(BaseBillLine):
|
||||||
|
|
||||||
|
|
||||||
class BillLine(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_last_bill_date = models.DateTimeField(null=True)
|
||||||
order_billed_until = models.DateTimeField(null=True)
|
order_billed_until = models.DateTimeField(null=True)
|
||||||
auto = models.BooleanField(default=False)
|
auto = models.BooleanField(default=False)
|
||||||
|
|
|
@ -1,8 +1,27 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
BILLS_IDENT_NUMBER_LENGTH = getattr(settings, 'BILLS_IDENT_NUMBER_LENGTH', 4)
|
BILLS_IDENT_NUMBER_LENGTH = getattr(settings, 'BILLS_IDENT_NUMBER_LENGTH', 4)
|
||||||
|
|
||||||
BILLS_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_INVOICE_IDENT_PREFIX', 'I')
|
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_AMENDMENT_INVOICE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_IDENT_PREFIX', 'A')
|
||||||
|
|
||||||
BILLS_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_FEE_IDENT_PREFIX', 'F')
|
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_AMENDMENT_FEE_IDENT_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_IDENT_PREFIX', 'B')
|
||||||
|
|
||||||
BILLS_BUDGET_IDENT_PREFIX = getattr(settings, 'BILLS_BUDGET_IDENT_PREFIX', 'Q')
|
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')
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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>
|
|
@ -31,6 +31,10 @@ class TransactionAdmin(admin.ModelAdmin):
|
||||||
account_link = admin_link('bill__account')
|
account_link = admin_link('bill__account')
|
||||||
display_state = admin_colored('state', colors=STATE_COLORS)
|
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):
|
class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||||
list_display = ('label', 'method', 'number', 'account_link', 'is_active')
|
list_display = ('label', 'method', 'number', 'account_link', 'is_active')
|
||||||
|
@ -54,7 +58,8 @@ class PaymentProcessAdmin(admin.ModelAdmin):
|
||||||
ids = []
|
ids = []
|
||||||
lines = []
|
lines = []
|
||||||
counter = 0
|
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:
|
for tx_id in tx_ids:
|
||||||
ids.append(str(tx_id))
|
ids.append(str(tx_id))
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
|
@ -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')
|
@register.filter(name='is_checkbox')
|
||||||
def is_checkbox(field):
|
def is_checkbox(field):
|
||||||
return isinstance(field.field.widget, CheckboxInput)
|
return isinstance(field.field.widget, CheckboxInput)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue