Fixes on billing
This commit is contained in:
parent
ca6b479e17
commit
fd8d805b5e
1
TODO.md
1
TODO.md
|
@ -413,3 +413,4 @@ touch /tmp/somefile
|
||||||
# inherit registers from parent?
|
# inherit registers from parent?
|
||||||
|
|
||||||
# Bill metric disk 5 GB: unialber: include not include 5, unialbert recheck period
|
# Bill metric disk 5 GB: unialber: include not include 5, unialbert recheck period
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from django.contrib.auth import models as auth
|
from django.contrib.auth import models as auth
|
||||||
|
from django.conf import settings as djsettings
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.utils import timezone
|
from django.utils import timezone, translation
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
||||||
|
@ -91,10 +92,18 @@ class Account(auth.AbstractBaseUser):
|
||||||
for obj in getattr(self, rel.get_accessor_name()).all():
|
for obj in getattr(self, rel.get_accessor_name()).all():
|
||||||
OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=[])
|
OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=[])
|
||||||
|
|
||||||
def send_email(self, template, context, contacts=[], attachments=[], html=None):
|
def send_email(self, template, context, email_from=None, contacts=[], attachments=[], html=None):
|
||||||
contacts = self.contacts.filter(email_usages=contacts)
|
contacts = self.contacts.filter(email_usages=contacts)
|
||||||
email_to = contacts.values_list('email', flat=True)
|
email_to = contacts.values_list('email', flat=True)
|
||||||
send_email_template(template, context, email_to, html=html, attachments=attachments)
|
extra_context = {
|
||||||
|
'account': self,
|
||||||
|
'email_from': email_from or djsettings.SERVER_EMAIL,
|
||||||
|
}
|
||||||
|
extra_context.update(context)
|
||||||
|
with translation.override(self.language):
|
||||||
|
send_email_template(
|
||||||
|
template, extra_context, ('marcay@pangea.org',), email_from=email_from, html=html,
|
||||||
|
attachments=attachments)
|
||||||
|
|
||||||
def get_full_name(self):
|
def get_full_name(self):
|
||||||
return self.full_name or self.short_name or self.username
|
return self.full_name or self.short_name or self.username
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from datetime import date
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -128,21 +129,53 @@ def undo_billing(modeladmin, request, queryset):
|
||||||
group[line.order].append(line)
|
group[line.order].append(line)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
group[line.order] = [line]
|
group[line.order] = [line]
|
||||||
# TODO force incomplete info
|
|
||||||
|
# Validate
|
||||||
for order, lines in group.items():
|
for order, lines in group.items():
|
||||||
# Find path from ini to end
|
prev = None
|
||||||
for attr in ('order_id', 'order_billed_on', 'order_billed_until'):
|
billed_on = date.max
|
||||||
if not getattr(self, attr):
|
billed_until = date.max
|
||||||
raise ValidationError(_("Not enough information stored for undoing"))
|
for line in sorted(lines, key=lambda l: l.start_on):
|
||||||
sorted(lines, key=lambda l: l.created_on)
|
if billed_on is not None:
|
||||||
if 'a' != order.billed_on:
|
if line.order_billed_on is None:
|
||||||
raise ValidationError(_("Dates don't match"))
|
billed_on = line.order_billed_on
|
||||||
prev = order.billed_on
|
else:
|
||||||
for ix in range(0, len(lines)):
|
billed_on = min(billed_on, line.order_billed_on)
|
||||||
if lines[ix].order_b: # TODO we need to look at the periods here
|
if billed_until is not None:
|
||||||
pass
|
if line.order_billed_until is None:
|
||||||
order.billed_until = self.order_billed_until
|
billed_until = line.order_billed_until
|
||||||
order.billed_on = self.order_billed_on
|
else:
|
||||||
|
billed_until = min(billed_until, line.order_billed_until)
|
||||||
|
if prev:
|
||||||
|
if line.start_on != prev:
|
||||||
|
messages.error(request, "Line dates doesn't match.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# First iteration
|
||||||
|
if order.billed_on < line.start_on:
|
||||||
|
messages.error(request, "billed on is smaller than first line start_on.")
|
||||||
|
return
|
||||||
|
prev = line.end_on
|
||||||
|
nlines += 1
|
||||||
|
if not prev:
|
||||||
|
messages.error(request, "Order does not have lines!.")
|
||||||
|
order.billed_until = billed_until
|
||||||
|
order.billed_on = billed_on
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
norders, nlines = 0, 0
|
||||||
|
for order, lines in group.items():
|
||||||
|
for line in lines:
|
||||||
|
nlines += 1
|
||||||
|
line.delete()
|
||||||
|
order.save(update_fields=('billed_until', 'billed_on'))
|
||||||
|
norders += 1
|
||||||
|
|
||||||
|
messages.success(request, _("%(norders)s orders and %(nlines)s lines undoed.") % {
|
||||||
|
'nlines': nlines,
|
||||||
|
'norders': norders
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
# TODO son't check for account equality
|
# TODO son't check for account equality
|
||||||
def move_lines(modeladmin, request, queryset):
|
def move_lines(modeladmin, request, queryset):
|
||||||
|
|
|
@ -16,7 +16,7 @@ from orchestra.contrib.accounts.admin import AccountAdminMixin, AccountAdmin
|
||||||
from orchestra.forms.widgets import paddingCheckboxSelectMultiple
|
from orchestra.forms.widgets import paddingCheckboxSelectMultiple
|
||||||
|
|
||||||
from . import settings, actions
|
from . import settings, actions
|
||||||
from .filters import BillTypeListFilter, HasBillContactListFilter
|
from .filters import BillTypeListFilter, HasBillContactListFilter, PositivePriceListFilter
|
||||||
from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine, BillContact
|
from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine, BillContact
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
'number', 'type_link', 'account_link', 'created_on_display',
|
'number', 'type_link', 'account_link', 'created_on_display',
|
||||||
'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')
|
list_filter = (BillTypeListFilter, 'is_open', 'is_sent', PositivePriceListFilter)
|
||||||
add_fields = ('account', 'type', 'is_open', 'due_on', 'comments')
|
add_fields = ('account', 'type', 'is_open', 'due_on', 'comments')
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
|
@ -188,7 +188,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
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')
|
created_on_display = admin_date('created_on', short_description=_("Created"))
|
||||||
|
|
||||||
def num_lines(self, bill):
|
def num_lines(self, bill):
|
||||||
return bill.lines__count
|
return bill.lines__count
|
||||||
|
|
|
@ -37,6 +37,23 @@ class BillTypeListFilter(SimpleListFilter):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PositivePriceListFilter(SimpleListFilter):
|
||||||
|
title = _("positive price")
|
||||||
|
parameter_name = 'positive_price'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return (
|
||||||
|
('True', _("Yes")),
|
||||||
|
('False', _("No")),
|
||||||
|
)
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() == 'True':
|
||||||
|
return queryset.filter(computed_total__gt=0)
|
||||||
|
if self.value() == 'False':
|
||||||
|
return queryset.filter(computed_total__lte=0)
|
||||||
|
|
||||||
|
|
||||||
class HasBillContactListFilter(SimpleListFilter):
|
class HasBillContactListFilter(SimpleListFilter):
|
||||||
""" Filter Nodes by group according to request.user """
|
""" Filter Nodes by group according to request.user """
|
||||||
title = _("has bill contact")
|
title = _("has bill contact")
|
||||||
|
|
|
@ -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-04-20 11:02+0000\n"
|
"POT-Creation-Date: 2015-05-27 13:28+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,112 +18,113 @@ 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:36
|
#: actions.py:37
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
msgstr "Descarrega"
|
msgstr "Descarrega"
|
||||||
|
|
||||||
#: actions.py:46
|
#: actions.py:47
|
||||||
msgid "View"
|
msgid "View"
|
||||||
msgstr "Vista"
|
msgstr "Vista"
|
||||||
|
|
||||||
#: actions.py:54
|
#: actions.py:55
|
||||||
msgid "Selected bills should be in open state"
|
msgid "Selected bills should be in open state"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:72
|
#: actions.py:73
|
||||||
msgid "Selected bills have been closed"
|
msgid "Selected bills have been closed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:81
|
|
||||||
#, python-format
|
|
||||||
msgid "<a href=\"%s\">One related transaction</a> has been created"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: actions.py:82
|
#: actions.py:82
|
||||||
#, python-format
|
#, python-format
|
||||||
|
msgid "<a href=\"%s\">One related transaction</a> has been created"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: actions.py:83
|
||||||
|
#, python-format
|
||||||
msgid "<a href=\"%s\">%i related transactions</a> have been created"
|
msgid "<a href=\"%s\">%i related transactions</a> have been created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:88
|
#: actions.py:89
|
||||||
msgid "Are you sure about closing the following bills?"
|
msgid "Are you sure about closing the following bills?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:89
|
#: actions.py:90
|
||||||
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"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:102
|
#: actions.py:103
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:113
|
#: actions.py:114
|
||||||
msgid "Resend"
|
msgid "Resend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:130 models.py:312
|
#: actions.py:174
|
||||||
msgid "Not enough information stored for undoing"
|
#, python-format
|
||||||
|
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:133 models.py:314
|
#: actions.py:187
|
||||||
msgid "Dates don't match"
|
msgid "Can not move lines from a closed bill."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:148
|
#: actions.py:192
|
||||||
msgid "Can not move lines which are not in open state."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: actions.py:153
|
|
||||||
msgid "Can not move lines from different accounts"
|
msgid "Can not move lines from different accounts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:161
|
#: actions.py:201
|
||||||
msgid "Target account different than lines account."
|
msgid "Target account different than lines account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:168
|
#: actions.py:208
|
||||||
msgid "Lines moved"
|
msgid "Lines moved"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:43 admin.py:86 forms.py:11
|
#: admin.py:48 admin.py:92 admin.py:121 forms.py:11
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:73
|
#: admin.py:79
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
|
||||||
#: admin.py:81
|
#: admin.py:87
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:113
|
#: admin.py:116
|
||||||
msgid "Manage bill lines of multiple bills."
|
msgid "Subline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:118
|
#: admin.py:153
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Manage %s bill lines."
|
msgid "Manage %s bill lines."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:138
|
#: admin.py:155
|
||||||
|
msgid "Manage bill lines of multiple bills."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:175
|
||||||
msgid "Raw"
|
msgid "Raw"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:157
|
#: admin.py:196
|
||||||
msgid "lines"
|
msgid "lines"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:162 templates/bills/microspective.html:107
|
#: admin.py:201 templates/bills/microspective.html:118
|
||||||
msgid "total"
|
msgid "total"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:170 models.py:87 models.py:342
|
#: admin.py:209 models.py:88 models.py:340
|
||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr "tipus"
|
msgstr "tipus"
|
||||||
|
|
||||||
#: admin.py:187
|
#: admin.py:226
|
||||||
msgid "Payment"
|
msgid "Payment"
|
||||||
msgstr "Pagament"
|
msgstr "Pagament"
|
||||||
|
|
||||||
|
@ -131,15 +132,15 @@ msgstr "Pagament"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tot"
|
msgstr "Tot"
|
||||||
|
|
||||||
#: filters.py:18 models.py:77
|
#: filters.py:18 models.py:78
|
||||||
msgid "Invoice"
|
msgid "Invoice"
|
||||||
msgstr "Factura"
|
msgstr "Factura"
|
||||||
|
|
||||||
#: filters.py:19 models.py:78
|
#: filters.py:19 models.py:79
|
||||||
msgid "Amendment invoice"
|
msgid "Amendment invoice"
|
||||||
msgstr "Factura rectificativa"
|
msgstr "Factura rectificativa"
|
||||||
|
|
||||||
#: filters.py:20 models.py:79
|
#: filters.py:20 models.py:80
|
||||||
msgid "Fee"
|
msgid "Fee"
|
||||||
msgstr "Quota de soci"
|
msgstr "Quota de soci"
|
||||||
|
|
||||||
|
@ -151,18 +152,22 @@ msgstr "Rectificació de quota de soci"
|
||||||
msgid "Pro-forma"
|
msgid "Pro-forma"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:42
|
#: filters.py:41
|
||||||
msgid "has bill contact"
|
msgid "positive price"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:47
|
#: filters.py:46 filters.py:64
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr "Si"
|
msgstr "Si"
|
||||||
|
|
||||||
#: filters.py:48
|
#: filters.py:47 filters.py:65
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr "No"
|
msgstr "No"
|
||||||
|
|
||||||
|
#: filters.py:59
|
||||||
|
msgid "has bill contact"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:9
|
#: forms.py:9
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -193,165 +198,202 @@ msgstr ""
|
||||||
msgid "Main"
|
msgid "Main"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:22 models.py:85
|
#: models.py:23 models.py:86
|
||||||
msgid "account"
|
msgid "account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:24
|
#: models.py:25
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:25
|
#: models.py:26
|
||||||
msgid "Account full name will be used when left blank."
|
msgid "Account full name will be used when left blank."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:26
|
#: models.py:27
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:27
|
#: models.py:28
|
||||||
msgid "city"
|
msgid "city"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:29
|
#: models.py:30
|
||||||
msgid "zip code"
|
msgid "zip code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:30
|
#: models.py:31
|
||||||
msgid "Enter a valid zipcode."
|
msgid "Enter a valid zipcode."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:31
|
#: models.py:32
|
||||||
msgid "country"
|
msgid "country"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:34
|
#: models.py:35
|
||||||
msgid "VAT number"
|
msgid "VAT number"
|
||||||
msgstr "NIF"
|
msgstr "NIF"
|
||||||
|
|
||||||
#: models.py:66
|
#: models.py:67
|
||||||
msgid "Paid"
|
msgid "Paid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:67
|
#: models.py:68
|
||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:68
|
#: models.py:69
|
||||||
msgid "Bad debt"
|
msgid "Bad debt"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:80
|
#: models.py:81
|
||||||
msgid "Amendment Fee"
|
msgid "Amendment Fee"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:81
|
#: models.py:82
|
||||||
msgid "Pro forma"
|
msgid "Pro forma"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:84
|
#: models.py:85
|
||||||
msgid "number"
|
msgid "number"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:88
|
#: models.py:89
|
||||||
msgid "created on"
|
msgid "created on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:89
|
#: models.py:90
|
||||||
msgid "closed on"
|
msgid "closed on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:90
|
#: models.py:91
|
||||||
msgid "open"
|
msgid "open"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:91
|
#: models.py:92
|
||||||
msgid "sent"
|
msgid "sent"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:92
|
#: models.py:93
|
||||||
msgid "due on"
|
msgid "due on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:93
|
#: models.py:94
|
||||||
msgid "updated on"
|
msgid "updated on"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:96
|
#: models.py:97
|
||||||
msgid "comments"
|
msgid "comments"
|
||||||
msgstr "comentaris"
|
msgstr "comentaris"
|
||||||
|
|
||||||
#: models.py:97
|
#: models.py:98
|
||||||
msgid "HTML"
|
msgid "HTML"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:273
|
#: models.py:280
|
||||||
msgid "bill"
|
msgid "bill"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:274 models.py:339 templates/bills/microspective.html:73
|
#: models.py:281 models.py:338 templates/bills/microspective.html:73
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr "descripció"
|
msgstr "descripció"
|
||||||
|
|
||||||
#: models.py:275
|
#: models.py:282
|
||||||
msgid "rate"
|
msgid "rate"
|
||||||
msgstr "tarifa"
|
msgstr "tarifa"
|
||||||
|
|
||||||
#: models.py:276
|
#: models.py:283
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr "quantitat"
|
msgstr "quantitat"
|
||||||
|
|
||||||
#: models.py:277
|
#: models.py:284
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "quantity"
|
#| msgid "quantity"
|
||||||
msgid "Verbose quantity"
|
msgid "Verbose quantity"
|
||||||
msgstr "quantitat"
|
msgstr "quantitat"
|
||||||
|
|
||||||
#: models.py:278 templates/bills/microspective.html:76
|
#: models.py:285 templates/bills/microspective.html:77
|
||||||
#: templates/bills/microspective.html:100
|
#: templates/bills/microspective.html:111
|
||||||
msgid "subtotal"
|
msgid "subtotal"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:279
|
#: models.py:286
|
||||||
msgid "tax"
|
msgid "tax"
|
||||||
msgstr "impostos"
|
msgstr "impostos"
|
||||||
|
|
||||||
#: models.py:284
|
#: models.py:287
|
||||||
|
msgid "start"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: models.py:288
|
||||||
|
msgid "end"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: models.py:290
|
||||||
msgid "Informative link back to the order"
|
msgid "Informative link back to the order"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:285
|
#: models.py:291
|
||||||
msgid "order billed"
|
msgid "order billed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:286
|
#: models.py:292
|
||||||
msgid "order billed until"
|
msgid "order billed until"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:287
|
#: models.py:293
|
||||||
msgid "created"
|
msgid "created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:289
|
#: models.py:295
|
||||||
msgid "amended line"
|
msgid "amended line"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:332
|
#: models.py:316
|
||||||
|
msgid "{ini} to {end}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: models.py:331
|
||||||
msgid "Volume"
|
msgid "Volume"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:333
|
#: models.py:332
|
||||||
msgid "Compensation"
|
msgid "Compensation"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:334
|
#: models.py:333
|
||||||
msgid "Other"
|
msgid "Other"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:338
|
#: models.py:337
|
||||||
msgid "bill line"
|
msgid "bill line"
|
||||||
msgstr ""
|
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:113
|
||||||
|
#, python-format
|
||||||
|
msgid "From %(period)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/bills/microspective-fee.html:118
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
"<strong>Con vuestras cuotas</strong>, ademas de obtener conexion y multitud "
|
||||||
|
"de servicios, estais<br>\n"
|
||||||
|
" Con vuestras cuotas, ademas de obtener conexion y multitud de servicios,"
|
||||||
|
"<br>\n"
|
||||||
|
" Con vuestras cuotas, ademas de obtener conexion <br>\n"
|
||||||
|
" Con vuestras cuotas, ademas de obtener <br>\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/bills/microspective.html:49
|
#: templates/bills/microspective.html:49
|
||||||
msgid "DUE DATE"
|
msgid "DUE DATE"
|
||||||
msgstr "VENCIMENT"
|
msgstr "VENCIMENT"
|
||||||
|
@ -366,52 +408,57 @@ msgid "%(bill_type|upper)s DATE "
|
||||||
msgstr "DATA %(bill_type|upper)s"
|
msgstr "DATA %(bill_type|upper)s"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:74
|
#: templates/bills/microspective.html:74
|
||||||
|
msgid "period"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/bills/microspective.html:75
|
||||||
msgid "hrs/qty"
|
msgid "hrs/qty"
|
||||||
msgstr "hrs/quant"
|
msgstr "hrs/quant"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:75
|
#: templates/bills/microspective.html:76
|
||||||
msgid "rate/price"
|
msgid "rate/price"
|
||||||
msgstr "tarifa/preu"
|
msgstr "tarifa/preu"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:100
|
#: templates/bills/microspective.html:111
|
||||||
#: templates/bills/microspective.html:103
|
#: templates/bills/microspective.html:114
|
||||||
msgid "VAT"
|
msgid "VAT"
|
||||||
msgstr "IVA"
|
msgstr "IVA"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:103
|
#: templates/bills/microspective.html:114
|
||||||
msgid "taxes"
|
msgid "taxes"
|
||||||
msgstr "impostos"
|
msgstr "impostos"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:119
|
#: templates/bills/microspective.html:130
|
||||||
msgid "COMMENTS"
|
msgid "COMMENTS"
|
||||||
msgstr "COMENTARIS"
|
msgstr "COMENTARIS"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:125
|
#: templates/bills/microspective.html:136
|
||||||
msgid "PAYMENT"
|
msgid "PAYMENT"
|
||||||
msgstr "PAGAMENT"
|
msgstr "PAGAMENT"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:129
|
#: templates/bills/microspective.html:140
|
||||||
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" You can pay our %(type)s by bank transfer. <br>\n"
|
" You can pay our <i>%(type)s</i> by bank transfer. <br>\n"
|
||||||
" Please make sure to state your name and the %(type)s "
|
" Please make sure to state your name and the <i>%(type)s</"
|
||||||
"number.\n"
|
"i> number.\n"
|
||||||
" Our bank account number is <br>\n"
|
" Our bank account number is <br>\n"
|
||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
"Pots pagar aquesta %(type)s per transferencia banacaria.<br>Inclou el teu "
|
"Pots pagar aquesta <i>%(type)s</i> per transferencia banacaria.<br>Inclou el "
|
||||||
"nom i el numero de %(type)s. El nostre compte bancari és"
|
"teu nom i el numero de <i>%(type)s</i>. El nostre compte bancari és"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:138
|
#: templates/bills/microspective.html:149
|
||||||
msgid "QUESTIONS"
|
msgid "QUESTIONS"
|
||||||
msgstr "PREGUNTES"
|
msgstr "PREGUNTES"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:139
|
#: templates/bills/microspective.html:150
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" If you have any question about your %(type)s, please\n"
|
" If you have any question about your <i>%(type)s</i>, please\n"
|
||||||
" feel free to contact us at your convinience. We will reply as "
|
" feel free to contact us at your convinience. We will reply as "
|
||||||
"soon as we get\n"
|
"soon as we get\n"
|
||||||
" your message.\n"
|
" your message.\n"
|
||||||
|
|
|
@ -128,6 +128,9 @@ class Bill(models.Model):
|
||||||
value = self.payment_state
|
value = self.payment_state
|
||||||
return force_text(dict(self.PAYMENT_STATES).get(value, value))
|
return force_text(dict(self.PAYMENT_STATES).get(value, value))
|
||||||
|
|
||||||
|
def get_current_transaction(self):
|
||||||
|
return self.transactions.exclude_rejected().first()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_class_type(cls):
|
def get_class_type(cls):
|
||||||
return cls.__name__.upper()
|
return cls.__name__.upper()
|
||||||
|
@ -187,7 +190,9 @@ class Bill(models.Model):
|
||||||
template=settings.BILLS_EMAIL_NOTIFICATION_TEMPLATE,
|
template=settings.BILLS_EMAIL_NOTIFICATION_TEMPLATE,
|
||||||
context={
|
context={
|
||||||
'bill': self,
|
'bill': self,
|
||||||
|
'settings': settings,
|
||||||
},
|
},
|
||||||
|
email_from=settings.BILLS_SELLER_EMAIL,
|
||||||
contacts=(Contact.BILLING,),
|
contacts=(Contact.BILLING,),
|
||||||
attachments=[
|
attachments=[
|
||||||
('%s.pdf' % self.number, html_to_pdf(html), 'application/pdf')
|
('%s.pdf' % self.number, html_to_pdf(html), 'application/pdf')
|
||||||
|
@ -288,7 +293,7 @@ class BillLine(models.Model):
|
||||||
created_on = models.DateField(_("created"), auto_now_add=True)
|
created_on = models.DateField(_("created"), auto_now_add=True)
|
||||||
# Amendment
|
# Amendment
|
||||||
amended_line = models.ForeignKey('self', verbose_name=_("amended line"),
|
amended_line = models.ForeignKey('self', verbose_name=_("amended line"),
|
||||||
related_name='amendment_lines', null=True, blank=True)
|
related_name='amendment_lines', null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "#%i" % self.pk
|
return "#%i" % self.pk
|
||||||
|
@ -302,24 +307,22 @@ class BillLine(models.Model):
|
||||||
return self.verbose_quantity or self.quantity
|
return self.verbose_quantity or self.quantity
|
||||||
|
|
||||||
def get_verbose_period(self):
|
def get_verbose_period(self):
|
||||||
ini = self.start_on.strftime("%b, %Y")
|
from django.template.defaultfilters import date
|
||||||
|
date_format = "N 'y"
|
||||||
|
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)
|
||||||
if not self.end_on:
|
if not self.end_on:
|
||||||
return ini
|
return ini
|
||||||
end = (self.end_on - datetime.timedelta(seconds=1)).strftime("%b, %Y")
|
|
||||||
if ini == end:
|
if ini == end:
|
||||||
return ini
|
return ini
|
||||||
return _("{ini} to {end}").format(ini=ini, end=end)
|
return "{ini} / {end}".format(ini=ini, end=end)
|
||||||
|
|
||||||
def undo(self):
|
|
||||||
# TODO warn user that undoing bills with compensations lead to compensation lost
|
|
||||||
for attr in ['order_id', 'order_billed_on', 'order_billed_until']:
|
|
||||||
if not getattr(self, attr):
|
|
||||||
raise ValidationError(_("Not enough information stored for undoing"))
|
|
||||||
if self.created_on != self.order.billed_on:
|
|
||||||
raise ValidationError(_("Dates don't match"))
|
|
||||||
self.order.billed_until = self.order_billed_until
|
|
||||||
self.order.billed_on = self.order_billed_on
|
|
||||||
self.delete()
|
|
||||||
|
|
||||||
# def save(self, *args, **kwargs):
|
# def save(self, *args, **kwargs):
|
||||||
# super(BillLine, self).save(*args, **kwargs)
|
# super(BillLine, self).save(*args, **kwargs)
|
||||||
|
@ -342,7 +345,6 @@ class BillSubline(models.Model):
|
||||||
# TODO: order info for undoing
|
# TODO: order info for undoing
|
||||||
line = models.ForeignKey(BillLine, verbose_name=_("bill line"), related_name='sublines')
|
line = models.ForeignKey(BillLine, verbose_name=_("bill line"), related_name='sublines')
|
||||||
description = models.CharField(_("description"), max_length=256)
|
description = models.CharField(_("description"), max_length=256)
|
||||||
# TODO rename to subtotal
|
|
||||||
total = models.DecimalField(max_digits=12, decimal_places=2)
|
total = models.DecimalField(max_digits=12, decimal_places=2)
|
||||||
type = models.CharField(_("type"), max_length=16, choices=TYPES, default=OTHER)
|
type = models.CharField(_("type"), max_length=16, choices=TYPES, default=OTHER)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% if subject %}Bill {{ bill.number }}{% endif %}
|
{% if subject %}Bill {{ bill.number }}{% endif %}
|
||||||
{% if message %}Dear {{ bill.account.username }},
|
{% if message %}Dear {{ bill.account.username }},
|
||||||
Find your {{ bill.get_type.lower }} attached.
|
Find your {{ bill.get_type_display.lower }} attached.
|
||||||
|
|
||||||
If you have any question, please write us at support@orchestra.lan
|
If you have any question, please write us at support@orchestra.lan
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
{% extends 'bills/microspective.html' %}
|
{% extends 'bills/microspective.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
{% with color="#809708" %}
|
{% with color="#809708" %}
|
||||||
|
@ -91,36 +93,37 @@ hr {
|
||||||
{{ buyer.vat }}<br>
|
{{ buyer.vat }}<br>
|
||||||
{{ buyer.address }}<br>
|
{{ buyer.address }}<br>
|
||||||
{{ buyer.zipcode }} - {{ buyer.city }}<br>
|
{{ buyer.zipcode }} - {{ buyer.city }}<br>
|
||||||
{{ buyer.country }}<br>
|
{{ buyer.get_country_display }}<br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="number" class="column-1">
|
<div id="number" class="column-1">
|
||||||
<span id="number-title">Membership Fee</span><br>
|
<span id="number-title">{% filter title %}{% trans bill.get_type_display %}{% endfilter %}</span><br>
|
||||||
<span id="number-value">{{ bill.number }}</span><br>
|
<span id="number-value">{{ bill.number }}</span><br>
|
||||||
<span id="number-date">{{ bill.closed_on | default:now | date }}</span><br>
|
<span id="number-date">{{ bill.closed_on | default:now | date | capfirst }}</span><br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="amount" class="column-2">
|
<div id="amount" class="column-2">
|
||||||
<span id="amount-value">{{ bill.get_total }} €</span><br>
|
<span id="amount-value">{{ bill.get_total }} &{{ currency.lower }};</span><br>
|
||||||
<span id="amount-note">Due date {{ payment.due_date | default:default_due_date | date }}<br>
|
<span id="amount-note">{% trans "Due date" %} {{ payment.due_date| default:default_due_date | date }}<br>
|
||||||
{% if not payment.message %}On {{ seller_info.bank_account }}{% endif %}<br>
|
{% if not payment.message %}{% blocktrans with bank_account=seller_info.bank_account %}On {{ bank_account }}{% endblocktrans %}{% endif %}<br>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="date" class="column-2">
|
<div id="date" class="column-2">
|
||||||
From {{ bill.lines.get.get_verbose_period }}
|
{% blocktrans with period=bill.lines.get.get_verbose_period %}From {{ period }}{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="text">
|
<div id="text">
|
||||||
|
{% blocktrans %}
|
||||||
<strong>Con vuestras cuotas</strong>, ademas de obtener conexion y multitud de servicios, estais<br>
|
<strong>Con vuestras cuotas</strong>, ademas de obtener conexion y multitud de servicios, estais<br>
|
||||||
Con vuestras cuotas, ademas de obtener conexion y multitud de servicios,<br>
|
Con vuestras cuotas, ademas de obtener conexion y multitud de servicios,<br>
|
||||||
Con vuestras cuotas, ademas de obtener conexion <br>
|
Con vuestras cuotas, ademas de obtener conexion <br>
|
||||||
Con vuestras cuotas, ademas de obtener <br>
|
Con vuestras cuotas, ademas de obtener <br>
|
||||||
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
<hr>
|
<hr>
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
|
|
||||||
{% block summary %}
|
{% block summary %}
|
||||||
<div id="bill-number">
|
<div id="bill-number">
|
||||||
{% trans bill.get_type_display.capitalize %}<br>
|
{% filter title %}{% trans bill.get_type_display %}{% endfilter %}<br>
|
||||||
<span class="value">{{ bill.number }}</span><br>
|
<span class="value">{{ bill.number }}</span><br>
|
||||||
</div>
|
</div>
|
||||||
<div id="bill-summary">
|
<div id="bill-summary">
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
<psan class="value">{{ bill.get_total }} &{{ currency.lower }};</span>
|
<psan class="value">{{ bill.get_total }} &{{ currency.lower }};</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="bill-date">
|
<div id="bill-date">
|
||||||
<span class="title">{% blocktrans with bill_type=bill.get_type_display %}{{ bill_type|upper }} DATE {% endblocktrans %}</span><br>
|
<span class="title">{% blocktrans with bill_type=bill.get_type_display.upper %}{{ bill_type }} DATE {% endblocktrans %}</span><br>
|
||||||
<psan class="value">{{ bill.closed_on | default:now | date }}</span>
|
<psan class="value">{{ bill.closed_on | default:now | date }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,9 +137,9 @@
|
||||||
{% if payment.message %}
|
{% if payment.message %}
|
||||||
{{ payment.message | safe }}
|
{{ payment.message | safe }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% blocktrans with type=bill.get_type_display %}
|
{% blocktrans with type=bill.get_type_display.lower %}
|
||||||
You can pay our {{ type }} by bank transfer. <br>
|
You can pay our <i>{{ type }}</i> by bank transfer. <br>
|
||||||
Please make sure to state your name and the {{ type }} number.
|
Please make sure to state your name and the <i>{{ type }}</i> number.
|
||||||
Our bank account number is <br>
|
Our bank account number is <br>
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
<strong>{{ seller_info.bank_account }}</strong>
|
<strong>{{ seller_info.bank_account }}</strong>
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
<div id="questions">
|
<div id="questions">
|
||||||
<span class="title">{% trans "QUESTIONS" %}</span>
|
<span class="title">{% trans "QUESTIONS" %}</span>
|
||||||
{% blocktrans with type=bill.get_type_display.lower %}
|
{% blocktrans with type=bill.get_type_display.lower %}
|
||||||
If you have any question about your {{ type }}, please
|
If you have any question about your <i>{{ type }}</i>, please
|
||||||
feel free to contact us at your convinience. We will reply as soon as we get
|
feel free to contact us at your convinience. We will reply as soon as we get
|
||||||
your message.
|
your message.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
|
@ -113,7 +113,7 @@ class BillSelectedOrders(object):
|
||||||
'title': _("Confirmation for billing selected orders"),
|
'title': _("Confirmation for billing selected orders"),
|
||||||
'step': 3,
|
'step': 3,
|
||||||
'form': form,
|
'form': form,
|
||||||
'bills': bills_with_total,
|
'bills': sorted(bills_with_total, key=lambda i: -i[1]),
|
||||||
})
|
})
|
||||||
return render(request, self.template, self.context)
|
return render(request, self.template, self.context)
|
||||||
|
|
||||||
|
|
|
@ -73,12 +73,18 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
display_cancelled_on = admin_date('cancelled_on')
|
display_cancelled_on = admin_date('cancelled_on')
|
||||||
|
|
||||||
def display_billed_until(self, order):
|
def display_billed_until(self, order):
|
||||||
value = order.billed_until
|
billed_until = order.billed_until
|
||||||
color = ''
|
red = False
|
||||||
if value and value < timezone.now().date():
|
if billed_until:
|
||||||
color = 'style="color:red;"'
|
if order.service.payment_style == order.service.POSTPAY:
|
||||||
|
boundary = order.service.handler.get_billing_point(order)
|
||||||
|
if billed_until < boundary:
|
||||||
|
red = True
|
||||||
|
elif billed_until < timezone.now().date():
|
||||||
|
red = True
|
||||||
|
color = 'style="color:red;"' if red else ''
|
||||||
return '<span title="{raw}" {color}>{human}</span>'.format(
|
return '<span title="{raw}" {color}>{human}</span>'.format(
|
||||||
raw=escape(str(value)), color=color, human=escape(naturaldate(value)),
|
raw=escape(str(billed_until)), color=color, human=escape(naturaldate(billed_until)),
|
||||||
)
|
)
|
||||||
display_billed_until.short_description = _("billed until")
|
display_billed_until.short_description = _("billed until")
|
||||||
display_billed_until.allow_tags = True
|
display_billed_until.allow_tags = True
|
||||||
|
|
|
@ -244,18 +244,14 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
'total': price,
|
'total': price,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
def generate_line(self, order, price, *dates, **kwargs):
|
def generate_line(self, order, price, *dates, metric=1, discounts=None, computed=False):
|
||||||
if len(dates) == 2:
|
if len(dates) == 2:
|
||||||
ini, end = dates
|
ini, end = dates
|
||||||
elif len(dates) == 1:
|
elif len(dates) == 1:
|
||||||
ini, end = dates[0], dates[0]
|
ini, end = dates[0], dates[0]
|
||||||
else:
|
else:
|
||||||
raise AttributeError
|
raise AttributeError("WTF is '%s'?" % str(dates))
|
||||||
metric = kwargs.pop('metric', 1)
|
discounts = discounts or ()
|
||||||
discounts = kwargs.pop('discounts', ())
|
|
||||||
computed = kwargs.pop('computed', False)
|
|
||||||
if kwargs:
|
|
||||||
raise AttributeError
|
|
||||||
|
|
||||||
size = self.get_price_size(ini, end)
|
size = self.get_price_size(ini, end)
|
||||||
if not computed:
|
if not computed:
|
||||||
|
@ -274,6 +270,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
for dtype, dprice in discounts:
|
for dtype, dprice in discounts:
|
||||||
self.generate_discount(line, dtype, dprice)
|
self.generate_discount(line, dtype, dprice)
|
||||||
discounted += dprice
|
discounted += dprice
|
||||||
|
# TODO this is needed for all discounts?
|
||||||
subtotal += discounted
|
subtotal += discounted
|
||||||
if subtotal > price:
|
if subtotal > price:
|
||||||
self.generate_discount(line, self._PLAN, price-subtotal)
|
self.generate_discount(line, self._PLAN, price-subtotal)
|
||||||
|
@ -490,6 +487,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
lines = []
|
lines = []
|
||||||
bp = None
|
bp = None
|
||||||
for order in orders:
|
for order in orders:
|
||||||
|
recharges = []
|
||||||
bp = self.get_billing_point(order, bp=bp, **options)
|
bp = self.get_billing_point(order, bp=bp, **options)
|
||||||
if (self.billing_period != self.NEVER and
|
if (self.billing_period != self.NEVER and
|
||||||
self.get_pricing_period() == self.NEVER and
|
self.get_pricing_period() == self.NEVER and
|
||||||
|
@ -508,14 +506,20 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
price = self.get_price(account, metric) * size
|
price = self.get_price(account, metric) * size
|
||||||
discounts = ()
|
discounts = ()
|
||||||
discount = min(price, max(cprice, 0))
|
discount = min(price, max(cprice, 0))
|
||||||
if pre_discount:
|
if discount:
|
||||||
cprice -= price
|
cprice -= price
|
||||||
|
price -= discount
|
||||||
discounts = (
|
discounts = (
|
||||||
('prepay', -discount),
|
('prepay', -discount),
|
||||||
)
|
)
|
||||||
# if price-pre_discount:
|
# if price-discount:
|
||||||
lines.append(self.generate_line(order, price, cini, cend, metric=metric,
|
recharges.append((order, price, cini, cend, metric, discounts))
|
||||||
computed=True, discounts=discounts))
|
# only recharge when appropiate in order to preserve bigger prepays.
|
||||||
|
if cmetric < metric or bp > order.billed_until:
|
||||||
|
for order, price, cini, cend, metric, discounts in recharges:
|
||||||
|
line = self.generate_line(order, price, cini, cend, metric=metric,
|
||||||
|
computed=True, discounts=discounts)
|
||||||
|
lines.append(line)
|
||||||
if order.billed_until and order.cancelled_on and order.cancelled_on >= order.billed_until:
|
if order.billed_until and order.cancelled_on and order.cancelled_on >= order.billed_until:
|
||||||
continue
|
continue
|
||||||
if self.billing_period != self.NEVER:
|
if self.billing_period != self.NEVER:
|
||||||
|
|
|
@ -56,9 +56,10 @@ class SiteDirective(Plugin):
|
||||||
def validate_uniqueness(self, directive, values, locations):
|
def validate_uniqueness(self, directive, values, locations):
|
||||||
""" Validates uniqueness location, name and value """
|
""" Validates uniqueness location, name and value """
|
||||||
errors = defaultdict(list)
|
errors = defaultdict(list)
|
||||||
|
value = directive.get('value', None)
|
||||||
# location uniqueness
|
# location uniqueness
|
||||||
location = None
|
location = None
|
||||||
if self.unique_location:
|
if self.unique_location and value is not None:
|
||||||
location = normurlpath(directive['value'].split()[0])
|
location = normurlpath(directive['value'].split()[0])
|
||||||
if location is not None and location in locations:
|
if location is not None and location in locations:
|
||||||
errors['value'].append(ValidationError(
|
errors['value'].append(ValidationError(
|
||||||
|
@ -74,7 +75,6 @@ class SiteDirective(Plugin):
|
||||||
))
|
))
|
||||||
|
|
||||||
# value uniqueness
|
# value uniqueness
|
||||||
value = directive.get('value', None)
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if self.unique_value and value in values.get(self.name, []):
|
if self.unique_value and value in values.get(self.name, []):
|
||||||
errors['value'].append(ValidationError(
|
errors['value'].append(ValidationError(
|
||||||
|
|
|
@ -30,7 +30,7 @@ def send_email_template(template, context, to, email_from=None, html=None, attac
|
||||||
|
|
||||||
#subject cannot have new lines
|
#subject cannot have new lines
|
||||||
subject = render_to_string(template, {'subject': True}, context).strip()
|
subject = render_to_string(template, {'subject': True}, context).strip()
|
||||||
message = render_to_string(template, {'message': True}, context)
|
message = render_to_string(template, {'message': True}, context).strip()
|
||||||
msg = EmailMultiAlternatives(subject, message, email_from, to, attachments=attachments)
|
msg = EmailMultiAlternatives(subject, message, email_from, to, attachments=attachments)
|
||||||
if html:
|
if html:
|
||||||
html_message = render_to_string(html, {'message': True}, context)
|
html_message = render_to_string(html, {'message': True}, context)
|
||||||
|
|
Loading…
Reference in New Issue