Preliminar implementation of amended invoices

This commit is contained in:
Marc Aymerich 2015-07-07 10:41:34 +00:00
parent 3520b3968b
commit 75c72ce8a5
12 changed files with 570 additions and 261 deletions

View file

@ -17,6 +17,7 @@ from orchestra.admin.forms import adminmodelformset_factory
from orchestra.admin.utils import get_object_from_url, change_url from orchestra.admin.utils import get_object_from_url, change_url
from orchestra.utils.html import html_to_pdf from orchestra.utils.html import html_to_pdf
from . import settings
from .forms import SelectSourceForm from .forms import SelectSourceForm
from .helpers import validate_contact from .helpers import validate_contact
from .models import Bill, BillLine from .models import Bill, BillLine
@ -217,7 +218,7 @@ def amend_bills(modeladmin, request, queryset):
if queryset.filter(is_open=True).exists(): if queryset.filter(is_open=True).exists():
messages.warning(request, _("Selected bills should be in closed state")) messages.warning(request, _("Selected bills should be in closed state"))
return return
ids = [] amend_ids = []
for bill in queryset: for bill in queryset:
with translation.override(bill.account.language): with translation.override(bill.account.language):
amend_type = bill.get_amend_type() amend_type = bill.get_amend_type()
@ -239,17 +240,33 @@ def amend_bills(modeladmin, request, queryset):
line = BillLine.objects.create( line = BillLine.objects.create(
bill=amend, bill=amend,
start_on=bill.created_on, start_on=bill.created_on,
description=_("Amend of %(related_type)s %(number)s, tax %(tax)s%%") % context, description=_("%(related_type)s %(number)s subtotal for tax %(tax)s%%") % context,
subtotal=subtotals[0], subtotal=subtotals[0],
tax=tax tax=tax
) )
ids.append(bill.pk) amend_ids.append(amend.pk)
amend_url = reverse('admin:bills_bill_changelist') num = len(amend_ids)
amend_url += '?id=%s' % ','.join(map(str, ids)) if num == 1:
amend_url = reverse('admin:bills_bill_change', args=amend_ids)
else:
amend_url = reverse('admin:bills_bill_changelist')
amend_url += '?id=%s' % ','.join(map(str, amend_ids))
context = {
'url': amend_url,
'num': num,
}
messages.success(request, mark_safe(ungettext( messages.success(request, mark_safe(ungettext(
_('<a href="%s">One amendment bill</a> have been generated.') % amend_url, _('<a href="%(url)s">One amendment bill</a> have been generated.') % context,
_('<a href="%s">%i amendment bills</a> have been generated.') % (amend_url, len(ids)), _('<a href="%(url)s">%(num)i amendment bills</a> have been generated.') % context,
len(ids) num
))) )))
amend_bills.verbose_name = _("Amend") amend_bills.verbose_name = _("Amend")
amend_bills.url_name = 'amend' amend_bills.url_name = 'amend'
def report(modeladmin, request, queryset):
context = {
'bills': queryset,
'currency': settings.BILLS_CURRENCY,
}
return render(request, 'admin/bills/report.html', context)

View file

