diff --git a/TODO.md b/TODO.md index c7d14ed1..4309f35e 100644 --- a/TODO.md +++ b/TODO.md @@ -412,5 +412,4 @@ touch /tmp/somefile # batch zone edditing # inherit registers from parent? -# Bill metric disk 5 GB: unialber: include not include 5, unialbert recheck period - +# Disable pagination on membership fees (allways one page) diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py index 48162ac2..62c5de22 100644 --- a/orchestra/contrib/bills/actions.py +++ b/orchestra/contrib/bills/actions.py @@ -32,7 +32,7 @@ def download_bills(modeladmin, request, queryset): response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"' return response bill = queryset.get() - pdf = html_to_pdf(bill.html or bill.render()) + pdf = html_to_pdf(bill.html or bill.render(), pagination=bill.has_multiple_pages) return HttpResponse(pdf, content_type='application/pdf') download_bills.verbose_name = _("Download") download_bills.url_name = 'download' @@ -78,9 +78,13 @@ def close_bills(modeladmin, request, queryset): else: url = reverse('admin:transactions_transaction_changelist') url += 'id__in=%s' % ','.join(map(str, transactions)) + context = { + 'url': url, + 'num': num, + } message = ungettext( - _('One related transaction has been created') % url, - _('%i related transactions have been created') % (url, num), + _('One related transaction has been created') % context, + _('%(num)i related transactions have been created') % context, num) messages.success(request, mark_safe(message)) return @@ -105,12 +109,22 @@ close_bills.url_name = 'close' def send_bills(modeladmin, request, queryset): + num = 0 for bill in queryset: if not validate_contact(request, bill): return - for bill in queryset: + num += 1 + if num == 1: bill.send() + else: + # Batch email + queryset.send() + for bill in queryset: modeladmin.log_change(request, bill, 'Sent') + messages.success(request, ungetetx( + _("One bill has been sent."), + _("%i bills have been sent.") % num, + num)) send_bills.verbose_name = lambda bill: _("Resend" if getattr(bill, 'is_sent', False) else "Send") send_bills.url_name = 'send' @@ -153,7 +167,7 @@ def undo_billing(modeladmin, request, queryset): else: # First iteration if order.billed_on < line.start_on: - messages.error(request, "billed on is smaller than first line start_on.") + messages.error(request, "Billed on is smaller than first line start_on.") return prev = line.end_on nlines += 1 @@ -168,6 +182,7 @@ def undo_billing(modeladmin, request, queryset): for line in lines: nlines += 1 line.delete() + # TODO update order history undo billing order.save(update_fields=('billed_until', 'billed_on')) norders += 1 @@ -177,29 +192,14 @@ def undo_billing(modeladmin, request, queryset): }) -# TODO son't check for account equality -def move_lines(modeladmin, request, queryset): +def move_lines(modeladmin, request, queryset, action=None): # Validate - account = None - for line in queryset.select_related('bill'): - bill = line.bill - if not bill.is_open: - messages.error(request, _("Can not move lines from a closed bill.")) - return - elif not account: - account = bill.account - elif bill.account != account: - messages.error(request, _("Can not move lines from different accounts")) - return target = request.GET.get('target') if not target: # select target context = {} return render(request, 'admin/orchestra/generic_confirmation.html', context) target = Bill.objects.get(pk=int(pk)) - if target.account != account: - messages.error(request, _("Target account different than lines account.")) - return if request.POST.get('post') == 'generic_confirmation': for line in queryset: line.bill = target @@ -212,9 +212,4 @@ def move_lines(modeladmin, request, queryset): def copy_lines(modeladmin, request, queryset): # same as move, but changing action behaviour - pass - - -def delete_lines(modeladmin, request, queryset): - # Call contrib.admin delete action if all lines in open bill - pass + return move_lines(modeladmin, request, queryset) diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py index 854094c6..eb431fd2 100644 --- a/orchestra/contrib/bills/admin.py +++ b/orchestra/contrib/bills/admin.py @@ -1,6 +1,6 @@ from django import forms from django.conf.urls import url -from django.contrib import admin +from django.contrib import admin, messages from django.contrib.admin.utils import unquote from django.core.urlresolvers import reverse from django.db import models @@ -9,6 +9,7 @@ from django.db.models.functions import Coalesce from django.templatetags.static import static from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +from django.shortcuts import redirect from orchestra.admin import ExtendedModelAdmin from orchestra.admin.utils import admin_date, insertattr, admin_link @@ -101,16 +102,22 @@ class ClosedBillLineInline(BillLineInline): class BillLineAdmin(admin.ModelAdmin): list_display = ( - 'description', 'bill_link', 'rate', 'quantity', 'tax', 'subtotal', 'display_sublinetotal', - 'display_total' + 'description', 'bill_link', 'display_is_open', 'account_link', 'rate', 'quantity', 'tax', + 'subtotal', 'display_sublinetotal', 'display_total' ) actions = (actions.undo_billing, actions.move_lines, actions.copy_lines,) - list_filter = ('tax', ('bill', admin.RelatedOnlyFieldListFilter)) + list_filter = ('tax', ('bill', admin.RelatedOnlyFieldListFilter), 'bill__is_open') list_select_related = ('bill',) search_fields = ('description', 'bill__number') + account_link = admin_link('bill__account') bill_link = admin_link('bill') + def display_is_open(self, instance): + return instance.bill.is_open + display_is_open.short_description = _("Is open") + display_is_open.boolean = True + def display_sublinetotal(self, instance): return instance.subline_total or '' display_sublinetotal.short_description = _("Subline") @@ -140,18 +147,26 @@ class BillLineManagerAdmin(BillLineAdmin): return qset def changelist_view(self, request, extra_context=None): - GET = request.GET.copy() - bill_ids = GET.pop('ids', None) + GET_copy = request.GET.copy() + bill_ids = GET_copy.pop('ids', None) if bill_ids: - request.GET = GET - bill_ids = list(map(int, bill_ids)) + bill_ids = bill_ids[0] + request.GET = GET_copy + bill_ids = list(map(int, bill_ids.split(','))) + else: + messages.error(request, _("No bills selected.")) + return redirect('..') self.bill_ids = bill_ids - if bill_ids and len(bill_ids) == 1: + if len(bill_ids) == 1: bill_url = reverse('admin:bills_bill_change', args=(bill_ids[0],)) bill = Bill.objects.get(pk=bill_ids[0]) bill_link = '%s' % (bill_url, bill.number) title = mark_safe(_("Manage %s bill lines.") % bill_link) + if not bill.is_open: + messages.warning(request, _("Bill not in open state.")) else: + if Bill.objects.filter(id__in=bill_ids, is_open=False).exists(): + messages.warning(request, _("Not all bills are in open state.")) title = _("Manage bill lines of multiple bills.") context = { 'title': title, @@ -177,13 +192,15 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): 'fields': ('html',), }), ) + list_prefetch_related = ('transactions',) + search_fields = ('number', 'account__username', 'comments') change_view_actions = [ actions.manage_lines, actions.view_bill, actions.download_bills, actions.send_bills, actions.close_bills ] - list_prefetch_related = ('transactions',) - search_fields = ('number', 'account__username', 'comments') - actions = [actions.download_bills, actions.close_bills, actions.send_bills] + actions = [ + actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills + ] change_readonly_fields = ('account_link', 'type', 'is_open') readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state') inlines = [BillLineInline, ClosedBillLineInline] diff --git a/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po b/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po index 787abd43..5939399a 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-27 13:28+0000\n" +"POT-Creation-Date: 2015-05-28 09:23+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -34,97 +34,114 @@ msgstr "" msgid "Selected bills have been closed" msgstr "" -#: actions.py:82 +#: actions.py:86 #, python-format -msgid "One related transaction has been created" +msgid "One related transaction has been created" msgstr "" -#: actions.py:83 +#: actions.py:87 #, python-format -msgid "%i related transactions have been created" +msgid "%(num)i related transactions have been created" msgstr "" -#: actions.py:89 +#: actions.py:93 msgid "Are you sure about closing the following bills?" msgstr "" -#: actions.py:90 +#: actions.py:94 msgid "" "Once a bill is closed it can not be further modified.

