Commit 6f6a1b21 authored by Élie Bouttier's avatar Élie Bouttier

transparence financière

parent be367594
......@@ -208,7 +208,7 @@ class RecurringPaymentAdmin(admin.ModelAdmin):
adherent=adhesion.adherent,
profile=profile,
)
return TemplateResponse(request, 'banking/debtor.html', context)
return TemplateResponse(request, 'banking/admin/debtor.html', context)
class PaymentUpdateAdmin(admin.ModelAdmin):
......
{% extends "base.html" %}
{% block transparencetab %} active{% endblock %}
{% block js_end %}
{{ block.super }}
<script type="text/javascript">
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
{% endblock %}
{% block content %}
<h1>Transparence financière</h1>
<h2>Services</h2>
<table class="table">
<thead>
<tr>
<th>Service</th>
<th data-toggle="tooltip" title="nombre total de services de ce type">total</th>
<th data-toggle="tooltip" title="nombre de services au nom de l’association">infra</th>
<th data-toggle="tooltip" title="nombre de services au nom d’un·e adhérent·e">adhérent·e·s</th>
<th data-toggle="tooltip" title="nombre de services fournis gracieusement">gratuits</th>
<th data-toggle="tooltip" title="nombre de services dont les informations de paiement sont manquantes (généralement non payées mais pas nécessairement volontairement)">manquants</th>
<th data-toggle="tooltip" title="nombre de services effectivement payés">payés</th>
<th data-toggle="tooltip" title="recette total pour ce type de service">euros</th>
<th data-toggle="tooltip" title="contribution moyenne (calculé sur les services effectivement payés uniquement)">moyenne</th>
<th data-toggle="tooltip" title="proportion des recettes de l’association dues à ce type de service">pourcent</th>
</tr>
</thead>
<tbody>
{% for service in services %}
<tr>
<th>{{ service.name }}</th>
<td>{{ service.total }}</td>
<td>{{ service.infra }}</td>
<td>{{ service.adh }}</td>
<td>{{ service.free }}</td>
<td>{{ service.miss }}</td>
<td>{{ service.income }}</td>
<td>{{ service.euros|floatformat:0 }} €</td>
<td>{{ service.mean|floatformat|default:"–" }} €</td>
<td>{{ service.percent|floatformat }} %</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2>Adhésions</h2>
<table class="table">
<thead>
<tr>
<th data-toggle="tooltip" title="nombre total d’adhésions">total</th>
<th data-toggle="tooltip" title="nombre d’adhésions gracieuses">gratuites</th>
<th data-toggle="tooltip" title="nombre d’adhésions dont les informations de paiement sont manquantes (généralement non payées mais pas nécessairement volontairement)">manquantes</th>
<th data-toggle="tooltip" title="nombre d’adhésions payées">payées</th>
<th data-toggle="tooltip" title="recette moyenne des adhésions sur un mois">euros</th>
<th data-toggle="tooltip" title="montant d’adhésion moyen (calculé uniquement sur les adhésions payées)">moyenne</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ adhesions.total }}</td>
<td>{{ adhesions.free }}</td>
<td>{{ adhesions.miss }}</td>
<td>{{ adhesions.pay }}</td>
<td>{{ adhesions.income|floatformat:"0" }} €</td>
<td>{{ adhesions.mean|floatformat|default:"–" }} €</td>
</tr>
</tbody>
</table>
{% endblock %}
from django.urls import path
from . import views
urlpatterns = [
path('transparence/', views.transparence, name='transparence'),
]
from .notifications import notify_payment_update
from .transparence import Transparency
......@@ -2,7 +2,7 @@ from django.urls import reverse
from django.conf import settings
from djadhere.utils import send_notification
from .models import PaymentUpdate
from banking.models import PaymentUpdate
def notify_payment_update(request, update, old_update=None):
......
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Q
from adhesions.models import Adhesion
from services.models import Service, ServiceType, IPResource, Route
from banking.models import RecurringPayment, PaymentUpdate
from djadhere.utils import get_active_filter
from operator import add
from collections import namedtuple
class Transparency:
def services(self):
ServiceStats = namedtuple('Service', ['name', 'total', 'infra', 'adh', 'free', 'miss', 'income', 'euros', 'mean', 'percent'])
ttnn = Adhesion.objects.get(id=100)
total = [0, 0, 0, 0, 0, 0, 0]
lines = []
stats = []
for service_type in ServiceType.objects.exclude(name='Contribution'):
ntotal = ninf = nadh = npay = nfree = nmiss = income = 0
for service in service_type.services.filter(get_active_filter('allocation')).order_by('pk').distinct().prefetch_related('adhesion', 'contribution'):
ntotal += 1
if service.adhesion == ttnn:
ninf += 1
continue
nadh += 1
contrib = service.contribution.get_current_payment()
if not contrib or contrib.payment_method == PaymentUpdate.STOP:
nmiss += 1
elif contrib.payment_method == PaymentUpdate.FREE:
nfree += 1
else:
npay += 1
income += float(contrib.amount) / contrib.period
assert(ntotal == ninf + nadh)
assert(nadh == npay + nfree + nmiss)
total = list(map(add, total, [ntotal, ninf, nadh, npay, nfree, nmiss, income]))
lines += [ (service_type, ntotal, ninf, nadh, npay, nfree, nmiss, income) ]
lines += [('TOTAL',) + tuple(total)]
total_income = total[6]
for service_type, ntotal, ninf, nadh, npay, nfree, nmiss, income in lines:
if total_income:
percent = income / total_income * 100
else:
percent = 0
if npay:
moy = income / npay
else:
moy = None
stats += [ ServiceStats(str(service_type), ntotal, ninf, nadh, nfree, nmiss, npay, income, moy, percent) ]
return stats
def adhesions(self):
adhesions = Adhesion.objects.filter(Q(active__isnull=True) | Q(active=True))
nadh = adhesions.count()
pmiss, pgra, ppay, income = 0, 0, 0, 0
payments = map(lambda adh: adh.membership.get_current_payment(), adhesions)
for payment in payments:
if payment is None:
pmiss += 1
elif payment.payment_method == PaymentUpdate.FREE:
pgra += 1
else:
assert(payment.payment_method != PaymentUpdate.STOP)
ppay += 1
income += float(payment.amount) / payment.period
AdhesionsStats = namedtuple('AdhesionsStats', ['total', 'free', 'miss', 'pay', 'income', 'mean'])
return AdhesionsStats(nadh, pgra, pmiss, ppay, income, 12 * income / ppay)
class Command(BaseCommand):
help = 'Afficher les statistiques financières'
def add_arguments(self, parser):
parser.add_argument('--services', action='store_true')
parser.add_argument('--adhesions', action='store_true')
parser.add_argument('--methodes', action='store_true')
def handle(self, *args, **options):
if options['services']:
self.handle_services()
if options['adhesions']:
self.handle_adhesions()
if options['methodes']:
self.handle_methodes()
def handle_services(self):
ttnn = Adhesion.objects.get(id=100)
total = [0, 0, 0, 0, 0, 0, 0]
lines = []
for service_type in ServiceType.objects.exclude(name='Contribution'):
ntotal = ninf = nadh = npay = nfree = nmiss = income = 0
for service in service_type.services.filter(get_active_filter('allocation')).order_by('pk').distinct():
ntotal += 1
if service.adhesion == ttnn:
ninf += 1
continue
nadh += 1
contrib = service.contribution.get_current_payment()
if not contrib or contrib.payment_method == PaymentUpdate.STOP:
nmiss += 1
elif contrib.payment_method == PaymentUpdate.FREE:
nfree += 1
else:
npay += 1
income += float(contrib.amount) / contrib.period
assert(ntotal == ninf + nadh)
assert(nadh == npay + nfree + nmiss)
total = list(map(add, total, [ntotal, ninf, nadh, npay, nfree, nmiss, income]))
lines += [(str(service_type), ntotal, ninf, nadh, npay, nfree, nmiss, income)]
self.stdout.write("%-18s%12s%12s%14s%12s%12s%12s%12s%12s%12s" % ('Service', 'total', 'infra', 'adhérent·e·s', 'gratuits', 'manquants', 'payés', 'euros', 'moyenne', 'pourcent'))
lines += [('TOTAL',) + tuple(total)]
total_income = total[6]
for service_type, ntotal, ninf, nadh, npay, nfree, nmiss, income in lines:
if total_income:
percent = income / total_income * 100
else:
percent = 0
if npay:
moy = '%12.2f' % (income / npay)
else:
moy = '%12s' % '-'
self.stdout.write("%-18s%12d%12d%14d%12d%12d%12d%12.2f%s%12.1f" % (service_type, ntotal, ninf, nadh, nfree, nmiss, npay, income, moy, percent))
def handle_adhesions(self):
adhesions = Adhesion.objects.filter(Q(active__isnull=True) | Q(active=True))
nadh = adhesions.count()
pmiss, pgra, ppay, income = 0, 0, 0, 0
payments = map(lambda adh: adh.membership.get_current_payment(), adhesions)
for payment in payments:
if payment is None:
pmiss += 1
elif payment.payment_method == PaymentUpdate.FREE:
pgra += 1
else:
assert(payment.payment_method != PaymentUpdate.STOP)
ppay += 1
income += float(payment.amount) / payment.period
self.stdout.write("%12s%12s%12s%12s%12s%12s" % ('Adhesions', 'gratuites', 'manquantes', 'payées', 'euros', 'moyenne'))
self.stdout.write("%12d%12d%12d%12d%12.2f%12.2f" % (nadh, pgra, pmiss, ppay, income, 12 * income / ppay))
def handle_methodes(self):
prelevement, virement, facture = 0, 0, 0
for payment in RecurringPayment.objects.all():
payment = payment.get_current_payment()
if not payment:
continue
if payment.payment_method == PaymentUpdate.DEBIT:
prelevement += payment.amount / payment.period
elif payment.payment_method == PaymentUpdate.TRANSFER:
virement += payment.amount / payment.period
elif payment.payment_method == PaymentUpdate.INVOICE:
facture += payment.amount / payment.period
self.stdout.write("%12s%12s%12s%12s" % ('prélèvement', 'virement', 'facture', 'total'))
self.stdout.write("%12.2f%12.2f%12.2f%12.2f" % (prelevement, virement, facture, prelevement + virement + facture))
from django.shortcuts import render
from .utils import Transparency
def transparence(request):
t = Transparency()
return render(request, 'banking/transparence.html', {
'services': t.services(),
'adhesions': t.adhesions(),
})
......@@ -14,6 +14,7 @@
{% block js %}
{% bootstrap_javascript jquery="full" %}
<script defer src="https://use.fontawesome.com/releases/v5.8.1/js/all.js" integrity="sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" crossorigin="anonymous"></script>
{% endblock %}
{% block extrahead %}{% endblock extrahead %}
......
......@@ -25,14 +25,14 @@
<ul class="navbar-nav mr-auto">
<li class="nav-item{% block usertab %}{% endblock %}">
<a class="nav-link" href="{% url 'adhesion-detail' %}">
<span class="glyphicon glyphicon-heart-empty"></span>&nbsp;Mon adhésion
<span class="fas fa-heart"></span>&nbsp;Mon adhésion
</a>
</li>
{% for corp in request.corporations %}
{% if forloop.first %}
<li class="nav-item dropdown{% block corptab %}{% endblock %}">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-globe"></span>&nbsp;Mes asso <span class="caret"></span>
<span class="fas fa-globe-europe"></span>&nbsp;Mes asso <span class="caret"></span>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
{% endif %}
......@@ -44,7 +44,12 @@
{% endfor %}
<li class="nav-item{% block todotab %}{% endblock %}">
<a class="nav-link" href="{% url 'todo:list-tasklists' %}">
<span class="glyphicon glyphicon-heart-empty"></span>&nbsp;Liste des tâches
<span class="far fa-list-alt"></span>&nbsp;Liste des tâches
</a>
</li>
<li class="nav-item{% block transparencetab %}{% endblock %}">
<a class="nav-link" href="{% url 'transparence' %}">
<span class="fas fa-coins"></span>&nbsp;Transparence
</a>
</li>
</ul>
......
......@@ -23,6 +23,7 @@ urlpatterns = [
path('todo/', include('todo.urls')),
path('', include('services.urls')),
path('', include('adhesions.urls')),
path('', include('banking.urls')),
path('admin/', admin.site.urls),
]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment