--- /dev/null
+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}")
--- /dev/null
+# 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'),
+ ),
+ ]
(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
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'),
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,
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):
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)