diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py
index dc5d8618..b8dc3e7d 100644
--- a/orchestra/contrib/bills/actions.py
+++ b/orchestra/contrib/bills/actions.py
@@ -17,6 +17,7 @@ from orchestra.admin.forms import adminmodelformset_factory
from orchestra.admin.utils import get_object_from_url, change_url
from orchestra.utils.html import html_to_pdf
+from . import settings
from .forms import SelectSourceForm
from .helpers import validate_contact
from .models import Bill, BillLine
@@ -217,7 +218,7 @@ def amend_bills(modeladmin, request, queryset):
if queryset.filter(is_open=True).exists():
messages.warning(request, _("Selected bills should be in closed state"))
return
- ids = []
+ amend_ids = []
for bill in queryset:
with translation.override(bill.account.language):
amend_type = bill.get_amend_type()
@@ -239,17 +240,33 @@ def amend_bills(modeladmin, request, queryset):
line = BillLine.objects.create(
bill=amend,
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],
tax=tax
)
- ids.append(bill.pk)
- amend_url = reverse('admin:bills_bill_changelist')
- amend_url += '?id=%s' % ','.join(map(str, ids))
+ amend_ids.append(amend.pk)
+ num = len(amend_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(
- _('One amendment bill have been generated.') % amend_url,
- _('%i amendment bills have been generated.') % (amend_url, len(ids)),
- len(ids)
+ _('One amendment bill have been generated.') % context,
+ _('%(num)i amendment bills have been generated.') % context,
+ num
)))
amend_bills.verbose_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)
diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py
index 427527e3..4c5c55fb 100644
--- a/orchestra/contrib/bills/admin.py
+++ b/orchestra/contrib/bills/admin.py
@@ -186,10 +186,10 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
'num_lines', 'display_total', 'display_payment_state', 'is_open', 'is_sent'
)
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 = (
(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'),
}),
(_("Raw"), {
@@ -205,13 +205,14 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
]
actions = [
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')
inlines = [BillLineInline, ClosedBillLineInline]
created_on_display = admin_date('created_on', short_description=_("Created"))
+ amend_of_link = admin_link('amend_of')
def num_lines(self, bill):
return bill.lines__count
@@ -267,8 +268,12 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
def get_fieldsets(self, request, obj=None):
fieldsets = super(BillAdmin, self).get_fieldsets(request, obj)
- if obj and obj.is_open:
- fieldsets = (fieldsets[0],)
+ if obj:
+# 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
def get_change_view_actions(self, obj=None):
@@ -289,10 +294,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
""" Make value input widget bigger """
if db_field.name == 'comments':
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})
- 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):
qs = super(BillAdmin, self).get_queryset(request)
qs = qs.annotate(
diff --git a/orchestra/contrib/bills/filters.py b/orchestra/contrib/bills/filters.py
index aad3d800..8df9ffee 100644
--- a/orchestra/contrib/bills/filters.py
+++ b/orchestra/contrib/bills/filters.py
@@ -4,6 +4,8 @@ from django.db.models import Q
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
+from . models import Bill
+
class BillTypeListFilter(SimpleListFilter):
""" Filter tickets by created_by according to request.user """
@@ -89,6 +91,7 @@ class PaymentStateListFilter(SimpleListFilter):
('PAID', _("Paid")),
('PENDING', _("Pending")),
('BAD_DEBT', _("Bad debt")),
+ ('AMENDED', _("Amended")),
)
def queryset(self, request, queryset):
@@ -96,10 +99,12 @@ class PaymentStateListFilter(SimpleListFilter):
if self.value() == 'OPEN':
return queryset.filter(Q(is_open=True)|Q(type=queryset.model.PROFORMA))
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')
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:
ammount = sum([t.ammount for t in ammounts[bill_id]])
except KeyError:
@@ -107,7 +112,11 @@ class PaymentStateListFilter(SimpleListFilter):
else:
if abs(total) <= abs(ammount):
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':
has_transaction = queryset.exclude(transactions__isnull=True)
non_rejected = has_transaction.exclude(transactions__state=Transaction.REJECTED)
@@ -115,4 +124,11 @@ class PaymentStateListFilter(SimpleListFilter):
return queryset.filter(pk__in=non_rejected)
elif self.value() == 'BAD_DEBT':
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)
diff --git a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo
index 0e97045d..943758b5 100644
Binary files a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo and b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo differ
diff --git a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po
index 67cc7bf4..eb755540 100644
--- a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po
+++ b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME
Please select a " "payment source for the selected bills" @@ -56,138 +56,183 @@ msgstr "" "Una vegada la factura estigui tancada no podrà ser modificada.
Si us "
"plau selecciona un mètode de pagament per les factures seleccionades"
-#: actions.py:107
+#: actions.py:110
msgid "Close"
msgstr "Tanca"
-#: actions.py:125
+#: actions.py:124
msgid "One bill has been sent."
msgstr "S'ha creat una factura"
-#: actions.py:126
+#: actions.py:125
#, python-format
msgid "%i bills have been sent."
msgstr "S'han enviat %i factures."
-#: actions.py:128
+#: actions.py:127
msgid "Resend"
msgstr "Reenviat"
-#: actions.py:189
+#: actions.py:188
#, python-format
msgid "%(norders)s orders and %(nlines)s lines undoed."
msgstr "%(norders)s ordres i %(nlines)s línies desfetes."
-#: actions.py:208
+#: actions.py:207
msgid "Lines moved"
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 "One amendment bill have been generated."
+msgstr "S'ha creat una transacció"
+
+#: actions.py:260
+#, python-format
+msgid "%(num)i amendment bills have been generated."
+msgstr "S'han creat les %(num)i següents transaccions"
+
+#: 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"
msgstr "Total"
-#: admin.py:80
+#: admin.py:85
msgid "Description"
msgstr "Descripció"
-#: admin.py:88
+#: admin.py:93
msgid "Subtotal"
msgstr "Subtotal"
-#: admin.py:118
+#: admin.py:123
msgid "Is open"
msgstr "És oberta"
-#: admin.py:123
+#: admin.py:128
msgid "Subline"
msgstr "Sublínia"
-#: admin.py:157
+#: admin.py:162
msgid "No bills selected."
msgstr "No hi ha factures seleccionades"
-#: admin.py:164
+#: admin.py:169
#, python-format
msgid "Manage %s bill lines."
msgstr "Gestiona %s línies de factura."
-#: admin.py:166
+#: admin.py:171
msgid "Bill not in open state."
msgstr "La factura no està en estat obert"
-#: admin.py:169
+#: admin.py:174
msgid "Not all bills are in open state."
msgstr "No totes les factures estan en estat obert"
-#: admin.py:170
+#: admin.py:175
msgid "Manage bill lines of multiple bills."
msgstr "Gestiona línies de factura de multiples factures."
-#: admin.py:190
+#: admin.py:195
msgid "Raw"
msgstr "Raw"
-#: admin.py:208
+#: admin.py:214 models.py:72
msgid "Created"
msgstr "Creada"
-#: admin.py:213
+#: admin.py:220
msgid "lines"
msgstr "línies"
-#: admin.py:218 templates/bills/microspective.html:118
+#: admin.py:225 filters.py:44 templates/bills/microspective.html:118
msgid "total"
msgstr "total"
-#: admin.py:226 models.py:88 models.py:352
+#: admin.py:233 models.py:103 models.py:446
msgid "type"
msgstr "tipus"
-#: admin.py:243
+#: admin.py:250
msgid "Payment"
msgstr "Pagament"
-#: filters.py:17
+#: filters.py:19
msgid "All"
msgstr "Tot"
-#: filters.py:18 models.py:78
+#: filters.py:20 models.py:87
msgid "Invoice"
msgstr "Factura"
-#: filters.py:19 models.py:79
+#: filters.py:21 models.py:88
msgid "Amendment invoice"
msgstr "Factura rectificativa"
-#: filters.py:20 models.py:80
+#: filters.py:22 models.py:89
msgid "Fee"
msgstr "Quota de soci"
-#: filters.py:21
+#: filters.py:23
msgid "Amendment fee"
msgstr "Rectificació de quota de soci"
-#: filters.py:22
+#: filters.py:24
msgid "Pro-forma"
msgstr "Pro-forma"
-#: filters.py:41
-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
+#: filters.py:66
msgid "has bill contact"
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"
msgstr "Número"
@@ -219,7 +264,7 @@ msgstr "Relacionat"
msgid "Main"
msgstr "Principal"
-#: models.py:23 models.py:86
+#: models.py:23 models.py:99
msgid "account"
msgstr "compte"
@@ -251,141 +296,186 @@ msgstr "Introdueix un codi postal vàlid."
msgid "country"
msgstr "país"
-#: models.py:35
+#: models.py:35 templates/admin/bills/report.html:38
msgid "VAT number"
msgstr "NIF"
-#: models.py:67
-msgid "Paid"
-msgstr "Pagat"
+#: models.py:73
+msgid "Processed"
+msgstr ""
-#: models.py:68
-msgid "Pending"
-msgstr "Pendent"
+#: models.py:74
+#, fuzzy
+#| msgid "amended line"
+msgid "Amended"
+msgstr "línia rectificada"
-#: models.py:69
-msgid "Bad debt"
-msgstr "Incobrable"
+#: models.py:76
+msgid "Incomplete"
+msgstr ""
-#: models.py:81
+#: models.py:77
+msgid "Executed"
+msgstr ""
+
+#: models.py:90
msgid "Amendment Fee"
msgstr "Rectificació de quota de soci"
-#: models.py:82
+#: models.py:91
msgid "Pro forma"
msgstr "Pro forma"
-#: models.py:85
+#: models.py:98
msgid "number"
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"
msgstr "creat el"
-#: models.py:90
+#: models.py:105
msgid "closed on"
msgstr "tancat el"
-#: models.py:91
+#: models.py:106
msgid "open"
msgstr "obert"
-#: models.py:92
+#: models.py:107
msgid "sent"
msgstr "enviat"
-#: models.py:93
+#: models.py:108
msgid "due on"
msgstr "es deu"
-#: models.py:94
+#: models.py:109
msgid "updated on"
msgstr "actualitzada el"
-#: models.py:97
+#: models.py:112
msgid "comments"
msgstr "comentaris"
-#: models.py:98
+#: models.py:113
msgid "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"
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"
msgstr "descripció"
-#: models.py:287
+#: models.py:380
msgid "rate"
msgstr "tarifa"
-#: models.py:288
+#: models.py:381
msgid "quantity"
msgstr "quantitat"
-#: models.py:289
+#: models.py:383
#, fuzzy
#| msgid "quantity"
msgid "Verbose quantity"
msgstr "quantitat"
-#: models.py:290 templates/bills/microspective.html:77
+#: models.py:384 templates/bills/microspective.html:77
#: templates/bills/microspective.html:111
msgid "subtotal"
msgstr "subtotal"
-#: models.py:291
+#: models.py:385
msgid "tax"
msgstr "impostos"
-#: models.py:292
+#: models.py:386
msgid "start"
msgstr "iniciar"
-#: models.py:293
+#: models.py:387
msgid "end"
msgstr "finalitzar"
-#: models.py:295
+#: models.py:389
msgid "Informative link back to the order"
msgstr "Enllaç informatiu de l'ordre"
-#: models.py:296
+#: models.py:390
msgid "order billed"
msgstr "ordre facturada"
-#: models.py:297
+#: models.py:391
msgid "order billed until"
msgstr "ordre facturada fins a"
-#: models.py:298
+#: models.py:392
msgid "created"
msgstr "creada"
-#: models.py:300
+#: models.py:394
msgid "amended line"
msgstr "línia rectificada"
-#: models.py:343
+#: models.py:437
msgid "Volume"
msgstr "Volum"
-#: models.py:344
+#: models.py:438
msgid "Compensation"
msgstr "Compensació"
-#: models.py:345
+#: models.py:439
msgid "Other"
msgstr "Altre"
-#: models.py:349
+#: models.py:443
msgid "bill line"
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
msgid "Due date"
msgstr "Data de pagament"
@@ -433,11 +523,6 @@ msgstr "hrs/qnt"
msgid "rate/price"
msgstr "tarifa/preu"
-#: templates/bills/microspective.html:111
-#: templates/bills/microspective.html:114
-msgid "VAT"
-msgstr "IVA"
-
#: templates/bills/microspective.html:114
msgid "taxes"
msgstr "impostos"
@@ -451,6 +536,7 @@ msgid "PAYMENT"
msgstr "PAGAMENT"
#: templates/bills/microspective.html:140
+#, python-format
msgid ""
"\n"
" You can pay our %(type)s by bank transfer.
\n"
@@ -483,3 +569,6 @@ msgstr ""
" contacta amb nosaltres a %(email)s. Et respondrem el més "
"ràpidament possible.\n"
" "
+
+#~ msgid "positive price"
+#~ msgstr "preu positiu"
diff --git a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo
index 258c2c4c..a9cc33e6 100644
Binary files a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo and b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo differ
diff --git a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po
index 406aa281..35811b42 100644
--- a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po
+++ b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME
Please select a " "payment source for the selected bills" @@ -56,138 +56,180 @@ msgstr "" "Una vez cerrada la factura ya no se podrá modificar.
Por favor " "seleciona un metodo de pago para las facturas seleccionadas" -#: actions.py:107 +#: actions.py:110 msgid "Close" msgstr "Cerrar" -#: actions.py:125 +#: actions.py:124 msgid "One bill has been sent." msgstr "Se ha enviado una factura" -#: actions.py:126 +#: actions.py:125 #, python-format msgid "%i bills have been sent." msgstr "" -#: actions.py:128 +#: actions.py:127 msgid "Resend" msgstr "" -#: actions.py:189 +#: actions.py:188 #, python-format msgid "%(norders)s orders and %(nlines)s lines undoed." msgstr "" -#: actions.py:208 +#: actions.py:207 msgid "Lines moved" 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 "One amendment bill have been generated." +msgstr "Se ha creado una transacción" + +#: actions.py:256 +msgid "%(num)i amendment bills have been generated." +msgstr "Se han creado %(num)i transacciones" + +#: 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" msgstr "" -#: admin.py:80 +#: admin.py:85 msgid "Description" msgstr "" -#: admin.py:88 +#: admin.py:93 msgid "Subtotal" msgstr "" -#: admin.py:118 +#: admin.py:123 msgid "Is open" msgstr "" -#: admin.py:123 +#: admin.py:128 msgid "Subline" msgstr "" -#: admin.py:157 +#: admin.py:162 msgid "No bills selected." msgstr "" -#: admin.py:164 +#: admin.py:169 #, python-format msgid "Manage %s bill lines." msgstr "" -#: admin.py:166 +#: admin.py:171 msgid "Bill not in open state." msgstr "" -#: admin.py:169 +#: admin.py:174 msgid "Not all bills are in open state." msgstr "" -#: admin.py:170 +#: admin.py:175 msgid "Manage bill lines of multiple bills." msgstr "" -#: admin.py:190 +#: admin.py:195 msgid "Raw" msgstr "" -#: admin.py:208 +#: admin.py:214 models.py:72 msgid "Created" msgstr "" -#: admin.py:213 +#: admin.py:220 msgid "lines" msgstr "" -#: admin.py:218 templates/bills/microspective.html:118 +#: admin.py:225 filters.py:44 templates/bills/microspective.html:118 msgid "total" msgstr "" -#: admin.py:226 models.py:88 models.py:352 +#: admin.py:233 models.py:103 models.py:446 msgid "type" msgstr "" -#: admin.py:243 +#: admin.py:250 msgid "Payment" msgstr "Pago" -#: filters.py:17 +#: filters.py:19 msgid "All" msgstr "" -#: filters.py:18 models.py:78 +#: filters.py:20 models.py:87 msgid "Invoice" msgstr "Factura" -#: filters.py:19 models.py:79 +#: filters.py:21 models.py:88 msgid "Amendment invoice" msgstr "Factura rectificative" -#: filters.py:20 models.py:80 +#: filters.py:22 models.py:89 msgid "Fee" msgstr "Quota de socio" -#: filters.py:21 +#: filters.py:23 msgid "Amendment fee" msgstr "Quota rectificativa" -#: filters.py:22 +#: filters.py:24 msgid "Pro-forma" msgstr "" -#: filters.py:41 -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 +#: filters.py:66 msgid "has bill contact" 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" msgstr "" @@ -217,7 +259,7 @@ msgstr "" msgid "Main" msgstr "" -#: models.py:23 models.py:86 +#: models.py:23 models.py:99 msgid "account" msgstr "" @@ -249,138 +291,179 @@ msgstr "" msgid "country" msgstr "" -#: models.py:35 +#: models.py:35 templates/admin/bills/report.html:38 msgid "VAT number" msgstr "" -#: models.py:67 -msgid "Paid" +#: models.py:73 +msgid "Processed" msgstr "" -#: models.py:68 -msgid "Pending" +#: models.py:74 +msgid "Amended" +msgstr "Quota rectificativa" + +#: models.py:76 +msgid "Incomplete" msgstr "" -#: models.py:69 -msgid "Bad debt" -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" +#: models.py:77 +msgid "Executed" msgstr "" #: models.py:90 -msgid "closed on" +msgid "Amendment Fee" msgstr "" #: models.py:91 -msgid "open" -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" +msgid "Pro forma" msgstr "" #: 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" +msgstr "HTML" + +#: models.py:192 +#, python-format +msgid "Type %s is not an amendment." 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" -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" -msgstr "" +msgstr "descripción" -#: models.py:287 +#: models.py:380 msgid "rate" -msgstr "" +msgstr "tarifa" -#: models.py:288 +#: models.py:381 msgid "quantity" -msgstr "" +msgstr "cantidad" -#: models.py:289 +#: models.py:383 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 msgid "subtotal" -msgstr "" +msgstr "subtotal" -#: models.py:291 +#: models.py:385 msgid "tax" -msgstr "" +msgstr "impuesto" -#: models.py:292 +#: models.py:386 msgid "start" -msgstr "" +msgstr "inicio" -#: models.py:293 +#: models.py:387 msgid "end" -msgstr "" +msgstr "fín" -#: models.py:295 +#: models.py:389 msgid "Informative link back to the order" msgstr "" -#: models.py:296 +#: models.py:390 msgid "order billed" msgstr "" -#: models.py:297 +#: models.py:391 msgid "order billed until" msgstr "" -#: models.py:298 +#: models.py:392 msgid "created" -msgstr "" +msgstr "creado" -#: models.py:300 +#: models.py:394 msgid "amended line" -msgstr "" +msgstr "linea rectificativa" -#: models.py:343 +#: models.py:437 msgid "Volume" -msgstr "" +msgstr "Volumen" -#: models.py:344 +#: models.py:438 msgid "Compensation" -msgstr "" +msgstr "Compensación" -#: models.py:345 +#: models.py:439 msgid "Other" -msgstr "" +msgstr "Otro" -#: models.py:349 +#: models.py:443 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 msgid "Due date" @@ -427,11 +510,6 @@ msgstr "hrs/cant" msgid "rate/price" msgstr "tarifa/precio" -#: templates/bills/microspective.html:111 -#: templates/bills/microspective.html:114 -msgid "VAT" -msgstr "IVA" - #: templates/bills/microspective.html:114 msgid "taxes" msgstr "impuestos" diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py index 2a3e31c5..d7d933ea 100644 --- a/orchestra/contrib/bills/models.py +++ b/orchestra/contrib/bills/models.py @@ -90,6 +90,10 @@ class Bill(models.Model): (AMENDMENTFEE, _("Amendment Fee")), (PROFORMA, _("Pro forma")), ) + AMEND_MAP = { + INVOICE: AMENDMENTINVOICE, + FEE: AMENDMENTFEE, + } number = models.CharField(_("number"), max_length=16, unique=True, blank=True) account = models.ForeignKey('accounts.Account', verbose_name=_("account"), @@ -181,6 +185,24 @@ class Bill(models.Model): return self.EXECUTED 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): if not self.is_open: return self.total @@ -201,11 +223,7 @@ class Bill(models.Model): return self.type or self.get_class_type() def get_amend_type(self): - amend_map = { - self.INVOICE: self.AMENDMENTINVOICE, - self.FEE: self.AMENDMENTFEE, - } - amend_type = amend_map.get(self.type) + amend_type = self.AMEND_MAP.get(self.type) if amend_type is None: raise TypeError("%s has no associated amend type." % self.type) return amend_type @@ -321,10 +339,17 @@ class Bill(models.Model): subtotals[tax] = (subtotal, round(tax/100*subtotal, 2)) 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): totals = self.lines.annotate( - totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100)) - return round(totals.aggregate(Sum('totals'))['totals__sum'], 2) + totals=(F('subtotal') + Coalesce(F('sublines__total'), 0)) * (1+F('tax')/100) + ) + return round(totals.aggregate(Sum('totals'))['totals__sum'] or 0, 2) class Invoice(Bill): @@ -363,7 +388,7 @@ class BillLine(models.Model): subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2) tax = models.DecimalField(_("tax"), max_digits=4, decimal_places=2) 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, help_text=_("Informative link back to the order"), on_delete=models.SET_NULL) order_billed_on = models.DateField(_("order billed"), null=True, blank=True) diff --git a/orchestra/contrib/bills/templates/admin/bills/report.html b/orchestra/contrib/bills/templates/admin/bills/report.html new file mode 100644 index 00000000..7cb63a83 --- /dev/null +++ b/orchestra/contrib/bills/templates/admin/bills/report.html @@ -0,0 +1,60 @@ +{% load i18n utils %} + + +
+{% trans "Number" %} | +{% trans "VAT number" %} | +{% trans "Contact" %} | +{% trans "Close date" %} | +{% trans "Base" %} | +{% trans "VAT" %} | +{% trans "Total" %} | +
---|---|---|---|---|---|---|
{{ bill.number }} | +{{ bill.buyer.vat }} | +{{ bill.buyer.get_name }} | +{{ bill.closed_on|date }} | + {% with base=bill.compute_base total=bill.compute_total %} +{{ base }} &{{ currency }}; | +{{ total|sub:base }} &{{ currency }}; | +{{ total }} &{{ currency }}; | + {% endwith %} +