Improvements on billing
This commit is contained in:
parent
75c72ce8a5
commit
41163b5e52
|
@ -4,7 +4,7 @@ from django.contrib import admin, messages
|
||||||
from django.contrib.admin.utils import unquote
|
from django.contrib.admin.utils import unquote
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Sum
|
from django.db.models import F, Sum, Prefetch
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -17,8 +17,10 @@ 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, TotalListFilter, PaymentStateListFilter
|
from .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter,
|
||||||
from .models import Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine, BillContact
|
PaymentStateListFilter, AmendedListFilter)
|
||||||
|
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine,
|
||||||
|
BillContact)
|
||||||
|
|
||||||
|
|
||||||
PAYMENT_STATE_COLORS = {
|
PAYMENT_STATE_COLORS = {
|
||||||
|
@ -185,8 +187,12 @@ 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', TotalListFilter, PaymentStateListFilter)
|
list_filter = (
|
||||||
|
BillTypeListFilter, 'is_open', 'is_sent', TotalListFilter, PaymentStateListFilter,
|
||||||
|
AmendedListFilter
|
||||||
|
)
|
||||||
add_fields = ('account', 'type', 'amend_of', 'is_open', 'due_on', 'comments')
|
add_fields = ('account', 'type', 'amend_of', 'is_open', 'due_on', 'comments')
|
||||||
|
change_list_template = 'admin/bills/change_list.html'
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ('number', 'type', 'amend_of_link', 'account_link', 'display_total',
|
'fields': ('number', 'type', 'amend_of_link', 'account_link', 'display_total',
|
||||||
|
@ -243,9 +249,13 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.model_name))
|
url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.model_name))
|
||||||
url += '?bill=%i' % bill.pk
|
url += '?bill=%i' % bill.pk
|
||||||
state = bill.get_payment_state_display().upper()
|
state = bill.get_payment_state_display().upper()
|
||||||
|
title = ''
|
||||||
|
if bill.closed_amends:
|
||||||
|
state += '*'
|
||||||
|
title = _("This bill has been amended, this value may not be valid.")
|
||||||
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
|
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
|
||||||
return '<a href="{url}" style="color:{color}">{name}</a>'.format(
|
return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format(
|
||||||
url=url, color=color, name=state)
|
url=url, color=color, name=state, title=title)
|
||||||
display_payment_state.allow_tags = True
|
display_payment_state.allow_tags = True
|
||||||
display_payment_state.short_description = _("Payment")
|
display_payment_state.short_description = _("Payment")
|
||||||
|
|
||||||
|
@ -309,6 +319,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
(F('lines__subtotal') + Coalesce(F('lines__sublines__total'), 0)) * (1+F('lines__tax')/100)
|
(F('lines__subtotal') + Coalesce(F('lines__sublines__total'), 0)) * (1+F('lines__tax')/100)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
qs = qs.prefetch_related(Prefetch('amends', queryset=Bill.objects.filter(is_open=False), to_attr='closed_amends'))
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def change_view(self, request, object_id, **kwargs):
|
def change_view(self, request, object_id, **kwargs):
|
||||||
|
|
|
@ -91,7 +91,6 @@ class PaymentStateListFilter(SimpleListFilter):
|
||||||
('PAID', _("Paid")),
|
('PAID', _("Paid")),
|
||||||
('PENDING', _("Pending")),
|
('PENDING', _("Pending")),
|
||||||
('BAD_DEBT', _("Bad debt")),
|
('BAD_DEBT', _("Bad debt")),
|
||||||
('AMENDED', _("Amended")),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
|
@ -128,7 +127,29 @@ class PaymentStateListFilter(SimpleListFilter):
|
||||||
Q(transactions__state=Transaction.REJECTED) |
|
Q(transactions__state=Transaction.REJECTED) |
|
||||||
Q(transactions__isnull=True)
|
Q(transactions__isnull=True)
|
||||||
)
|
)
|
||||||
elif self.value() == 'AMENDED':
|
|
||||||
amendeds = queryset.filter(type__in=Bill.AMEND_MAP.values(), is_open=False)
|
|
||||||
amendeds_ids = amendeds.values_list('amend_of', flat=True)
|
class AmendedListFilter(SimpleListFilter):
|
||||||
return queryset.filter(id__in=amendeds)
|
title = _("amended")
|
||||||
|
parameter_name = 'amended'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return (
|
||||||
|
('1', _("Closed amends")),
|
||||||
|
('-1', _("Open or closed amends")),
|
||||||
|
('0', _("No closed amends")),
|
||||||
|
('-0', _("No amends")),
|
||||||
|
)
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() is None:
|
||||||
|
return queryset
|
||||||
|
amended = queryset.filter(type__in=Bill.AMEND_MAP.values(), amend_of__isnull=False)
|
||||||
|
if not self.value().startswith('-'):
|
||||||
|
amended = amended.filter(is_open=False)
|
||||||
|
amended_ids = amended.distinct().values_list('amend_of_id', flat=True)
|
||||||
|
if self.value().endswith('1'):
|
||||||
|
return queryset.filter(id__in=amended_ids)
|
||||||
|
else:
|
||||||
|
return queryset.exclude(id__in=amended_ids)
|
||||||
|
|
||||||
|
|
|
@ -140,8 +140,6 @@ class Bill(models.Model):
|
||||||
def payment_state(self):
|
def payment_state(self):
|
||||||
if self.is_open or self.get_type() == self.PROFORMA:
|
if self.is_open or self.get_type() == self.PROFORMA:
|
||||||
return self.OPEN
|
return self.OPEN
|
||||||
elif self.amends.filter(is_open=False).exists():
|
|
||||||
return self.AMENDED
|
|
||||||
secured = 0
|
secured = 0
|
||||||
pending = 0
|
pending = 0
|
||||||
created = False
|
created = False
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "admin/change_list.html" %}
|
||||||
|
{% load i18n admin_urls %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block object-tools-items %}
|
||||||
|
<li>
|
||||||
|
{% url 'admin:bills_bill_add' as add_url %}
|
||||||
|
<a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
|
||||||
|
{% trans "Add bill" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
|
@ -19,19 +19,19 @@ class MailmanVirtualDomainBackend(ServiceController):
|
||||||
('LISTS_VIRTUAL_ALIAS_DOMAINS_PATH',)
|
('LISTS_VIRTUAL_ALIAS_DOMAINS_PATH',)
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_local_domain(self, domain):
|
def is_hosted_domain(self, domain):
|
||||||
""" whether or not domain MX points to this server """
|
""" whether or not domain MX points to this server """
|
||||||
return domain.has_default_mx()
|
return domain.has_default_mx()
|
||||||
|
|
||||||
def include_virtual_alias_domain(self, context):
|
def include_virtual_alias_domain(self, context):
|
||||||
domain = context['address_domain']
|
domain = context['address_domain']
|
||||||
if domain and self.is_local_domain(domain):
|
if domain and self.is_hosted_domain(domain):
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
# Add virtual domain %(address_domain)s
|
# Add virtual domain %(address_domain)s
|
||||||
[[ $(grep '^\s*%(address_domain)s\s*$' %(virtual_alias_domains)s) ]] || {
|
[[ $(grep '^\s*%(address_domain)s\s*$' %(virtual_alias_domains)s) ]] || {
|
||||||
echo '%(address_domain)s' >> %(virtual_alias_domains)s
|
echo '%(address_domain)s' >> %(virtual_alias_domains)s
|
||||||
UPDATED_VIRTUAL_ALIAS_DOMAINS=1
|
UPDATED_VIRTUAL_ALIAS_DOMAINS=1
|
||||||
}""") % self.context
|
}""") % context
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_last_domain(self, domain):
|
def is_last_domain(self, domain):
|
||||||
|
@ -108,8 +108,9 @@ class MailmanBackend(MailmanVirtualDomainBackend):
|
||||||
context['suffix'] = suffix
|
context['suffix'] = suffix
|
||||||
# Because mailman doesn't properly handle lists aliases we need two virtual aliases
|
# Because mailman doesn't properly handle lists aliases we need two virtual aliases
|
||||||
aliases.append("%(address_name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
|
aliases.append("%(address_name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
|
||||||
# And another with the original list name; Mailman generates links with it
|
if context['address_name'] != context['name']:
|
||||||
aliases.append("%(name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
|
# And another with the original list name; Mailman generates links with it
|
||||||
|
aliases.append("%(name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
|
||||||
return '\n'.join(aliases)
|
return '\n'.join(aliases)
|
||||||
|
|
||||||
def save(self, mail_list):
|
def save(self, mail_list):
|
||||||
|
@ -122,7 +123,10 @@ class MailmanBackend(MailmanVirtualDomainBackend):
|
||||||
}""") % context)
|
}""") % context)
|
||||||
# Custom domain
|
# Custom domain
|
||||||
if mail_list.address:
|
if mail_list.address:
|
||||||
context['aliases'] = self.get_virtual_aliases(context)
|
context.update({
|
||||||
|
'aliases': self.get_virtual_aliases(context),
|
||||||
|
'num_entries': 2 if context['address_name'] != context['name'] else 1,
|
||||||
|
})
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
# Create list alias for custom domain
|
# Create list alias for custom domain
|
||||||
aliases='%(aliases)s'
|
aliases='%(aliases)s'
|
||||||
|
@ -130,7 +134,7 @@ class MailmanBackend(MailmanVirtualDomainBackend):
|
||||||
echo "${aliases}" >> %(virtual_alias)s
|
echo "${aliases}" >> %(virtual_alias)s
|
||||||
UPDATED_VIRTUAL_ALIAS=1
|
UPDATED_VIRTUAL_ALIAS=1
|
||||||
else
|
else
|
||||||
if [[ ! $(grep '^\s*%(address_name)s@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
|
if [[ $(grep -E '^\s*(%(address_name)s|%(name)s)@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s|wc -l) -ne %(num_entries)s ]]; then
|
||||||
sed -i -e '/^.*\s%(name)s\(%(suffixes_regex)s\)\s*$/d' \\
|
sed -i -e '/^.*\s%(name)s\(%(suffixes_regex)s\)\s*$/d' \\
|
||||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
|
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
|
||||||
echo "${aliases}" >> %(virtual_alias)s
|
echo "${aliases}" >> %(virtual_alias)s
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Operation():
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
""" set() """
|
""" set() """
|
||||||
return hash(self.backend) + hash(self.instance) + hash(self.action)
|
return hash((self.backend, self.instance, self.action))
|
||||||
|
|
||||||
def __eq__(self, operation):
|
def __eq__(self, operation):
|
||||||
""" set() """
|
""" set() """
|
||||||
|
|
|
@ -109,9 +109,9 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
exclude = []
|
exclude = []
|
||||||
if obj:
|
if obj:
|
||||||
if obj.state == Transaction.WAITTING_PROCESSING:
|
if obj.state == Transaction.WAITTING_PROCESSING:
|
||||||
exclude = ['mark_as_executed', 'mark_as_secured', 'mark_as_rejected']
|
exclude = ['mark_as_executed', 'mark_as_secured']
|
||||||
elif obj.state == Transaction.WAITTING_EXECUTION:
|
elif obj.state == Transaction.WAITTING_EXECUTION:
|
||||||
exclude = ['process_transactions', 'mark_as_secured', 'mark_as_rejected']
|
exclude = ['process_transactions', 'mark_as_secured']
|
||||||
if obj.state == Transaction.EXECUTED:
|
if obj.state == Transaction.EXECUTED:
|
||||||
exclude = ['process_transactions', 'mark_as_executed']
|
exclude = ['process_transactions', 'mark_as_executed']
|
||||||
elif obj.state in [Transaction.REJECTED, Transaction.SECURED]:
|
elif obj.state in [Transaction.REJECTED, Transaction.SECURED]:
|
||||||
|
|
|
@ -192,7 +192,6 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = Setting('WEBAPPS_PHP_DISABLED_FUNCTION', (
|
||||||
'system',
|
'system',
|
||||||
'proc_open',
|
'proc_open',
|
||||||
'popen',
|
'popen',
|
||||||
'curl_exec',
|
|
||||||
'curl_multi_exec',
|
'curl_multi_exec',
|
||||||
'show_source',
|
'show_source',
|
||||||
'pcntl_exec',
|
'pcntl_exec',
|
||||||
|
|
Loading…
Reference in New Issue