@ -186,10 +186,10 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
'num_lines', 'display_total', 'display_payment_state', 'is_open', 'is_sent' 'num_lines', 'display_total', 'display_payment_state', 'is_open', 'is_sent'
) )
list_filter = (BillTypeListFilter, 'is_open', 'is_sent', TotalListFilter, PaymentStateListFilter) list_filter = (BillTypeListFilter, 'is_open', 'is_sent', TotalListFilter, PaymentStateListFilter)
add_fields = ('account', 'type', 'is_open', 'due_on', 'comments') add_fields = ('account', 'type', 'amend_of', 'is_open', 'due_on', 'comments')
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': ('number', 'type', 'account_link', 'display_total', 'fields': ('number', 'type', 'amend_of_link', 'account_link', 'display_total',
'display_payment_state', 'is_sent', 'due_on', 'comments'), 'display_payment_state', 'is_sent', 'due_on', 'comments'),
}), }),
(_("Raw"), { (_("Raw"), {
@ -205,13 +205,14 @@ 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.amend_bills, actions.report
] ]
change_readonly_fields = ('account_link', 'type', 'is_open') change_readonly_fields = ('account_link', 'type', 'is_open', 'amend_of_link')
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')
inlines = [BillLineInline, ClosedBillLineInline] inlines = [BillLineInline, ClosedBillLineInline]
created_on_display = admin_date('created_on', short_description=_("Created")) created_on_display = admin_date('created_on', short_description=_("Created"))
amend_of_link = admin_link('amend_of')
def num_lines(self, bill): def num_lines(self, bill):
return bill.lines__count return bill.lines__count
@ -267,8 +268,12 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
fieldsets = super(BillAdmin, self).get_fieldsets(request, obj) fieldsets = super(BillAdmin, self).get_fieldsets(request, obj)
if obj and obj.is_open: if obj:
fieldsets = (fieldsets[0],) # if obj.amend_of_id:
# fieldsets = list(fieldsets)
# fieldsets[0][1]['fields'] = fieldsets[0][1]['fields'] + ('amend_of_link',)
if obj.is_open:
fieldsets = (fieldsets[0],)
return fieldsets return fieldsets
def get_change_view_actions(self, obj=None): def get_change_view_actions(self, obj=None):
@ -289,10 +294,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
""" Make value input widget bigger """ """ Make value input widget bigger """
if db_field.name == 'comments': if db_field.name == 'comments':
kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 4}) kwargs['widget'] = forms.Textarea(attrs={'cols': 70, 'rows': 4})
if db_field.name == 'html': elif db_field.name == 'html':
kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20}) kwargs['widget'] = forms.Textarea(attrs={'cols': 150, 'rows': 20})
return super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs) formfield = super(BillAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'amend_of':
formfield.queryset = formfield.queryset.filter(is_open=False)
return formfield
def get_queryset(self, request): def get_queryset(self, request):
qs = super(BillAdmin, self).get_queryset(request) qs = super(BillAdmin, self).get_queryset(request)
qs = qs.annotate( qs = qs.annotate(

View file

@ -4,6 +4,8 @@ from django.db.models import Q
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from . models import Bill
class BillTypeListFilter(SimpleListFilter): class BillTypeListFilter(SimpleListFilter):
""" Filter tickets by created_by according to request.user """ """ Filter tickets by created_by according to request.user """
@ -89,6 +91,7 @@ class PaymentStateListFilter(SimpleListFilter):
('PAID', _("Paid")), ('PAID', _("Paid")),
('PENDING', _("Pending")), ('PENDING', _("Pending")),
('BAD_DEBT', _("Bad debt")), ('BAD_DEBT', _("Bad debt")),
('AMENDED', _("Amended")),
) )
def queryset(self, request, queryset): def queryset(self, request, queryset):
@ -96,10 +99,12 @@ class PaymentStateListFilter(SimpleListFilter):
if self.value() == 'OPEN': if self.value() == 'OPEN':
return queryset.filter(Q(is_open=True)|Q(type=queryset.model.PROFORMA)) return queryset.filter(Q(is_open=True)|Q(type=queryset.model.PROFORMA))
elif self.value() == 'PAID': elif self.value() == 'PAID':
zeros = queryset.filter(computed_total=0, computed_total__isnull=True).values_list('id', flat=True) zeros = queryset.filter(computed_total=0, computed_total__isnull=True)
zeros = zeros.values_list('id', flat=True)
ammounts = Transaction.objects.exclude(bill_id__in=zeros).secured().group_by('bill_id') ammounts = Transaction.objects.exclude(bill_id__in=zeros).secured().group_by('bill_id')
paid = [] paid = []
for bill_id, total in queryset.exclude(computed_total=0, computed_total__isnull=True, is_open=True).values_list('id', 'computed_total'): relevant = queryset.exclude(computed_total=0, computed_total__isnull=True, is_open=True)
for bill_id, total in relevant.values_list('id', 'computed_total'):
try: try:
ammount = sum([t.ammount for t in ammounts[bill_id]]) ammount = sum([t.ammount for t in ammounts[bill_id]])
except KeyError: except KeyError:
@ -107,7 +112,11 @@ class PaymentStateListFilter(SimpleListFilter):
else: else:
if abs(total) <= abs(ammount): if abs(total) <= abs(ammount):
paid.append(bill_id) paid.append(bill_id)
return queryset.filter(Q(computed_total=0)|Q(computed_total__isnull=True)|Q(id__in=paid)).exclude(is_open=True) return queryset.filter(
Q(computed_total=0) |
Q(computed_total__isnull=True) |
Q(id__in=paid)
).exclude(is_open=True)
elif self.value() == 'PENDING': elif self.value() == 'PENDING':
has_transaction = queryset.exclude(transactions__isnull=True) has_transaction = queryset.exclude(transactions__isnull=True)
non_rejected = has_transaction.exclude(transactions__state=Transaction.REJECTED) non_rejected = has_transaction.exclude(transactions__state=Transaction.REJECTED)
@ -115,4 +124,11 @@ class PaymentStateListFilter(SimpleListFilter):
return queryset.filter(pk__in=non_rejected) return queryset.filter(pk__in=non_rejected)
elif self.value() == 'BAD_DEBT': elif self.value() == 'BAD_DEBT':
closed = queryset.filter(is_open=False).exclude(computed_total=0) closed = queryset.filter(is_open=False).exclude(computed_total=0)
return closed.filter(Q(transactions__state=Transaction.REJECTED)|Q(transactions__isnull=True)) return closed.filter(
Q(transactions__state=Transaction.REJECTED) |
Q(transactions__isnull=True)
)
elif self.value() == 'AMENDED':
amendeds = queryset.filter(type__in=Bill.AMEND_MAP.values(), is_open=False)
amendeds_ids = amendeds.values_list('amend_of', flat=True)
return queryset.filter(id__in=amendeds)

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-29 09:39+0000\n" "POT-Creation-Date: 2015-07-07 10:18+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,37 +18,37 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:37 #: actions.py:40
msgid "Download" msgid "Download"
msgstr "Descarrega" msgstr "Descarrega"
#: actions.py:47 #: actions.py:50
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: actions.py:55 #: actions.py:58
msgid "Selected bills should be in open state" msgid "Selected bills should be in open state"
msgstr "Les factures seleccionades han d'estar en estat obert" msgstr "Les factures seleccionades han d'estar en estat obert"
#: actions.py:73 #: actions.py:76
msgid "Selected bills have been closed" msgid "Selected bills have been closed"
msgstr "Les factures seleccionades han estat tancades" msgstr "Les factures seleccionades han estat tancades"
#: actions.py:86 #: actions.py:89
#, python-format #, python-format
msgid "<a href=\"%(url)s\">One related transaction</a> has been created" msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>" msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
#: actions.py:87 #: actions.py:90
#, python-format #, python-format
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created" msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>" msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
#: actions.py:93 #: actions.py:96
msgid "Are you sure about closing the following bills?" msgid "Are you sure about closing the following bills?"
msgstr "Estàs a punt de tancar les següents factures, estàs segur?" msgstr "Estàs a punt de tancar les següents factures, estàs segur?"
#: actions.py:94 #: actions.py:97
msgid "" msgid ""
"Once a bill is closed it can not be further modified.</p><p>Please select a " "Once a bill is closed it can not be further modified.</p><p>Please select a "
"payment source for the selected bills" "payment source for the selected bills"
@ -56,138 +56,183 @@ msgstr ""
"Una vegada la factura estigui tancada no podrà ser modificada.</p><p>Si us " "Una vegada la factura estigui tancada no podrà ser modificada.</p><p>Si us "
"plau selecciona un mètode de pagament per les factures seleccionades" "plau selecciona un mètode de pagament per les factures seleccionades"
#: actions.py:107 #: actions.py:110
msgid "Close" msgid "Close"
msgstr "Tanca" msgstr "Tanca"
#: actions.py:125 #: actions.py:124
msgid "One bill has been sent." msgid "One bill has been sent."
msgstr "S'ha creat una factura" msgstr "S'ha creat una factura"
#: actions.py:126 #: actions.py:125
#, python-format #, python-format
msgid "%i bills have been sent." msgid "%i bills have been sent."
msgstr "S'han enviat %i factures." msgstr "S'han enviat %i factures."
#: actions.py:128 #: actions.py:127
msgid "Resend" msgid "Resend"
msgstr "Reenviat" msgstr "Reenviat"
#: actions.py:189 #: actions.py:188
#, python-format #, python-format
msgid "%(norders)s orders and %(nlines)s lines undoed." msgid "%(norders)s orders and %(nlines)s lines undoed."
msgstr "%(norders)s ordres i %(nlines)s línies desfetes." msgstr "%(norders)s ordres i %(nlines)s línies desfetes."
#: actions.py:208 #: actions.py:207
msgid "Lines moved" msgid "Lines moved"
msgstr "Línies mogudes" msgstr "Línies mogudes"
#: admin.py:49 admin.py:93 admin.py:128 forms.py:11 #: actions.py:219
msgid "Selected bills should be in closed state"
msgstr "Les factures seleccionades han d'estar en estat obert"
#: actions.py:236
#, python-format
msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s"
msgstr "%(type)s de %(related_type)s %(number)s amb data de creació %(date)s"
#: actions.py:243
#, python-format
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
#: actions.py:259
#, python-format
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
#: actions.py:260
#, python-format
msgid "<a href=\"%(url)s\">%(num)i amendment bills</a> have been generated."
msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
#: actions.py:263
msgid "Amend"
msgstr ""
#: admin.py:54 admin.py:98 admin.py:133 forms.py:11
#: templates/admin/bills/report.html:43
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
#: admin.py:80 #: admin.py:85
msgid "Description" msgid "Description"
msgstr "Descripció" msgstr "Descripció"
#: admin.py:88 #: admin.py:93
msgid "Subtotal" msgid "Subtotal"
msgstr "Subtotal" msgstr "Subtotal"
#: admin.py:118 #: admin.py:123
msgid "Is open" msgid "Is open"
msgstr "És oberta" msgstr "És oberta"
#: admin.py:123 #: admin.py:128
msgid "Subline" msgid "Subline"
msgstr "Sublínia" msgstr "Sublínia"
#: admin.py:157 #: admin.py:162
msgid "No bills selected." msgid "No bills selected."
msgstr "No hi ha factures seleccionades" msgstr "No hi ha factures seleccionades"
#: admin.py:164 #: admin.py:169
#, python-format #, python-format
msgid "Manage %s bill lines." msgid "Manage %s bill lines."
msgstr "Gestiona %s línies de factura." msgstr "Gestiona %s línies de factura."
#: admin.py:166 #: admin.py:171
msgid "Bill not in open state." msgid "Bill not in open state."
msgstr "La factura no està en estat obert" msgstr "La factura no està en estat obert"
#: admin.py:169 #: admin.py:174
msgid "Not all bills are in open state." msgid "Not all bills are in open state."
msgstr "No totes les factures estan en estat obert" msgstr "No totes les factures estan en estat obert"
#: admin.py:170 #: admin.py:175
msgid "Manage bill lines of multiple bills." msgid "Manage bill lines of multiple bills."
msgstr "Gestiona línies de factura de multiples factures." msgstr "Gestiona línies de factura de multiples factures."
#: admin.py:190 #: admin.py:195
msgid "Raw" msgid "Raw"
msgstr "Raw" msgstr "Raw"
#: admin.py:208 #: admin.py:214 models.py:72
msgid "Created" msgid "Created"
msgstr "Creada" msgstr "Creada"
#: admin.py:213 #: admin.py:220
msgid "lines" msgid "lines"
msgstr "línies" msgstr "línies"
#: admin.py:218 templates/bills/microspective.html:118 #: admin.py:225 filters.py:44 templates/bills/microspective.html:118
msgid "total" msgid "total"
msgstr "total" msgstr "total"
#: admin.py:226 models.py:88 models.py:352 #: admin.py:233 models.py:103 models.py:446
msgid "type" msgid "type"
msgstr "tipus" msgstr "tipus"
#: admin.py:243 #: admin.py:250
msgid "Payment" msgid "Payment"
msgstr "Pagament" msgstr "Pagament"
#: filters.py:17 #: filters.py:19
msgid "All" msgid "All"
msgstr "Tot" msgstr "Tot"
#: filters.py:18 models.py:78 #: filters.py:20 models.py:87
msgid "Invoice" msgid "Invoice"
msgstr "Factura" msgstr "Factura"
#: filters.py:19 models.py:79 #: filters.py:21 models.py:88
msgid "Amendment invoice" msgid "Amendment invoice"
msgstr "Factura rectificativa" msgstr "Factura rectificativa"
#: filters.py:20 models.py:80 #: filters.py:22 models.py:89
msgid "Fee" msgid "Fee"
msgstr "Quota de soci" msgstr "Quota de soci"
#: filters.py:21 #: filters.py:23
msgid "Amendment fee" msgid "Amendment fee"
msgstr "Rectificació de quota de soci" msgstr "Rectificació de quota de soci"
#: filters.py:22 #: filters.py:24
msgid "Pro-forma" msgid "Pro-forma"
msgstr "Pro-forma" msgstr "Pro-forma"
#: filters.py:41 #: filters.py:66
msgid "positive price"
msgstr "preu positiu"
#: filters.py:46 filters.py:64
msgid "Yes"
msgstr "Si"
#: filters.py:47 filters.py:65
msgid "No"
msgstr "No"
#: filters.py:59
msgid "has bill contact" msgid "has bill contact"
msgstr "té contacte de facturació" msgstr "té contacte de facturació"
#: forms.py:9 #: filters.py:71
msgid "Yes"
msgstr "Si"
#: filters.py:72
msgid "No"
msgstr "No"
#: filters.py:83
msgid "payment state"
msgstr "Pagament"
#: filters.py:88 models.py:71
msgid "Open"
msgstr ""
#: filters.py:89 models.py:75
msgid "Paid"
msgstr "Pagat"
#: filters.py:90
msgid "Pending"
msgstr "Pendent"
#: filters.py:91 models.py:78
msgid "Bad debt"
msgstr "Incobrable"
#: forms.py:9 templates/admin/bills/report.html:37
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
@ -219,7 +264,7 @@ msgstr "Relacionat"
msgid "Main" msgid "Main"
msgstr "Principal" msgstr "Principal"
#: models.py:23 models.py:86 #: models.py:23 models.py:99
msgid "account" msgid "account"
msgstr "compte" msgstr "compte"
@ -251,141 +296,186 @@ msgstr "Introdueix un codi postal vàlid."
msgid "country" msgid "country"
msgstr "país" msgstr "país"
#: models.py:35 #: models.py:35 templates/admin/bills/report.html:38
msgid "VAT number" msgid "VAT number"
msgstr "NIF" msgstr "NIF"
#: models.py:67 #: models.py:73
msgid "Paid" msgid "Processed"
msgstr "Pagat" msgstr ""
#: models.py:68 #: models.py:74
msgid "Pending" #, fuzzy
msgstr "Pendent" #| msgid "amended line"
msgid "Amended"
msgstr "línia rectificada"
#: models.py:69 #: models.py:76
msgid "Bad debt" msgid "Incomplete"
msgstr "Incobrable" msgstr ""
#: models.py:81 #: models.py:77
msgid "Executed"
msgstr ""
#: models.py:90
msgid "Amendment Fee" msgid "Amendment Fee"
msgstr "Rectificació de quota de soci" msgstr "Rectificació de quota de soci"
#: models.py:82 #: models.py:91
msgid "Pro forma" msgid "Pro forma"
msgstr "Pro forma" msgstr "Pro forma"
#: models.py:85 #: models.py:98
msgid "number" msgid "number"
msgstr "número" msgstr "número"
#: models.py:89 #: models.py:101
#, fuzzy
#| msgid "amended line"
msgid "amend of"
msgstr "línia rectificada"
#: models.py:104
msgid "created on" msgid "created on"
msgstr "creat el" msgstr "creat el"
#: models.py:90 #: models.py:105
msgid "closed on" msgid "closed on"
msgstr "tancat el" msgstr "tancat el"
#: models.py:91 #: models.py:106
msgid "open" msgid "open"
msgstr "obert" msgstr "obert"
#: models.py:92 #: models.py:107
msgid "sent" msgid "sent"
msgstr "enviat" msgstr "enviat"
#: models.py:93 #: models.py:108
msgid "due on" msgid "due on"
msgstr "es deu" msgstr "es deu"
#: models.py:94 #: models.py:109
msgid "updated on" msgid "updated on"
msgstr "actualitzada el" msgstr "actualitzada el"
#: models.py:97 #: models.py:112
msgid "comments" msgid "comments"
msgstr "comentaris" msgstr "comentaris"
#: models.py:98 #: models.py:113
msgid "HTML" msgid "HTML"
msgstr "HTML" msgstr "HTML"
#: models.py:285 #: models.py:192
#, python-format
msgid "Type %s is not an amendment."
msgstr ""
#: models.py:194
msgid "Amend of related account doesn't match bill account."
msgstr ""
#: models.py:199
#, python-format
msgid "Type %s requires an amend of link."
msgstr ""
#: models.py:378
msgid "bill" msgid "bill"
msgstr "factura" msgstr "factura"
#: models.py:286 models.py:350 templates/bills/microspective.html:73 #: models.py:379 models.py:444 templates/bills/microspective.html:73
msgid "description" msgid "description"
msgstr "descripció" msgstr "descripció"
#: models.py:287 #: models.py:380
msgid "rate" msgid "rate"
msgstr "tarifa" msgstr "tarifa"
#: models.py:288 #: models.py:381
msgid "quantity" msgid "quantity"
msgstr "quantitat" msgstr "quantitat"
#: models.py:289 #: models.py:383
#, fuzzy #, fuzzy
#| msgid "quantity" #| msgid "quantity"
msgid "Verbose quantity" msgid "Verbose quantity"
msgstr "quantitat" msgstr "quantitat"
#: models.py:290 templates/bills/microspective.html:77 #: models.py:384 templates/bills/microspective.html:77
#: templates/bills/microspective.html:111 #: templates/bills/microspective.html:111
msgid "subtotal" msgid "subtotal"
msgstr "subtotal" msgstr "subtotal"
#: models.py:291 #: models.py:385
msgid "tax" msgid "tax"
msgstr "impostos" msgstr "impostos"
#: models.py:292 #: models.py:386
msgid "start" msgid "start"
msgstr "iniciar" msgstr "iniciar"
#: models.py:293 #: models.py:387
msgid "end" msgid "end"
msgstr "finalitzar" msgstr "finalitzar"
#: models.py:295 #: models.py:389
msgid "Informative link back to the order" msgid "Informative link back to the order"
msgstr "Enllaç informatiu de l'ordre" msgstr "Enllaç informatiu de l'ordre"
#: models.py:296 #: models.py:390
msgid "order billed" msgid "order billed"
msgstr "ordre facturada" msgstr "ordre facturada"
#: models.py:297 #: models.py:391
msgid "order billed until" msgid "order billed until"
msgstr "ordre facturada fins a" msgstr "ordre facturada fins a"
#: models.py:298 #: models.py:392
msgid "created" msgid "created"
msgstr "creada" msgstr "creada"
#: models.py:300 #: models.py:394
msgid "amended line" msgid "amended line"
msgstr "línia rectificada" msgstr "línia rectificada"
#: models.py:343 #: models.py:437
msgid "Volume" msgid "Volume"
msgstr "Volum" msgstr "Volum"
#: models.py:344 #: models.py:438
msgid "Compensation" msgid "Compensation"
msgstr "Compensació" msgstr "Compensació"
#: models.py:345 #: models.py:439
msgid "Other" msgid "Other"
msgstr "Altre" msgstr "Altre"
#: models.py:349 #: models.py:443
msgid "bill line" msgid "bill line"
msgstr "línia de factura" msgstr "línia de factura"
#: templates/admin/bills/report.html:39
msgid "Contact"
msgstr ""
#: templates/admin/bills/report.html:40
#, fuzzy
#| msgid "Due date"
msgid "Close date"
msgstr "Data de pagament"
#: templates/admin/bills/report.html:41
msgid "Base"
msgstr ""
#: templates/admin/bills/report.html:42 templates/bills/microspective.html:111
#: templates/bills/microspective.html:114
msgid "VAT"
msgstr "IVA"
#: templates/bills/microspective-fee.html:107 #: templates/bills/microspective-fee.html:107
msgid "Due date" msgid "Due date"
msgstr "Data de pagament" msgstr "Data de pagament"
@ -433,11 +523,6 @@ msgstr "hrs/qnt"
msgid "rate/price" msgid "rate/price"
msgstr "tarifa/preu" msgstr "tarifa/preu"
#: templates/bills/microspective.html:111
#: templates/bills/microspective.html:114
msgid "VAT"
msgstr "IVA"
#: templates/bills/microspective.html:114 #: templates/bills/microspective.html:114
msgid "taxes" msgid "taxes"
msgstr "impostos" msgstr "impostos"
@ -451,6 +536,7 @@ msgid "PAYMENT"
msgstr "PAGAMENT" msgstr "PAGAMENT"
#: templates/bills/microspective.html:140 #: templates/bills/microspective.html:140
#, python-format
msgid "" msgid ""
"\n" "\n"
" You can pay our <i>%(type)s</i> by bank transfer.<br>\n" " You can pay our <i>%(type)s</i> by bank transfer.<br>\n"
@ -483,3 +569,6 @@ msgstr ""
" contacta amb nosaltres a %(email)s. Et respondrem el més " " contacta amb nosaltres a %(email)s. Et respondrem el més "
"ràpidament possible.\n" "ràpidament possible.\n"
" " " "
#~ msgid "positive price"
#~ msgstr "preu positiu"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-05-28 12:31+0000\n" "POT-Creation-Date: 2015-07-07 10:10+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,37 +18,37 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: actions.py:37 #: actions.py:40
msgid "Download" msgid "Download"
msgstr "Descarga" msgstr "Descarga"
#: actions.py:47 #: actions.py:50
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: actions.py:55 #: actions.py:58
msgid "Selected bills should be in open state" msgid "Selected bills should be in open state"
msgstr "Las facturas seleccionadas están en estado abierto" msgstr "Las facturas seleccionadas están en estado abierto"
#: actions.py:73 #: actions.py:76
msgid "Selected bills have been closed" msgid "Selected bills have been closed"
msgstr "Las facturas seleccionadas han sido cerradas" msgstr "Las facturas seleccionadas han sido cerradas"
#: actions.py:86 #: actions.py:89
#, python-format #, python-format
msgid "<a href=\"%(url)s\">One related transaction</a> has been created" msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>" msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
#: actions.py:87 #: actions.py:90
#, python-format #, python-format
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created" msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>" msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
#: actions.py:93 #: actions.py:96
msgid "Are you sure about closing the following bills?" msgid "Are you sure about closing the following bills?"
msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?" msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?"
#: actions.py:94 #: actions.py:97
msgid "" msgid ""
"Once a bill is closed it can not be further modified.</p><p>Please select a " "Once a bill is closed it can not be further modified.</p><p>Please select a "
"payment source for the selected bills" "payment source for the selected bills"
@ -56,138 +56,180 @@ msgstr ""
"Una vez cerrada la factura ya no se podrá modificar.</p><p>Por favor " "Una vez cerrada la factura ya no se podrá modificar.</p><p>Por favor "
"seleciona un metodo de pago para las facturas seleccionadas" "seleciona un metodo de pago para las facturas seleccionadas"
#: actions.py:107 #: actions.py:110
msgid "Close" msgid "Close"
msgstr "Cerrar" msgstr "Cerrar"
#: actions.py:125 #: actions.py:124
msgid "One bill has been sent." msgid "One bill has been sent."
msgstr "Se ha enviado una factura" msgstr "Se ha enviado una factura"
#: actions.py:126 #: actions.py:125
#, python-format #, python-format
msgid "%i bills have been sent." msgid "%i bills have been sent."
msgstr "" msgstr ""
#: actions.py:128 #: actions.py:127
msgid "Resend" msgid "Resend"
msgstr "" msgstr ""
#: actions.py:189 #: actions.py:188
#, python-format #, python-format
msgid "%(norders)s orders and %(nlines)s lines undoed." msgid "%(norders)s orders and %(nlines)s lines undoed."
msgstr "" msgstr ""
#: actions.py:208 #: actions.py:207
msgid "Lines moved" msgid "Lines moved"
msgstr "" msgstr ""
#: admin.py:49 admin.py:93 admin.py:128 forms.py:11 #: actions.py:219
msgid "Selected bills should be in closed state"
msgstr "Las facturas seleccionadas están en estado abierto"
#: actions.py:236
#, python-format
msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s"
msgstr "%(type)s de %(related_type)s %(number)s con fecha de creación %(date)s"
#: actions.py:243
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
#: actions.py:255
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
#: actions.py:256
msgid "<a href=\"%(url)s\">%(num)i amendment bills</a> have been generated."
msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
#: actions.py:259
msgid "Amend"
msgstr ""
#: admin.py:54 admin.py:98 admin.py:133 forms.py:11
#: templates/admin/bills/report.html:43
msgid "Total" msgid "Total"
msgstr "" msgstr ""
#: admin.py:80 #: admin.py:85
msgid "Description" msgid "Description"
msgstr "" msgstr ""
#: admin.py:88 #: admin.py:93
msgid "Subtotal" msgid "Subtotal"
msgstr "" msgstr ""
#: admin.py:118 #: admin.py:123
msgid "Is open" msgid "Is open"
msgstr "" msgstr ""
#: admin.py:123 #: admin.py:128
msgid "Subline" msgid "Subline"
msgstr "" msgstr ""
#: admin.py:157 #: admin.py:162
msgid "No bills selected." msgid "No bills selected."
msgstr "" msgstr ""
#: admin.py:164 #: admin.py:169
#, python-format #, python-format
msgid "Manage %s bill lines." msgid "Manage %s bill lines."
msgstr "" msgstr ""
#: admin.py:166 #: admin.py:171
msgid "Bill not in open state." msgid "Bill not in open state."
msgstr "" msgstr ""
#: admin.py:169 #: admin.py:174
msgid "Not all bills are in open state." msgid "Not all bills are in open state."
msgstr "" msgstr ""
#: admin.py:170 #: admin.py:175
msgid "Manage bill lines of multiple bills." msgid "Manage bill lines of multiple bills."
msgstr "" msgstr ""
#: admin.py:190 #: admin.py:195
msgid "Raw" msgid "Raw"
msgstr "" msgstr ""
#: admin.py:208 #: admin.py:214 models.py:72
msgid "Created" msgid "Created"
msgstr "" msgstr ""
#: admin.py:213 #: admin.py:220
msgid "lines" msgid "lines"
msgstr "" msgstr ""
#: admin.py:218 templates/bills/microspective.html:118 #: admin.py:225 filters.py:44 templates/bills/microspective.html:118
msgid "total" msgid "total"
msgstr "" msgstr ""
#: admin.py:226 models.py:88 models.py:352 #: admin.py:233 models.py:103 models.py:446
msgid "type" msgid "type"
msgstr "" msgstr ""
#: admin.py:243 #: admin.py:250
msgid "Payment" msgid "Payment"
msgstr "Pago" msgstr "Pago"
#: filters.py:17 #: filters.py:19
msgid "All" msgid "All"
msgstr "" msgstr ""
#: filters.py:18 models.py:78 #: filters.py:20 models.py:87
msgid "Invoice" msgid "Invoice"
msgstr "Factura" msgstr "Factura"
#: filters.py:19 models.py:79 #: filters.py:21 models.py:88
msgid "Amendment invoice" msgid "Amendment invoice"
msgstr "Factura rectificative" msgstr "Factura rectificative"
#: filters.py:20 models.py:80 #: filters.py:22 models.py:89
msgid "Fee" msgid "Fee"
msgstr "Quota de socio" msgstr "Quota de socio"
#: filters.py:21 #: filters.py:23
msgid "Amendment fee" msgid "Amendment fee"
msgstr "Quota rectificativa" msgstr "Quota rectificativa"
#: filters.py:22 #: filters.py:24
msgid "Pro-forma" msgid "Pro-forma"
msgstr "" msgstr ""
#: filters.py:41 #: filters.py:66
msgid "positive price"
msgstr ""
#: filters.py:46 filters.py:64
msgid "Yes"
msgstr ""
#: filters.py:47 filters.py:65
msgid "No"
msgstr ""
#: filters.py:59
msgid "has bill contact" msgid "has bill contact"
msgstr "" msgstr ""
#: forms.py:9 #: filters.py:71
msgid "Yes"
msgstr ""
#: filters.py:72
msgid "No"
msgstr ""
#: filters.py:83
msgid "payment state"
msgstr "Pago"
#: filters.py:88 models.py:71
msgid "Open"
msgstr ""
#: filters.py:89 models.py:75
msgid "Paid"
msgstr ""
#: filters.py:90
msgid "Pending"
msgstr ""
#: filters.py:91 models.py:78
msgid "Bad debt"
msgstr ""
#: forms.py:9 templates/admin/bills/report.html:37
msgid "Number" msgid "Number"
msgstr "" msgstr ""
@ -217,7 +259,7 @@ msgstr ""
msgid "Main" msgid "Main"
msgstr "" msgstr ""
#: models.py:23 models.py:86 #: models.py:23 models.py:99
msgid "account" msgid "account"
msgstr "" msgstr ""
@ -249,138 +291,179 @@ msgstr ""
msgid "country" msgid "country"
msgstr "" msgstr ""
#: models.py:35 #: models.py:35 templates/admin/bills/report.html:38
msgid "VAT number" msgid "VAT number"
msgstr "" msgstr ""
#: models.py:67 #: models.py:73
msgid "Paid" msgid "Processed"
msgstr "" msgstr ""
#: models.py:68 #: models.py:74
msgid "Pending" msgid "Amended"
msgstr "Quota rectificativa"
#: models.py:76
msgid "Incomplete"
msgstr "" msgstr ""
#: models.py:69 #: models.py:77
msgid "Bad debt" msgid "Executed"
msgstr ""
#: models.py:81
msgid "Amendment Fee"
msgstr ""
#: models.py:82
msgid "Pro forma"
msgstr ""
#: models.py:85
msgid "number"
msgstr ""
#: models.py:89
msgid "created on"
msgstr "" msgstr ""
#: models.py:90 #: models.py:90
msgid "closed on" msgid "Amendment Fee"
msgstr "" msgstr ""
#: models.py:91 #: models.py:91
msgid "open" msgid "Pro forma"
msgstr ""
#: models.py:92
msgid "sent"
msgstr ""
#: models.py:93
msgid "due on"
msgstr ""
#: models.py:94
msgid "updated on"
msgstr ""
#: models.py:97
msgid "comments"
msgstr "" msgstr ""
#: models.py:98 #: models.py:98
msgid "number"
msgstr "número"
#: models.py:101
msgid "amend of"
msgstr "rectificación de"
#: models.py:104
msgid "created on"
msgstr "creado en"
#: models.py:105
msgid "closed on"
msgstr "cerrada en"
#: models.py:106
msgid "open"
msgstr "abierta"
#: models.py:107
msgid "sent"
msgstr "enviada"
#: models.py:108
msgid "due on"
msgstr "vencimiento"
#: models.py:109
msgid "updated on"
msgstr "actualizada en"
#: models.py:112
msgid "comments"
msgstr "comentarios"
#: models.py:113
msgid "HTML" msgid "HTML"
msgstr "HTML"
#: models.py:192
#, python-format
msgid "Type %s is not an amendment."
msgstr "" msgstr ""
#: models.py:285 #: models.py:194
msgid "Amend of related account doesn't match bill account."
msgstr ""
#: models.py:199
#, python-format
msgid "Type %s requires an amend of link."
msgstr ""
#: models.py:378
msgid "bill" msgid "bill"
msgstr "" msgstr "factura"
#: models.py:286 models.py:350 templates/bills/microspective.html:73 #: models.py:379 models.py:444 templates/bills/microspective.html:73
msgid "description" msgid "description"
msgstr "" msgstr "descripción"
#: models.py:287 #: models.py:380
msgid "rate" msgid "rate"
msgstr "" msgstr "tarifa"
#: models.py:288 #: models.py:381
msgid "quantity" msgid "quantity"
msgstr "" msgstr "cantidad"
#: models.py:289 #: models.py:383
msgid "Verbose quantity" msgid "Verbose quantity"
msgstr "" msgstr "Cantidad"
#: models.py:290 templates/bills/microspective.html:77 #: models.py:384 templates/bills/microspective.html:77
#: templates/bills/microspective.html:111 #: templates/bills/microspective.html:111
msgid "subtotal" msgid "subtotal"
msgstr "" msgstr "subtotal"
#: models.py:291 #: models.py:385
msgid "tax" msgid "tax"
msgstr "" msgstr "impuesto"
#: models.py:292 #: models.py:386
msgid "start" msgid "start"
msgstr "" msgstr "inicio"
#: models.py:293 #: models.py:387
msgid "end" msgid "end"
msgstr "" msgstr "fín"
#: models.py:295 #: models.py:389
msgid "Informative link back to the order" msgid "Informative link back to the order"
msgstr "" msgstr ""
#: models.py:296 #: models.py:390
msgid "order billed" msgid "order billed"
msgstr "" msgstr ""
#: models.py:297 #: models.py:391
msgid "order billed until" msgid "order billed until"
msgstr "" msgstr ""
#: models.py:298 #: models.py:392
msgid "created" msgid "created"
msgstr "" msgstr "creado"
#: models.py:300 #: models.py:394
msgid "amended line" msgid "amended line"
msgstr "" msgstr "linea rectificativa"
#: models.py:343 #: models.py:437
msgid "Volume" msgid "Volume"
msgstr "" msgstr "Volumen"
#: models.py:344 #: models.py:438
msgid "Compensation" msgid "Compensation"
msgstr "" msgstr "Compensación"
#: models.py:345 #: models.py:439
msgid "Other" msgid "Other"
msgstr "" msgstr "Otro"
#: models.py:349 #: models.py:443
msgid "bill line" msgid "bill line"
msgstr "" msgstr "linea de factura"
#: templates/admin/bills/report.html:39
msgid "Contact"
msgstr "Contacto"
#: templates/admin/bills/report.html:40
#, fuzzy
#| msgid "Due date"
msgid "Close date"
msgstr "Fecha de pago"
#: templates/admin/bills/report.html:41
msgid "Base"
msgstr "Base"
#: templates/admin/bills/report.html:42 templates/bills/microspective.html:111
#: templates/bills/microspective.html:114
msgid "VAT"
msgstr "IVA"
#: templates/bills/microspective-fee.html:107 #: templates/bills/microspective-fee.html:107
msgid "Due date" msgid "Due date"
@ -427,11 +510,6 @@ msgstr "hrs/cant"
msgid "rate/price" msgid "rate/price"
msgstr "tarifa/precio" msgstr "tarifa/precio"
#: templates/bills/microspective.html:111
#: templates/bills/microspective.html:114
msgid "VAT"
msgstr "IVA"
#: templates/bills/microspective.html:114 #: templates/bills/microspective.html:114
msgid "taxes" msgid "taxes"
msgstr "impuestos" msgstr "impuestos"

View file

@ -90,6 +90,10 @@ class Bill(models.Model):
(AMENDMENTFEE, _("Amendment Fee")), (AMENDMENTFEE, _("Amendment Fee")),
(PROFORMA, _("Pro forma")), (PROFORMA, _("Pro forma")),
) )
AMEND_MAP = {
INVOICE: AMENDMENTINVOICE,
FEE: AMENDMENTFEE,
}
number = models.CharField(_("number"), max_length=16, unique=True, blank=True) number = models.CharField(_("number"), max_length=16, unique=True, blank=True)
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
@ -181,6 +185,24 @@ class Bill(models.Model):
return self.EXECUTED return self.EXECUTED
return self.BAD_DEBT return self.BAD_DEBT
def clean(self):
if self.amend_of_id:
errors = {}
if self.type not in self.AMEND_MAP.values():
errors['amend_of'] = _("Type %s is not an amendment.") % self.get_type_display()
if self.amend_of.account_id != self.account_id:
errors['account'] = _("Amend of related account doesn't match bill account.")
if self.amend_of.is_open:
errors['amend_of'] = _("Related invoice is in open state.")
if self.amend_of.type in self.AMEND_MAP.values():
errors['amend_of'] = _("Related invoice is an amendment.")
if errors:
raise ValidationError(errors)
elif self.type in self.AMEND_MAP.values():
raise ValidationError({
'amend_of': _("Type %s requires an amend of link.") % self.get_type_display()
})
def get_total(self): def get_total(self):
if not self.is_open: if not self.is_open:
return self.total return self.total
@ -201,11 +223,7 @@ class Bill(models.Model):
return self.type or self.get_class_type() return self.type or self.get_class_type()
def get_amend_type(self): def get_amend_type(self):
amend_map = { amend_type = self.AMEND_MAP.get(self.type)
self.INVOICE: self.AMENDMENTINVOICE,
self.FEE: self.AMENDMENTFEE,
}
amend_type = amend_map.get(self.type)
if amend_type is None: if amend_type is None:
raise TypeError("%s has no associated amend type." % self.type) raise TypeError("%s has no associated amend type." % self.type)
return amend_type return amend_type
@ -321,10 +339,17 @@ class Bill(models.Model):
subtotals[tax] = (subtotal, round(tax/100*subtotal, 2)) subtotals[tax] = (subtotal, round(tax/100*subtotal, 2))
return subtotals return subtotals
def compute_base(self):
bases = self.lines.annotate(
bases=F('subtotal') + Coalesce(F('sublines__total'), 0)
)
return round(bases.aggregate(Sum('bases'))['bases__sum'] or 0, 2)
def compute_total(self): def compute_total(self):
totals = self.lines.annotate( totals = self.lines.annotate(
totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100)) totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100)
return round(totals.aggregate(Sum('totals'))['totals__sum'], 2) )
return round(totals.aggregate(Sum('totals'))['totals__sum'] or 0, 2)
class Invoice(Bill): class Invoice(Bill):
@ -363,7 +388,7 @@ class BillLine(models.Model):
subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2) subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2)
tax = models.DecimalField(_("tax"), max_digits=4, decimal_places=2) tax = models.DecimalField(_("tax"), max_digits=4, decimal_places=2)
start_on = models.DateField(_("start")) start_on = models.DateField(_("start"))
end_on = models.DateField(_("end"), null=True) end_on = models.DateField(_("end"), null=True, blank=True)
order = models.ForeignKey(settings.BILLS_ORDER_MODEL, null=True, blank=True, order = models.ForeignKey(settings.BILLS_ORDER_MODEL, null=True, blank=True,
help_text=_("Informative link back to the order"), on_delete=models.SET_NULL) help_text=_("Informative link back to the order"), on_delete=models.SET_NULL)
order_billed_on = models.DateField(_("order billed"), null=True, blank=True) order_billed_on = models.DateField(_("order billed"), null=True, blank=True)

