From c24aa2beffe3af31d1772ec17eea532138f74d70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 26 Jul 2022 10:45:05 +0200
Subject: [PATCH 05/14] pricing: flat fee schedule & overlapping (#67675)

---
 lingo/pricing/forms.py                        |  39 ++++-
 .../lingo/pricing/manager_agenda_detail.html  |  18 +++
 lingo/pricing/views.py                        |   7 +-
 tests/pricing/manager/test_agenda_pricing.py  | 151 +++++++++++++++++-
 4 files changed, 212 insertions(+), 3 deletions(-)

diff --git a/lingo/pricing/forms.py b/lingo/pricing/forms.py
index 4d7caab..0d6823c 100644
--- a/lingo/pricing/forms.py
+++ b/lingo/pricing/forms.py
@@ -184,6 +184,41 @@ class AgendaPricingForm(NewAgendaPricingForm):
                 )
         return subscription_required
 
+    def clean(self):
+        cleaned_data = super().clean()
+        if (
+            'date_start' in cleaned_data
+            and 'date_end' in cleaned_data
+            and 'flat_fee_schedule' in cleaned_data
+        ):
+            old_date_start = self.instance.date_start
+            old_date_end = self.instance.date_end
+            old_flat_fee_schedule = self.instance.flat_fee_schedule
+            new_date_start = cleaned_data['date_start']
+            new_date_end = cleaned_data['date_end']
+            new_flat_fee_schedule = cleaned_data['flat_fee_schedule']
+            if (
+                old_date_start != new_date_start
+                or old_date_end != new_date_end
+                or old_flat_fee_schedule != new_flat_fee_schedule
+            ):
+                overlapping_qs = (
+                    AgendaPricing.objects.filter(flat_fee_schedule=new_flat_fee_schedule)
+                    .exclude(pk=self.instance.pk)
+                    .extra(
+                        where=["(date_start, date_end) OVERLAPS (%s, %s)"],
+                        params=[new_date_start, new_date_end],
+                    )
+                )
+                for agenda in self.instance.agendas.all():
+                    if overlapping_qs.filter(agendas=agenda).exists():
+                        self.add_error(
+                            None,
+                            _('Agenda "%s" has already a pricing overlapping this period.') % agenda.label,
+                        )
+
+        return cleaned_data
+
 
 class AgendaPricingAgendaAddForm(forms.Form):
     agenda = forms.ModelChoiceField(label=_('Agenda to add'), queryset=Agenda.objects.none(), required=True)
@@ -195,7 +230,9 @@ class AgendaPricingAgendaAddForm(forms.Form):
 
     def clean_agenda(self):
         agenda = self.cleaned_data['agenda']