Please select a " "payment source for the selected bills" msgstr "" -#: actions.py:103 +#: actions.py:107 msgid "Close" msgstr "" -#: actions.py:114 +#: actions.py:125 +msgid "One bill has been sent." +msgstr "" + +#: actions.py:126 +#, python-format +msgid "%i bills have been sent." +msgstr "" + +#: actions.py:128 msgid "Resend" msgstr "" -#: actions.py:174 +#: actions.py:189 #, python-format msgid "%(norders)s orders and %(nlines)s lines undoed." msgstr "" -#: actions.py:187 -msgid "Can not move lines from a closed bill." -msgstr "" - -#: actions.py:192 -msgid "Can not move lines from different accounts" -msgstr "" - -#: actions.py:201 -msgid "Target account different than lines account." -msgstr "" - #: actions.py:208 msgid "Lines moved" msgstr "" -#: admin.py:48 admin.py:92 admin.py:121 forms.py:11 +#: admin.py:49 admin.py:93 admin.py:128 forms.py:11 msgid "Total" msgstr "" -#: admin.py:79 +#: admin.py:80 msgid "Description" msgstr "DescripciĆ³" -#: admin.py:87 +#: admin.py:88 msgid "Subtotal" msgstr "" -#: admin.py:116 +#: admin.py:118 +msgid "Is open" +msgstr "" + +#: admin.py:123 msgid "Subline" msgstr "" -#: admin.py:153 +#: admin.py:157 +msgid "No bills selected." +msgstr "" + +#: admin.py:164 #, python-format msgid "Manage %s bill lines." msgstr "" -#: admin.py:155 +#: admin.py:166 +msgid "Bill not in open state." +msgstr "" + +#: admin.py:169 +msgid "Not all bills are in open state." +msgstr "" + +#: admin.py:170 msgid "Manage bill lines of multiple bills." msgstr "" -#: admin.py:175 +#: admin.py:190 msgid "Raw" msgstr "" -#: admin.py:196 +#: admin.py:208 +msgid "Created" +msgstr "" + +#: admin.py:213 msgid "lines" msgstr "" -#: admin.py:201 templates/bills/microspective.html:118 +#: admin.py:218 templates/bills/microspective.html:118 msgid "total" msgstr "" -#: admin.py:209 models.py:88 models.py:340 +#: admin.py:226 models.py:88 models.py:352 msgid "type" msgstr "tipus" -#: admin.py:226 +#: admin.py:243 msgid "Payment" msgstr "Pagament" @@ -290,82 +307,78 @@ msgstr "comentaris" msgid "HTML" msgstr "" -#: models.py:280 +#: models.py:285 msgid "bill" msgstr "" -#: models.py:281 models.py:338 templates/bills/microspective.html:73 +#: models.py:286 models.py:350 templates/bills/microspective.html:73 msgid "description" msgstr "descripciĆ³" -#: models.py:282 +#: models.py:287 msgid "rate" msgstr "tarifa" -#: models.py:283 +#: models.py:288 msgid "quantity" msgstr "quantitat" -#: models.py:284 +#: models.py:289 #, fuzzy #| msgid "quantity" msgid "Verbose quantity" msgstr "quantitat" -#: models.py:285 templates/bills/microspective.html:77 +#: models.py:290 templates/bills/microspective.html:77 #: templates/bills/microspective.html:111 msgid "subtotal" msgstr "" -#: models.py:286 +#: models.py:291 msgid "tax" msgstr "impostos" -#: models.py:287 +#: models.py:292 msgid "start" msgstr "" -#: models.py:288 +#: models.py:293 msgid "end" msgstr "" -#: models.py:290 +#: models.py:295 msgid "Informative link back to the order" msgstr "" -#: models.py:291 +#: models.py:296 msgid "order billed" msgstr "" -#: models.py:292 +#: models.py:297 msgid "order billed until" msgstr "" -#: models.py:293 +#: models.py:298 msgid "created" msgstr "" -#: models.py:295 +#: models.py:300 msgid "amended line" msgstr "" -#: models.py:316 -msgid "{ini} to {end}" -msgstr "" - -#: models.py:331 +#: models.py:343 msgid "Volume" msgstr "" -#: models.py:332 +#: models.py:344 msgid "Compensation" msgstr "" -#: models.py:333 +#: models.py:345 msgid "Other" msgstr "" -#: models.py:337 +#: models.py:349 msgid "bill line" msgstr "" @@ -378,20 +391,15 @@ msgstr "" msgid "On %(bank_account)s" msgstr "" -#: templates/bills/microspective-fee.html:113 +#: templates/bills/microspective-fee.html:114 #, python-format -msgid "From %(period)s" +msgid "From %(ini)s to %(end)s" msgstr "" -#: templates/bills/microspective-fee.html:118 +#: templates/bills/microspective-fee.html:121 msgid "" "\n" -"Con vuestras cuotas, ademas de obtener conexion y multitud " -"de servicios, estais
\n" -" Con vuestras cuotas, ademas de obtener conexion y multitud de servicios," -"
\n" -" Con vuestras cuotas, ademas de obtener conexion
\n" -" Con vuestras cuotas, ademas de obtener
\n" +"With your membership you are supporting ...\n" msgstr "" #: templates/bills/microspective.html:49 @@ -403,8 +411,9 @@ msgid "TOTAL" msgstr "TOTAL" #: templates/bills/microspective.html:57 -#, python-format -msgid "%(bill_type|upper)s DATE " +#, fuzzy, python-format +#| msgid "%(bill_type|upper)s DATE " +msgid "%(bill_type)s DATE" msgstr "DATA %(bill_type|upper)s" #: templates/bills/microspective.html:74 @@ -437,10 +446,18 @@ msgid "PAYMENT" msgstr "PAGAMENT" #: templates/bills/microspective.html:140 -#, python-format +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| " You can pay our %(type)s by bank transfer. " +#| "
\n" +#| " Please make sure to state your name and the " +#| "%(type)s number.\n" +#| " Our bank account number is
\n" +#| " " msgid "" "\n" -" You can pay our %(type)s by bank transfer.
\n" +" You can pay our %(type)s by bank transfer.
\n" " Please make sure to state your name and the %(type)s number.\n" " Our bank account number is
\n" @@ -459,8 +476,8 @@ msgstr "PREGUNTES" msgid "" "\n" " If you have any question about your %(type)s, please\n" -" feel free to contact us at your convinience. We will reply as " -"soon as we get\n" +" feel free to write us at %(email)s. We will reply as soon as we " +"get\n" " your message.\n" " " msgstr "" diff --git a/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..962304c8 --- /dev/null +++ b/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,469 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-05-28 09:23+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: actions.py:37 +msgid "Download" +msgstr "" + +#: actions.py:47 +msgid "View" +msgstr "" + +#: actions.py:55 +msgid "Selected bills should be in open state" +msgstr "" + +#: actions.py:73 +msgid "Selected bills have been closed" +msgstr "" + +#: actions.py:86 +#, python-format +msgid "One related transaction has been created" +msgstr "" + +#: actions.py:87 +#, python-format +msgid "%(num)i related transactions have been created" +msgstr "" + +#: actions.py:93 +msgid "Are you sure about closing the following bills?" +msgstr "" + +#: actions.py:94 +msgid "" +"Once a bill is closed it can not be further modified.

