Improvements on billing

This commit is contained in:
Marc Aymerich 2015-07-08 10:21:19 +00:00
parent 75c72ce8a5
commit 41163b5e52
8 changed files with 69 additions and 24 deletions

View file

@ -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):

View file

@ -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)

View file

@ -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

View file

@ -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 %}

View file

@ -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

View file

@ -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() """

View file

@ -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]:

View file

@ -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',