-        overlapping_qs = AgendaPricing.objects.filter(agendas=agenda).extra(
+        overlapping_qs = AgendaPricing.objects.filter(
+            flat_fee_schedule=self.agenda_pricing.flat_fee_schedule, agendas=agenda
+        ).extra(
             where=["(date_start, date_end) OVERLAPS (%s, %s)"],
             params=[self.agenda_pricing.date_start, self.agenda_pricing.date_end],
         )
diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
index 662fb4c..af08741 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
@@ -26,6 +26,7 @@
   <div class="pk-tabs--container">
 
     <div aria-labelledby="tab-pricings" id="panel-pricings" role="tabpanel" tabindex="0">
+      <h3>{% trans "Pricings" context 'agenda pricing' %}</h3>
       {% if agenda_pricings %}
       <ul class="objects-list single-links">
       {% for agenda_pricing in agenda_pricings %}
@@ -42,6 +43,23 @@
         {% endblocktrans %}
       </div>
       {% endif %}
+      <h3>{% trans "Flat fee schedule pricings" %}</h3>
+      {% if agenda_pricings_flat %}
+      <ul class="objects-list single-links">
+      {% for agenda_pricing in agenda_pricings_flat %}
+        <li>
+          <a href="{% url 'lingo-manager-agenda-pricing-detail' agenda_pricing.pk %}">{{ agenda_pricing.pricing }} ({{ agenda_pricing.date_start|date:'d/m/Y' }} - {{ agenda_pricing.date_end|date:'d/m/Y' }})</a>
+          <a href="{% url 'lingo-manager-pricing-detail' agenda_pricing.pricing.pk %}" class="link-action-icon link">{% trans "see" %}</a>
+        </li>
+      {% endfor %}
+      </ul>
+      {% else %}
+      <div class="big-msg-info">
+        {% blocktrans %}
+        This agenda doesn't have any flat fee schedule pricing defined yet.
+        {% endblocktrans %}
+      </div>
+      {% endif %}
     </div>
 
     <div aria-labelledby="tab-check" hidden="" id="panel-check" role="tabpanel" tabindex="0">
diff --git a/lingo/pricing/views.py b/lingo/pricing/views.py
index eef24ac..c72a219 100644
--- a/lingo/pricing/views.py
+++ b/lingo/pricing/views.py
@@ -683,7 +683,12 @@ class AgendaDetailView(AgendaMixin, DetailView):
 
     def get_context_data(self, **kwargs):
         kwargs['agenda_pricings'] = (
-            AgendaPricing.objects.filter(agendas=self.agenda)
+            AgendaPricing.objects.filter(agendas=self.agenda, flat_fee_schedule=False)
+            .select_related('pricing')
+            .order_by('date_start', 'date_end')
+        )
+        kwargs['agenda_pricings_flat'] = (
+            AgendaPricing.objects.filter(agendas=self.agenda, flat_fee_schedule=True)
             .select_related('pricing')
             .order_by('date_start', 'date_end')
         )
diff --git a/tests/pricing/manager/test_agenda_pricing.py b/tests/pricing/manager/test_agenda_pricing.py
index 87d178d..b48e73f 100644
--- a/tests/pricing/manager/test_agenda_pricing.py
+++ b/tests/pricing/manager/test_agenda_pricing.py
@@ -125,6 +125,144 @@ def test_edit_agenda_pricing(app, admin_user):
     assert resp.location.endswith('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
 
 
+def test_edit_agenda_pricing_overlapping_flat_fee_schedule(app, admin_user):
+    agenda1 = Agenda.objects.create(label='Foo bar 1')
+    agenda2 = Agenda.objects.create(label='Foo bar 2')
+    pricing = Pricing.objects.create(label='Model')
+    agenda_pricing1 = AgendaPricing.objects.create(
+        label='Foo Bar',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2022, month=9, day=1),
+    )
+    agenda_pricing1.agendas.add(agenda1, agenda2)
+    agenda_pricing2 = AgendaPricing.objects.create(
+        label='Foo Bar',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2022, month=9, day=1),
+    )
+    agenda_pricing2.agendas.add(agenda1, agenda2)
+
+    app = login(app)
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    # ok, flat_fee_schedule has not changed
+    resp.form.submit().follow()
+
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    # ok, flat_fee_schedule is different
+    resp.form['flat_fee_schedule'] = True
+    resp.form.submit().follow()
+
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    resp.form['flat_fee_schedule'] = False
+    resp = resp.form.submit()
+    assert resp.context['form'].non_field_errors() == [
+        'Agenda "Foo bar 1" has already a pricing overlapping this period.',
+        'Agenda "Foo bar 2" has already a pricing overlapping this period.',
+    ]
+    agenda_pricing2.agendas.remove(agenda1)
+    resp = resp.form.submit()
+    assert resp.context['form'].non_field_errors() == [
+        'Agenda "Foo bar 2" has already a pricing overlapping this period.'
+    ]
+    agenda_pricing2.agendas.remove(agenda2)
+    # ok
+    resp.form.submit().follow()
+
+
+def test_edit_agenda_pricing_overlapping_date_start(app, admin_user):
+    agenda1 = Agenda.objects.create(label='Foo bar 1')
+    agenda2 = Agenda.objects.create(label='Foo bar 2')
+    pricing = Pricing.objects.create(label='Model')
+    agenda_pricing1 = AgendaPricing.objects.create(
+        label='Foo Bar',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=10, day=1),
+        date_end=datetime.date(year=2022, month=10, day=1),
+    )
+    agenda_pricing1.agendas.add(agenda1, agenda2)
+    agenda_pricing2 = AgendaPricing.objects.create(
+        label='Foo Bar',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2022, month=9, day=1),
+    )
+    agenda_pricing2.agendas.add(agenda1, agenda2)
+
+    app = login(app)
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    # ok, date_start has not changed
+    resp.form.submit().follow()
+
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    # ok, no overlapping
+    resp.form['date_start'] = '2022-09-01'
+    resp.form.submit().follow()
+
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    resp.form['date_start'] = '2021-10-01'
+    resp = resp.form.submit()
+    assert resp.context['form'].non_field_errors() == [
+        'Agenda "Foo bar 1" has already a pricing overlapping this period.',
+        'Agenda "Foo bar 2" has already a pricing overlapping this period.',
+    ]
+    agenda_pricing2.agendas.remove(agenda1)
+    resp = resp.form.submit()
+    assert resp.context['form'].non_field_errors() == [
+        'Agenda "Foo bar 2" has already a pricing overlapping this period.'
+    ]
+    agenda_pricing2.agendas.remove(agenda2)
+    # ok
+    resp.form.submit().follow()
+
+
+def test_edit_agenda_pricing_overlapping_date_end(app, admin_user):
+    agenda1 = Agenda.objects.create(label='Foo bar 1')
+    agenda2 = Agenda.objects.create(label='Foo bar 2')
+    pricing = Pricing.objects.create(label='Model')
+    agenda_pricing1 = AgendaPricing.objects.create(
+        label='Foo Bar',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=8, day=1),
+        date_end=datetime.date(year=2022, month=8, day=1),
+    )
+    agenda_pricing1.agendas.add(agenda1, agenda2)
+    agenda_pricing2 = AgendaPricing.objects.create(
+        label='Foo Bar',
+        pricing=pricing,
+        date_start=datetime.date(year=2021, month=9, day=1),
+        date_end=datetime.date(year=2022, month=9, day=1),
+    )
+    agenda_pricing2.agendas.add(agenda1, agenda2)
+
+    app = login(app)
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    # ok, date_end has not changed
+    resp.form.submit().follow()
+
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    # ok, no overlapping
+    resp.form['date_end'] = '2021-09-01'
+    resp.form.submit().follow()
+
+    resp = app.get('/manage/pricing/agenda-pricing/%s/edit/' % agenda_pricing1.pk)
+    resp.form['date_end'] = '2022-08-01'
+    resp = resp.form.submit()
+    assert resp.context['form'].non_field_errors() == [
+        'Agenda "Foo bar 1" has already a pricing overlapping this period.',
+        'Agenda "Foo bar 2" has already a pricing overlapping this period.',
+    ]
+    agenda_pricing2.agendas.remove(agenda1)
+    resp = resp.form.submit()
+    assert resp.context['form'].non_field_errors() == [
+        'Agenda "Foo bar 2" has already a pricing overlapping this period.'
+    ]
+    agenda_pricing2.agendas.remove(agenda2)
+    # ok
+    resp.form.submit().follow()
+
+
 def test_detail_agenda_pricing(app, admin_user):
     pricing = Pricing.objects.create(label='Model')
     agenda_pricing = AgendaPricing.objects.create(
@@ -209,9 +347,20 @@ def test_agenda_pricing_add_agenda(app, admin_user):
         'This agendas has already a pricing overlapping this period.'
     ]
     resp.form['agenda'] = agenda3.pk
-    resp = resp.form.submit().follow()
+    resp.form.submit().follow()
     assert list(agenda_pricing2.agendas.all()) == [agenda3]
 
+    agenda_pricing.flat_fee_schedule = True
+    agenda_pricing.save()
+    resp = app.get('/manage/pricing/agenda-pricing/%s/agenda/add/' % agenda_pricing2.pk)
+    assert list(resp.context['form'].fields['agenda'].queryset) == [
+        agenda1,
+        agenda2,
+    ]
+    resp.form['agenda'] = agenda1.pk
+    resp.form.submit()
+    assert list(agenda_pricing2.agendas.all()) == [agenda1, agenda3]
+
     agenda_pricing.subscription_required = False
     agenda_pricing.save()
     app.get('/manage/pricing/agenda-pricing/%s/agenda/add/' % agenda_pricing.pk, status=404)
-- 
2.35.1