View file

@ -0,0 +1,60 @@
{% load i18n utils %}
<html>
<head>
<title>Bill Report</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style type="text/css">
@page {
size: 11.69in 8.27in;
}
table {
font-family: sans;
font-size: 10px;
max-width: 10in;
}
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-base, .item.column-vat, .item.column-total, .item.column-number {
text-align: right;
}
.column-vat-number {
text-align: center;
}
</style>
</head>
<body>
<table>
<tr id="transaction">
<th class="title column-number">{% trans "Number" %}</th>
<th class="title column-vat-number">{% trans "VAT number" %}</th>
<th class="title column-billcontant">{% trans "Contact" %}</th>
<th class="title column-date">{% trans "Close date" %}</th>
<th class="title column-base">{% trans "Base" %}</th>
<th class="title column-vat">{% trans "VAT" %}</th>
<th class="title column-total">{% trans "Total" %}</th>
</tr>
{% for bill in bills %}
<tr>
<td class="item column-number">{{ bill.number }}</td>
<td class="item column-vat-number">{{ bill.buyer.vat }}</td>
<td class="item column-billcontant">{{ bill.buyer.get_name }}</td>
<td class="item column-date">{{ bill.closed_on|date }}</td>
{% with base=bill.compute_base total=bill.compute_total %}
<td class="item column-base">{{ base }} &{{ currency }};</td>
<td class="item column-vat">{{ total|sub:base }} &{{ currency }};</td>
<td class="item column-total">{{ total }} &{{ currency }};</td>
{% endwith %}
</tr>
{% endfor %}
</table>
</body>
</html>

