mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-22 11:23:10 +00:00
Fix #123 Subscription voucher
This commit is contained in:
parent
713b4b5c66
commit
b83bfc0da9
18 changed files with 265 additions and 149 deletions
|
@ -1,31 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.7 on 2019-01-03 16:48
|
# Generated by Django 1.10.7 on 2019-01-03 16:48
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import os
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
from django.core.files import File
|
|
||||||
from django.conf import settings
|
|
||||||
import re2o.mixins
|
import re2o.mixins
|
||||||
|
|
||||||
|
|
||||||
def create_default_templates(apps, schema_editor):
|
|
||||||
DocumentTemplate = apps.get_model('cotisations', 'DocumentTemplate')
|
|
||||||
invoice_path = os.path.join(
|
|
||||||
settings.BASE_DIR,
|
|
||||||
"cotisations",
|
|
||||||
"templates",
|
|
||||||
"cotisations",
|
|
||||||
"factures.tex"
|
|
||||||
)
|
|
||||||
with open(invoice_path) as f:
|
|
||||||
tpl, _ = DocumentTemplate.objects.get_or_create(
|
|
||||||
name="Re2o default invoice",
|
|
||||||
)
|
|
||||||
tpl.template.save('default_invoice.tex', File(f))
|
|
||||||
tpl.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -46,5 +26,4 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model),
|
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model),
|
||||||
),
|
),
|
||||||
migrations.RunPython(create_default_templates),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -50,7 +50,9 @@ from machines.models import regen
|
||||||
from re2o.field_permissions import FieldPermissionModelMixin
|
from re2o.field_permissions import FieldPermissionModelMixin
|
||||||
from re2o.mixins import AclMixin, RevMixin
|
from re2o.mixins import AclMixin, RevMixin
|
||||||
|
|
||||||
from cotisations.utils import find_payment_method, send_mail_invoice
|
from cotisations.utils import (
|
||||||
|
find_payment_method, send_mail_invoice, send_mail_voucher
|
||||||
|
)
|
||||||
from cotisations.validators import check_no_balance
|
from cotisations.validators import check_no_balance
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,20 +240,22 @@ class Facture(BaseInvoice):
|
||||||
self.__original_valid = self.valid
|
self.__original_valid = self.valid
|
||||||
self.__original_control = self.control
|
self.__original_control = self.control
|
||||||
|
|
||||||
def get_subscribtion(self):
|
def get_subscription(self):
|
||||||
return self.vent_set.filter(
|
return Cotisation.objects.filter(
|
||||||
Q(type_cotisation='All') |
|
vente__in=self.vente_set.filter(
|
||||||
Q(type_cotisation='Cotisation')
|
Q(type_cotisation='All') |
|
||||||
|
Q(type_cotisation='Cotisation')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_subscribtion(self):
|
def is_subscription(self):
|
||||||
return bool(self.get_subscribtion())
|
return bool(self.get_subscription())
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super(Facture, self).save(*args, **kwargs)
|
super(Facture, self).save(*args, **kwargs)
|
||||||
if not self.__original_valid and self.valid:
|
if not self.__original_valid and self.valid:
|
||||||
send_mail_invoice(self)
|
send_mail_invoice(self)
|
||||||
if self.is_subscribtion() and not self.__original_control and self.control:
|
if self.is_subscription() and not self.__original_control and self.control:
|
||||||
send_mail_voucher(self)
|
send_mail_voucher(self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -267,10 +271,6 @@ def facture_post_save(**kwargs):
|
||||||
user = facture.user
|
user = facture.user
|
||||||
user.set_active()
|
user.set_active()
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||||
if facture.control:
|
|
||||||
user = facture.user
|
|
||||||
if user.is_adherent():
|
|
||||||
user.notif_subscription_accepted()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Facture)
|
@receiver(post_delete, sender=Facture)
|
||||||
|
@ -955,7 +955,7 @@ def cotisation_post_delete(**_kwargs):
|
||||||
|
|
||||||
class DocumentTemplate(RevMixin, AclMixin, models.Model):
|
class DocumentTemplate(RevMixin, AclMixin, models.Model):
|
||||||
"""Represent a template in order to create documents such as invoice or
|
"""Represent a template in order to create documents such as invoice or
|
||||||
subscribtion voucher.
|
subscription voucher.
|
||||||
"""
|
"""
|
||||||
template = models.FileField(
|
template = models.FileField(
|
||||||
upload_to='templates/',
|
upload_to='templates/',
|
||||||
|
@ -972,18 +972,3 @@ class DocumentTemplate(RevMixin, AclMixin, models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.name)
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class Voucher(RevMixin, AclMixin, models.Model):
|
|
||||||
"""A Subscription Voucher."""
|
|
||||||
user = models.ForeignKey(
|
|
||||||
'users.User',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("user")
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("subscription voucher")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "voucher {} {}".format(self.user, self.date)
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% if estimate.final_invoice %}
|
{% if estimate.final_invoice %}
|
||||||
<a href="{% url 'cotisations:edit-custom-invoice' estimate.final_invoice.pk %}"><i style="color: #1ECA18;" class="fa fa-check"></i></a>
|
<a href="{% url 'cotisations:edit-custom-invoice' estimate.final_invoice.pk %}"><i style="color: #1ECA18;" class="fa fa-check"></i></a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i style="color: #D10115;" class="fa fa-times"></i>'
|
<i style="color: #D10115;" class="fa fa-times"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -48,7 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% trans "Date" as tr_date %}
|
{% trans "Date" as tr_date %}
|
||||||
{% include 'buttons/sort.html' with prefix='cotis' col='date' text=tr_date %}
|
{% include 'buttons/sort.html' with prefix='cotis' col='date' text=tr_date %}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Invoice ID" as tr_invoice_id %}
|
{% trans "Invoice ID" as tr_invoice_id %}
|
||||||
{% include 'buttons/sort.html' with prefix='cotis' col='id' text=tr_invoice_id %}
|
{% include 'buttons/sort.html' with prefix='cotis' col='id' text=tr_invoice_id %}
|
||||||
</th>
|
</th>
|
||||||
|
@ -63,17 +63,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{ facture.prix_total }}</td>
|
<td>{{ facture.prix_total }}</td>
|
||||||
<td>{{ facture.paiement }}</td>
|
<td>{{ facture.paiement }}</td>
|
||||||
<td>{{ facture.date }}</td>
|
<td>{{ facture.date }}</td>
|
||||||
<td>{{ facture.id }}</td>
|
<td>{{ facture.id }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% can_edit facture %}
|
{% can_edit facture %}
|
||||||
{% include 'buttons/edit.html' with href='cotisations:edit-facture' id=facture.id %}
|
{% include 'buttons/edit.html' with href='cotisations:edit-facture' id=facture.id %}
|
||||||
{% acl_else %}
|
{% acl_else %}
|
||||||
{% trans "Controlled invoice" %}
|
{% trans "Controlled invoice" %}
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% can_delete facture %}
|
{% can_delete facture %}
|
||||||
{% include 'buttons/suppr.html' with href='cotisations:del-facture' id=facture.id %}
|
{% include 'buttons/suppr.html' with href='cotisations:del-facture' id=facture.id %}
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% history_button facture %}
|
{% history_button facture %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if facture.valid %}
|
{% if facture.valid %}
|
||||||
|
@ -83,13 +83,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="text-danger">{% trans "Invalidated invoice" %}</i>
|
<i class="text-danger">{% trans "Invalidated invoice" %}</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if facture.control and facture.is_subscription %}
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:voucher-pdf' facture.id %}">
|
||||||
|
<i class="fa fa-file-pdf-o"></i> {% trans "Voucher" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if facture_list.paginator %}
|
{% if facture_list.paginator %}
|
||||||
{% include 'pagination.html' with list=facture_list %}
|
{% include 'pagination.html' with list=facture_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
Bonjour {{name}} !
|
||||||
|
|
||||||
|
Nous vous informons que votre cotisation auprès de {{asso_name}} a été acceptée. Vous voilà donc membre de l'association.
|
||||||
|
|
||||||
|
Vous trouverez en pièce jointe un reçu.
|
||||||
|
|
||||||
|
Pour nous faire part de toute remarque, suggestion ou problème vous pouvez nous envoyer un mail à {{asso_email}}.
|
||||||
|
|
||||||
|
À bientôt,
|
||||||
|
L'équipe de {{asso_name}}.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Your subscription to {{asso_name}} has just been accepted. You are now a full member of {{asso_name}}.
|
||||||
|
|
||||||
|
You will find with this email a subscription voucher.
|
||||||
|
|
||||||
|
For any information, suggestion or problem, you can contact us via email at
|
||||||
|
{{asso_email}}.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
The {{asso_name}} team.
|
87
cotisations/templates/cotisations/voucher.tex
Normal file
87
cotisations/templates/cotisations/voucher.tex
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% language 'fr' %}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% Invoice Template
|
||||||
|
% LaTeX Template
|
||||||
|
% Version 1.0 (3/11/12)
|
||||||
|
%% This template has been downloaded from:
|
||||||
|
% http://www.LaTeXTemplates.com
|
||||||
|
%
|
||||||
|
% Original author:
|
||||||
|
% Trey Hunner (http://www.treyhunner.com/)
|
||||||
|
%
|
||||||
|
% License:
|
||||||
|
% CC BY-NC-SA 3.0 (http://creativecommons.org/licenses/by-nc-sa/3.0/)
|
||||||
|
%
|
||||||
|
% Important note:
|
||||||
|
% This template requires the invoice.cls file to be in the same directory as
|
||||||
|
% the .tex file. The invoice.cls file provides the style used for structuring the
|
||||||
|
% document.
|
||||||
|
%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
% DOCUMENT CONFIGURATION
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
\documentclass[12pt]{article} % Use the custom invoice class (invoice.cls)
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[letterpaper,hmargin=0.79in,vmargin=0.79in]{geometry}
|
||||||
|
\usepackage{longtable}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{tabularx}
|
||||||
|
\usepackage{eurosym}
|
||||||
|
\usepackage{multicol}
|
||||||
|
|
||||||
|
\pagestyle{empty} % No page numbers
|
||||||
|
|
||||||
|
\linespread{1.5}
|
||||||
|
|
||||||
|
\newcommand{\doublehline}{\noalign{\hrule height 1pt}}
|
||||||
|
\setlength{\parindent}{0cm}
|
||||||
|
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
% HEADING SECTION
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
\begin{center}
|
||||||
|
{\Huge\bf Reçu d'adhésion \\ {{asso_name|safe}} } % Company providing the invoice
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\bigskip
|
||||||
|
\hrule
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
Je sousigné, {{pres_name|safe}}, déclare par la présente avoir reçu le bulletin d'adhésion de:
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\setlength{\tabcolsep}{10pt} % Make table columns tighter, usefull for postionning
|
||||||
|
\begin{tabular}{r l r l}
|
||||||
|
{\bf Prénom :}~ & {{firstname|safe}} & {% if phone %}{\bf Téléphone :}~ & {{phone}}{% else %} & {% endif %} \\
|
||||||
|
{\bf Nom :}~ & {{lastname|safe}} & {\bf Mail :}~ & {{email|safe}} \\
|
||||||
|
\end{tabular}
|
||||||
|
\end{center}
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
ainsi que sa cotisation.
|
||||||
|
|
||||||
|
Le postulant, déclare reconnaître l'objet de l'association, et en a accepté les statuts ainsi que le règlement intérieur qui sont mis à sa disposition dans les locaux de l'association. L'adhésion du membre sus-nommé est ainsi validée. Ce reçu confirme la qualité de membre du postulant, et ouvre droit à la participation à l'assemblée générale de l'association jusqu'au {{date_end|date:"d F Y"}}.
|
||||||
|
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
Validé électroniquement par {{pres_name|safe}}, le {{date_begin|date:"d/m/Y"}}.
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
\hrule
|
||||||
|
\smallskip
|
||||||
|
\footnotesize
|
||||||
|
Les informations recueillies sont nécessaires pour votre adhésion. Conformément à la loi "Informatique et Libertés" du 6 janvier 1978, vous disposez d'un droit d'accès et de rectification aux données personnelles vous concernant. Pour l'exercer, adressez-vous au secrétariat de l'association.
|
||||||
|
|
||||||
|
|
||||||
|
\end{document}
|
||||||
|
{% endlanguage %}
|
|
@ -79,13 +79,14 @@ def render_voucher(_request, ctx={}):
|
||||||
filename = '_'.join([
|
filename = '_'.join([
|
||||||
'voucher',
|
'voucher',
|
||||||
slugify(ctx.get('asso_name', "")),
|
slugify(ctx.get('asso_name', "")),
|
||||||
slugify(ctx.get('recipient_name', "")),
|
slugify(ctx.get('firstname', "")),
|
||||||
str(ctx.get('DATE', datetime.now()).year),
|
slugify(ctx.get('lastname', "")),
|
||||||
str(ctx.get('DATE', datetime.now()).month),
|
str(ctx.get('date_begin', datetime.now()).year),
|
||||||
str(ctx.get('DATE', datetime.now()).day),
|
str(ctx.get('date_begin', datetime.now()).month),
|
||||||
|
str(ctx.get('date_begin', datetime.now()).day),
|
||||||
])
|
])
|
||||||
templatename = options.voucher_template.template.name.split('/')[-1]
|
templatename = options.voucher_template.template.name.split('/')[-1]
|
||||||
r = create_pdf(templatename, ctx)
|
r = render_tex(_request, templatename, ctx)
|
||||||
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
|
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
|
||||||
name=filename
|
name=filename
|
||||||
)
|
)
|
||||||
|
@ -110,12 +111,13 @@ def create_pdf(template, ctx={}):
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tempdir:
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
process = Popen(
|
with open("/var/www/re2o/out.log", "w") as f:
|
||||||
['pdflatex', '-output-directory', tempdir],
|
process = Popen(
|
||||||
stdin=PIPE,
|
['pdflatex', '-output-directory', tempdir],
|
||||||
stdout=PIPE,
|
stdin=PIPE,
|
||||||
)
|
stdout=f,#PIPE,
|
||||||
process.communicate(rendered_tpl)
|
)
|
||||||
|
process.communicate(rendered_tpl)
|
||||||
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
|
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
|
||||||
pdf = f.read()
|
pdf = f.read()
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,11 @@ urlpatterns = [
|
||||||
views.facture_pdf,
|
views.facture_pdf,
|
||||||
name='facture-pdf'
|
name='facture-pdf'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r'^voucher_pdf/(?P<factureid>[0-9]+)$',
|
||||||
|
views.voucher_pdf,
|
||||||
|
name='voucher-pdf'
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r'^new_cost_estimate/$',
|
r'^new_cost_estimate/$',
|
||||||
views.new_cost_estimate,
|
views.new_cost_estimate,
|
||||||
|
|
|
@ -25,7 +25,7 @@ from django.template.loader import get_template
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
|
|
||||||
from .tex import create_pdf
|
from .tex import create_pdf
|
||||||
from preferences.models import AssoOption, GeneralOption
|
from preferences.models import AssoOption, GeneralOption, CotisationsOption
|
||||||
from re2o.settings import LOGO_PATH
|
from re2o.settings import LOGO_PATH
|
||||||
from re2o import settings
|
from re2o import settings
|
||||||
|
|
||||||
|
@ -97,52 +97,34 @@ def send_mail_invoice(invoice):
|
||||||
|
|
||||||
def send_mail_voucher(invoice):
|
def send_mail_voucher(invoice):
|
||||||
"""Creates a voucher from an invoice and sends it by email to the client"""
|
"""Creates a voucher from an invoice and sends it by email to the client"""
|
||||||
purchases_info = []
|
|
||||||
for purchase in invoice.vente_set.all():
|
|
||||||
purchases_info.append({
|
|
||||||
'name': purchase.name,
|
|
||||||
'price': purchase.prix,
|
|
||||||
'quantity': purchase.number,
|
|
||||||
'total_price': purchase.prix_total
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
'paid': True,
|
|
||||||
'fid': invoice.id,
|
|
||||||
'DATE': invoice.date,
|
|
||||||
'recipient_name': "{} {}".format(
|
|
||||||
invoice.user.name,
|
|
||||||
invoice.user.surname
|
|
||||||
),
|
|
||||||
'address': invoice.user.room,
|
|
||||||
'article': purchases_info,
|
|
||||||
'total': invoice.prix_total(),
|
|
||||||
'asso_name': AssoOption.get_cached_value('name'),
|
'asso_name': AssoOption.get_cached_value('name'),
|
||||||
'line1': AssoOption.get_cached_value('adresse1'),
|
'pres_name': AssoOption.get_cached_value('pres_name'),
|
||||||
'line2': AssoOption.get_cached_value('adresse2'),
|
'firstname': invoice.user.name,
|
||||||
'siret': AssoOption.get_cached_value('siret'),
|
'lastname': invoice.user.surname,
|
||||||
'email': AssoOption.get_cached_value('contact'),
|
'email': invoice.user.email,
|
||||||
'phone': AssoOption.get_cached_value('telephone'),
|
'phone': invoice.user.telephone,
|
||||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
|
'date_end': invoice.get_subscription().latest('date_end').date_end,
|
||||||
|
'date_begin': invoice.get_subscription().earliest('date_start').date_start
|
||||||
}
|
}
|
||||||
|
templatename = CotisationsOption.get_cached_value('voucher_template').template.name.split('/')[-1]
|
||||||
pdf = create_pdf('cotisations/factures.tex', ctx)
|
pdf = create_pdf(templatename, ctx)
|
||||||
template = get_template('cotisations/email_invoice')
|
template = get_template('cotisations/email_subscription_accepted')
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
'name': "{} {}".format(
|
'name': "{} {}".format(
|
||||||
invoice.user.name,
|
invoice.user.name,
|
||||||
invoice.user.surname
|
invoice.user.surname
|
||||||
),
|
),
|
||||||
'contact_mail': AssoOption.get_cached_value('contact'),
|
'asso_email': AssoOption.get_cached_value('contact'),
|
||||||
'asso_name': AssoOption.get_cached_value('name')
|
'asso_name': AssoOption.get_cached_value('name')
|
||||||
}
|
}
|
||||||
|
|
||||||
mail = EmailMessage(
|
mail = EmailMessage(
|
||||||
'Votre facture / Your invoice',
|
'Votre reçu / Your voucher',
|
||||||
template.render(ctx),
|
template.render(ctx),
|
||||||
GeneralOption.get_cached_value('email_from'),
|
GeneralOption.get_cached_value('email_from'),
|
||||||
[invoice.user.get_mail],
|
[invoice.user.get_mail],
|
||||||
attachments=[('invoice.pdf', pdf, 'application/pdf')]
|
attachments=[('voucher.pdf', pdf, 'application/pdf')]
|
||||||
)
|
)
|
||||||
mail.send()
|
mail.send()
|
||||||
|
|
|
@ -88,7 +88,7 @@ from .forms import (
|
||||||
DocumentTemplateForm,
|
DocumentTemplateForm,
|
||||||
DelDocumentTemplateForm
|
DelDocumentTemplateForm
|
||||||
)
|
)
|
||||||
from .tex import render_invoice, escape_chars
|
from .tex import render_invoice, render_voucher, escape_chars
|
||||||
from .payment_methods.forms import payment_method_factory
|
from .payment_methods.forms import payment_method_factory
|
||||||
from .utils import find_payment_method
|
from .utils import find_payment_method
|
||||||
|
|
||||||
|
@ -220,6 +220,7 @@ def new_cost_estimate(request):
|
||||||
number=quantity
|
number=quantity
|
||||||
)
|
)
|
||||||
discount_form.apply_to_invoice(cost_estimate_instance)
|
discount_form.apply_to_invoice(cost_estimate_instance)
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("The cost estimate was created.")
|
_("The cost estimate was created.")
|
||||||
|
@ -485,7 +486,6 @@ def cost_estimate_pdf(request, invoice, **_kwargs):
|
||||||
invoice with the total price, the payment method, the address and the
|
invoice with the total price, the payment method, the address and the
|
||||||
legal information for the user.
|
legal information for the user.
|
||||||
"""
|
"""
|
||||||
# TODO : change vente to purchase
|
|
||||||
purchases_objects = Vente.objects.all().filter(facture=invoice)
|
purchases_objects = Vente.objects.all().filter(facture=invoice)
|
||||||
# Get the article list and build an list out of it
|
# Get the article list and build an list out of it
|
||||||
# contiaining (article_name, article_price, quantity, total_price)
|
# contiaining (article_name, article_price, quantity, total_price)
|
||||||
|
@ -1145,3 +1145,30 @@ def index_document_template(request):
|
||||||
return render(request, 'cotisations/index_document_template.html', {
|
return render(request, 'cotisations/index_document_template.html', {
|
||||||
'document_template_list': document_template_list
|
'document_template_list': document_template_list
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@can_view(Facture)
|
||||||
|
def voucher_pdf(request, invoice, **_kwargs):
|
||||||
|
"""
|
||||||
|
View used to generate a PDF file from a controlled invoice
|
||||||
|
Creates a line for each Purchase (thus article sold) and generate the
|
||||||
|
invoice with the total price, the payment method, the address and the
|
||||||
|
legal information for the user.
|
||||||
|
"""
|
||||||
|
if not invoice.control:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
_("Could not find a voucher for that invoice.")
|
||||||
|
)
|
||||||
|
return redirect(reverse('cotisations:index'))
|
||||||
|
return render_voucher(request, {
|
||||||
|
'asso_name': AssoOption.get_cached_value('name'),
|
||||||
|
'pres_name': AssoOption.get_cached_value('pres_name'),
|
||||||
|
'firstname': invoice.user.name,
|
||||||
|
'lastname': invoice.user.surname,
|
||||||
|
'email': invoice.user.email,
|
||||||
|
'phone': invoice.user.telephone,
|
||||||
|
'date_end': invoice.get_subscription().latest('date_end').date_end,
|
||||||
|
'date_begin': invoice.get_subscription().earliest('date_start').date_start
|
||||||
|
})
|
||||||
|
|
|
@ -331,7 +331,8 @@ copy_templates_files() {
|
||||||
|
|
||||||
echo "Copying LaTeX templates ..."
|
echo "Copying LaTeX templates ..."
|
||||||
mkdir -p media/templates/
|
mkdir -p media/templates/
|
||||||
cp cotisations/templates/cotisations/factures.tex media/templates
|
cp cotisations/templates/cotisations/factures.tex media/templates/default_invoice.tex
|
||||||
|
cp cotisations/templates/cotisations/voucher.tex media/templates/default_voucher.tex
|
||||||
echo "Copying LaTeX templates: Done"
|
echo "Copying LaTeX templates: Done"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -183,9 +183,6 @@ class EditAssoOptionForm(ModelForm):
|
||||||
self.fields['pseudo'].label = _("Usual name")
|
self.fields['pseudo'].label = _("Usual name")
|
||||||
self.fields['utilisateur_asso'].label = _("Account used for editing"
|
self.fields['utilisateur_asso'].label = _("Account used for editing"
|
||||||
" from /admin")
|
" from /admin")
|
||||||
self.fields['payment'].label = _("Payment")
|
|
||||||
self.fields['payment_id'].label = _("Payment ID")
|
|
||||||
self.fields['payment_pass'].label = _("Payment password")
|
|
||||||
self.fields['description'].label = _("Description")
|
self.fields['description'].label = _("Description")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,47 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.7 on 2019-01-03 19:56
|
# Generated by Django 1.10.7 on 2019-01-03 19:56
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.core.files import File
|
||||||
|
from django.conf import settings
|
||||||
import re2o.mixins
|
import re2o.mixins
|
||||||
|
|
||||||
|
|
||||||
def initialize_invoice_template(apps, schema_editor):
|
def initialize_invoice_template(apps, schema_editor):
|
||||||
CotisationsOption = apps.get_model('preferences', 'CotisationsOption')
|
CotisationsOption = apps.get_model('preferences', 'CotisationsOption')
|
||||||
DocumentTemplate = apps.get_model('cotisations', 'DocumentTemplate')
|
DocumentTemplate = apps.get_model('cotisations', 'DocumentTemplate')
|
||||||
|
invoice_path = os.path.join(
|
||||||
|
settings.BASE_DIR,
|
||||||
|
"cotisations",
|
||||||
|
"templates",
|
||||||
|
"cotisations",
|
||||||
|
"factures.tex"
|
||||||
|
)
|
||||||
|
voucher_path = os.path.join(
|
||||||
|
settings.BASE_DIR,
|
||||||
|
"cotisations",
|
||||||
|
"templates",
|
||||||
|
"cotisations",
|
||||||
|
"voucher.tex"
|
||||||
|
)
|
||||||
|
with open(invoice_path) as f:
|
||||||
|
tpl_invoice, _ = DocumentTemplate.objects.get_or_create(
|
||||||
|
name="Re2o default invoice",
|
||||||
|
)
|
||||||
|
tpl_invoice.template.save('default_invoice.tex', File(f))
|
||||||
|
tpl_invoice.save()
|
||||||
|
with open(voucher_path) as f:
|
||||||
|
tpl_voucher, _ = DocumentTemplate.objects.get_or_create(
|
||||||
|
name="Re2o default voucher",
|
||||||
|
)
|
||||||
|
tpl_voucher.template.save('default_voucher.tex', File(f))
|
||||||
|
tpl_voucher.save()
|
||||||
CotisationsOption.objects.create(
|
CotisationsOption.objects.create(
|
||||||
invoice_template=DocumentTemplate.objects.first()
|
invoice_template=tpl_invoice,
|
||||||
|
voucher_template=tpl_voucher,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +58,7 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('invoice_template', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='invoice_template', to='cotisations.DocumentTemplate', verbose_name='Template for invoices')),
|
('invoice_template', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='invoice_template', to='cotisations.DocumentTemplate', verbose_name='Template for invoices')),
|
||||||
|
('voucher_template', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='voucher_template', to='cotisations.DocumentTemplate', verbose_name='Template for subscription voucher')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'cotisations options',
|
'verbose_name': 'cotisations options',
|
||||||
|
|
20
preferences/migrations/0058_assooption_pres_name.py
Normal file
20
preferences/migrations/0058_assooption_pres_name.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2019-01-10 22:13
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('preferences', '0057_cotisationsoption'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='assooption',
|
||||||
|
name='pres_name',
|
||||||
|
field=models.CharField(default='', help_text='Displayed on subscription vouchers', max_length=255, verbose_name='President of the association'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -521,6 +521,12 @@ class AssoOption(AclMixin, PreferencesModel):
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
pres_name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
default="",
|
||||||
|
verbose_name=_("President of the association"),
|
||||||
|
help_text=_("Displayed on subscription vouchers")
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
|
|
@ -343,6 +343,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>{% trans "Description of the organisation" %}</th>
|
<th>{% trans "Description of the organisation" %}</th>
|
||||||
<td>{{ assooptions.description|safe }}</td>
|
<td>{{ assooptions.description|safe }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "President of the association"%}</th>
|
||||||
|
<td>{{ assooptions.pres_name }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -361,6 +365,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>{% trans "Invoices' template" %}</th>
|
<th>{% trans "Invoices' template" %}</th>
|
||||||
<td>{{ cotisationsoptions.invoice_template }}</td>
|
<td>{{ cotisationsoptions.invoice_template }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Vouchers' template" %}</th>
|
||||||
|
<td>{{ cotisationsoptions.voucher_template }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -663,26 +663,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
def notif_subscription_accepted(self):
|
def reset_passwd_mail(self, request):
|
||||||
"""Send an email when the subscription has been accepted"""
|
|
||||||
template = loader.get_template('users/email_subscription_accepted')
|
|
||||||
mailmessageoptions, _created = MailMessageOption\
|
|
||||||
.objects.get_or_create()
|
|
||||||
context = Context({
|
|
||||||
'nom': self.get_full_name(),
|
|
||||||
'asso_name': AssoOption.get_cached_value('name'),
|
|
||||||
'asso_email': AssoOption.get_cached_value('contact'),
|
|
||||||
})
|
|
||||||
send_mail(
|
|
||||||
'Votre inscription a été validée / Your subscription has been accepted',
|
|
||||||
'',
|
|
||||||
GeneralOption.get_cached_value('email_from'),
|
|
||||||
[self.email],
|
|
||||||
html_message=template.render(context)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
def reset_passwd_mail(self, request):
|
|
||||||
""" Prend en argument un request, envoie un mail de
|
""" Prend en argument un request, envoie un mail de
|
||||||
réinitialisation de mot de pass """
|
réinitialisation de mot de pass """
|
||||||
req = Request()
|
req = Request()
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<p>Bonjour {{nom}} !</p>
|
|
||||||
|
|
||||||
<p>Nous vous informons que votre cotisation auprès de {{asso_name}} a été acceptée. Vous voilà donc membre de l'association.</p>
|
|
||||||
|
|
||||||
<p>Vous trouverez en pièce jointe un reçu.</p>
|
|
||||||
|
|
||||||
<p>Pour nous faire part de toute remarque, suggestion ou problème vous pouvez nous envoyer un mail à {{asso_email}}.</p>
|
|
||||||
|
|
||||||
<p>À bientôt,<br>
|
|
||||||
L'équipe de {{asso_name}}.</p>
|
|
||||||
|
|
||||||
<p>---</p>
|
|
||||||
|
|
||||||
<p>Your subscription to {{asso_name}} has just been accepted. You are now a full member of {{asso_name}}.
|
|
||||||
|
|
||||||
<p>You will find with this email a subscription voucher.</p>
|
|
||||||
|
|
||||||
<p>For any information, suggestion or problem, you can contact us via email at<br>
|
|
||||||
{{asso_email}}.</p>
|
|
||||||
|
|
||||||
<p>Regards,<br>
|
|
||||||
The {{asso_name}} team.</p>
|
|
Loading…
Reference in a new issue