Please select a " +"payment source for the selected bills" +msgstr "" + +#: actions.py:107 +msgid "Close" +msgstr "" + +#: actions.py:125 +msgid "One bill has been sent." +msgstr "" + +#: actions.py:126 +#, python-format +msgid "%i bills have been sent." +msgstr "" + +#: actions.py:128 +msgid "Resend" +msgstr "" + +#: actions.py:189 +#, python-format +msgid "%(norders)s orders and %(nlines)s lines undoed." +msgstr "" + +#: actions.py:208 +msgid "Lines moved" +msgstr "" + +#: admin.py:49 admin.py:93 admin.py:128 forms.py:11 +msgid "Total" +msgstr "" + +#: admin.py:80 +msgid "Description" +msgstr "" + +#: admin.py:88 +msgid "Subtotal" +msgstr "" + +#: admin.py:118 +msgid "Is open" +msgstr "" + +#: admin.py:123 +msgid "Subline" +msgstr "" + +#: admin.py:157 +msgid "No bills selected." +msgstr "" + +#: admin.py:164 +#, python-format +msgid "Manage %s bill lines." +msgstr "" + +#: admin.py:166 +msgid "Bill not in open state." +msgstr "" + +#: admin.py:169 +msgid "Not all bills are in open state." +msgstr "" + +#: admin.py:170 +msgid "Manage bill lines of multiple bills." +msgstr "" + +#: admin.py:190 +msgid "Raw" +msgstr "" + +#: admin.py:208 +msgid "Created" +msgstr "" + +#: admin.py:213 +msgid "lines" +msgstr "" + +#: admin.py:218 templates/bills/microspective.html:118 +msgid "total" +msgstr "" + +#: admin.py:226 models.py:88 models.py:352 +msgid "type" +msgstr "" + +#: admin.py:243 +msgid "Payment" +msgstr "" + +#: filters.py:17 +msgid "All" +msgstr "" + +#: filters.py:18 models.py:78 +msgid "Invoice" +msgstr "" + +#: filters.py:19 models.py:79 +msgid "Amendment invoice" +msgstr "" + +#: filters.py:20 models.py:80 +msgid "Fee" +msgstr "" + +#: filters.py:21 +msgid "Amendment fee" +msgstr "" + +#: filters.py:22 +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 +msgid "has bill contact" +msgstr "" + +#: forms.py:9 +msgid "Number" +msgstr "" + +#: forms.py:10 +msgid "Account" +msgstr "" + +#: forms.py:12 +msgid "Type" +msgstr "" + +#: forms.py:13 +msgid "Source" +msgstr "" + +#: helpers.py:10 +msgid "" +"{relation} account \"{account}\" does not have a declared invoice contact. " +"You should provide one" +msgstr "" + +#: helpers.py:17 +msgid "Related" +msgstr "" + +#: helpers.py:24 +msgid "Main" +msgstr "" + +#: models.py:23 models.py:86 +msgid "account" +msgstr "" + +#: models.py:25 +msgid "name" +msgstr "" + +#: models.py:26 +msgid "Account full name will be used when left blank." +msgstr "" + +#: models.py:27 +msgid "address" +msgstr "" + +#: models.py:28 +msgid "city" +msgstr "" + +#: models.py:30 +msgid "zip code" +msgstr "" + +#: models.py:31 +msgid "Enter a valid zipcode." +msgstr "" + +#: models.py:32 +msgid "country" +msgstr "" + +#: models.py:35 +msgid "VAT number" +msgstr "" + +#: models.py:67 +msgid "Paid" +msgstr "" + +#: models.py:68 +msgid "Pending" +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" +msgstr "" + +#: models.py:90 +msgid "closed on" +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" +msgstr "" + +#: models.py:98 +msgid "HTML" +msgstr "" + +#: models.py:285 +msgid "bill" +msgstr "" + +#: models.py:286 models.py:350 templates/bills/microspective.html:73 +msgid "description" +msgstr "" + +#: models.py:287 +msgid "rate" +msgstr "" + +#: models.py:288 +msgid "quantity" +msgstr "" + +#: models.py:289 +msgid "Verbose quantity" +msgstr "" + +#: models.py:290 templates/bills/microspective.html:77 +#: templates/bills/microspective.html:111 +msgid "subtotal" +msgstr "" + +#: models.py:291 +msgid "tax" +msgstr "" + +#: models.py:292 +msgid "start" +msgstr "" + +#: models.py:293 +msgid "end" +msgstr "" + +#: models.py:295 +msgid "Informative link back to the order" +msgstr "" + +#: models.py:296 +msgid "order billed" +msgstr "" + +#: models.py:297 +msgid "order billed until" +msgstr "" + +#: models.py:298 +msgid "created" +msgstr "" + +#: models.py:300 +msgid "amended line" +msgstr "" + +#: models.py:343 +msgid "Volume" +msgstr "" + +#: models.py:344 +msgid "Compensation" +msgstr "" + +#: models.py:345 +msgid "Other" +msgstr "" + +#: models.py:349 +msgid "bill line" +msgstr "" + +#: templates/bills/microspective-fee.html:107 +msgid "Due date" +msgstr "" + +#: templates/bills/microspective-fee.html:108 +#, python-format +msgid "On %(bank_account)s" +msgstr "" + +#: templates/bills/microspective-fee.html:114 +#, python-format +msgid "From %(ini)s to %(end)s" +msgstr "" + +#: templates/bills/microspective-fee.html:121 +msgid "" +"\n" +"With your membership you are supporting ...\n" +msgstr "" + +#: templates/bills/microspective.html:49 +msgid "DUE DATE" +msgstr "" + +#: templates/bills/microspective.html:53 +msgid "TOTAL" +msgstr "" + +#: templates/bills/microspective.html:57 +#, python-format +msgid "%(bill_type)s DATE" +msgstr "" + +#: templates/bills/microspective.html:74 +msgid "period" +msgstr "" + +#: templates/bills/microspective.html:75 +msgid "hrs/qty" +msgstr "" + +#: templates/bills/microspective.html:76 +msgid "rate/price" +msgstr "" + +#: templates/bills/microspective.html:111 +#: templates/bills/microspective.html:114 +msgid "VAT" +msgstr "" + +#: templates/bills/microspective.html:114 +msgid "taxes" +msgstr "" + +#: templates/bills/microspective.html:130 +msgid "COMMENTS" +msgstr "" + +#: templates/bills/microspective.html:136 +msgid "PAYMENT" +msgstr "" + +#: templates/bills/microspective.html:140 +#, python-format +msgid "" +"\n" +" You can pay our %(type)s by bank transfer.
\n" +" Please make sure to state your name and the %(type)s number.\n" +" Our bank account number is
\n" +" " +msgstr "" + +#: templates/bills/microspective.html:149 +msgid "QUESTIONS" +msgstr "" + +#: templates/bills/microspective.html:150 +#, python-format +msgid "" +"\n" +" If you have any question about your %(type)s, please\n" +" feel free to write us at %(email)s. We will reply as soon as we " +"get\n" +" your message.\n" +" " +msgstr "" diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py index 4cec7a24..8ad88c96 100644 --- a/orchestra/contrib/bills/models.py +++ b/orchestra/contrib/bills/models.py @@ -124,6 +124,10 @@ class Bill(models.Model): return self.PENDING return self.BAD_DEBT + @property + def has_multiple_pages(self): + return self.type != self.FEE + def get_payment_state_display(self): value = self.payment_state return force_text(dict(self.PAYMENT_STATES).get(value, value)) @@ -186,6 +190,7 @@ class Bill(models.Model): def send(self): html = self.html or self.render() + pdf = html_to_pdf(html, pagination=self.has_multiple_pages) self.account.send_email( template=settings.BILLS_EMAIL_NOTIFICATION_TEMPLATE, context={ @@ -195,7 +200,7 @@ class Bill(models.Model): email_from=settings.BILLS_SELLER_EMAIL, contacts=(Contact.BILLING,), attachments=[ - ('%s.pdf' % self.number, html_to_pdf(html), 'application/pdf') + ('%s.pdf' % self.number, pdf, 'application/pdf') ] ) self.is_sent = True @@ -312,12 +317,10 @@ class BillLine(models.Model): if self.start_on.day != 1 or self.end_on.day != 1: date_format = "N j, 'y" end = date(self.end_on, date_format) -# .strftime(date_format) else: end = date((self.end_on - datetime.timedelta(days=1)), date_format) -# ).strftime(date_format) - ini = date(self.start_on, date_format) - #.strftime(date_format) + ini = date(self.start_on, date_format).capitalize() + end = end.capitalize() if not self.end_on: return ini if ini == end: diff --git a/orchestra/contrib/bills/templates/bills/microspective-fee.html b/orchestra/contrib/bills/templates/bills/microspective-fee.html index a6449d33..5825feec 100644 --- a/orchestra/contrib/bills/templates/bills/microspective-fee.html +++ b/orchestra/contrib/bills/templates/bills/microspective-fee.html @@ -99,30 +99,31 @@ hr {