View file

@ -4,7 +4,7 @@ from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from .utils import normurlpath from .utils import normurlpath
from .validators import validate_domain_protocol from .validators import validate_domain_protocol, validate_server_name
class WebsiteAdminForm(forms.ModelForm): class WebsiteAdminForm(forms.ModelForm):
@ -15,12 +15,16 @@ class WebsiteAdminForm(forms.ModelForm):
if not domains: if not domains:
return self.cleaned_data return self.cleaned_data
protocol = self.cleaned_data.get('protocol') protocol = self.cleaned_data.get('protocol')
for domain in domains.all(): domains = domains.all()
for domain in domains:
try: try:
validate_domain_protocol(self.instance, domain, protocol) validate_domain_protocol(self.instance, domain, protocol)
except ValidationError as e: except ValidationError as err:
# TODO not sure about this one self.add_error(None, err)
self.add_error(None, e) try:
validate_server_name(domains)
except ValidationError as err:
self.add_error('domains', err)
return self.cleaned_data return self.cleaned_data

View file

@ -28,3 +28,11 @@ def validate_domain_protocol(website, domain, protocol):
raise ValidationError({ raise ValidationError({
'domains': 'A website is already defined for "%s" on protocol %s' % (domain, protocol), 'domains': 'A website is already defined for "%s" on protocol %s' % (domain, protocol),
}) })
def validate_server_name(domains):
if domains:
for domain in domains:
if not domain.name.startswith('*'):
return
raise ValidationError(_("At least one non-wildcard domain should be provided."))

View file

@ -96,3 +96,7 @@ def admin_url(obj):
def isactive(obj): def isactive(obj):
return getattr(obj, 'is_active', True) return getattr(obj, 'is_active', True)
@register.filter
def sub(value, arg):
return value - arg