From 48d8d7921db99f8365c9c8790b7431852ea2bc5c Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Sat, 5 Jan 2019 19:45:21 +0100 Subject: [PATCH] subscripbtion voucher --- cotisations/admin.py | 3 +- cotisations/forms.py | 3 +- cotisations/models.py | 52 ++++++++++++++++++ cotisations/tex.py | 42 +++++++-------- cotisations/utils.py | 53 +++++++++++++++++++ cotisations/views.py | 5 +- .../migrations/0057_cotisationsoption.py | 38 +++++++++++++ preferences/models.py | 7 ++- users/models.py | 21 +++++++- .../users/email_subscription_accepted | 22 ++++++++ 10 files changed, 217 insertions(+), 29 deletions(-) create mode 100644 preferences/migrations/0057_cotisationsoption.py create mode 100644 users/templates/users/email_subscription_accepted diff --git a/cotisations/admin.py b/cotisations/admin.py index 93ad1ab5..9eb0f071 100644 --- a/cotisations/admin.py +++ b/cotisations/admin.py @@ -30,8 +30,7 @@ from django.contrib import admin from reversion.admin import VersionAdmin from .models import Facture, Article, Banque, Paiement, Cotisation, Vente -from .models import CustomInvoice, CostEstimate -from .tex import DocumentTemplate +from .models import CustomInvoice, CostEstimate, DocumentTemplate class FactureAdmin(VersionAdmin): diff --git a/cotisations/forms.py b/cotisations/forms.py index d512ea80..21f8f42f 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -48,9 +48,8 @@ from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin from .models import ( Article, Paiement, Facture, Banque, - CustomInvoice, Vente, CostEstimate + CustomInvoice, Vente, CostEstimate, DocumentTemplate ) -from .tex import DocumentTemplate from .payment_methods import balance diff --git a/cotisations/models.py b/cotisations/models.py index ea565ac8..7ca4d4a9 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -236,11 +236,23 @@ class Facture(BaseInvoice): 'control': self.can_change_control, } 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 is_subscribtion(self): + return bool(self.get_subscribtion()) 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: + send_mail_voucher(self) def __str__(self): return str(self.user) + ' ' + str(self.date) @@ -255,6 +267,10 @@ 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) @@ -935,3 +951,39 @@ def cotisation_post_delete(**_kwargs): """ regen('mac_ip_list') regen('mailing') + + +class DocumentTemplate(RevMixin, AclMixin, models.Model): + """Represent a template in order to create documents such as invoice or + subscribtion voucher. + """ + template = models.FileField( + upload_to='templates/', + verbose_name=_('template') + ) + name = models.CharField( + max_length=255, + verbose_name=_('name') + ) + + class Meta: + verbose_name = _("document template") + verbose_name_plural = _("document templates") + + 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) diff --git a/cotisations/tex.py b/cotisations/tex.py index b253d12b..0d6dc31f 100644 --- a/cotisations/tex.py +++ b/cotisations/tex.py @@ -48,27 +48,6 @@ CACHE_PREFIX = getattr(settings, 'TEX_CACHE_PREFIX', 'render-tex') CACHE_TIMEOUT = getattr(settings, 'TEX_CACHE_TIMEOUT', 86400) # 1 day -class DocumentTemplate(RevMixin, AclMixin, models.Model): - """Represent a template in order to create documents such as invoice or - subscribtion voucher. - """ - template = models.FileField( - upload_to='templates/', - verbose_name=_('template') - ) - name = models.CharField( - max_length=255, - verbose_name=_('name') - ) - - class Meta: - verbose_name = _("document template") - verbose_name_plural = _("document templates") - - def __str__(self): - return str(self.name) - - def render_invoice(_request, ctx={}): """ Render an invoice using some available information such as the current @@ -92,6 +71,27 @@ def render_invoice(_request, ctx={}): return r +def render_voucher(_request, ctx={}): + """ + Render a subscribtion voucher. + """ + options, _ = CotisationsOption.objects.get_or_create() + 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), + ]) + templatename = options.voucher_template.template.name.split('/')[-1] + r = create_pdf(templatename, ctx) + r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format( + name=filename + ) + return r + + def create_pdf(template, ctx={}): """Creates and returns a PDF from a LaTeX template using pdflatex. diff --git a/cotisations/utils.py b/cotisations/utils.py index a8c2768e..a7856540 100644 --- a/cotisations/utils.py +++ b/cotisations/utils.py @@ -93,3 +93,56 @@ def send_mail_invoice(invoice): attachments=[('invoice.pdf', pdf, 'application/pdf')] ) mail.send() + + +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) + } + + pdf = create_pdf('cotisations/factures.tex', ctx) + template = get_template('cotisations/email_invoice') + + ctx = { + 'name': "{} {}".format( + invoice.user.name, + invoice.user.surname + ), + 'contact_mail': AssoOption.get_cached_value('contact'), + 'asso_name': AssoOption.get_cached_value('name') + } + + mail = EmailMessage( + 'Votre facture / Your invoice', + template.render(ctx), + GeneralOption.get_cached_value('email_from'), + [invoice.user.get_mail], + attachments=[('invoice.pdf', pdf, 'application/pdf')] + ) + mail.send() diff --git a/cotisations/views.py b/cotisations/views.py index 08139dfc..38609305 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -69,7 +69,8 @@ from .models import ( Banque, CustomInvoice, BaseInvoice, - CostEstimate + CostEstimate, + DocumentTemplate ) from .forms import ( FactureForm, @@ -87,7 +88,7 @@ from .forms import ( DocumentTemplateForm, DelDocumentTemplateForm ) -from .tex import render_invoice, escape_chars, DocumentTemplate +from .tex import render_invoice, escape_chars from .payment_methods.forms import payment_method_factory from .utils import find_payment_method diff --git a/preferences/migrations/0057_cotisationsoption.py b/preferences/migrations/0057_cotisationsoption.py new file mode 100644 index 00000000..d02d9c28 --- /dev/null +++ b/preferences/migrations/0057_cotisationsoption.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2019-01-03 19:56 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import re2o.mixins + + +def initialize_invoice_template(apps, schema_editor): + CotisationsOption = apps.get_model('preferences', 'CotisationsOption') + DocumentTemplate = apps.get_model('cotisations', 'DocumentTemplate') + CotisationsOption.objects.create( + invoice_template=DocumentTemplate.objects.first() + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('cotisations', '0039_documenttemplate'), + ('preferences', '0056_4_radiusoption'), + ] + + operations = [ + migrations.CreateModel( + name='CotisationsOption', + 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')), + ], + options={ + 'verbose_name': 'cotisations options', + }, + bases=(re2o.mixins.AclMixin, models.Model), + ), + migrations.RunPython(initialize_invoice_template), + ] diff --git a/preferences/models.py b/preferences/models.py index 781107af..99750850 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -698,4 +698,9 @@ class CotisationsOption(AclMixin, PreferencesModel): related_name="invoice_template", on_delete=models.PROTECT, ) - + voucher_template = models.OneToOneField( + 'cotisations.DocumentTemplate', + verbose_name=_("Template for subscription voucher"), + related_name="voucher_template", + on_delete=models.PROTECT, + ) diff --git a/users/models.py b/users/models.py index c1d0789a..7d91b2b5 100755 --- a/users/models.py +++ b/users/models.py @@ -663,7 +663,26 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser, ) return - def reset_passwd_mail(self, request): + 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): """ Prend en argument un request, envoie un mail de réinitialisation de mot de pass """ req = Request() diff --git a/users/templates/users/email_subscription_accepted b/users/templates/users/email_subscription_accepted new file mode 100644 index 00000000..2ce5c839 --- /dev/null +++ b/users/templates/users/email_subscription_accepted @@ -0,0 +1,22 @@ +

Bonjour {{nom}} !

+ +

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.