0014-api-endpoint-to-get-pricing-data-for-flat-fee-schedu.patch
lingo/api/serializers.py | ||
---|---|---|
36 | 36 | |
37 | 37 |
class PricingComputeSerializer(serializers.Serializer): |
38 | 38 |
slots = CommaSeparatedStringField( |
39 |
required=True, child=serializers.CharField(max_length=160, allow_blank=False)
|
|
39 |
required=False, child=serializers.CharField(max_length=160, allow_blank=False)
|
|
40 | 40 |
) |
41 |
agenda = serializers.SlugField(required=False, allow_blank=False, max_length=160) |
|
42 |
agenda_pricing = serializers.SlugField(required=False, allow_blank=False, max_length=160) |
|
43 |
start_date = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d']) |
|
41 | 44 |
user_external_id = serializers.CharField(required=True, max_length=250) |
42 | 45 |
adult_external_id = serializers.CharField(required=True, max_length=250) |
43 | 46 | |
44 |
agenda_slugs = [] |
|
45 |
agendas = {} |
|
46 |
serialized_events = {} |
|
47 |
event_subscriptions = {} |
|
47 |
def __init__(self, *args, **kwargs): |
|
48 |
super().__init__(*args, **kwargs) |
|
49 | ||
50 |
self._agenda_slugs = [] |
|
51 |
self._agendas = {} |
|
52 |
self._serialized_events = {} |
|
53 |
self._event_subscriptions = {} |
|
54 |
self._agenda = None |
|
55 |
self._agenda_subscription = None |
|
56 |
self._agenda_pricing = None |
|
57 |
self._billing_date = None |
|
48 | 58 | |
49 | 59 |
def validate_slots(self, value): |
50 |
self.agendas = {a.slug: a for a in Agenda.objects.all()} |
|
51 |
allowed_agenda_slugs = self.agendas.keys() |
|
60 |
self._agendas = {a.slug: a for a in Agenda.objects.all()}
|
|
61 |
allowed_agenda_slugs = self._agendas.keys()
|
|
52 | 62 |
agenda_slugs = set() |
53 | 63 |
for slot in value: |
54 | 64 |
try: |
... | ... | |
64 | 74 |
if extra_agendas: |
65 | 75 |
extra_agendas = ', '.join(sorted(extra_agendas)) |
66 | 76 |
raise ValidationError(_('Unknown agendas: %s') % extra_agendas) |
67 |
self.agenda_slugs = sorted(agenda_slugs) |
|
77 |
self._agenda_slugs = sorted(agenda_slugs)
|
|
68 | 78 | |
69 | 79 |
try: |
70 | 80 |
serialized_events = get_events(value) |
... | ... | |
73 | 83 |
else: |
74 | 84 |
for serialized_event in serialized_events: |
75 | 85 |
event_slug = '%s@%s' % (serialized_event['agenda'], serialized_event['slug']) |
76 |
self.serialized_events[event_slug] = serialized_event |
|
86 |
self._serialized_events[event_slug] = serialized_event
|
|
77 | 87 | |
78 | 88 |
return value |
79 | 89 | |
80 |
def get_subscriptions(self, user_external_id): |
|
90 |
def _validate_agenda(self, value, start_date): |
|
91 |
try: |
|
92 |
self._agenda = Agenda.objects.get(slug=value) |
|
93 |
try: |
|
94 |
self._agenda_pricing = AgendaPricing.get_agenda_pricing( |
|
95 |
agenda=self._agenda, |
|
96 |
start_date=start_date.date(), |
|
97 |
flat_fee_schedule=True, |
|
98 |
) |
|
99 |
if not self._agenda_pricing.subscription_required: |
|
100 |
self._agenda_pricing = None |
|
101 |
except AgendaPricingNotFound: |
|
102 |
self._agenda_pricing = None |
|
103 |
except Agenda.DoesNotExist: |
|
104 |
raise ValidationError({'agenda': _('Unknown agenda: %s') % value}) |
|
105 |
return self._agenda |
|
106 | ||
107 |
def _validate_agenda_pricing(self, value, start_date): |
|
108 |
try: |
|
109 |
self._agenda_pricing = AgendaPricing.objects.get( |
|
110 |
slug=value, |
|
111 |
flat_fee_schedule=True, |
|
112 |
subscription_required=False, |
|
113 |
date_start__lte=start_date.date(), |
|
114 |
date_end__gt=start_date.date(), |
|
115 |
) |
|
116 |
except AgendaPricing.DoesNotExist: |
|
117 |
raise ValidationError({'agenda_pricing': _('Unknown pricing: %s') % value}) |
|
118 |
return self._agenda_pricing |
|
119 | ||
120 |
def validate(self, attrs): |
|
121 |
super().validate(attrs) |
|
122 |
if 'slots' not in attrs and 'agenda' not in attrs and 'agenda_pricing' not in attrs: |
|
123 |
raise ValidationError(_('Either "slots", "agenda" or "agenda_pricing" parameter is required.')) |
|
124 |
if 'agenda' in attrs: |
|
125 |
# flat_fee_schedule mode + subscription_required True |
|
126 |
if 'start_date' not in attrs: |
|
127 |
raise ValidationError( |
|
128 |
{'start_date': _('This field is required when using "agenda" parameter.')} |
|
129 |
) |
|
130 |
self._validate_agenda(attrs['agenda'], attrs['start_date']) |
|
131 |
if 'agenda_pricing' in attrs: |
|
132 |
# flat_fee_schedule mode + subscription_required False |
|
133 |
if 'start_date' not in attrs: |
|
134 |
raise ValidationError( |
|
135 |
{'start_date': _('This field is required when using "agenda_pricing" parameter.')} |
|
136 |
) |
|
137 |
self._validate_agenda_pricing(attrs['agenda_pricing'], attrs['start_date']) |
|
138 |
if attrs.get('start_date'): |
|
139 |
# flat_fee_schedule mode: get billing_date from start_date param |
|
140 |
self.get_billing_date(attrs['start_date']) |
|
141 |
if attrs.get('user_external_id') and not attrs.get('agenda_pricing'): |
|
142 |
# don't check subscriptions if agenda_pricing in params (== subscription_required is False) |
|
143 |
self.get_subscriptions(attrs['user_external_id'], attrs.get('start_date')) |
|
144 |
return attrs |
|
145 | ||
146 |
def get_billing_date(self, start_date): |
|
147 |
if self._agenda_pricing: |
|
148 |
self._billing_date = ( |
|
149 |
self._agenda_pricing.billingdates.filter(date_start__lte=start_date) |
|
150 |
.order_by('date_start') |
|
151 |
.last() |
|
152 |
) |
|
153 |
if not self._billing_date: |
|
154 |
self._billing_date = self._agenda_pricing.billingdates.order_by('date_start').first() |
|
155 | ||
156 |
def get_subscriptions(self, user_external_id, start_date): |
|
157 |
if self._serialized_events: |
|
158 |
# event mode |
|
159 |
self.get_subscriptions_for_events(user_external_id) |
|
160 |
elif self._agenda and self._agenda_pricing: |
|
161 |
# flat_fee_schedule mode |
|
162 |
self.get_subscriptions_for_agenda(user_external_id, start_date) |
|
163 | ||
164 |
def _get_subscription(self, subscriptions, start_date, end_date): |
|
165 |
# get subscription matching start_date & end_date |
|
166 |
for subscription in subscriptions: |
|
167 |
sub_start_date = datetime.date.fromisoformat(subscription['date_start']) |
|
168 |
sub_end_date = datetime.date.fromisoformat(subscription['date_end']) |
|
169 |
if sub_start_date >= end_date: |
|
170 |
continue |
|
171 |
if sub_end_date <= start_date: |
|
172 |
continue |
|
173 |
return subscription |
|
174 | ||
175 |
def get_subscriptions_for_events(self, user_external_id): |
|
81 | 176 |
agenda_subscriptions = {} |
82 |
for agenda_slug in self.agenda_slugs: |
|
177 |
# get subscription list for each agenda |
|
178 |
for agenda_slug in self._agenda_slugs: |
|
83 | 179 |
try: |
84 | 180 |
agenda_subscriptions[agenda_slug] = get_subscriptions(agenda_slug, user_external_id) |
85 | 181 |
except ChronoError as e: |
86 | 182 |
raise ValidationError({'user_external_id': e}) |
87 | 183 | |
88 |
self.event_subscriptions = {}
|
|
89 |
for event_slug, serialized_event in self.serialized_events.items(): |
|
184 |
# for each event, get a matching subscription
|
|
185 |
for event_slug, serialized_event in self._serialized_events.items():
|
|
90 | 186 |
start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date() |
91 | 187 |
end_date = start_date + datetime.timedelta(days=1) |
92 | 188 |
agenda_slug = serialized_event['agenda'] |
93 |
event_subscription = None |
|
94 |
for subscription in agenda_subscriptions[agenda_slug]: |
|
95 |
sub_start_date = datetime.date.fromisoformat(subscription['date_start']) |
|
96 |
sub_end_date = datetime.date.fromisoformat(subscription['date_end']) |
|
97 |
if sub_start_date >= end_date: |
|
98 |
continue |
|
99 |
if sub_end_date <= start_date: |
|
100 |
continue |
|
101 |
event_subscription = subscription |
|
102 |
break |
|
189 |
event_subscription = self._get_subscription( |
|
190 |
agenda_subscriptions[agenda_slug], start_date, end_date |
|
191 |
) |
|
103 | 192 |
if event_subscription is None: |
104 | 193 |
raise ValidationError( |
105 | 194 |
{'user_external_id': _('No subscription found for event %s') % event_slug} |
106 | 195 |
) |
107 |
self.event_subscriptions[event_slug] = event_subscription |
|
196 |
self._event_subscriptions[event_slug] = event_subscription
|
|
108 | 197 | |
109 |
def validate(self, attrs): |
|
110 |
super().validate(attrs) |
|
111 |
if attrs.get('user_external_id') and self.serialized_events: |
|
112 |
self.get_subscriptions(attrs['user_external_id']) |
|
113 |
return attrs |
|
198 |
def get_subscriptions_for_agenda(self, user_external_id, start_date): |
|
199 |
# get subscription list for this agenda |
|
200 |
try: |
|
201 |
subscriptions = get_subscriptions(self._agenda.slug, user_external_id) |
|
202 |
except ChronoError as e: |
|
203 |
raise ValidationError({'user_external_id': e}) |
|
204 | ||
205 |
# get correct period, from billing_date or agenda_pricing dates |
|
206 |
if not self._agenda_pricing.billingdates.exists(): |
|
207 |
start_date = self._agenda_pricing.date_start |
|
208 |
end_date = self._agenda_pricing.date_end |
|
209 |
else: |
|
210 |
start_date = self._billing_date.date_start |
|
211 |
next_billing_date = self._agenda_pricing.billingdates.filter(date_start__gt=start_date).first() |
|
212 |
end_date = next_billing_date.date_start if next_billing_date else self._agenda_pricing.date_end |
|
213 | ||
214 |
# get a matching subscriptions |
|
215 |
self._agenda_subscription = self._get_subscription(subscriptions, start_date, end_date) |
|
216 |
if not self._agenda_subscription: |
|
217 |
raise ValidationError( |
|
218 |
{'user_external_id': _('No subscription found for agenda %s') % self._agenda.slug} |
|
219 |
) |
|
114 | 220 | |
115 | 221 |
def compute(self, request): |
116 |
if not self.serialized_events or not self.event_subscriptions: |
|
117 |
return |
|
222 |
try: |
|
223 |
if not self.validated_data.get('slots'): |
|
224 |
return self.compute_for_flat_fee_schedule(request) |
|
225 |
return self.compute_for_event(request) |
|
226 |
except PricingError as e: |
|
227 |
return { |
|
228 |
'error': type(e), |
|
229 |
'error_details': e.details, |
|
230 |
} |
|
231 | ||
232 |
def compute_for_event(self, request): |
|
118 | 233 |
result = [] |
119 |
event_slugs = sorted(self.serialized_events.keys()) |
|
234 |
event_slugs = sorted(self._serialized_events.keys())
|
|
120 | 235 |
for event_slug in event_slugs: |
121 |
serialized_event = self.serialized_events[event_slug] |
|
236 |
serialized_event = self._serialized_events[event_slug]
|
|
122 | 237 |
start_date = datetime.datetime.fromisoformat(serialized_event['start_datetime']).date() |
123 |
agenda = self.agendas[serialized_event['agenda']] |
|
238 |
agenda = self._agendas[serialized_event['agenda']]
|
|
124 | 239 |
try: |
125 |
agenda_pricing = AgendaPricing.get_agenda_pricing(agenda=agenda, start_date=start_date) |
|
240 |
agenda_pricing = AgendaPricing.get_agenda_pricing( |
|
241 |
agenda=agenda, start_date=start_date, flat_fee_schedule=False |
|
242 |
) |
|
126 | 243 |
pricing_data = agenda_pricing.get_pricing_data_for_event( |
127 | 244 |
request=request, |
128 | 245 |
agenda=agenda, |
129 | 246 |
event=serialized_event, |
130 |
subscription=self.event_subscriptions[event_slug], |
|
247 |
subscription=self._event_subscriptions[event_slug],
|
|
131 | 248 |
check_status={ |
132 | 249 |
'status': 'presence', |
133 | 250 |
'check_type': None, |
... | ... | |
151 | 268 | |
152 | 269 |
result = sorted(result, key=lambda d: d['event']) |
153 | 270 |
return result |
271 | ||
272 |
def compute_for_flat_fee_schedule(self, request): |
|
273 |
result = {} |
|
274 |
if self._agenda: |
|
275 |
result['agenda'] = self._agenda.slug |
|
276 |
if not self._agenda_pricing: |
|
277 |
result['error'] = _('No agenda pricing found for agenda %s') % self._agenda.slug |
|
278 |
return result |
|
279 |
else: |
|
280 |
result['agenda_pricing'] = self._agenda_pricing.slug |
|
281 | ||
282 |
try: |
|
283 |
pricing_data = self._agenda_pricing.get_pricing_data( |
|
284 |
request=request, |
|
285 |
pricing_date=( |
|
286 |
self._billing_date.date_start if self._billing_date else self._agenda_pricing.date_start |
|
287 |
), |
|
288 |
subscription=self._agenda_subscription, |
|
289 |
user_external_id=self.validated_data['user_external_id'], |
|
290 |
adult_external_id=self.validated_data['adult_external_id'], |
|
291 |
) |
|
292 |
result['pricing_data'] = pricing_data |
|
293 |
return result |
|
294 |
except PricingError as e: |
|
295 |
result['error'] = type(e).__name__ |
|
296 |
result['error_details'] = e.details |
|
297 |
return result |
tests/api/test_pricing.py | ||
---|---|---|
13 | 13 |
def test_pricing_compute_params(app, user): |
14 | 14 |
app.authorization = ('Basic', ('john.doe', 'password')) |
15 | 15 | |
16 |
# missing slots |
|
16 |
Agenda.objects.create(label='Foo') |
|
17 |
pricing = Pricing.objects.create(label='Foo bar') |
|
18 |
AgendaPricing.objects.create( |
|
19 |
label='Foo', |
|
20 |
pricing=pricing, |
|
21 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
22 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
23 |
flat_fee_schedule=True, |
|
24 |
) |
|
25 | ||
26 |
# missing slots, agenda, agenda_pricing |
|
17 | 27 |
resp = app.get( |
18 | 28 |
'/api/pricing/compute/', |
19 | 29 |
params={'user_external_id': 'user:1', 'adult_external_id': 'adult:1'}, |
... | ... | |
21 | 31 |
) |
22 | 32 |
assert resp.json['err'] == 1 |
23 | 33 |
assert resp.json['err_desc'] == 'invalid payload' |
24 |
assert resp.json['errors']['slots'] == ['This field is required.'] |
|
34 |
assert resp.json['errors'] == { |
|
35 |
'non_field_errors': ['Either "slots", "agenda" or "agenda_pricing" parameter is required.'] |
|
36 |
} |
|
25 | 37 | |
26 |
# missing user_external_id
|
|
38 |
# missing start_date
|
|
27 | 39 |
resp = app.get( |
28 | 40 |
'/api/pricing/compute/', |
29 |
params={'slots': 'foo@foo', 'adult_external_id': 'adult:1'},
|
|
41 |
params={'agenda': 'foo', 'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
|
|
30 | 42 |
status=400, |
31 | 43 |
) |
32 | 44 |
assert resp.json['err'] == 1 |
33 | 45 |
assert resp.json['err_desc'] == 'invalid payload' |
34 |
assert resp.json['errors']['user_external_id'] == ['This field is required.'] |
|
35 | ||
36 |
# missing adult_external_id |
|
46 |
assert resp.json['errors']['start_date'] == ['This field is required when using "agenda" parameter.'] |
|
37 | 47 |
resp = app.get( |
38 | 48 |
'/api/pricing/compute/', |
39 |
params={'slots': 'foo@foo', 'user_external_id': 'user:1'},
|
|
49 |
params={'agenda_pricing': 'foo', 'user_external_id': 'user:1', 'adult_external_id': 'adult:1'},
|
|
40 | 50 |
status=400, |
41 | 51 |
) |
42 | 52 |
assert resp.json['err'] == 1 |
43 | 53 |
assert resp.json['err_desc'] == 'invalid payload' |
44 |
assert resp.json['errors']['adult_external_id'] == ['This field is required.'] |
|
54 |
assert resp.json['errors']['start_date'] == [ |
|
55 |
'This field is required when using "agenda_pricing" parameter.' |
|
56 |
] |
|
57 | ||
58 |
params = [ |
|
59 |
{'slots': 'foo@foo'}, |
|
60 |
{'agenda': 'foo', 'start_date': '2021-09-01'}, |
|
61 |
{'agenda_pricing': 'foo', 'start_date': '2021-09-01'}, |
|
62 |
] |
|
63 |
for param in params: |
|
64 |
# missing user_external_id |
|
65 |
_param = param.copy() |
|
66 |
_param.update({'adult_external_id': 'adult:1'}) |
|
67 |
resp = app.get( |
|
68 |
'/api/pricing/compute/', |
|
69 |
params=_param, |
|
70 |
status=400, |
|
71 |
) |
|
72 |
assert resp.json['err'] == 1 |
|
73 |
assert resp.json['err_desc'] == 'invalid payload' |
|
74 |
assert resp.json['errors']['user_external_id'] == ['This field is required.'] |
|
75 | ||
76 |
# missing adult_external_id |
|
77 |
_param = param.copy() |
|
78 |
_param.update({'user_external_id': 'user:1'}) |
|
79 |
resp = app.get( |
|
80 |
'/api/pricing/compute/', |
|
81 |
params=_param, |
|
82 |
status=400, |
|
83 |
) |
|
84 |
assert resp.json['err'] == 1 |
|
85 |
assert resp.json['err_desc'] == 'invalid payload' |
|
86 |
assert resp.json['errors']['adult_external_id'] == ['This field is required.'] |
|
45 | 87 | |
46 | 88 | |
47 | 89 |
def test_pricing_compute_slots(app, user): |
... | ... | |
100 | 142 |
assert resp.json['errors']['slots'] == ['Unknown agendas: agenda, agenda2'] |
101 | 143 | |
102 | 144 | |
145 |
def test_pricing_compute_agenda(app, user): |
|
146 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
147 | ||
148 |
# unknown agenda |
|
149 |
resp = app.get( |
|
150 |
'/api/pricing/compute/', |
|
151 |
params={ |
|
152 |
'agenda': 'agenda', |
|
153 |
'start_date': '2021-09-01', |
|
154 |
'user_external_id': 'user:1', |
|
155 |
'adult_external_id': 'adult:1', |
|
156 |
}, |
|
157 |
status=400, |
|
158 |
) |
|
159 |
assert resp.json['err'] == 1 |
|
160 |
assert resp.json['err_desc'] == 'invalid payload' |
|
161 |
assert resp.json['errors']['agenda'] == ['Unknown agenda: agenda'] |
|
162 | ||
163 | ||
164 |
def test_pricing_compute_agenda_pricing(app, user): |
|
165 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
166 | ||
167 |
def test(): |
|
168 |
resp = app.get( |
|
169 |
'/api/pricing/compute/', |
|
170 |
params={ |
|
171 |
'agenda_pricing': 'baz', |
|
172 |
'start_date': '2021-09-01', |
|
173 |
'user_external_id': 'user:1', |
|
174 |
'adult_external_id': 'adult:1', |
|
175 |
}, |
|
176 |
status=400, |
|
177 |
) |
|
178 |
assert resp.json['err'] == 1 |
|
179 |
assert resp.json['err_desc'] == 'invalid payload' |
|
180 |
assert resp.json['errors']['agenda_pricing'] == ['Unknown pricing: baz'] |
|
181 | ||
182 |
# unknown agenda_pricing |
|
183 |
test() |
|
184 | ||
185 |
# bad dates |
|
186 |
pricing = Pricing.objects.create(label='Foo bar') |
|
187 |
agenda_pricing = AgendaPricing.objects.create( |
|
188 |
label='Baz', |
|
189 |
pricing=pricing, |
|
190 |
date_start=datetime.date(year=2021, month=8, day=1), |
|
191 |
date_end=datetime.date(year=2021, month=9, day=1), |
|
192 |
flat_fee_schedule=True, |
|
193 |
subscription_required=False, |
|
194 |
) |
|
195 |
test() |
|
196 |
agenda_pricing.date_start = datetime.date(year=2021, month=9, day=3) |
|
197 |
agenda_pricing.date_end = datetime.date(year=2021, month=10, day=1) |
|
198 |
agenda_pricing.save() |
|
199 |
test() |
|
200 | ||
201 |
# wrong flat_fee_schedule value |
|
202 |
agenda_pricing.flat_fee_schedule = False |
|
203 |
agenda_pricing.save() |
|
204 |
test() |
|
205 | ||
206 |
# wrong subscription_required value |
|
207 |
agenda_pricing.flat_fee_schedule = True |
|
208 |
agenda_pricing.subscription_required = True |
|
209 |
agenda_pricing.save() |
|
210 |
test() |
|
211 | ||
212 | ||
103 | 213 |
@mock.patch('lingo.api.serializers.get_events') |
104 | 214 |
def test_pricing_compute_events_error(mock_events, app, user): |
105 | 215 |
Agenda.objects.create(label='Agenda') |
... | ... | |
147 | 257 |
@mock.patch('lingo.api.serializers.get_events') |
148 | 258 |
@mock.patch('lingo.api.serializers.get_subscriptions') |
149 | 259 |
@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): |
|
260 |
def test_pricing_compute_for_event(mock_pricing_data_event, mock_subscriptions, mock_events, app, user):
|
|
151 | 261 |
agenda = Agenda.objects.create(label='Agenda') |
152 | 262 |
agenda2 = Agenda.objects.create(label='Agenda2') |
153 | 263 |
app.authorization = ('Basic', ('john.doe', 'password')) |
... | ... | |
277 | 387 |
}, |
278 | 388 |
] |
279 | 389 | |
280 |
# ok |
|
281 | 390 |
pricing = Pricing.objects.create(label='Foo bar') |
282 | 391 |
agenda_pricing = AgendaPricing.objects.create( |
283 | 392 |
pricing=pricing, |
284 | 393 |
date_start=datetime.date(year=2021, month=9, day=1), |
285 | 394 |
date_end=datetime.date(year=2021, month=10, day=1), |
395 |
flat_fee_schedule=True, # wrong config |
|
286 | 396 |
) |
287 | 397 |
agenda_pricing.agendas.add(agenda, agenda2) |
288 | 398 |
resp = app.get( |
... | ... | |
293 | 403 |
'adult_external_id': 'adult:1', |
294 | 404 |
}, |
295 | 405 |
) |
406 |
assert resp.json['data'] == [ |
|
407 |
{ |
|
408 |
'event': 'agenda2@event-baz-slug', |
|
409 |
'error': 'No agenda pricing found for event agenda2@event-baz-slug', |
|
410 |
}, |
|
411 |
{ |
|
412 |
'event': 'agenda@event-bar-slug', |
|
413 |
'error': 'No agenda pricing found for event agenda@event-bar-slug', |
|
414 |
}, |
|
415 |
] |
|
416 | ||
417 |
# ok |
|
418 |
agenda_pricing.flat_fee_schedule = False |
|
419 |
agenda_pricing.save() |
|
420 |
resp = app.get( |
|
421 |
'/api/pricing/compute/', |
|
422 |
params={ |
|
423 |
'slots': 'agenda@event-bar-slug, agenda2@event-baz-slug', |
|
424 |
'user_external_id': 'user:1', |
|
425 |
'adult_external_id': 'adult:1', |
|
426 |
}, |
|
427 |
) |
|
296 | 428 |
assert resp.json['data'] == [ |
297 | 429 |
{'event': 'agenda2@event-baz-slug', 'pricing_data': {'foo': 'baz'}}, |
298 | 430 |
{'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}}, |
... | ... | |
345 | 477 |
{'event': 'agenda2@event-baz-slug', 'error': {'foo': 'error'}}, |
346 | 478 |
{'event': 'agenda@event-bar-slug', 'pricing_data': {'foo': 'bar'}}, |
347 | 479 |
] |
480 | ||
481 | ||
482 |
@mock.patch('lingo.api.serializers.get_subscriptions') |
|
483 |
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data') |
|
484 |
def test_pricing_compute_for_flat_fee_schedule_with_subscription( |
|
485 |
mock_pricing_data, mock_subscriptions, app, user |
|
486 |
): |
|
487 |
agenda = Agenda.objects.create(label='Foo bar') |
|
488 |
pricing = Pricing.objects.create(label='Foo bar') |
|
489 |
agenda_pricing = AgendaPricing.objects.create( |
|
490 |
label='Foo bar pricing', |
|
491 |
pricing=pricing, |
|
492 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
493 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
494 |
flat_fee_schedule=True, |
|
495 |
subscription_required=True, |
|
496 |
) |
|
497 |
agenda_pricing.agendas.add(agenda) |
|
498 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
499 | ||
500 |
# no subscription |
|
501 |
mock_subscriptions.return_value = [] |
|
502 |
resp = app.get( |
|
503 |
'/api/pricing/compute/', |
|
504 |
params={ |
|
505 |
'agenda': 'foo-bar', |
|
506 |
'start_date': '2021-09-01', |
|
507 |
'user_external_id': 'user:1', |
|
508 |
'adult_external_id': 'adult:1', |
|
509 |
}, |
|
510 |
status=400, |
|
511 |
) |
|
512 |
assert resp.json['err'] == 1 |
|
513 |
assert resp.json['err_desc'] == 'invalid payload' |
|
514 |
assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar'] |
|
515 |
assert mock_subscriptions.call_args_list == [mock.call('foo-bar', 'user:1')] |
|
516 | ||
517 |
# no matching subscription |
|
518 |
mock_subscriptions.reset_mock() |
|
519 |
mock_subscriptions.return_value = [ |
|
520 |
{ |
|
521 |
'date_start': '2021-08-01', |
|
522 |
'date_end': '2021-09-01', |
|
523 |
}, |
|
524 |
{ |
|
525 |
'date_start': '2021-10-01', |
|
526 |
'date_end': '2021-11-01', |
|
527 |
}, |
|
528 |
] |
|
529 |
resp = app.get( |
|
530 |
'/api/pricing/compute/', |
|
531 |
params={ |
|
532 |
'agenda': 'foo-bar', |
|
533 |
'start_date': '2021-09-02', |
|
534 |
'user_external_id': 'user:1', |
|
535 |
'adult_external_id': 'adult:1', |
|
536 |
}, |
|
537 |
status=400, |
|
538 |
) |
|
539 |
assert resp.json['err'] == 1 |
|
540 |
assert resp.json['err_desc'] == 'invalid payload' |
|
541 |
assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar'] |
|
542 |
assert mock_subscriptions.call_args_list == [mock.call('foo-bar', 'user:1')] |
|
543 | ||
544 |
# no agenda_pricing found |
|
545 |
agenda_pricing.delete() |
|
546 |
agenda_pricing = AgendaPricing.objects.create( |
|
547 |
pricing=pricing, |
|
548 |
# bad dates |
|
549 |
date_start=datetime.date(year=2021, month=8, day=1), |
|
550 |
date_end=datetime.date(year=2021, month=9, day=1), |
|
551 |
) |
|
552 |
agenda_pricing.agendas.add(agenda) |
|
553 |
agenda_pricing = AgendaPricing.objects.create( |
|
554 |
pricing=pricing, |
|
555 |
# bad dates |
|
556 |
date_start=datetime.date(year=2021, month=9, day=3), |
|
557 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
558 |
) |
|
559 |
agenda_pricing.agendas.add(agenda) |
|
560 |
mock_subscriptions.return_value = [ |
|
561 |
{ |
|
562 |
'date_start': '2021-08-01', |
|
563 |
'date_end': '2021-09-01', |
|
564 |
}, |
|
565 |
{ |
|
566 |
'date_start': '2021-09-02', |
|
567 |
'date_end': '2021-09-03', |
|
568 |
}, |
|
569 |
{ |
|
570 |
'date_start': '2021-10-01', |
|
571 |
'date_end': '2021-11-01', |
|
572 |
}, |
|
573 |
] |
|
574 |
mock_pricing_data.return_value = {'foo': 'bar'} |
|
575 |
resp = app.get( |
|
576 |
'/api/pricing/compute/', |
|
577 |
params={ |
|
578 |
'agenda': 'foo-bar', |
|
579 |
'start_date': '2021-09-02', |
|
580 |
'user_external_id': 'user:1', |
|
581 |
'adult_external_id': 'adult:1', |
|
582 |
}, |
|
583 |
) |
|
584 |
assert resp.json['data'] == { |
|
585 |
'agenda': 'foo-bar', |
|
586 |
'error': 'No agenda pricing found for agenda foo-bar', |
|
587 |
} |
|
588 | ||
589 |
agenda_pricing.delete() |
|
590 |
agenda_pricing = AgendaPricing.objects.create( |
|
591 |
pricing=pricing, |
|
592 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
593 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
594 |
flat_fee_schedule=False, # wrong config |
|
595 |
subscription_required=True, |
|
596 |
) |
|
597 |
agenda_pricing.agendas.add(agenda) |
|
598 |
resp = app.get( |
|
599 |
'/api/pricing/compute/', |
|
600 |
params={ |
|
601 |
'agenda': 'foo-bar', |
|
602 |
'start_date': '2021-09-02', |
|
603 |
'user_external_id': 'user:1', |
|
604 |
'adult_external_id': 'adult:1', |
|
605 |
}, |
|
606 |
) |
|
607 |
assert resp.json['data'] == { |
|
608 |
'agenda': 'foo-bar', |
|
609 |
'error': 'No agenda pricing found for agenda foo-bar', |
|
610 |
} |
|
611 | ||
612 |
agenda_pricing.flat_fee_schedule = True |
|
613 |
agenda_pricing.subscription_required = False # wrong config |
|
614 |
agenda_pricing.save() |
|
615 |
resp = app.get( |
|
616 |
'/api/pricing/compute/', |
|
617 |
params={ |
|
618 |
'agenda': 'foo-bar', |
|
619 |
'start_date': '2021-09-02', |
|
620 |
'user_external_id': 'user:1', |
|
621 |
'adult_external_id': 'adult:1', |
|
622 |
}, |
|
623 |
) |
|
624 |
assert resp.json['data'] == { |
|
625 |
'agenda': 'foo-bar', |
|
626 |
'error': 'No agenda pricing found for agenda foo-bar', |
|
627 |
} |
|
628 | ||
629 |
# ok |
|
630 |
agenda_pricing.subscription_required = True |
|
631 |
agenda_pricing.save() |
|
632 |
resp = app.get( |
|
633 |
'/api/pricing/compute/', |
|
634 |
params={ |
|
635 |
'agenda': 'foo-bar', |
|
636 |
'start_date': '2021-09-02', |
|
637 |
'user_external_id': 'user:1', |
|
638 |
'adult_external_id': 'adult:1', |
|
639 |
}, |
|
640 |
) |
|
641 |
assert resp.json['data'] == {'agenda': 'foo-bar', 'pricing_data': {'foo': 'bar'}} |
|
642 |
assert mock_pricing_data.call_args_list == [ |
|
643 |
mock.call( |
|
644 |
request=mock.ANY, |
|
645 |
pricing_date=datetime.date(2021, 9, 1), |
|
646 |
subscription={'date_start': '2021-09-02', 'date_end': '2021-09-03'}, |
|
647 |
user_external_id='user:1', |
|
648 |
adult_external_id='adult:1', |
|
649 |
), |
|
650 |
] |
|
651 | ||
652 |
# get_pricing_data with error |
|
653 |
mock_pricing_data.side_effect = PricingError(details={'foo': 'error'}) |
|
654 |
resp = app.get( |
|
655 |
'/api/pricing/compute/', |
|
656 |
params={ |
|
657 |
'agenda': 'foo-bar', |
|
658 |
'start_date': '2021-09-02', |
|
659 |
'user_external_id': 'user:1', |
|
660 |
'adult_external_id': 'adult:1', |
|
661 |
}, |
|
662 |
) |
|
663 |
assert resp.json['data'] == { |
|
664 |
'agenda': 'foo-bar', |
|
665 |
'error': 'PricingError', |
|
666 |
'error_details': {'foo': 'error'}, |
|
667 |
} |
|
668 | ||
669 |
# check with billing dates |
|
670 |
mock_pricing_data.return_value = {'foo': 'bar'} |
|
671 |
agenda_pricing.billingdates.create( |
|
672 |
date_start=datetime.date(2021, 9, 1), |
|
673 |
label='Foo 1', |
|
674 |
) |
|
675 |
agenda_pricing.billingdates.create( |
|
676 |
date_start=datetime.date(2021, 9, 15), |
|
677 |
label='Foo 2', |
|
678 |
) |
|
679 |
mock_subscriptions.return_value = [ |
|
680 |
{ |
|
681 |
'date_start': '2021-09-01', |
|
682 |
'date_end': '2021-09-15', |
|
683 |
}, |
|
684 |
] |
|
685 |
resp = app.get( |
|
686 |
'/api/pricing/compute/', |
|
687 |
params={ |
|
688 |
'agenda': 'foo-bar', |
|
689 |
'start_date': '2021-09-16', |
|
690 |
'user_external_id': 'user:1', |
|
691 |
'adult_external_id': 'adult:1', |
|
692 |
}, |
|
693 |
status=400, |
|
694 |
) |
|
695 |
assert resp.json['err'] == 1 |
|
696 |
assert resp.json['err_desc'] == 'invalid payload' |
|
697 |
assert resp.json['errors']['user_external_id'] == ['No subscription found for agenda foo-bar'] |
|
698 |
mock_subscriptions.return_value = [ |
|
699 |
{ |
|
700 |
'date_start': '2021-09-15', |
|
701 |
'date_end': '2021-09-16', |
|
702 |
}, |
|
703 |
] |
|
704 |
mock_pricing_data.reset_mock() |
|
705 |
resp = app.get( |
|
706 |
'/api/pricing/compute/', |
|
707 |
params={ |
|
708 |
'agenda': 'foo-bar', |
|
709 |
'start_date': '2021-09-16', |
|
710 |
'user_external_id': 'user:1', |
|
711 |
'adult_external_id': 'adult:1', |
|
712 |
}, |
|
713 |
) |
|
714 |
assert mock_pricing_data.call_args_list == [ |
|
715 |
mock.call( |
|
716 |
request=mock.ANY, |
|
717 |
pricing_date=datetime.date(2021, 9, 15), |
|
718 |
subscription={'date_start': '2021-09-15', 'date_end': '2021-09-16'}, |
|
719 |
user_external_id='user:1', |
|
720 |
adult_external_id='adult:1', |
|
721 |
), |
|
722 |
] |
|
723 |
mock_subscriptions.return_value = [ |
|
724 |
{ |
|
725 |
'date_start': '2021-09-30', |
|
726 |
'date_end': '2021-10-01', |
|
727 |
}, |
|
728 |
] |
|
729 |
mock_pricing_data.reset_mock() |
|
730 |
resp = app.get( |
|
731 |
'/api/pricing/compute/', |
|
732 |
params={ |
|
733 |
'agenda': 'foo-bar', |
|
734 |
'start_date': '2021-09-16', |
|
735 |
'user_external_id': 'user:1', |
|
736 |
'adult_external_id': 'adult:1', |
|
737 |
}, |
|
738 |
) |
|
739 |
assert mock_pricing_data.call_args_list == [ |
|
740 |
mock.call( |
|
741 |
request=mock.ANY, |
|
742 |
pricing_date=datetime.date(2021, 9, 15), |
|
743 |
subscription={'date_start': '2021-09-30', 'date_end': '2021-10-01'}, |
|
744 |
user_external_id='user:1', |
|
745 |
adult_external_id='adult:1', |
|
746 |
), |
|
747 |
] |
|
748 | ||
749 | ||
750 |
@mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data') |
|
751 |
def test_pricing_compute_for_flat_fee_schedule_without_subscription(mock_pricing_data, app, user): |
|
752 |
pricing = Pricing.objects.create(label='Foo bar') |
|
753 |
agenda_pricing = AgendaPricing.objects.create( |
|
754 |
label='Foo bar pricing', |
|
755 |
pricing=pricing, |
|
756 |
date_start=datetime.date(year=2021, month=9, day=1), |
|
757 |
date_end=datetime.date(year=2021, month=10, day=1), |
|
758 |
flat_fee_schedule=True, |
|
759 |
subscription_required=False, |
|
760 |
) |
|
761 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
762 | ||
763 |
mock_pricing_data.return_value = {'foo': 'bar'} |
|
764 |
resp = app.get( |
|
765 |
'/api/pricing/compute/', |
|
766 |
params={ |
|
767 |
'agenda_pricing': 'foo-bar-pricing', |
|
768 |
'start_date': '2021-09-02', |
|
769 |
'user_external_id': 'user:1', |
|
770 |
'adult_external_id': 'adult:1', |
|
771 |
}, |
|
772 |
) |
|
773 |
assert resp.json['data'] == {'agenda_pricing': 'foo-bar-pricing', 'pricing_data': {'foo': 'bar'}} |
|
774 |
assert mock_pricing_data.call_args_list == [ |
|
775 |
mock.call( |
|
776 |
request=mock.ANY, |
|
777 |
pricing_date=datetime.date(2021, 9, 1), |
|
778 |
subscription=None, |
|
779 |
user_external_id='user:1', |
|
780 |
adult_external_id='adult:1', |
|
781 |
), |
|
782 |
] |
|
783 | ||
784 |
# get_pricing_data with error |
|
785 |
mock_pricing_data.side_effect = PricingError(details={'foo': 'error'}) |
|
786 |
resp = app.get( |
|
787 |
'/api/pricing/compute/', |
|
788 |
params={ |
|
789 |
'agenda_pricing': 'foo-bar-pricing', |
|
790 |
'start_date': '2021-09-02', |
|
791 |
'user_external_id': 'user:1', |
|
792 |
'adult_external_id': 'adult:1', |
|
793 |
}, |
|
794 |
) |
|
795 |
assert resp.json['data'] == { |
|
796 |
'agenda_pricing': 'foo-bar-pricing', |
|
797 |
'error': 'PricingError', |
|
798 |
'error_details': {'foo': 'error'}, |
|
799 |
} |
|
800 | ||
801 |
# check with billing dates |
|
802 |
mock_pricing_data.return_value = {'foo': 'bar'} |
|
803 |
agenda_pricing.billingdates.create( |
|
804 |
date_start=datetime.date(2021, 9, 1), |
|
805 |
label='Foo 1', |
|
806 |
) |
|
807 |
agenda_pricing.billingdates.create( |
|
808 |
date_start=datetime.date(2021, 9, 15), |
|
809 |
label='Foo 2', |
|
810 |
) |
|
811 |
mock_pricing_data.reset_mock() |
|
812 |
resp = app.get( |
|
813 |
'/api/pricing/compute/', |
|
814 |
params={ |
|
815 |
'agenda_pricing': 'foo-bar-pricing', |
|
816 |
'start_date': '2021-09-16', |
|
817 |
'user_external_id': 'user:1', |
|
818 |
'adult_external_id': 'adult:1', |
|
819 |
}, |
|
820 |
) |
|
821 |
assert mock_pricing_data.call_args_list == [ |
|
822 |
mock.call( |
|
823 |
request=mock.ANY, |
|
824 |
pricing_date=datetime.date(2021, 9, 15), |
|
825 |
subscription=None, |
|
826 |
user_external_id='user:1', |
|
827 |
adult_external_id='adult:1', |
|
828 |
), |
|
829 |
] |
|
348 |
- |