toulouse-maelis: trigger wcs when basket subscription is removed (#76398)
This commit is contained in:
parent
80fc536e2b
commit
0276de78c2
|
@ -21,10 +21,11 @@ import json
|
||||||
import re
|
import re
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin, urlparse
|
||||||
|
|
||||||
import zeep
|
import zeep
|
||||||
from dateutil import rrule
|
from dateutil import rrule
|
||||||
|
from django.conf import settings
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import JSONField
|
from django.db.models import JSONField
|
||||||
|
@ -43,6 +44,7 @@ from passerelle.utils.conversion import simplify
|
||||||
from passerelle.utils.jsonresponse import APIError
|
from passerelle.utils.jsonresponse import APIError
|
||||||
from passerelle.utils.soap import SOAPFault, SOAPServiceUnreachable
|
from passerelle.utils.soap import SOAPFault, SOAPServiceUnreachable
|
||||||
from passerelle.utils.templates import render_to_string
|
from passerelle.utils.templates import render_to_string
|
||||||
|
from passerelle.utils.wcs import WcsApi, WcsApiError
|
||||||
|
|
||||||
from . import activity_schemas, family_schemas, invoice_schemas, schemas, utils
|
from . import activity_schemas, family_schemas, invoice_schemas, schemas, utils
|
||||||
|
|
||||||
|
@ -310,9 +312,24 @@ class ToulouseMaelis(BaseResource, HTTPResource):
|
||||||
):
|
):
|
||||||
invoice.cancel()
|
invoice.cancel()
|
||||||
|
|
||||||
|
def trigger_subscriptions_cron(self):
|
||||||
|
# find subscriptions removed from baskets
|
||||||
|
pending_basket_subscriptions = self.subscription_set.filter(invoice__isnull=True)
|
||||||
|
family_ids = {x.family_id for x in pending_basket_subscriptions}
|
||||||
|
for family_id in family_ids:
|
||||||
|
self.get_baskets_raw(family_id)
|
||||||
|
|
||||||
|
subscriptions = self.subscription_set.filter(
|
||||||
|
wcs_trigger_date__isnull=True,
|
||||||
|
)
|
||||||
|
for subscription in subscriptions:
|
||||||
|
if subscription.trigger_status() == 'triggering':
|
||||||
|
subscription.trigger()
|
||||||
|
|
||||||
def hourly(self):
|
def hourly(self):
|
||||||
self.notify_invoices_paid()
|
self.notify_invoices_paid()
|
||||||
self.cancel_basket_invoices()
|
self.cancel_basket_invoices()
|
||||||
|
self.trigger_subscriptions_cron()
|
||||||
|
|
||||||
def get_referential(self, referential_name, id=None, q=None, limit=None, distinct=True):
|
def get_referential(self, referential_name, id=None, q=None, limit=None, distinct=True):
|
||||||
if id is not None:
|
if id is not None:
|
||||||
|
@ -4158,6 +4175,13 @@ class ToulouseMaelis(BaseResource, HTTPResource):
|
||||||
return
|
return
|
||||||
invoice.notify()
|
invoice.notify()
|
||||||
|
|
||||||
|
def trigger_subscription_job(self, pk):
|
||||||
|
try:
|
||||||
|
subscription = self.subscription_set.get(pk=pk)
|
||||||
|
except Invoice.DoesNotExist:
|
||||||
|
return
|
||||||
|
subscription.trigger()
|
||||||
|
|
||||||
@endpoint(
|
@endpoint(
|
||||||
display_category='Facture',
|
display_category='Facture',
|
||||||
name='regie',
|
name='regie',
|
||||||
|
@ -4392,13 +4416,86 @@ class Subscription(models.Model):
|
||||||
return 'pending_basket'
|
return 'pending_basket'
|
||||||
|
|
||||||
def trigger_status(self):
|
def trigger_status(self):
|
||||||
if self.status() in ['paid', 'cancelled', 'removed']:
|
if self.wcs_trigger_date is not None:
|
||||||
|
# wcs demand was triggered
|
||||||
|
return 'triggered'
|
||||||
|
if self.status() in ['removed']:
|
||||||
# wcs demand can be triggered
|
# wcs demand can be triggered
|
||||||
return 'triggering'
|
return 'triggering'
|
||||||
else:
|
else:
|
||||||
# waiting for a definive subscription status
|
# waiting for a definive subscription status
|
||||||
return 'pending'
|
return 'pending'
|
||||||
|
|
||||||
|
def set_trigger(self):
|
||||||
|
if self.trigger_status() != 'triggering':
|
||||||
|
return
|
||||||
|
if self.wcs_trigger_payload:
|
||||||
|
return
|
||||||
|
self.wcs_trigger_payload = {
|
||||||
|
'err': 1 if self.status() in ['removed'] else 0,
|
||||||
|
'data': {
|
||||||
|
'regie_id': self.regie_id,
|
||||||
|
'regie_text': self.resource.get_referential_value('Regie', self.regie_id),
|
||||||
|
'invoice_id': self.invoice.invoice_id if self.invoice else None,
|
||||||
|
'invoice_status': self.invoice.status() if self.invoice else None,
|
||||||
|
'invoice_data': self.invoice.maelis_data if self.invoice else None,
|
||||||
|
'subscription_id': self.pk,
|
||||||
|
'subscription_status': self.status(),
|
||||||
|
'subscription_data': self.maelis_data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if self.status() == 'removed':
|
||||||
|
self.wcs_trigger_payload['err_desc'] = "Le panier n'a pas été validé"
|
||||||
|
self.save()
|
||||||
|
self.resource.add_job(
|
||||||
|
'trigger_subscription_job',
|
||||||
|
pk=self.pk,
|
||||||
|
natural_id='%s/%s' % (self.wcs_form_number, self.pk),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_wcs_api(self, base_url):
|
||||||
|
scheme, netloc, dummy, dummy, dummy, dummy = urlparse(base_url)
|
||||||
|
services = settings.KNOWN_SERVICES.get('wcs', {})
|
||||||
|
service = None
|
||||||
|
for service in services.values():
|
||||||
|
remote_url = service.get('url')
|
||||||
|
r_scheme, r_netloc, dummy, dummy, dummy, dummy = urlparse(remote_url)
|
||||||
|
if r_scheme == scheme and r_netloc == netloc:
|
||||||
|
return WcsApi(
|
||||||
|
base_url,
|
||||||
|
orig=service.get('orig'),
|
||||||
|
key=service.get('secret'),
|
||||||
|
session=self.resource.requests,
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def trigger(self):
|
||||||
|
obj = Subscription.objects.select_for_update().get(pk=self.pk)
|
||||||
|
if obj.trigger_status() != 'triggering':
|
||||||
|
return
|
||||||
|
base_url = '%shooks/update_subscription/' % (obj.wcs_form_api_url)
|
||||||
|
wcs_api = obj.get_wcs_api(base_url)
|
||||||
|
if not wcs_api:
|
||||||
|
err_desc = 'Cannot find wcs service for %s' % obj.wcs_form_api_url
|
||||||
|
self.resource.logger.warning(err_desc)
|
||||||
|
result = err_desc
|
||||||
|
else:
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
self.resource.logger.info(
|
||||||
|
'trigger wcs: %s -> %s' % (base_url, self.wcs_trigger_payload['data']['subscription_status'])
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
result = wcs_api.post_json(obj.wcs_trigger_payload, [], headers=headers)
|
||||||
|
except WcsApiError as e:
|
||||||
|
self.resource.logger.warning(e)
|
||||||
|
return
|
||||||
|
obj.wcs_trigger_date = now()
|
||||||
|
obj.wcs_trigger_response = result
|
||||||
|
obj.save()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('resource', 'wcs_form_number')
|
ordering = ('resource', 'wcs_form_number')
|
||||||
unique_together = [['resource', 'wcs_form_number']]
|
unique_together = [['resource', 'wcs_form_number']]
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
@ -138,6 +139,21 @@ def ape_service():
|
||||||
yield mock
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def wcs_service(settings, requests_mock):
|
||||||
|
wcs_service = {
|
||||||
|
'default': {
|
||||||
|
'title': 'test',
|
||||||
|
'url': 'https://wcs.example.com',
|
||||||
|
'secret': 'xxx',
|
||||||
|
'orig': 'passerelle',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
settings.KNOWN_SERVICES = {'wcs': wcs_service}
|
||||||
|
with requests_mock as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='module')
|
@pytest.fixture(scope='module')
|
||||||
def django_db_setup(django_db_setup, django_db_blocker):
|
def django_db_setup(django_db_setup, django_db_blocker):
|
||||||
with django_db_blocker.unblock():
|
with django_db_blocker.unblock():
|
||||||
|
@ -10131,3 +10147,226 @@ def test_invoice_pdf_error(invoice_service, con, app):
|
||||||
assert resp.json['err'] == 1
|
assert resp.json['err'] == 1
|
||||||
assert resp.json['err_class'] == 'django.http.response.Http404'
|
assert resp.json['err_class'] == 'django.http.response.Http404'
|
||||||
assert resp.json['err_desc'] == 'Fichier PDF non trouvé'
|
assert resp.json['err_desc'] == 'Fichier PDF non trouvé'
|
||||||
|
|
||||||
|
|
||||||
|
def test_trigger_wcs_on_removed_subscriptions_cron(
|
||||||
|
family_service, activity_service, wcs_service, con, app, freezer, caplog
|
||||||
|
):
|
||||||
|
family_service.add_soap_response('readFamily', get_xml_file('R_read_family_for_subscription.xml'))
|
||||||
|
activity_service.add_soap_response('getPersonUnitInfo', get_xml_file('R_get_person_unit_info.xml'))
|
||||||
|
activity_service.add_soap_response('addPersonUnitBasket', get_xml_file('R_add_person_unit_basket.xml'))
|
||||||
|
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
|
||||||
|
wcs_service.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/',
|
||||||
|
json={'err': 0},
|
||||||
|
status=200,
|
||||||
|
)
|
||||||
|
Link.objects.create(resource=con, family_id='1312', name_id='local')
|
||||||
|
|
||||||
|
# subscribe providing a wcs demand
|
||||||
|
freezer.move_to('2023-03-03 18:20:00')
|
||||||
|
resp = app.post_json(
|
||||||
|
get_endpoint('add-person-basket-subscription') + '?NameID=local',
|
||||||
|
params={
|
||||||
|
'person_id': '266145',
|
||||||
|
'activity_id': 'A10053179798',
|
||||||
|
'unit_id': 'A10053179809',
|
||||||
|
'place_id': 'A10053179757',
|
||||||
|
'start_date': '2023-02-01',
|
||||||
|
'end_date': '2023-06-30',
|
||||||
|
'form_api_url': 'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/',
|
||||||
|
'form_number': '13-12',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert resp.json['err'] == 0
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.status() == 'pending_basket'
|
||||||
|
assert [x['idIns'] for x in subscription.maelis_data['basket']['lignes']] == ['S10055641658']
|
||||||
|
|
||||||
|
# basket was removed, send trigger to wcs
|
||||||
|
con.hourly()
|
||||||
|
assert (
|
||||||
|
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/'
|
||||||
|
in wcs_service.calls[-1].request.url
|
||||||
|
)
|
||||||
|
trigger_body = json.loads(wcs_service.calls[-1].request.body)
|
||||||
|
assert trigger_body['err'] == 1
|
||||||
|
assert trigger_body['err_desc'] == "Le panier n'a pas été validé"
|
||||||
|
assert trigger_body['data']['subscription_status'] == 'removed'
|
||||||
|
assert trigger_body['data']['regie_text'] == 'DSBL'
|
||||||
|
assert any(['trigger wcs' in x.message for x in caplog.records])
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.trigger_status() == 'triggered'
|
||||||
|
|
||||||
|
|
||||||
|
def test_trigger_wcs_on_removed_subscriptions_job(
|
||||||
|
family_service, activity_service, wcs_service, con, app, freezer, caplog
|
||||||
|
):
|
||||||
|
family_service.add_soap_response('readFamily', get_xml_file('R_read_family_for_subscription.xml'))
|
||||||
|
activity_service.add_soap_response('getPersonUnitInfo', get_xml_file('R_get_person_unit_info.xml'))
|
||||||
|
activity_service.add_soap_response('addPersonUnitBasket', get_xml_file('R_add_person_unit_basket.xml'))
|
||||||
|
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
|
||||||
|
wcs_service.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/',
|
||||||
|
json={'err': 0},
|
||||||
|
status=200,
|
||||||
|
)
|
||||||
|
Link.objects.create(resource=con, family_id='1312', name_id='local')
|
||||||
|
|
||||||
|
# subscribe providing a wcs demand
|
||||||
|
freezer.move_to('2023-03-03 18:20:00')
|
||||||
|
resp = app.post_json(
|
||||||
|
get_endpoint('add-person-basket-subscription') + '?NameID=local',
|
||||||
|
params={
|
||||||
|
'person_id': '266145',
|
||||||
|
'activity_id': 'A10053179798',
|
||||||
|
'unit_id': 'A10053179809',
|
||||||
|
'place_id': 'A10053179757',
|
||||||
|
'start_date': '2023-02-01',
|
||||||
|
'end_date': '2023-06-30',
|
||||||
|
'form_api_url': 'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/',
|
||||||
|
'form_number': '13-12',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert resp.json['err'] == 0
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.status() == 'pending_basket'
|
||||||
|
assert [x['idIns'] for x in subscription.maelis_data['basket']['lignes']] == ['S10055641658']
|
||||||
|
|
||||||
|
# get basket having subscription removed
|
||||||
|
resp = app.get(get_endpoint('get-baskets') + '?NameID=local')
|
||||||
|
assert resp.json['err'] == 0
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.status() == 'removed'
|
||||||
|
assert subscription.trigger_status() == 'triggering'
|
||||||
|
|
||||||
|
# send trigger to wcs
|
||||||
|
con.jobs()
|
||||||
|
assert (
|
||||||
|
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/'
|
||||||
|
in wcs_service.calls[-1].request.url
|
||||||
|
)
|
||||||
|
trigger_body = json.loads(wcs_service.calls[-1].request.body)
|
||||||
|
assert trigger_body['err'] == 1
|
||||||
|
assert trigger_body['err_desc'] == "Le panier n'a pas été validé"
|
||||||
|
assert trigger_body['data']['subscription_status'] == 'removed'
|
||||||
|
assert trigger_body['data']['regie_text'] == 'DSBL'
|
||||||
|
assert any(['trigger wcs' in x.message for x in caplog.records])
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.trigger_status() == 'triggered'
|
||||||
|
|
||||||
|
|
||||||
|
def test_trigger_wcs_service_error(family_service, activity_service, con, app, freezer, caplog):
|
||||||
|
family_service.add_soap_response('readFamily', get_xml_file('R_read_family_for_subscription.xml'))
|
||||||
|
activity_service.add_soap_response('getPersonUnitInfo', get_xml_file('R_get_person_unit_info.xml'))
|
||||||
|
activity_service.add_soap_response('addPersonUnitBasket', get_xml_file('R_add_person_unit_basket.xml'))
|
||||||
|
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
|
||||||
|
Link.objects.create(resource=con, family_id='1312', name_id='local')
|
||||||
|
|
||||||
|
# subscribe providing a wcs demand
|
||||||
|
freezer.move_to('2023-03-03 18:20:00')
|
||||||
|
resp = app.post_json(
|
||||||
|
get_endpoint('add-person-basket-subscription') + '?NameID=local',
|
||||||
|
params={
|
||||||
|
'person_id': '266145',
|
||||||
|
'activity_id': 'A10053179798',
|
||||||
|
'unit_id': 'A10053179809',
|
||||||
|
'place_id': 'A10053179757',
|
||||||
|
'start_date': '2023-02-01',
|
||||||
|
'end_date': '2023-06-30',
|
||||||
|
'form_api_url': 'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/',
|
||||||
|
'form_number': '13-12',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert resp.json['err'] == 0
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.status() == 'pending_basket'
|
||||||
|
|
||||||
|
# basket was removed, send trigger to wcs
|
||||||
|
con.hourly()
|
||||||
|
assert caplog.records[-1].levelno == logging.WARNING
|
||||||
|
assert (
|
||||||
|
caplog.records[-1].message
|
||||||
|
== 'Cannot find wcs service for https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/'
|
||||||
|
)
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.trigger_status() == 'triggered'
|
||||||
|
assert (
|
||||||
|
subscription.wcs_trigger_response
|
||||||
|
== 'Cannot find wcs service for https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_trigger_wcs_api_error(family_service, activity_service, wcs_service, con, app, freezer):
|
||||||
|
family_service.add_soap_response('readFamily', get_xml_file('R_read_family_for_subscription.xml'))
|
||||||
|
activity_service.add_soap_response('getPersonUnitInfo', get_xml_file('R_get_person_unit_info.xml'))
|
||||||
|
activity_service.add_soap_response('addPersonUnitBasket', get_xml_file('R_add_person_unit_basket.xml'))
|
||||||
|
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
|
||||||
|
Link.objects.create(resource=con, family_id='1312', name_id='local')
|
||||||
|
|
||||||
|
# subscribe providing a wcs demand
|
||||||
|
freezer.move_to('2023-03-03 18:20:00')
|
||||||
|
resp = app.post_json(
|
||||||
|
get_endpoint('add-person-basket-subscription') + '?NameID=local',
|
||||||
|
params={
|
||||||
|
'person_id': '266145',
|
||||||
|
'activity_id': 'A10053179798',
|
||||||
|
'unit_id': 'A10053179809',
|
||||||
|
'place_id': 'A10053179757',
|
||||||
|
'start_date': '2023-02-01',
|
||||||
|
'end_date': '2023-06-30',
|
||||||
|
'form_api_url': 'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/',
|
||||||
|
'form_number': '13-12',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert resp.json['err'] == 0
|
||||||
|
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.status() == 'pending_basket'
|
||||||
|
|
||||||
|
assert len([x for x in wcs_service.calls if '/hooks/' in x.request.url]) == 0
|
||||||
|
|
||||||
|
# basket was removed, send trigger to wcs
|
||||||
|
wcs_service.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/',
|
||||||
|
json={'err': 1, 'err_class': 'Access denied', 'err_desc': None},
|
||||||
|
status=403,
|
||||||
|
)
|
||||||
|
con.hourly()
|
||||||
|
assert len([x for x in wcs_service.calls if '/hooks/' in x.request.url]) == 1
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.trigger_status() == 'triggering'
|
||||||
|
|
||||||
|
# retry
|
||||||
|
wcs_service.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/',
|
||||||
|
body=CONNECTION_ERROR,
|
||||||
|
)
|
||||||
|
con.hourly()
|
||||||
|
assert len([x for x in wcs_service.calls if '/hooks/' in x.request.url]) == 2
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.trigger_status() == 'triggering'
|
||||||
|
|
||||||
|
# retry again
|
||||||
|
wcs_service.add(
|
||||||
|
responses.POST,
|
||||||
|
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/',
|
||||||
|
body='plop',
|
||||||
|
status=500,
|
||||||
|
)
|
||||||
|
con.hourly()
|
||||||
|
assert len([x for x in wcs_service.calls if '/hooks/' in x.request.url]) == 3
|
||||||
|
subscription = con.subscription_set.get(wcs_form_number='13-12')
|
||||||
|
assert subscription.trigger_status() == 'triggering'
|
||||||
|
assert subscription.wcs_trigger_response is None
|
||||||
|
|
Loading…
Reference in New Issue