From: Yoan Le Clanche Date: Wed, 6 May 2026 11:13:39 +0000 (+0200) Subject: Article 98 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=372767645ee51988cb6aa1d02ab002499ddab7f1;p=teleforma.git Article 98 --- diff --git a/teleforma/management/commands/test_avis_list.py b/teleforma/management/commands/test_avis_list.py new file mode 100644 index 00000000..f3e86d14 --- /dev/null +++ b/teleforma/management/commands/test_avis_list.py @@ -0,0 +1,26 @@ +import requests +from django.core.management.base import BaseCommand +import datetime + +class Command(BaseCommand): + help = "Fetch product reviews" + + def handle(self, *args, **kwargs): + product_id = "CONF0124" + url = f'http://cl.avis-verifies.com/fr/cache/8/d/a/8da32059-03b0-23c4-1554-4f4b505a8c6a/AWS/PRODUCT_API/REVIEWS/{product_id}.json' + response = requests.get(url) + if response.status_code == 200: + avis_list = response.json() + for avis in avis_list: + print("*"*7) + print(avis) + print("*"*7) + date = avis['publish_date'] + if date == 'Array': + date = avis['review_date'] + if date == 'Array': + continue + avis['publish_date'] = datetime.datetime.strptime( + date, '%Y-%m-%d %H:%M:%S') + else: + self.stderr.write(f"Failed to fetch reviews: {response.status_code}") diff --git a/teleforma/migrations/0026_conference_secondary_subject.py b/teleforma/migrations/0026_conference_secondary_subject.py new file mode 100644 index 00000000..88f63261 --- /dev/null +++ b/teleforma/migrations/0026_conference_secondary_subject.py @@ -0,0 +1,33 @@ +# Generated for secondary_subject split between Seminar and Conference + +from django.db import migrations, models + + +def clear_oral_blanc_on_seminars(apps, schema_editor): + Seminar = apps.get_model('teleforma', 'Seminar') + Seminar.objects.filter(secondary_subject='article_98_1_oral_blanc').update(secondary_subject='') + + +def noop_reverse(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('teleforma', '0025_auto_20251105_1331'), + ] + + operations = [ + migrations.RunPython(clear_oral_blanc_on_seminars, noop_reverse), + migrations.AlterField( + model_name='seminar', + name='secondary_subject', + field=models.CharField(blank=True, choices=[('', 'Aucune'), ('article_98_1', 'Article 98-1')], default='', max_length=50, verbose_name='Matière secondaire'), + ), + migrations.AddField( + model_name='conference', + name='secondary_subject', + field=models.CharField(blank=True, choices=[('', 'Aucune'), ('article_98_1_oral_blanc', 'Article 98-1 - Oral blanc')], default='', max_length=50, verbose_name='Matière secondaire'), + ), + ] diff --git a/teleforma/models/core.py b/teleforma/models/core.py index db517c6d..37af8230 100755 --- a/teleforma/models/core.py +++ b/teleforma/models/core.py @@ -114,6 +114,12 @@ TRUE_FALSE_CHOICES = ( (False, 'Non') ) + +class ConferenceSecondarySubjectChoices(models.TextChoices): + NONE = '', _("Aucune") + ARTICLE_98_1_ORAL_BLANC = 'article_98_1_oral_blanc', _("Article 98-1 - Oral blanc") + + class MetaCore: app_label = app_label @@ -927,10 +933,28 @@ class WebclassMixin(Model): class Conference(Displayable, WebclassMixin, ProductCodeMixin, SuggestionsMixin): + # secondary_subject -> {pack_size, pack_price, extra_unit_price}: + # from pack_size units the price drops to pack_price; each additional + # unit beyond the pack is billed at extra_unit_price. + PACK_PRICING = { + ConferenceSecondarySubjectChoices.ARTICLE_98_1_ORAL_BLANC: { + 'pack_size': 3, + 'pack_price': 490, + 'extra_unit_price': 190, + }, + } + fif_pl = models.BooleanField(_('Eligible FIF-PL'), default=False) new = models.BooleanField(_('Nouveau'), default=False) private = models.BooleanField(_('private'), help_text="Hide in shop", default=False) upcoming = models.BooleanField("A venir (shop)", default=False) + secondary_subject = models.CharField( + _("Matière secondaire"), + max_length=50, + choices=ConferenceSecondarySubjectChoices.choices, + blank=True, + default=ConferenceSecondarySubjectChoices.NONE + ) sub_title = models.CharField(_('sub title'), max_length=1024, blank=True) public_id = models.CharField(_('public id'), max_length=255, blank=True) department = models.ForeignKey('Department', related_name='conference', verbose_name=_('department'), @@ -1002,6 +1026,52 @@ class Conference(Displayable, WebclassMixin, ProductCodeMixin, SuggestionsMixin) def get_absolute_url(self): return reverse('conference-view', kwargs={"pk": self.id}) + @property + def pack_pricing(self): + """The pack pricing rule for this conference, or None.""" + return self.PACK_PRICING.get(self.secondary_subject) + + def get_price_for_quantity(self, quantity, unit_price=None): + """ + Total price for the given quantity, applying any pack rule from + PACK_PRICING. Pass unit_price (e.g. CartItem's locked-in price) + to override self.price. + """ + if unit_price is None: + unit_price = self.price or 0 + rule = self.pack_pricing + if not rule or quantity < rule['pack_size']: + return unit_price * quantity + cast = type(unit_price) + pack_price = cast(rule['pack_price']) + extra_price = cast(rule['extra_unit_price']) + return pack_price + (quantity - rule['pack_size']) * extra_price + + def get_invoice_lines(self, unit_price, quantity, description): + """ + Yield invoice line dicts for this conference at the given quantity, + splitting into a pack line + extras line when a pack rule applies. + """ + rule = self.pack_pricing + if not rule or quantity < rule['pack_size']: + yield {'designation': description, 'VAT': 0, 'qte': quantity, 'PUHT': unit_price} + return + cast = type(unit_price) + yield { + 'designation': "Pack de %d - %s" % (rule['pack_size'], description), + 'VAT': 0, + 'qte': 1, + 'PUHT': cast(rule['pack_price']), + } + extra = quantity - rule['pack_size'] + if extra > 0: + yield { + 'designation': description, + 'VAT': 0, + 'qte': extra, + 'PUHT': cast(rule['extra_unit_price']), + } + def __str__(self): if self.professor: list = [self.title, self.course.title, diff --git a/teleforma/models/pro.py b/teleforma/models/pro.py index 38e06cf4..b8c38871 100644 --- a/teleforma/models/pro.py +++ b/teleforma/models/pro.py @@ -65,10 +65,9 @@ class SeminarType(models.Model): verbose_name = _('Seminar type') -class SecondarySubjectChoices(models.TextChoices): +class SeminarSecondarySubjectChoices(models.TextChoices): NONE = '', _("Aucune") ARTICLE_98_1 = 'article_98_1', _("Article 98-1") - ARTICLE_98_1_ORAL_BLANC = 'article_98_1_oral_blanc', _("Article 98-1 - Oral blanc") class Seminar(ClonableMixin, Displayable, ProductCodeMixin, SuggestionsMixin): @@ -80,9 +79,9 @@ class Seminar(ClonableMixin, Displayable, ProductCodeMixin, SuggestionsMixin): secondary_subject = models.CharField( _("Matière secondaire"), max_length=50, - choices=SecondarySubjectChoices.choices, + choices=SeminarSecondarySubjectChoices.choices, blank=True, - default=SecondarySubjectChoices.NONE + default=SeminarSecondarySubjectChoices.NONE ) private = models.BooleanField(_('private'), default=False) upcoming = models.BooleanField("A venir (shop)", default=False)