From: Gael Le Mignot Date: Fri, 3 Sep 2021 10:20:16 +0000 (+0200) Subject: Auto-generating payment objects X-Git-Tag: 2.5.0~67 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=1628bb163cacf6d9aaccb16fe057f58e0dd6a86c;p=teleforma.git Auto-generating payment objects --- diff --git a/app/settings.py b/app/settings.py index 1a9382c7..b37cf045 100644 --- a/app/settings.py +++ b/app/settings.py @@ -472,6 +472,8 @@ PAYMENT_PARAMETERS = { 'merchant_id' : { 'Semestrielle': "014295303911111", } +ORAL_OPTION_PRICE = 250 + if DEBUG_TOOLBAR: def show_toolbar(request): return True diff --git a/teleforma/migrations/0010_student_payment_generated.py b/teleforma/migrations/0010_student_payment_generated.py new file mode 100644 index 00000000..bc22ba20 --- /dev/null +++ b/teleforma/migrations/0010_student_payment_generated.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.3 on 2021-09-02 16:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('teleforma', '0009_alter_conference_public_id'), + ] + + operations = [ + migrations.AddField( + model_name='student', + name='payment_generated', + field=models.BooleanField(default=False, verbose_name='échances générées'), + ), + ] diff --git a/teleforma/models/crfpa.py b/teleforma/models/crfpa.py index 7f482a3f..8fbe6a53 100755 --- a/teleforma/models/crfpa.py +++ b/teleforma/models/crfpa.py @@ -36,6 +36,7 @@ import datetime +from django.conf import settings import django.db.models as models from django.contrib.auth.models import User from django.db.models import signals @@ -207,6 +208,8 @@ class Student(models.Model): receipt_id = models.IntegerField('numéro de facture', blank=True, null=True, unique=True) + payment_generated = models.BooleanField(_('échances générées'), default=False) + def __str__(self): try: return self.user.last_name + ' ' + self.user.first_name @@ -280,6 +283,106 @@ class Student(models.Model): def get_absolute_url(self): return reverse_lazy('teleforma-profile-detail', kwargs={'username': self.user.username}) + def create_payment_objects(self): + """ + Create the payment objects if the account is activated + """ + if not self.is_subscribed: + return + + if self.payment_generated: + return + + # If we already have some payments, then forget about it + if self.payments.count(): + self.payment_generated = True + self.save() + return + + total = self.total_fees + oneday = datetime.timedelta(days = 1) + tomorrow = datetime.date.today() + oneday + period = self.period + + oral_1 = self.oral_1 and self.oral_1.title != 'Aucune' + oral_price = settings.ORAL_OPTION_PRICE if oral_1 else 0 + total -= oral_price + + def endofmonth(year, month): + """ + Get the last day of a month. To avoid quirky varying month length, + we look at 01/m+1 and then remove one day + """ + month = month + 1 + if month > 12: + year += 1 + month -= 12 + return datetime.date(year, month + 1, 1) - oneday + + oral_date = None + payments = None + # Full or partial ? + if self.payment_schedule == 'split': + if period.name == 'Semestrielle': + part = int(total * 0.25) + remaining = total - 3 * part + payments = ((remaining, tomorrow),) + if tomorrow.month <= 6: + # Late registration, so next three end of month + for i in range(3): + date = endofmonth(tomorrow.year, tomorrow.month + 1 + i) + payments += ((part, date),) + oral_date = endofmonth(tomorrow.year, 6) + else: + # Normal registration, so first three months of next year + for month in (1, 2, 3): + # To avoid quirky varying month length, we + # look at 01/m+1 and then remove one day + date = endofmonth(tomorrow.year + 1, month) + payments += ((part, date),) + oral_date = endofmonth(tomorrow.year + 1, 6) + elif period.name == 'Estivale': + part = int(total * 0.35) + remaining = total - 2 * part + payments = ((remaining, tomorrow),) + if tomorrow.month >= 6: + # Late registration, so next two end of month + for i in range(2): + # To avoid quirky varying month length, we + # look at 01/m+1 and then remove one day + date = endofmonth(tomorrow.year, tomorrow.month + 1 + i) + payments += ((part, date),) + oral_date = endofmonth(tomorrow.year, 8) + else: + # Normal registration, so end of june and end of + # july + for month in (6, 7): + # To avoid quirky varying month length, we + # look at 01/m+1 and then remove one day + date = endofmonth(tomorrow.year, month) + payments += ((part, date),) + oral_date = endofmonth(tomorrow.year, 8) + elif self.payment_schedule == 'once': + payments = ((total, tomorrow), ) + + if not payments: + return + + if oral_price and oral_date: + payments += ((oral_price, oral_date), ) + + self.payment_generated = True + self.save() + + for amount, date in payments: + payment = Payment(student = self, + value = amount, + month = date.month, + scheduled = date, + online_paid = False, + type = self.payment_type) + payment.save() + class Meta(MetaCore): db_table = app_label + '_' + 'student' verbose_name = _('Student') @@ -294,7 +397,11 @@ def update_balance_signal(sender, instance, *args, **kwargs): instance.student.update_balance() +def create_payment_objects(sender, instance, *args, **kwargs): + instance.create_payment_objects() + signals.post_save.connect(update_balance_signal) +signals.post_save.connect(create_payment_objects, sender=Student) signals.post_delete.connect(update_balance_signal) @@ -462,7 +569,7 @@ class Home(models.Model): verbose_name = "Page d'accueil" verbose_name_plural = "Page d'accueil" - def is_for_period(self, period): + def is_for_period(self, period): """ Check if it's available for given period """ diff --git a/teleforma/views/crfpa.py b/teleforma/views/crfpa.py index 62a7bf04..aa1a5003 100644 --- a/teleforma/views/crfpa.py +++ b/teleforma/views/crfpa.py @@ -57,6 +57,7 @@ from django.views.generic.list import ListView from postman.forms import AnonymousWriteForm from postman.views import WriteView as PostmanWriteView from xlwt import Workbook +from django.conf import settings from ..decorators import access_required from ..forms import (CorrectorForm, NewsItemForm, UserForm, WriteForm, @@ -68,8 +69,6 @@ from ..views.core import (PDFTemplateResponseMixin, format_courses, get_courses, get_periods) from ..views.profile import ProfileView -ORAL_OPTION_PRICE = 250 - def get_course_code(obj): if obj: return str(obj.code) @@ -877,7 +876,7 @@ class ReceiptPDFView(PDFTemplateResponseMixin, TemplateView): oral_1 = student.oral_1 and student.oral_1.title != 'Aucune' if oral_1: - substract += ORAL_OPTION_PRICE + substract += settings.ORAL_OPTION_PRICE items.append({ 'label': label, 'unit_price': student.total_fees - substract - student.total_discount, @@ -885,7 +884,7 @@ class ReceiptPDFView(PDFTemplateResponseMixin, TemplateView): 'discount': student.total_discount, }, ) if oral_1: items.append({ 'label': "Option langue", - 'unit_price': ORAL_OPTION_PRICE, + 'unit_price': settings.ORAL_OPTION_PRICE, 'amount': 1, 'discount': 0, }, ) for item in items: