Projet

Général

Profil

0011-pricing-adapt-test-tool-for-flat-fee-schedule-mode-6.patch

Lauréline Guérin, 29 juillet 2022 22:03

Télécharger (20,5 ko)

Voir les différences:

Subject: [PATCH 11/14] pricing: adapt test tool for flat fee schedule mode
 (#67675)

 lingo/pricing/forms.py                        | 115 ++++++---
 lingo/pricing/models.py                       |   3 +
 .../manager_agenda_pricing_detail.html        |   4 +-
 tests/pricing/manager/test_agenda_pricing.py  | 223 +++++++++++++++++-
 4 files changed, 310 insertions(+), 35 deletions(-)
lingo/pricing/forms.py
289 289
class PricingTestToolForm(forms.Form):
290 290
    agenda = forms.ModelChoiceField(label=_('Agenda'), empty_label=None, queryset=Agenda.objects.none())
291 291
    event_slug = forms.CharField(label=_('Event identifier'))
292
    billing_date = forms.ModelChoiceField(
293
        label=_('Billing date'), empty_label=None, queryset=BillingDate.objects.none()
294
    )
292 295
    user_external_id = forms.CharField(label=_('User external identifier'))
293 296
    adult_external_id = forms.CharField(label=_('Adult external identifier'))
294 297
    booking_status = forms.ChoiceField(label=_('Booking status'), choices=[])
......
304 307
        self.check_type_slug = None
305 308
        self.booking_status = None
306 309
        super().__init__(*args, **kwargs)
307
        self.fields['agenda'].queryset = self.agenda_pricing.agendas.all()
310
        if self.agenda_pricing.subscription_required:
311
            self.fields['agenda'].queryset = self.agenda_pricing.agendas.all()
312
        else:
313
            del self.fields['agenda']
314
        if self.agenda_pricing.flat_fee_schedule:
315
            del self.fields['event_slug']
316
            del self.fields['booking_status']
317
            self.init_billing_date()
318
        else:
319
            del self.fields['billing_date']
320
            self.init_booking_status()
321

  
322
    def init_agenda(self, agenda_id):
323
        try:
324
            self.agenda = self.agenda_pricing.agendas.get(pk=agenda_id)
325
        except Agenda.DoesNotExist:
326
            pass
327

  
328
    def init_booking_status(self):
308 329
        presence_check_types = (
309 330
            self.agenda.check_type_group.check_types.presences()
310 331
            if self.agenda and self.agenda.check_type_group
......
327 348
        ]
328 349
        self.fields['booking_status'].choices = status_choices
329 350

  
330
    def init_agenda(self, agenda_id):
331
        try:
332
            self.agenda = self.agenda_pricing.agendas.get(pk=agenda_id)
333
        except Agenda.DoesNotExist:
334
            pass
351
    def init_billing_date(self):
352
        billing_dates = self.agenda_pricing.billingdates.order_by('date_start')
353
        if not billing_dates:
354
            del self.fields['billing_date']
355
            return
356
        self.fields['billing_date'].queryset = billing_dates
335 357

  
336 358
    def clean_event_slug(self):
337 359
        event_slug = self.cleaned_data['event_slug']
......
356 378
            self.booking_status, self.check_type_slug = original_booking_status.split('::')
357 379
        return original_booking_status
358 380

  
359
    def get_subscription(self, user_external_id):
360
        start_date = datetime.datetime.fromisoformat(self.serialized_event['start_datetime']).date()
361
        end_date = start_date + datetime.timedelta(days=1)
381
    def get_subscription(self, user_external_id, start_date, end_date):
362 382
        try:
363 383
            subscriptions = get_subscriptions(self.agenda.slug, user_external_id)
364 384
        except ChronoError as e:
......
372 392
            if sub_end_date <= start_date:
373 393
                continue
374 394
            return subscription
375
        self.add_error('user_external_id', _('No subscription found for this event'))
395
        error_message = _('No subscription found for this event')
396
        if self.agenda_pricing.flat_fee_schedule:
397
            error_message = _('No subscription found for this period')
398
        self.add_error('user_external_id', error_message)
376 399

  
377 400
    def clean(self):
378 401
        super().clean()
379
        if self.cleaned_data.get('user_external_id') and self.serialized_event:
380
            user_external_id = self.cleaned_data['user_external_id']
381
            self.serialized_subscription = self.get_subscription(user_external_id)
402

  
403
        user_external_id = self.cleaned_data.get('user_external_id')
404
        start_date = None
405
        end_date = None
406
        if self.agenda_pricing.flat_fee_schedule and self.agenda_pricing.subscription_required:
407
            if self.cleaned_data.get('billing_date'):
408
                start_date = self.cleaned_data['billing_date'].date_start
409
                next_billing_date = (
410
                    self.fields['billing_date'].queryset.filter(date_start__gt=start_date).first()
411
                )
412
                end_date = next_billing_date.date_start if next_billing_date else self.agenda_pricing.date_end
413
            else:
414
                start_date = self.agenda_pricing.date_start
415
                end_date = self.agenda_pricing.date_end
416
        elif self.serialized_event:
417
            start_date = datetime.datetime.fromisoformat(self.serialized_event['start_datetime']).date()
418
            end_date = start_date + datetime.timedelta(days=1)
419
        if user_external_id and start_date and end_date:
420
            self.serialized_subscription = self.get_subscription(user_external_id, start_date, end_date)
382 421

  
383 422
    def compute(self):
384
        if not self.serialized_event or not self.serialized_subscription:
385
            return
386 423
        try:
387
            return self.agenda_pricing.get_pricing_data_for_event(
388
                request=self.request,
389
                agenda=self.agenda,
390
                event=self.serialized_event,
391
                subscription=self.serialized_subscription,
392
                check_status={
393
                    'status': self.booking_status,
394
                    'check_type': self.check_type_slug,
395
                },
396
                booking={},
397
                user_external_id=self.cleaned_data['user_external_id'],
398
                adult_external_id=self.cleaned_data['adult_external_id'],
399
            )
424
            if self.agenda_pricing.flat_fee_schedule:
425
                return self.compute_for_flat_fee_schedule()
426
            return self.compute_for_event()
400 427
        except PricingError as e:
401
            return {'error': e.details}
428
            return {
429
                'error': type(e),
430
                'error_details': e.details,
431
            }
432

  
433
    def compute_for_flat_fee_schedule(self):
434
        if self.agenda_pricing.subscription_required and not self.serialized_subscription:
435
            return
436
        return self.agenda_pricing.get_pricing_data(
437
            request=self.request,
438
            subscription=self.serialized_subscription,
439
            user_external_id=self.cleaned_data['user_external_id'],
440
            adult_external_id=self.cleaned_data['adult_external_id'],
441
        )
442

  
443
    def compute_for_event(self):
444
        if not self.serialized_event or not self.serialized_subscription:
445
            return
446
        return self.agenda_pricing.get_pricing_data_for_event(
447
            request=self.request,
448
            agenda=self.agenda,
449
            event=self.serialized_event,
450
            subscription=self.serialized_subscription,
451
            check_status={
452
                'status': self.booking_status,
453
                'check_type': self.check_type_slug,
454
            },
455
            booking={},
456
            user_external_id=self.cleaned_data['user_external_id'],
457
            adult_external_id=self.cleaned_data['adult_external_id'],
458
        )
402 459

  
403 460

  
404 461
class NewCheckTypeForm(forms.ModelForm):
lingo/pricing/models.py
694 694
    date_start = models.DateField(_('Billing start date'))
695 695
    label = models.CharField(_('Label'), max_length=150)
696 696

  
697
    def __str__(self):
698
        return '%s (%s)' % (self.date_start.strftime('%d/%m/%Y'), self.label)
699

  
697 700
    def export_json(self):
698 701
        return {
699 702
            'date_start': self.date_start.strftime('%Y-%m-%d'),
lingo/pricing/templates/lingo/pricing/manager_agenda_pricing_detail.html
74 74
    <div aria-labelledby="tab-debug" hidden id="panel-debug" role="tabpanel" tabindex="0">
75 75
      <form method="get" enctype="multipart/form-data" action="{% url 'lingo-manager-agenda-pricing-test-tool' object.pk %}">
76 76
        {{ test_tool_form.as_p }}
77
        {% if not object.flat_fee_schedule %}
77 78
        <script>
78 79
          $(function() {
79 80
            var presences = {};
......
109 110
            });
110 111
          });
111 112
        </script>
113
        {% endif %}
112 114
        <div class="buttons">
113 115
          <button class="submit-button">{% trans "Compute" %}</button>
114 116
        </div>
......
133 135
        {% for billing_date in billing_dates %}
134 136
        <li>
135 137
          <a rel="popup" href="{% url 'lingo-manager-agenda-pricing-billing-date-edit' object.pk billing_date.pk %}">
136
            {{ billing_date.date_start|date:'d/m/Y' }} ({{ billing_date.label }})
138
            {{ billing_date }}
137 139
          </a>
138 140
          <a class="delete" rel="popup" href="{% url 'lingo-manager-agenda-pricing-billing-date-delete' object.pk billing_date.pk %}">{% trans "remove"%}</a>
139 141
        </li>
tests/pricing/manager/test_agenda_pricing.py
822 822
@mock.patch('lingo.pricing.forms.get_event')
823 823
@mock.patch('lingo.pricing.forms.get_subscriptions')
824 824
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
825
def test_detail_agenda_pricing_test_tool(
825
def test_detail_agenda_pricing_test_tool_for_event(
826 826
    mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
827 827
):
828 828
    agenda = Agenda.objects.create(label='Foo bar')
......
836 836

  
837 837
    app = login(app)
838 838
    resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
839
    assert 'billing_date' not in resp.context['test_tool_form'].fields
839 840
    assert 'Computed pricing data' not in resp
840 841

  
841 842
    # check event date
......
921 922
    mock_pricing_data_event.side_effect = PricingError(details={'foo': 'error'})
922 923
    resp = resp.form.submit().follow()
923 924
    assert 'Computed pricing data' in resp
924
    assert '<pre>{&#39;error&#39;: {&#39;foo&#39;: &#39;error&#39;}}</pre>' in resp
925
    assert '&#39;error&#39;: &lt;class &#39;lingo.pricing.models.PricingError&#39;&gt;' in resp
926
    assert '&#39;error_details&#39;: {&#39;foo&#39;: &#39;error&#39;}' in resp
925 927

  
926 928

  
927 929
@mock.patch('lingo.pricing.forms.get_event')
928 930
@mock.patch('lingo.pricing.forms.get_subscriptions')
929
def test_detail_agenda_pricing_test_tool_event_error(mock_subscriptions, mock_event, app, admin_user):
931
def test_detail_agenda_pricing_test_tool_for_event_event_error(
932
    mock_subscriptions, mock_event, app, admin_user
933
):
930 934
    agenda = Agenda.objects.create(label='Foo bar')
931 935
    pricing = Pricing.objects.create(label='Foo bar')
932 936
    agenda_pricing = AgendaPricing.objects.create(
......
951 955

  
952 956
@mock.patch('lingo.pricing.forms.get_event')
953 957
@mock.patch('lingo.pricing.forms.get_subscriptions')
954
def test_detail_agenda_pricing_test_tool_subscription_error(mock_subscriptions, mock_event, app, admin_user):
958
def test_detail_agenda_pricing_test_tool_for_event_subscription_error(
959
    mock_subscriptions, mock_event, app, admin_user
960
):
955 961
    agenda = Agenda.objects.create(label='Foo bar')
956 962
    pricing = Pricing.objects.create(label='Foo bar')
957 963
    agenda_pricing = AgendaPricing.objects.create(
......
978 984
@mock.patch('lingo.pricing.forms.get_event')
979 985
@mock.patch('lingo.pricing.forms.get_subscriptions')
980 986
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
981
def test_detail_agenda_pricing_test_tool_booking_status(
987
def test_detail_agenda_pricing_test_tool_for_event_booking_status(
982 988
    mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
983 989
):
984 990
    agenda = Agenda.objects.create(label='Foo bar')
......
1058 1064
    }
1059 1065

  
1060 1066

  
1067
@mock.patch('lingo.pricing.forms.get_subscriptions')
1068
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
1069
def test_detail_agenda_pricing_test_tool_for_flat_fee_schedule(
1070
    mock_pricing_data, mock_subscriptions, app, admin_user
1071
):
1072
    agenda = Agenda.objects.create(label='Foo bar')
1073
    pricing = Pricing.objects.create(label='Foo bar')
1074
    agenda_pricing = AgendaPricing.objects.create(
1075
        pricing=pricing,
1076
        date_start=datetime.date(year=2021, month=9, day=1),
1077
        date_end=datetime.date(year=2021, month=10, day=1),
1078
        flat_fee_schedule=True,
1079
        subscription_required=True,
1080
    )
1081
    agenda_pricing.agendas.add(agenda)
1082

  
1083
    app = login(app)
1084
    resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
1085
    assert 'event_slug' not in resp.context['test_tool_form'].fields
1086
    assert 'booking_status' not in resp.context['test_tool_form'].fields
1087
    assert 'billing_date' not in resp.context['test_tool_form'].fields
1088
    assert 'Computed pricing data' not in resp
1089

  
1090
    # check subscriptions dates
1091
    mock_subscriptions.return_value = []
1092
    resp.form['agenda'] = agenda.pk
1093
    resp.form['user_external_id'] = 'user:1'
1094
    resp.form['adult_external_id'] = 'adult:1'
1095
    resp = resp.form.submit().follow()
1096
    assert mock_pricing_data.call_args_list == []
1097
    assert 'Computed pricing data' not in resp
1098
    assert resp.context['test_tool_form'].errors['user_external_id'] == [
1099
        'No subscription found for this period'
1100
    ]
1101

  
1102
    mock_subscriptions.return_value = [
1103
        {
1104
            'date_start': '2021-08-01',
1105
            'date_end': '2021-09-01',
1106
        },
1107
        {
1108
            'date_start': '2021-10-01',
1109
            'date_end': '2021-11-01',
1110
        },
1111
    ]
1112
    resp = resp.form.submit().follow()
1113
    assert 'Computed pricing data' not in resp
1114
    assert resp.context['test_tool_form'].errors['user_external_id'] == [
1115
        'No subscription found for this period'
1116
    ]
1117

  
1118
    mock_subscriptions.return_value = [
1119
        {
1120
            'date_start': '2021-08-01',
1121
            'date_end': '2021-09-01',
1122
        },
1123
        {
1124
            'date_start': '2021-09-02',
1125
            'date_end': '2021-09-03',
1126
        },
1127
        {
1128
            'date_start': '2021-10-01',
1129
            'date_end': '2021-11-01',
1130
        },
1131
    ]
1132
    mock_pricing_data.return_value = {'foo': 'bar', 'pricing': Decimal('42')}
1133
    resp = resp.form.submit().follow()
1134
    assert 'Computed pricing data' in resp
1135
    assert mock_pricing_data.call_args_list == [
1136
        mock.call(
1137
            request=mock.ANY,
1138
            subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'},
1139
            user_external_id='user:1',
1140
            adult_external_id='adult:1',
1141
        )
1142
    ]
1143
    assert '<p>Pricing: 42.00</p>' in resp
1144
    assert '<pre>{&#39;foo&#39;: &#39;bar&#39;, &#39;pricing&#39;: Decimal(&#39;42&#39;)}</pre>' in resp
1145

  
1146
    mock_pricing_data.side_effect = PricingError(details={'foo': 'error'})
1147
    resp = resp.form.submit().follow()
1148
    assert 'Computed pricing data' in resp
1149
    assert '&#39;error&#39;: &lt;class &#39;lingo.pricing.models.PricingError&#39;&gt;' in resp
1150
    assert '&#39;error_details&#39;: {&#39;foo&#39;: &#39;error&#39;}' in resp
1151

  
1152
    billing_date1 = agenda_pricing.billingdates.create(
1153
        date_start=datetime.date(2021, 9, 1),
1154
        label='Foo 1',
1155
    )
1156
    billing_date2 = agenda_pricing.billingdates.create(
1157
        date_start=datetime.date(2021, 9, 15),
1158
        label='Foo 2',
1159
    )
1160

  
1161
    # check with billing dates
1162
    mock_pricing_data.reset_mock()
1163
    resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
1164
    resp.form['agenda'] = agenda.pk
1165
    resp.form['billing_date'] = billing_date1.pk
1166
    resp.form['user_external_id'] = 'user:1'
1167
    resp.form['adult_external_id'] = 'adult:1'
1168
    resp = resp.form.submit().follow()
1169
    assert mock_pricing_data.call_args_list == [
1170
        mock.call(
1171
            request=mock.ANY,
1172
            subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'},
1173
            user_external_id='user:1',
1174
            adult_external_id='adult:1',
1175
        )
1176
    ]
1177

  
1178
    mock_pricing_data.reset_mock()
1179
    resp.form['billing_date'] = billing_date2.pk
1180
    resp = resp.form.submit().follow()
1181
    assert mock_pricing_data.call_args_list == []
1182

  
1183
    mock_subscriptions.return_value = [
1184
        {
1185
            'date_start': '2021-09-01',
1186
            'date_end': '2021-09-15',
1187
        },
1188
    ]
1189
    mock_pricing_data.reset_mock()
1190
    resp = resp.form.submit().follow()
1191
    assert mock_pricing_data.call_args_list == []
1192

  
1193
    mock_subscriptions.return_value = [
1194
        {
1195
            'date_start': '2021-09-15',
1196
            'date_end': '2021-09-16',
1197
        },
1198
    ]
1199
    mock_pricing_data.reset_mock()
1200
    resp = resp.form.submit().follow()
1201
    assert mock_pricing_data.call_args_list == [
1202
        mock.call(
1203
            request=mock.ANY,
1204
            subscription={'date_start': '2021-09-15', 'date_end': '2021-09-16'},
1205
            user_external_id='user:1',
1206
            adult_external_id='adult:1',
1207
        )
1208
    ]
1209

  
1210
    mock_subscriptions.return_value = [
1211
        {
1212
            'date_start': '2021-09-30',
1213
            'date_end': '2021-10-01',
1214
        },
1215
    ]
1216
    mock_pricing_data.reset_mock()
1217
    resp = resp.form.submit().follow()
1218
    assert mock_pricing_data.call_args_list == [
1219
        mock.call(
1220
            request=mock.ANY,
1221
            subscription={'date_start': '2021-09-30', 'date_end': '2021-10-01'},
1222
            user_external_id='user:1',
1223
            adult_external_id='adult:1',
1224
        )
1225
    ]
1226

  
1227
    # check with subscription_required False
1228
    agenda_pricing.subscription_required = False
1229
    agenda_pricing.save()
1230
    mock_pricing_data.reset_mock()
1231
    mock_subscriptions.reset_mock()
1232
    resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
1233
    assert 'agenda' not in resp.context['test_tool_form'].fields
1234
    resp.form['billing_date'] = billing_date1.pk
1235
    resp.form['user_external_id'] = 'user:1'
1236
    resp.form['adult_external_id'] = 'adult:1'
1237
    resp = resp.form.submit().follow()
1238
    assert mock_subscriptions.call_args_list == []
1239
    assert mock_pricing_data.call_args_list == [
1240
        mock.call(
1241
            request=mock.ANY,
1242
            subscription=None,
1243
            user_external_id='user:1',
1244
            adult_external_id='adult:1',
1245
        )
1246
    ]
1247

  
1248

  
1249
@mock.patch('lingo.pricing.forms.get_subscriptions')
1250
def test_detail_agenda_pricing_test_tool_for_flat_fee_schedule_subscription_error(
1251
    mock_subscriptions, app, admin_user
1252
):
1253
    agenda = Agenda.objects.create(label='Foo bar')
1254
    pricing = Pricing.objects.create(label='Foo bar')
1255
    agenda_pricing = AgendaPricing.objects.create(
1256
        pricing=pricing,
1257
        date_start=datetime.date(year=2021, month=9, day=1),
1258
        date_end=datetime.date(year=2021, month=10, day=1),
1259
        flat_fee_schedule=True,
1260
    )
1261
    agenda_pricing.agendas.add(agenda)
1262

  
1263
    mock_subscriptions.side_effect = ChronoError('foo bar')
1264

  
1265
    app = login(app)
1266
    resp = app.get('/manage/pricing/agenda-pricing/%s/' % agenda_pricing.pk)
1267
    resp.form['agenda'] = agenda.pk
1268
    resp.form['user_external_id'] = 'user:1'
1269
    resp.form['adult_external_id'] = 'adult:1'
1270
    resp = resp.form.submit().follow()
1271
    assert resp.context['test_tool_form'].errors['user_external_id'] == ['foo bar']
1272

  
1273

  
1061 1274
def test_edit_agenda_pricing_matrix_3_categories(app, admin_user):
1062 1275
    category1 = CriteriaCategory.objects.create(label='Cat 1')
1063 1276
    criteria11 = Criteria.objects.create(label='Crit 1-1', slug='crit-1-1', category=category1, order=1)
1064
-