Projet

Général

Profil

0008-pricing-rename-rewrite-get_pricing_data-method-67675.patch

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

Télécharger (20,5 ko)

Voir les différences:

Subject: [PATCH 08/14] pricing: rename & rewrite get_pricing_data method
 (#67675)

renamed into get_pricing_data_for_event, to prepare flat_fee_schedule
mode
 lingo/api/serializers.py                     | 13 +++--
 lingo/pricing/forms.py                       |  3 +-
 lingo/pricing/models.py                      | 31 ++++--------
 tests/api/test_pricing.py                    | 51 ++++++++++++++++----
 tests/pricing/manager/test_agenda_pricing.py | 38 ++++++++-------
 tests/pricing/test_models.py                 | 30 +++++-------
 6 files changed, 97 insertions(+), 69 deletions(-)
lingo/api/serializers.py
22 22

  
23 23
from lingo.agendas.chrono import ChronoError, get_events, get_subscriptions
24 24
from lingo.agendas.models import Agenda
25
from lingo.pricing.models import AgendaPricing, PricingError
25
from lingo.pricing.models import AgendaPricing, AgendaPricingNotFound, PricingError
26 26

  
27 27

  
28 28
class CommaSeparatedStringField(serializers.ListField):
......
119 119
        event_slugs = sorted(self.serialized_events.keys())
120 120
        for event_slug in event_slugs:
121 121
            serialized_event = self.serialized_events[event_slug]
122
            start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date()
123
            agenda = self.agendas[serialized_event['agenda']]
122 124
            try:
123
                pricing_data = AgendaPricing.get_pricing_data(
125
                agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
126
                pricing_data = agenda_pricing.get_pricing_data_for_event(
124 127
                    request=request,
125
                    agenda=self.agendas[serialized_event['agenda']],
128
                    agenda=agenda,
126 129
                    event=serialized_event,
127 130
                    subscription=self.event_subscriptions[event_slug],
128 131
                    check_status={
......
139 142
                        'pricing_data': pricing_data,
140 143
                    }
141 144
                )
145
            except AgendaPricingNotFound:
146
                result.append(
147
                    {'event': event_slug, 'error': _('No agenda pricing found for event %s') % event_slug}
148
                )
142 149
            except PricingError as e:
143 150
                result.append({'event': event_slug, 'error': e.details})
144 151

  
lingo/pricing/forms.py
386 386
        if not self.serialized_event or not self.serialized_subscription:
387 387
            return
388 388
        try:
389
            return AgendaPricing.get_pricing_data(
389
            return self.agenda_pricing.get_pricing_data_for_event(
390 390
                request=self.request,
391 391
                agenda=self.agenda,
392
                agenda_pricing=self.agenda_pricing,
393 392
                event=self.serialized_event,
394 393
                subscription=self.serialized_subscription,
395 394
                check_status={
lingo/pricing/models.py
16 16

  
17 17
import copy
18 18
import dataclasses
19
import datetime
20 19
import decimal
21 20
from typing import List
22 21

  
......
400 399

  
401 400
        return created, agenda_pricing
402 401

  
403
    @staticmethod
404
    def get_pricing_data(
405
        request,
406
        agenda,
407
        event,
408
        subscription,
409
        check_status,
410
        booking,
411
        user_external_id,
412
        adult_external_id,
413
        agenda_pricing=None,
402
    def get_pricing_data_for_event(
403
        self, request, agenda, event, subscription, check_status, booking, user_external_id, adult_external_id
414 404
    ):
415
        agenda_pricing = agenda_pricing or AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
405
        # compute pricing for an event
416 406
        data = {
417 407
            'event': event,
418 408
            'subscription': subscription,
419 409
            'booking': booking,
420 410
        }
421
        context = agenda_pricing.get_pricing_context(
411
        context = self.get_pricing_context(
422 412
            request=request,
423 413
            data=data,
424 414
            user_external_id=user_external_id,
425 415
            adult_external_id=adult_external_id,
426 416
        )
427
        pricing, criterias = agenda_pricing.compute_pricing(context=context)
428
        modifier = agenda_pricing.get_booking_modifier(agenda=agenda, check_status=check_status)
429
        return agenda_pricing.aggregate_pricing_data(
417
        pricing, criterias = self.compute_pricing(context=context)
418
        modifier = self.get_booking_modifier(agenda=agenda, check_status=check_status)
419
        return self.aggregate_pricing_data(
430 420
            pricing=pricing, criterias=criterias, context=context, modifier=modifier
431 421
        )
432 422

  
......
446 436
        }
447 437

  
448 438
    @staticmethod
449
    def get_agenda_pricing(agenda, event):
450
        start_datetime = datetime.datetime.fromisoformat(event['start_datetime'])
439
    def get_agenda_pricing(agenda, start_date):
451 440
        try:
452 441
            return agenda.agendapricings.get(
453
                date_start__lte=start_datetime,
454
                date_end__gt=start_datetime,
442
                date_start__lte=start_date,
443
                date_end__gt=start_date,
455 444
            )
456 445
        except (AgendaPricing.DoesNotExist, AgendaPricing.MultipleObjectsReturned):
457 446
            raise AgendaPricingNotFound
tests/api/test_pricing.py
1
import datetime
1 2
from unittest import mock
2 3

  
3 4
import pytest
4 5

  
5 6
from lingo.agendas.chrono import ChronoError
6 7
from lingo.agendas.models import Agenda
7
from lingo.pricing.models import PricingError
8
from lingo.pricing.models import AgendaPricing, Pricing, PricingError
8 9

  
9 10
pytestmark = pytest.mark.django_db
10 11

  
......
145 146

  
146 147
@mock.patch('lingo.api.serializers.get_events')
147 148
@mock.patch('lingo.api.serializers.get_subscriptions')
148
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
149
def test_pricing_compute(mock_pricing_data, mock_subscriptions, mock_events, app, user):
149
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
150
def test_pricing_compute(mock_pricing_data_event, mock_subscriptions, mock_events, app, user):
150 151
    agenda = Agenda.objects.create(label='Agenda')
151 152
    agenda2 = Agenda.objects.create(label='Agenda2')
152 153
    app.authorization = ('Basic', ('john.doe', 'password'))
153 154

  
155
    # no subscription
154 156
    mock_events.return_value = [
155 157
        {'start_datetime': '2021-09-01T12:00:00+02:00', 'agenda': 'agenda', 'slug': 'event-bar-slug'}
156 158
    ]
......
169 171
    assert resp.json['errors']['user_external_id'] == [
170 172
        'No subscription found for event agenda@event-bar-slug'
171 173
    ]
172
    assert mock_pricing_data.call_args_list == []
174
    assert mock_pricing_data_event.call_args_list == []
173 175
    assert mock_subscriptions.call_args_list == [mock.call('agenda', 'user:1')]
174 176

  
177
    # no matching subscription
175 178
    mock_subscriptions.reset_mock()
176 179
    mock_subscriptions.return_value = [
177 180
        {
......
197 200
    assert resp.json['errors']['user_external_id'] == [
198 201
        'No subscription found for event agenda@event-bar-slug'
199 202
    ]
200
    assert mock_pricing_data.call_args_list == []
203
    assert mock_pricing_data_event.call_args_list == []
201 204
    assert mock_subscriptions.call_args_list == [mock.call('agenda', 'user:1')]
202 205

  
206
    # no matching subscription
203 207
    mock_events.return_value = [
204 208
        {'start_datetime': '2021-09-02T12:00:00+02:00', 'agenda': 'agenda', 'slug': 'event-bar-slug'},
205 209
        {'start_datetime': '2021-09-01T12:00:00+02:00', 'agenda': 'agenda2', 'slug': 'event-baz-slug'},
......
229 233
    assert resp.json['errors']['user_external_id'] == [
230 234
        'No subscription found for event agenda2@event-baz-slug'
231 235
    ]
232
    assert mock_pricing_data.call_args_list == []
236
    assert mock_pricing_data_event.call_args_list == []
233 237
    assert mock_subscriptions.call_args_list == [
234 238
        mock.call('agenda', 'user:1'),
235 239
        mock.call('agenda2', 'user:1'),
236 240
    ]
237 241

  
242
    # no agenda_pricing found
238 243
    mock_subscriptions.return_value = [
239 244
        {
240 245
            'date_start': '2021-08-01',
......
249 254
            'date_end': '2021-09-03',
250 255
        },
251 256
    ]
252
    mock_pricing_data.side_effect = [
257
    mock_pricing_data_event.side_effect = [
253 258
        {'foo': 'baz'},
254 259
        {'foo': 'bar'},
255 260
    ]
......
261 266
            'adult_external_id': 'adult:1',
262 267
        },
263 268
    )
269
    assert resp.json['data'] == [
270
        {
271
            'event': 'agenda2@event-baz-slug',
272
            'error': 'No agenda pricing found for event agenda2@event-baz-slug',
273
        },
274
        {
275
            'event': 'agenda@event-bar-slug',
276
            'error': 'No agenda pricing found for event agenda@event-bar-slug',
277
        },
278
    ]
279

  
280
    # ok
281
    pricing = Pricing.objects.create(label='Foo bar')
282
    agenda_pricing = AgendaPricing.objects.create(
283
        pricing=pricing,
284
        date_start=datetime.date(year=2021, month=9, day=1),
285
        date_end=datetime.date(year=2021, month=10, day=1),
286
    )
287
    agenda_pricing.agendas.add(agenda, agenda2)
288
    resp = app.get(
289
        '/api/pricing/compute/',
290
        params={
291
            'slots': 'agenda@event-bar-slug, agenda2@event-baz-slug',
292
            'user_external_id': 'user:1',
293
            'adult_external_id': 'adult:1',
294
        },
295
    )
264 296
    assert resp.json['data'] == [
265 297
        {'event': 'agenda2@event-baz-slug', 'pricing_data': {'foo': 'baz'}},
266 298
        {'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}},
267 299
    ]
268
    assert mock_pricing_data.call_args_list == [
300
    assert mock_pricing_data_event.call_args_list == [
269 301
        mock.call(
270 302
            request=mock.ANY,
271 303
            agenda=agenda2,
......
296 328
        ),
297 329
    ]
298 330

  
299
    mock_pricing_data.side_effect = [
331
    # get_pricing_data_for_event with error
332
    mock_pricing_data_event.side_effect = [
300 333
        PricingError(details={'foo': 'error'}),
301 334
        {'foo': 'bar'},
302 335
    ]
tests/pricing/manager/test_agenda_pricing.py
821 821

  
822 822
@mock.patch('lingo.pricing.forms.get_event')
823 823
@mock.patch('lingo.pricing.forms.get_subscriptions')
824
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
825
def test_detail_agenda_pricing_test_tool(mock_pricing_data, mock_subscriptions, mock_event, app, admin_user):
824
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
825
def test_detail_agenda_pricing_test_tool(
826
    mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
827
):
826 828
    agenda = Agenda.objects.create(label='Foo bar')
827 829
    pricing = Pricing.objects.create(label='Foo bar')
828 830
    agenda_pricing = AgendaPricing.objects.create(
......
847 849
    assert resp.context['test_tool_form'].errors['event_slug'] == [
848 850
        'This event takes place outside the period covered by this pricing'
849 851
    ]
850
    assert mock_pricing_data.call_args_list == []
852
    assert mock_pricing_data_event.call_args_list == []
851 853
    mock_event.return_value = {'start_datetime': '2021-10-01T12:00:00+02:00'}
852 854
    resp = resp.form.submit().follow()
853 855
    assert 'Computed pricing data' not in resp
......
870 872
    mock_event.reset_mock()
871 873
    resp.form['event_slug'] = 'foo-bar@foo'
872 874
    resp = resp.form.submit().follow()
873
    assert mock_pricing_data.call_args_list == []
875
    assert mock_pricing_data_event.call_args_list == []
874 876
    assert 'Computed pricing data' not in resp
875 877
    assert resp.context['test_tool_form'].errors['user_external_id'] == [
876 878
        'No subscription found for this event'
......
906 908
            'date_end': '2021-09-03',
907 909
        },
908 910
    ]
909
    mock_pricing_data.return_value = {'foo': 'bar', 'pricing': Decimal('42')}
911
    mock_pricing_data_event.return_value = {'foo': 'bar', 'pricing': Decimal('42')}
910 912
    resp = resp.form.submit().follow()
911 913
    assert 'Computed pricing data' in resp
912
    assert mock_pricing_data.call_args_list == [
914
    assert mock_pricing_data_event.call_args_list == [
913 915
        mock.call(
914 916
            request=mock.ANY,
915 917
            agenda=agenda,
916
            agenda_pricing=agenda_pricing,
917 918
            event={'start_datetime': '2021-09-01T12:00:00+02:00'},
918 919
            subscription={'date_start': '2021-09-01', 'date_end': '2021-09-02'},
919 920
            check_status={'status': 'presence', 'check_type': None},
......
925 926
    assert '<p>Pricing: 42.00</p>' in resp
926 927
    assert '<pre>{&#39;foo&#39;: &#39;bar&#39;, &#39;pricing&#39;: Decimal(&#39;42&#39;)}</pre>' in resp
927 928

  
928
    mock_pricing_data.side_effect = PricingError(details={'foo': 'error'})
929
    mock_pricing_data_event.side_effect = PricingError(details={'foo': 'error'})
929 930
    resp = resp.form.submit().follow()
930 931
    assert 'Computed pricing data' in resp
931 932
    assert '<pre>{&#39;error&#39;: {&#39;foo&#39;: &#39;error&#39;}}</pre>' in resp
......
982 983

  
983 984
@mock.patch('lingo.pricing.forms.get_event')
984 985
@mock.patch('lingo.pricing.forms.get_subscriptions')
985
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data')
986
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event')
986 987
def test_detail_agenda_pricing_test_tool_booking_status(
987
    mock_pricing_data, mock_subscriptions, mock_event, app, admin_user
988
    mock_pricing_data_event, mock_subscriptions, mock_event, app, admin_user
988 989
):
989 990
    agenda = Agenda.objects.create(label='Foo bar')
990 991
    pricing = Pricing.objects.create(label='Foo bar')
......
1032 1033
        ('absence', False, 'Absence'),
1033 1034
        ('absence::foo-absence-reason', False, 'Absence (Foo absence reason)'),
1034 1035
    ]
1035
    assert mock_pricing_data.call_args_list[0][1]['check_status'] == {
1036
    assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
1036 1037
        'check_type': None,
1037 1038
        'status': 'presence',
1038 1039
    }
1039 1040

  
1040
    mock_pricing_data.reset_mock()
1041
    mock_pricing_data_event.reset_mock()
1041 1042
    resp.form['booking_status'] = 'presence::foo-presence-reason'
1042 1043
    resp = resp.form.submit().follow()
1043
    assert mock_pricing_data.call_args_list[0][1]['check_status'] == {
1044
    assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
1044 1045
        'check_type': 'foo-presence-reason',
1045 1046
        'status': 'presence',
1046 1047
    }
1047 1048

  
1048
    mock_pricing_data.reset_mock()
1049
    mock_pricing_data_event.reset_mock()
1049 1050
    resp.form['booking_status'] = 'absence'
1050 1051
    resp = resp.form.submit().follow()
1051
    assert mock_pricing_data.call_args_list[0][1]['check_status'] == {'check_type': None, 'status': 'absence'}
1052
    assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
1053
        'check_type': None,
1054
        'status': 'absence',
1055
    }
1052 1056

  
1053
    mock_pricing_data.reset_mock()
1057
    mock_pricing_data_event.reset_mock()
1054 1058
    resp.form['booking_status'] = 'absence::foo-absence-reason'
1055 1059
    resp = resp.form.submit().follow()
1056
    assert mock_pricing_data.call_args_list[0][1]['check_status'] == {
1060
    assert mock_pricing_data_event.call_args_list[0][1]['check_status'] == {
1057 1061
        'check_type': 'foo-absence-reason',
1058 1062
        'status': 'absence',
1059 1063
    }
tests/pricing/test_models.py
283 283
def test_get_agenda_pricing():
284 284
    agenda = Agenda.objects.create(label='Foo bar')
285 285
    pricing = Pricing.objects.create(label='Foo bar')
286
    event = {
287
        'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat(),
288
    }
286
    start_date = datetime.datetime(2021, 9, 15)
289 287

  
290 288
    # not found
291 289
    with pytest.raises(AgendaPricingNotFound):
292
        AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
290
        AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
293 291

  
294 292
    # ok
295 293
    agenda_pricing = AgendaPricing.objects.create(
......
298 296
        date_end=datetime.date(year=2021, month=10, day=1),
299 297
    )
300 298
    agenda_pricing.agendas.add(agenda)
301
    assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
299
    assert AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date) == agenda_pricing
302 300

  
303 301
    # more than one matching
304 302
    agenda_pricing = AgendaPricing.objects.create(
......
308 306
    )
309 307
    agenda_pricing.agendas.add(agenda)
310 308
    with pytest.raises(AgendaPricingNotFound):
311
        AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
309
        AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
312 310

  
313 311

  
314 312
@pytest.mark.parametrize(
315 313
    'event_date, found',
316 314
    [
317 315
        # just before first day
318
        ((2021, 8, 31, 12, 00), False),
316
        ((2021, 8, 31), False),
319 317
        # first day
320
        ((2021, 9, 1, 12, 00), True),
318
        ((2021, 9, 1), True),
321 319
        # last day
322
        ((2021, 9, 30, 12, 00), True),
320
        ((2021, 9, 30), True),
323 321
        # just after last day
324
        ((2021, 10, 1, 12, 00), False),
322
        ((2021, 10, 1), False),
325 323
    ],
326 324
)
327 325
def test_get_agenda_pricing_event_date(event_date, found):
328 326
    agenda = Agenda.objects.create(label='Foo bar')
329 327
    pricing = Pricing.objects.create(label='Foo bar')
330
    event = {
331
        'start_datetime': make_aware(datetime.datetime(*event_date)).isoformat(),
332
    }
333 328
    agenda_pricing = AgendaPricing.objects.create(
334 329
        pricing=pricing,
335 330
        date_start=datetime.date(year=2021, month=9, day=1),
336 331
        date_end=datetime.date(year=2021, month=10, day=1),
337 332
    )
338 333
    agenda_pricing.agendas.add(agenda)
334
    start_date = datetime.date(*event_date)
339 335
    if found:
340
        assert AgendaPricing.get_agenda_pricing(agenda=agenda, event=event) == agenda_pricing
336
        assert AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date) == agenda_pricing
341 337
    else:
342 338
        with pytest.raises(AgendaPricingNotFound):
343
            AgendaPricing.get_agenda_pricing(agenda=agenda, event=event)
339
            AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date)
344 340

  
345 341

  
346 342
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
......
931 927
    }
932 928

  
933 929

  
934
def test_get_pricing_data(context):
930
def test_get_pricing_data_for_event(context):
935 931
    agenda = Agenda.objects.create(label='Foo bar')
936 932
    category = CriteriaCategory.objects.create(label='Foo', slug='foo')
937 933
    criteria = Criteria.objects.create(label='Bar', slug='bar', condition='True', category=category)
......
953 949
        },
954 950
    )
955 951
    agenda_pricing.agendas.add(agenda)
956
    assert AgendaPricing.get_pricing_data(
952
    assert agenda_pricing.get_pricing_data_for_event(
957 953
        request=context['request'],
958 954
        agenda=agenda,
959 955
        event={'start_datetime': make_aware(datetime.datetime(2021, 9, 15, 12, 00)).isoformat()},
960
-