{% filter title %}{% trans bill.get_type_display %}{% endfilter %}
{{ bill.number }}
- {{ bill.closed_on | default:now | date | capfirst }}
+ {{ bill.closed_on | default:now | date:"F j, Y" | capfirst }}
{{ bill.get_total }} &{{ currency.lower }};
- {% trans "Due date" %} {{ payment.due_date| default:default_due_date | date }}
+ {% trans "Due date" %} {{ payment.due_date| default:default_due_date | date:"F j, Y" }}
{% if not payment.message %}{% blocktrans with bank_account=seller_info.bank_account %}On {{ bank_account }}{% endblocktrans %}{% endif %}
-{% blocktrans with period=bill.lines.get.get_verbose_period %}From {{ period }}{% endblocktrans %} +{% with line=bill.lines.get %} +{% blocktrans with ini=line.start_on|date:"F j, Y" end=line.end_on|date:"F j, Y" %}From {{ ini }} to {{ end }}{% endblocktrans %} +{% endwith %}
{% endblock %} + {% block content %}
{% blocktrans %} -Con vuestras cuotas, ademas de obtener conexion y multitud de servicios, estais
- Con vuestras cuotas, ademas de obtener conexion y multitud de servicios,
- Con vuestras cuotas, ademas de obtener conexion
- Con vuestras cuotas, ademas de obtener
+With your membership you are supporting ... {% endblocktrans %}
{% endblock %} + {% block footer %}
{{ block.super }} diff --git a/orchestra/contrib/bills/templates/bills/microspective.css b/orchestra/contrib/bills/templates/bills/microspective.css index b9c3205f..d2e8f4dd 100644 --- a/orchestra/contrib/bills/templates/bills/microspective.css +++ b/orchestra/contrib/bills/templates/bills/microspective.css @@ -175,12 +175,12 @@ a:hover { } #lines .column-description { - width: 42%; + width: 40%; text-align: left; } #lines .column-period { - width: 20%; + width: 22%; } #lines .column-quantity { diff --git a/orchestra/contrib/bills/templates/bills/microspective.html b/orchestra/contrib/bills/templates/bills/microspective.html index 753b1cc2..1b2e7162 100644 --- a/orchestra/contrib/bills/templates/bills/microspective.html +++ b/orchestra/contrib/bills/templates/bills/microspective.html @@ -47,15 +47,15 @@
{% trans "DUE DATE" %}
- {{ bill.due_on | default:default_due_date | date }} + {{ bill.due_on | default:default_due_date | date | capfirst }}
{% trans "TOTAL" %}
{{ bill.get_total }} &{{ currency.lower }};
- {% blocktrans with bill_type=bill.get_type_display.upper %}{{ bill_type }} DATE {% endblocktrans %}
- {{ bill.closed_on | default:now | date }} + {% blocktrans with bill_type=bill.get_type_display.upper %}{{ bill_type }} DATE{% endblocktrans %}
+ {{ bill.closed_on | default:now | date | capfirst }}
@@ -77,9 +77,9 @@ {% trans "subtotal" %}
{% for line in lines %} - {% with sublines=line.sublines.all description=line.description|slice:"40:" %} + {% with sublines=line.sublines.all description=line.description|slice:"38:" %} {% if not line.order_id %}L{% endif %}{{ line.order_id }} - {{ line.description|slice:":40" }} + {{ line.description|slice:":38" }} {{ line.get_verbose_period }} {{ line.get_verbose_quantity|default:" "|safe }} {% if line.rate %}{{ line.rate }} &{{ currency.lower }};{% else %} {% endif %} @@ -87,7 +87,7 @@
{% if description %}   - {{ description|truncatechars:41 }} + {{ description|truncatechars:39 }}       @@ -95,7 +95,7 @@ {% endif %} {% for subline in sublines %}   - {{ subline.description|truncatechars:41 }} + {{ subline.description|truncatechars:39 }}       @@ -138,7 +138,7 @@ {{ payment.message | safe }} {% else %} {% blocktrans with type=bill.get_type_display.lower %} - You can pay our {{ type }} by bank transfer.
+ You can pay our {{ type }} by bank transfer.
Please make sure to state your name and the {{ type }} number. Our bank account number is
{% endblocktrans %} @@ -147,9 +147,9 @@
{% trans "QUESTIONS" %} - {% blocktrans with type=bill.get_type_display.lower %} + {% blocktrans with type=bill.get_type_display.lower email=seller_info.email %} If you have any question about your {{ type }}, please - feel free to contact us at your convinience. We will reply as soon as we get + feel free to write us at {{ email }}. We will reply as soon as we get your message. {% endblocktrans %}
diff --git a/orchestra/contrib/domains/serializers.py b/orchestra/contrib/domains/serializers.py index 3d23cc33..cc4d22cf 100644 --- a/orchestra/contrib/domains/serializers.py +++ b/orchestra/contrib/domains/serializers.py @@ -21,7 +21,7 @@ class RecordSerializer(serializers.ModelSerializer): class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): """ Validates if this zone generates a correct zone file """ - records = RecordSerializer(required=False, many=True) #allow_add_remove=True) + records = RecordSerializer(required=False, many=True) class Meta: model = Domain @@ -44,3 +44,35 @@ class DomainSerializer(AccountSerializerMixin, HyperlinkedModelSerializer): domain = domain_for_validation(self.instance, records) validators.validate_zone(domain.render_zone()) return data + + def create(self, validated_data): + records = validated_data.pop('records') + domain = super(DomainSerializer, self).create(validated_data) + for record in records: + domain.records.create(type=record['type'], value=record['value']) + return domain + + def update(self, validated_data): + precords = validated_data.pop('records') + domain = super(DomainSerializer, self).update(validated_data) + to_delete = [] + for erecord in domain.records.all(): + match = False + for ix, precord in enumerate(precords): + if erecord.type == precord['type'] and erecord.value == precord['value']: + match = True + break + if match: + precords.remove(ix) + else: + to_delete.append(erecord) + for precord in precords: + try: + recycled = to_delete.pop() + except IndexError: + domain.records.create(type=precord['type'], value=precord['value']) + else: + recycled.type = precord['type'] + recycled.value = precord['value'] + recycled.save() + return domain diff --git a/orchestra/contrib/orders/actions.py b/orchestra/contrib/orders/actions.py index 748970ce..a7e74922 100644 --- a/orchestra/contrib/orders/actions.py +++ b/orchestra/contrib/orders/actions.py @@ -92,7 +92,7 @@ class BillSelectedOrders(object): url = change_url(bills[0]) else: url = reverse('admin:bills_bill_changelist') - ids = ','.join(map(str, bills)) + ids = ','.join([str(b.id) for b in bills]) url += '?id__in=%s' % ids msg = ungettext( 'One bill has been created.', diff --git a/orchestra/contrib/services/handlers.py b/orchestra/contrib/services/handlers.py index 4e9f3ba3..5344f355 100644 --- a/orchestra/contrib/services/handlers.py +++ b/orchestra/contrib/services/handlers.py @@ -245,6 +245,10 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): })) def generate_line(self, order, price, *dates, metric=1, discounts=None, computed=False): + """ + discounts: already applied discounts on price + computed: price = price*size already performed + """ if len(dates) == 2: ini, end = dates elif len(dates) == 1: @@ -377,7 +381,8 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): size = self.get_price_size(order.new_billed_until, new_end) price += price*size order.new_billed_until = new_end - line = self.generate_line(order, price, ini, new_end or end, discounts=discounts, computed=True) + line = self.generate_line( + order, price, ini, new_end or end, discounts=discounts, computed=True) lines.append(line) return lines diff --git a/orchestra/utils/html.py b/orchestra/utils/html.py index 79bee5a6..7e51df9e 100644 --- a/orchestra/utils/html.py +++ b/orchestra/utils/html.py @@ -3,17 +3,24 @@ import textwrap from orchestra.utils.sys import run -def html_to_pdf(html): +def html_to_pdf(html, pagination=False): """ converts HTL to PDF using wkhtmltopdf """ - return run(textwrap.dedent("""\ - PATH=$PATH:/usr/local/bin/ - xvfb-run -a -s "-screen 0 2480x3508x16" wkhtmltopdf -q \\ - --use-xserver \\ + print(pagination) + context = { + 'pagination': textwrap.dedent("""\ + --footer-center "Page [page] of [topage]"\\ --footer-center "Page [page] of [topage]" \\ --footer-font-name sans \\ --footer-font-size 7 \\ - --footer-spacing 7 \\ + --footer-spacing 7""" + ) if pagination else '', + } + cmd = textwrap.dedent("""\ + PATH=$PATH:/usr/local/bin/ + xvfb-run -a -s "-screen 0 2480x3508x16" wkhtmltopdf -q \\ + --use-xserver \\ + %(pagination)s \\ --margin-bottom 22 \\ - --margin-top 20 - - """), - stdin=html.encode('utf-8') - ).stdout + --margin-top 20 - -\ + """) % context + return run(cmd, stdin=html.encode('utf-8')).stdout