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?
|
||||
|
||||
# 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.conf import settings as djsettings
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
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 orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
||||
|
@ -91,10 +92,18 @@ class Account(auth.AbstractBaseUser):
|
|||
for obj in getattr(self, rel.get_accessor_name()).all():
|
||||
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)
|
||||
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):
|
||||
return self.full_name or self.short_name or self.username
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import zipfile
|
||||
from datetime import date
|
||||
from io import StringIO
|
||||
|
||||
from django.contrib import messages
|
||||
|
@ -128,21 +129,53 @@ def undo_billing(modeladmin, request, queryset):
|
|||
group[line.order].append(line)
|
||||
except KeyError:
|
||||
group[line.order] = [line]
|
||||
# TODO force incomplete info
|
||||
|
||||
# Validate
|
||||
for order, lines in group.items():
|
||||
# Find path from ini to end
|
||||
for attr in ('order_id', 'order_billed_on', 'order_billed_until'):
|
||||
if not getattr(self, attr):
|
||||
raise ValidationError(_("Not enough information stored for undoing"))
|
||||
sorted(lines, key=lambda l: l.created_on)
|
||||
if 'a' != order.billed_on:
|
||||
raise ValidationError(_("Dates don't match"))
|
||||
prev = order.billed_on
|
||||
for ix in range(0, len(lines)):
|
||||
if lines[ix].order_b: # TODO we need to look at the periods here
|
||||
pass
|
||||
order.billed_until = self.order_billed_until
|
||||
order.billed_on = self.order_billed_on
|
||||
prev = None
|
||||
billed_on = date.max
|
||||
billed_until = date.max
|
||||
for line in sorted(lines, key=lambda l: l.start_on):
|
||||
if billed_on is not None:
|
||||
if line.order_billed_on is None:
|
||||
billed_on = line.order_billed_on
|
||||
else:
|
||||
billed_on = min(billed_on, line.order_billed_on)
|
||||
if billed_until is not None:
|
||||
if line.order_billed_until is None:
|
||||
billed_until = line.order_billed_until
|
||||
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
|
||||
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 . 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
|
||||
|
||||
|
||||
|
@ -165,7 +165,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
'number', 'type_link', 'account_link', 'created_on_display',
|
||||
'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')
|
||||
fieldsets = (
|
||||
(None, {
|
||||
|
@ -188,7 +188,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
readonly_fields = ('number', 'display_total', 'is_sent', 'display_payment_state')
|
||||
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):
|
||||
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):
|
||||
""" Filter Nodes by group according to request.user """
|
||||
title = _("has bill contact")
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -18,112 +18,113 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: actions.py:36
|
||||
#: actions.py:37
|
||||
msgid "Download"
|
||||
msgstr "Descarrega"
|
||||
|
||||
#: actions.py:46
|
||||
#: actions.py:47
|
||||
msgid "View"
|
||||
msgstr "Vista"
|
||||
|
||||
#: actions.py:54
|
||||
#: actions.py:55
|
||||
msgid "Selected bills should be in open state"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:72
|
||||
#: actions.py:73
|
||||
msgid "Selected bills have been closed"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:81
|
||||
#, python-format
|
||||
msgid "<a href=\"%s\">One related transaction</a> has been created"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:82
|
||||
#, 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"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:88
|
||||
#: actions.py:89
|
||||
msgid "Are you sure about closing the following bills?"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:89
|
||||
#: actions.py:90
|
||||
msgid ""
|
||||
"Once a bill is closed it can not be further modified.</p><p>Please select a "
|
||||
"payment source for the selected bills"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:102
|
||||
#: actions.py:103
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:113
|
||||
#: actions.py:114
|
||||
msgid "Resend"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:130 models.py:312
|
||||
msgid "Not enough information stored for undoing"
|
||||
#: actions.py:174
|
||||
#, python-format
|
||||
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:133 models.py:314
|
||||
msgid "Dates don't match"
|
||||
#: actions.py:187
|
||||
msgid "Can not move lines from a closed bill."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:148
|
||||
msgid "Can not move lines which are not in open state."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:153
|
||||
#: actions.py:192
|
||||
msgid "Can not move lines from different accounts"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:161
|
||||
#: actions.py:201
|
||||
msgid "Target account different than lines account."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:168
|
||||
#: actions.py:208
|
||||
msgid "Lines moved"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:43 admin.py:86 forms.py:11
|
||||
#: admin.py:48 admin.py:92 admin.py:121 forms.py:11
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:73
|
||||
#: admin.py:79
|
||||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: admin.py:81
|
||||
#: admin.py:87
|
||||
msgid "Subtotal"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:113
|
||||
msgid "Manage bill lines of multiple bills."
|
||||
#: admin.py:116
|
||||
msgid "Subline"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:118
|
||||
#: admin.py:153
|
||||
#, python-format
|
||||
msgid "Manage %s bill lines."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:138
|
||||
#: admin.py:155
|
||||
msgid "Manage bill lines of multiple bills."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:175
|
||||
msgid "Raw"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:157
|
||||
#: admin.py:196
|
||||
msgid "lines"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:162 templates/bills/microspective.html:107
|
||||
#: admin.py:201 templates/bills/microspective.html:118
|
||||
msgid "total"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:170 models.py:87 models.py:342
|
||||
#: admin.py:209 models.py:88 models.py:340
|
||||
msgid "type"
|
||||
msgstr "tipus"
|
||||
|
||||
#: admin.py:187
|
||||
#: admin.py:226
|
||||
msgid "Payment"
|
||||
msgstr "Pagament"
|
||||
|
||||
|
@ -131,15 +132,15 @@ msgstr "Pagament"
|
|||
msgid "All"
|
||||
msgstr "Tot"
|
||||
|
||||
#: filters.py:18 models.py:77
|
||||
#: filters.py:18 models.py:78
|
||||
msgid "Invoice"
|
||||
msgstr "Factura"
|
||||
|
||||
#: filters.py:19 models.py:78
|
||||
#: filters.py:19 models.py:79
|
||||
msgid "Amendment invoice"
|
||||
msgstr "Factura rectificativa"
|
||||
|
||||
#: filters.py:20 models.py:79
|
||||
#: filters.py:20 models.py:80
|
||||
msgid "Fee"
|
||||
msgstr "Quota de soci"
|
||||
|
||||
|
@ -151,18 +152,22 @@ msgstr "Rectificació de quota de soci"
|
|||
msgid "Pro-forma"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:42
|
||||
msgid "has bill contact"
|
||||
#: filters.py:41
|
||||
msgid "positive price"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:47
|
||||
#: filters.py:46 filters.py:64
|
||||
msgid "Yes"
|
||||
msgstr "Si"
|
||||
|
||||
#: filters.py:48
|
||||
#: filters.py:47 filters.py:65
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
#: filters.py:59
|
||||
msgid "has bill contact"
|
||||
msgstr ""
|
||||
|
||||
#: forms.py:9
|
||||
msgid "Number"
|
||||
msgstr ""
|
||||
|
@ -193,165 +198,202 @@ msgstr ""
|
|||
msgid "Main"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:22 models.py:85
|
||||
#: models.py:23 models.py:86
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:24
|
||||
#: models.py:25
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:25
|
||||
#: models.py:26
|
||||
msgid "Account full name will be used when left blank."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:26
|
||||
#: models.py:27
|
||||
msgid "address"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:27
|
||||
#: models.py:28
|
||||
msgid "city"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:29
|
||||
#: models.py:30
|
||||
msgid "zip code"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:30
|
||||
#: models.py:31
|
||||
msgid "Enter a valid zipcode."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:31
|
||||
#: models.py:32
|
||||
msgid "country"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:34
|
||||
#: models.py:35
|
||||
msgid "VAT number"
|
||||
msgstr "NIF"
|
||||
|
||||
#: models.py:66
|
||||
#: models.py:67
|
||||
msgid "Paid"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:67
|
||||
#: models.py:68
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:68
|
||||
#: models.py:69
|
||||
msgid "Bad debt"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:80
|
||||
#: models.py:81
|
||||
msgid "Amendment Fee"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:81
|
||||
#: models.py:82
|
||||
msgid "Pro forma"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:84
|
||||
#: models.py:85
|
||||
msgid "number"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:88
|
||||
#: models.py:89
|
||||
msgid "created on"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:89
|
||||
#: models.py:90
|
||||
msgid "closed on"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:90
|
||||
#: models.py:91
|
||||
msgid "open"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:91
|
||||
#: models.py:92
|
||||
msgid "sent"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:92
|
||||
#: models.py:93
|
||||
msgid "due on"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:93
|
||||
#: models.py:94
|
||||
msgid "updated on"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:96
|
||||
#: models.py:97
|
||||
msgid "comments"
|
||||
msgstr "comentaris"
|
||||
|
||||
#: models.py:97
|
||||
#: models.py:98
|
||||
msgid "HTML"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:273
|
||||
#: models.py:280
|
||||
msgid "bill"
|
||||
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"
|
||||
msgstr "descripció"
|
||||
|
||||
#: models.py:275
|
||||
#: models.py:282
|
||||
msgid "rate"
|
||||
msgstr "tarifa"
|
||||
|
||||
#: models.py:276
|
||||
#: models.py:283
|
||||
msgid "quantity"
|
||||
msgstr "quantitat"
|
||||
|
||||
#: models.py:277
|
||||
#: models.py:284
|
||||
#, fuzzy
|
||||
#| msgid "quantity"
|
||||
msgid "Verbose quantity"
|
||||
msgstr "quantitat"
|
||||
|
||||
#: models.py:278 templates/bills/microspective.html:76
|
||||
#: templates/bills/microspective.html:100
|
||||
#: models.py:285 templates/bills/microspective.html:77
|
||||
#: templates/bills/microspective.html:111
|
||||
msgid "subtotal"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:279
|
||||
#: models.py:286
|
||||
msgid "tax"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:285
|
||||
#: models.py:291
|
||||
msgid "order billed"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:286
|
||||
#: models.py:292
|
||||
msgid "order billed until"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:287
|
||||
#: models.py:293
|
||||
msgid "created"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:289
|
||||
#: models.py:295
|
||||
msgid "amended line"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:332
|
||||
#: models.py:316
|
||||
msgid "{ini} to {end}"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:331
|
||||
msgid "Volume"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:333
|
||||
#: models.py:332
|
||||
msgid "Compensation"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:334
|
||||
#: models.py:333
|
||||
msgid "Other"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:338
|
||||
#: models.py:337
|
||||
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: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
|
||||
msgid "DUE DATE"
|
||||
msgstr "VENCIMENT"
|
||||
|
@ -366,52 +408,57 @@ msgid "%(bill_type|upper)s DATE "
|
|||
msgstr "DATA %(bill_type|upper)s"
|
||||
|
||||
#: templates/bills/microspective.html:74
|
||||
msgid "period"
|
||||
msgstr ""
|
||||
|
||||
#: templates/bills/microspective.html:75
|
||||
msgid "hrs/qty"
|
||||
msgstr "hrs/quant"
|
||||
|
||||
#: templates/bills/microspective.html:75
|
||||
#: templates/bills/microspective.html:76
|
||||
msgid "rate/price"
|
||||
msgstr "tarifa/preu"
|
||||
|
||||
#: templates/bills/microspective.html:100
|
||||
#: templates/bills/microspective.html:103
|
||||
#: templates/bills/microspective.html:111
|
||||
#: templates/bills/microspective.html:114
|
||||
msgid "VAT"
|
||||
msgstr "IVA"
|
||||
|
||||
#: templates/bills/microspective.html:103
|
||||
#: templates/bills/microspective.html:114
|
||||
msgid "taxes"
|
||||
msgstr "impostos"
|
||||
|
||||
#: templates/bills/microspective.html:119
|
||||
#: templates/bills/microspective.html:130
|
||||
msgid "COMMENTS"
|
||||
msgstr "COMENTARIS"
|
||||
|
||||
#: templates/bills/microspective.html:125
|
||||
#: templates/bills/microspective.html:136
|
||||
msgid "PAYMENT"
|
||||
msgstr "PAGAMENT"
|
||||
|
||||
#: templates/bills/microspective.html:129
|
||||
#: templates/bills/microspective.html:140
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" You can pay our %(type)s by bank transfer. <br>\n"
|
||||
" Please make sure to state your name and the %(type)s "
|
||||
"number.\n"
|
||||
" You can pay our <i>%(type)s</i> by bank transfer. <br>\n"
|
||||
" Please make sure to state your name and the <i>%(type)s</"
|
||||
"i> number.\n"
|
||||
" Our bank account number is <br>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Pots pagar aquesta %(type)s per transferencia banacaria.<br>Inclou el teu "
|
||||
"nom i el numero de %(type)s. El nostre compte bancari és"
|
||||
"Pots pagar aquesta <i>%(type)s</i> per transferencia banacaria.<br>Inclou el "
|
||||
"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"
|
||||
msgstr "PREGUNTES"
|
||||
|
||||
#: templates/bills/microspective.html:139
|
||||
#: templates/bills/microspective.html:150
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\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 "
|
||||
"soon as we get\n"
|
||||
" your message.\n"
|
||||
|
|
|
@ -128,6 +128,9 @@ class Bill(models.Model):
|
|||
value = self.payment_state
|
||||
return force_text(dict(self.PAYMENT_STATES).get(value, value))
|
||||
|
||||
def get_current_transaction(self):
|
||||
return self.transactions.exclude_rejected().first()
|
||||
|
||||
@classmethod
|
||||
def get_class_type(cls):
|
||||
return cls.__name__.upper()
|
||||
|
@ -187,7 +190,9 @@ class Bill(models.Model):
|
|||
template=settings.BILLS_EMAIL_NOTIFICATION_TEMPLATE,
|
||||
context={
|
||||
'bill': self,
|
||||
'settings': settings,
|
||||
},
|
||||
email_from=settings.BILLS_SELLER_EMAIL,
|
||||
contacts=(Contact.BILLING,),
|
||||
attachments=[
|
||||
('%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)
|
||||
# Amendment
|
||||
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):
|
||||
return "#%i" % self.pk
|
||||
|
@ -302,24 +307,22 @@ class BillLine(models.Model):
|
|||
return self.verbose_quantity or self.quantity
|
||||
|
||||
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:
|
||||
return ini
|
||||
end = (self.end_on - datetime.timedelta(seconds=1)).strftime("%b, %Y")
|
||||
if ini == end:
|
||||
return ini
|
||||
return _("{ini} to {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()
|
||||
return "{ini} / {end}".format(ini=ini, end=end)
|
||||
|
||||
# def save(self, *args, **kwargs):
|
||||
# super(BillLine, self).save(*args, **kwargs)
|
||||
|
@ -342,7 +345,6 @@ class BillSubline(models.Model):
|
|||
# TODO: order info for undoing
|
||||
line = models.ForeignKey(BillLine, verbose_name=_("bill line"), related_name='sublines')
|
||||
description = models.CharField(_("description"), max_length=256)
|
||||
# TODO rename to subtotal
|
||||
total = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
type = models.CharField(_("type"), max_length=16, choices=TYPES, default=OTHER)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% if subject %}Bill {{ bill.number }}{% endif %}
|
||||
{% 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
|
||||
{% endif %}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{% extends 'bills/microspective.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block head %}
|
||||
<style type="text/css">
|
||||
{% with color="#809708" %}
|
||||
|
@ -91,36 +93,37 @@ hr {
|
|||
{{ buyer.vat }}<br>
|
||||
{{ buyer.address }}<br>
|
||||
{{ buyer.zipcode }} - {{ buyer.city }}<br>
|
||||
{{ buyer.country }}<br>
|
||||
{{ buyer.get_country_display }}<br>
|
||||
</div>
|
||||
|
||||
<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-date">{{ bill.closed_on | default:now | date }}</span><br>
|
||||
<span id="number-date">{{ bill.closed_on | default:now | date | capfirst }}</span><br>
|
||||
</div>
|
||||
|
||||
<div id="amount" class="column-2">
|
||||
<span id="amount-value">{{ bill.get_total }} €</span><br>
|
||||
<span id="amount-note">Due date {{ payment.due_date | default:default_due_date | date }}<br>
|
||||
{% if not payment.message %}On {{ seller_info.bank_account }}{% endif %}<br>
|
||||
<span id="amount-value">{{ bill.get_total }} &{{ currency.lower }};</span><br>
|
||||
<span id="amount-note">{% trans "Due date" %} {{ payment.due_date| default:default_due_date | date }}<br>
|
||||
{% if not payment.message %}{% blocktrans with bank_account=seller_info.bank_account %}On {{ bank_account }}{% endblocktrans %}{% endif %}<br>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="text">
|
||||
{% blocktrans %}
|
||||
<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 <br>
|
||||
Con vuestras cuotas, ademas de obtener <br>
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
<hr>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
{% block summary %}
|
||||
<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>
|
||||
</div>
|
||||
<div id="bill-summary">
|
||||
|
@ -54,7 +54,7 @@
|
|||
<psan class="value">{{ bill.get_total }} &{{ currency.lower }};</span>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -137,9 +137,9 @@
|
|||
{% if payment.message %}
|
||||
{{ payment.message | safe }}
|
||||
{% else %}
|
||||
{% blocktrans with type=bill.get_type_display %}
|
||||
You can pay our {{ type }} by bank transfer. <br>
|
||||
Please make sure to state your name and the {{ type }} number.
|
||||
{% blocktrans with type=bill.get_type_display.lower %}
|
||||
You can pay our <i>{{ type }}</i> by bank transfer. <br>
|
||||
Please make sure to state your name and the <i>{{ type }}</i> number.
|
||||
Our bank account number is <br>
|
||||
{% endblocktrans %}
|
||||
<strong>{{ seller_info.bank_account }}</strong>
|
||||
|
@ -148,7 +148,7 @@
|
|||
<div id="questions">
|
||||
<span class="title">{% trans "QUESTIONS" %}</span>
|
||||
{% 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
|
||||
your message.
|
||||
{% endblocktrans %}
|
||||
|
|
|
@ -113,7 +113,7 @@ class BillSelectedOrders(object):
|
|||
'title': _("Confirmation for billing selected orders"),
|
||||
'step': 3,
|
||||
'form': form,
|
||||
'bills': bills_with_total,
|
||||
'bills': sorted(bills_with_total, key=lambda i: -i[1]),
|
||||
})
|
||||
return render(request, self.template, self.context)
|
||||
|
||||
|
|
|
@ -73,12 +73,18 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
display_cancelled_on = admin_date('cancelled_on')
|
||||
|
||||
def display_billed_until(self, order):
|
||||
value = order.billed_until
|
||||
color = ''
|
||||
if value and value < timezone.now().date():
|
||||
color = 'style="color:red;"'
|
||||
billed_until = order.billed_until
|
||||
red = False
|
||||
if billed_until:
|
||||
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(
|
||||
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.allow_tags = True
|
||||
|
|
|
@ -244,18 +244,14 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
|||
'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:
|
||||
ini, end = dates
|
||||
elif len(dates) == 1:
|
||||
ini, end = dates[0], dates[0]
|
||||
else:
|
||||
raise AttributeError
|
||||
metric = kwargs.pop('metric', 1)
|
||||
discounts = kwargs.pop('discounts', ())
|
||||
computed = kwargs.pop('computed', False)
|
||||
if kwargs:
|
||||
raise AttributeError
|
||||
raise AttributeError("WTF is '%s'?" % str(dates))
|
||||
discounts = discounts or ()
|
||||
|
||||
size = self.get_price_size(ini, end)
|
||||
if not computed:
|
||||
|
@ -274,6 +270,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
|||
for dtype, dprice in discounts:
|
||||
self.generate_discount(line, dtype, dprice)
|
||||
discounted += dprice
|
||||
# TODO this is needed for all discounts?
|
||||
subtotal += discounted
|
||||
if subtotal > price:
|
||||
self.generate_discount(line, self._PLAN, price-subtotal)
|
||||
|
@ -490,6 +487,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
|||
lines = []
|
||||
bp = None
|
||||
for order in orders:
|
||||
recharges = []
|
||||
bp = self.get_billing_point(order, bp=bp, **options)
|
||||
if (self.billing_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
|
||||
discounts = ()
|
||||
discount = min(price, max(cprice, 0))
|
||||
if pre_discount:
|
||||
if discount:
|
||||
cprice -= price
|
||||
price -= discount
|
||||
discounts = (
|
||||
('prepay', -discount),
|
||||
)
|
||||
# if price-pre_discount:
|
||||
lines.append(self.generate_line(order, price, cini, cend, metric=metric,
|
||||
computed=True, discounts=discounts))
|
||||
# if price-discount:
|
||||
recharges.append((order, price, cini, cend, metric, 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:
|
||||
continue
|
||||
if self.billing_period != self.NEVER:
|
||||
|
|
|
@ -56,9 +56,10 @@ class SiteDirective(Plugin):
|
|||
def validate_uniqueness(self, directive, values, locations):
|
||||
""" Validates uniqueness location, name and value """
|
||||
errors = defaultdict(list)
|
||||
value = directive.get('value', None)
|
||||
# location uniqueness
|
||||
location = None
|
||||
if self.unique_location:
|
||||
if self.unique_location and value is not None:
|
||||
location = normurlpath(directive['value'].split()[0])
|
||||
if location is not None and location in locations:
|
||||
errors['value'].append(ValidationError(
|
||||
|
@ -74,7 +75,6 @@ class SiteDirective(Plugin):
|
|||
))
|
||||
|
||||
# value uniqueness
|
||||
value = directive.get('value', None)
|
||||
if value is not None:
|
||||
if self.unique_value and value in values.get(self.name, []):
|
||||
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 = 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)
|
||||
if html:
|
||||
html_message = render_to_string(html, {'message': True}, context)
|
||||
|
|
Loading…
Reference in a new issue