Commit b83bfc0d authored by klafyvel's avatar klafyvel

Fix #123 Subscription voucher

parent 713b4b5c
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-03 16:48
from __future__ import unicode_literals
import os
from django.db import migrations, models
from django.core.files import File
from django.conf import settings
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):
dependencies = [
......@@ -46,5 +26,4 @@ class Migration(migrations.Migration):
},
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.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
......@@ -238,20 +240,22 @@ class Facture(BaseInvoice):
self.__original_valid = self.valid
self.__original_control = self.control
def get_subscribtion(self):
return self.vent_set.filter(
Q(type_cotisation='All') |
Q(type_cotisation='Cotisation')
def get_subscription(self):
return Cotisation.objects.filter(
vente__in=self.vente_set.filter(
Q(type_cotisation='All') |
Q(type_cotisation='Cotisation')
)
)
def is_subscribtion(self):
return bool(self.get_subscribtion())
def is_subscription(self):
return bool(self.get_subscription())
def save(self, *args, **kwargs):
super(Facture, self).save(*args, **kwargs)
if not self.__original_valid and self.valid:
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)
def __str__(self):
......@@ -267,10 +271,6 @@ def facture_post_save(**kwargs):
user = facture.user
user.set_active()
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)
......@@ -955,7 +955,7 @@ def cotisation_post_delete(**_kwargs):
class DocumentTemplate(RevMixin, AclMixin, models.Model):
"""Represent a template in order to create documents such as invoice or
subscribtion voucher.
subscription voucher.
"""
template = models.FileField(
upload_to='templates/',
......@@ -972,18 +972,3 @@ class DocumentTemplate(RevMixin, AclMixin, models.Model):
def __str__(self):
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 %}
<a href="{% url 'cotisations:edit-custom-invoice' estimate.final_invoice.pk %}"><i style="color: #1ECA18;" class="fa fa-check"></i></a>
{% else %}
<i style="color: #D10115;" class="fa fa-times"></i>'
<i style="color: #D10115;" class="fa fa-times"></i>
{% endif %}
</td>
<td>
......
......@@ -48,7 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Date" as tr_date %}
{% include 'buttons/sort.html' with prefix='cotis' col='date' text=tr_date %}
</th>
<th>
<th>
{% trans "Invoice ID" as tr_invoice_id %}
{% include 'buttons/sort.html' with prefix='cotis' col='id' text=tr_invoice_id %}
</th>
......@@ -63,17 +63,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ facture.prix_total }}</td>
<td>{{ facture.paiement }}</td>
<td>{{ facture.date }}</td>
<td>{{ facture.id }}</td>
<td>{{ facture.id }}</td>
<td>
{% 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 %}
{% trans "Controlled invoice" %}
{% acl_end %}
{% 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 %}
{% history_button facture %}
{% history_button facture %}
</td>
<td>
{% if facture.valid %}
......@@ -83,13 +83,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% else %}
<i class="text-danger">{% trans "Invalidated invoice" %}</i>
{% 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>
</tr>
{% endfor %}
</table>
{% if facture_list.paginator %}
{% if facture_list.paginator %}
{% include 'pagination.html' with list=facture_list %}
{% endif %}
{% endif %}
</div>
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.
{% 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([
'voucher',
slugify(ctx.get('asso_name', "")),
slugify(ctx.get('recipient_name', "")),
str(ctx.get('DATE', datetime.now()).year),
str(ctx.get('DATE', datetime.now()).month),
str(ctx.get('DATE', datetime.now()).day),
slugify(ctx.get('firstname', "")),
slugify(ctx.get('lastname', "")),
str(ctx.get('date_begin', datetime.now()).year),
str(ctx.get('date_begin', datetime.now()).month),
str(ctx.get('date_begin', datetime.now()).day),
])
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(
name=filename
)
......@@ -110,12 +111,13 @@ def create_pdf(template, ctx={}):
with tempfile.TemporaryDirectory() as tempdir:
for _ in range(2):
process = Popen(
['pdflatex', '-output-directory', tempdir],
stdin=PIPE,
stdout=PIPE,
)
process.communicate(rendered_tpl)
with open("/var/www/re2o/out.log", "w") as f:
process = Popen(
['pdflatex', '-output-directory', tempdir],
stdin=PIPE,
stdout=f,#PIPE,
)
process.communicate(rendered_tpl)
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
pdf = f.read()
......
......@@ -51,6 +51,11 @@ urlpatterns = [
views.facture_pdf,
name='facture-pdf'
),
url(
r'^voucher_pdf/(?P<factureid>[0-9]+)$',
views.voucher_pdf,
name='voucher-pdf'
),
url(
r'^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 .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 import settings
......@@ -97,52 +97,34 @@ def send_mail_invoice(invoice):
def send_mail_voucher(invoice):
"""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 = {
'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'),
'line1': AssoOption.get_cached_value('adresse1'),
'line2': AssoOption.get_cached_value('adresse2'),
'siret': AssoOption.get_cached_value('siret'),
'email': AssoOption.get_cached_value('contact'),
'phone': AssoOption.get_cached_value('telephone'),
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
'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
}
pdf = create_pdf('cotisations/factures.tex', ctx)
template = get_template('cotisations/email_invoice')
templatename = CotisationsOption.get_cached_value('voucher_template').template.name.split('/')[-1]
pdf = create_pdf(templatename, ctx)
template = get_template('cotisations/email_subscription_accepted')
ctx = {
'name': "{} {}".format(
invoice.user.name,
invoice.user.surname
),
'contact_mail': AssoOption.get_cached_value('contact'),
'asso_email': AssoOption.get_cached_value('contact'),
'asso_name': AssoOption.get_cached_value('name')
}
mail = EmailMessage(
'Votre facture / Your invoice',
'Votre reçu / Your voucher',
template.render(ctx),
GeneralOption.get_cached_value('email_from'),
[invoice.user.get_mail],
attachments=[('invoice.pdf', pdf, 'application/pdf')]
attachments=[('voucher.pdf', pdf, 'application/pdf')]
)
mail.send()
......@@ -88,7 +88,7 @@ from .forms import (
DocumentTemplateForm,
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 .utils import find_payment_method
......@@ -220,6 +220,7 @@ def new_cost_estimate(request):
number=quantity
)
discount_form.apply_to_invoice(cost_estimate_instance)
messages.success(
request,
_("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
legal information for the user.
"""
# TODO : change vente to purchase
purchases_objects = Vente.objects.all().filter(facture=invoice)
# Get the article list and build an list out of it
# 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', {
'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 ..."
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"
}
......
......@@ -183,9 +183,6 @@ class EditAssoOptionForm(ModelForm):
self.fields['pseudo'].label = _("Usual name")
self.fields['utilisateur_asso'].label = _("Account used for editing"
" 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")
......
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-03 19:56
from __future__ import unicode_literals
import os
from django.db import migrations, models
import django.db.models.deletion
from django.core.files import File
from django.conf import settings
import re2o.mixins
def initialize_invoice_template(apps, schema_editor):
CotisationsOption = apps.get_model('preferences', 'CotisationsOption')
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(
invoice_template=DocumentTemplate.objects.first()
invoice_template=tpl_invoice,
voucher_template=tpl_voucher,
)
......@@ -28,6 +58,7 @@ class Migration(migrations.Migration):
fields=[
('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')),
('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={
'verbose_name': 'cotisations options',
......
# -*- 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,
blank=True,
)
pres_name = models.CharField(
max_length=255,
default="",
verbose_name=_("President of the association"),
help_text=_("Displayed on subscription vouchers")
)
class Meta:
permissions = (
......
......@@ -343,6 +343,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>{% trans "Description of the organisation" %}</th>
<td>{{ assooptions.description|safe }}</td>
</tr>
<tr>
<th>{% trans "President of the association"%}</th>
<td>{{ assooptions.pres_name }}</td>
</tr>
</table>
</div>
</div>
......@@ -361,6 +365,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>{% trans "Invoices' template" %}</th>
<td>{{ cotisationsoptions.invoice_template }}</td>
</tr>
<tr>
<th>{% trans "Vouchers' template" %}</th>
<td>{{ cotisationsoptions.voucher_template }}</td>
</tr>
</table>
</div>
</div>
......
......@@ -663,26 +663,7 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
)
return
def notif_subscription_accepted(self):
"""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):
def reset_passwd_mail(self, request):
""" Prend en argument un request, envoie un mail de
réinitialisation de mot de pass """
req = Request()
......
<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>
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