toulouse-maelis : notification d'une demande lors du paiement (#76398) #231

Merged
nroche merged 6 commits from wip/76398-parsifal-notify-wcs into main 2023-05-17 12:07:35 +02:00
7 changed files with 1634 additions and 60 deletions

View File

@ -179,6 +179,23 @@ SUBSCRIPTION_SCHEMA = {
},
},
},
'form_api_url': {
'description': "Adresse de la vue API du formulaire Publik : {{ form_api_url }}",
'oneOf': [
{'type': 'string'},
{'type': 'null'},
],
},
'form_number': {
'description': 'Numéro de la demande Publik : {{ form_number }}',
'oneOf': [
{
'type': 'string',
'pattern': '^[0-9]+-[0-9]+$',
},
{'type': 'null'},
],
},
},
'required': [
'person_id',

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.18 on 2023-04-26 13:34
import django.core.serializers.json
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('toulouse_maelis', '0011_invoice_start_payment_date'),
]
operations = [
migrations.CreateModel(
name='Subscription',
fields=[
(
'id',
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
('wcs_form_number', models.CharField(max_length=16)),
('wcs_form_api_url', models.CharField(max_length=256)),
('regie_id', models.CharField(max_length=128)),
('family_id', models.CharField(max_length=128)),
('maelis_data', models.JSONField(encoder=django.core.serializers.json.DjangoJSONEncoder)),
('basket_removal_date', models.DateTimeField(null=True)),
(
'wcs_trigger_payload',
models.JSONField(encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
),
('wcs_trigger_response', models.JSONField(null=True)),
('wcs_trigger_date', models.DateTimeField(null=True)),
('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')),
('updated', models.DateTimeField(auto_now=True, verbose_name='Updated')),
(
'invoice',
models.ForeignKey(
null=True, on_delete=django.db.models.deletion.CASCADE, to='toulouse_maelis.invoice'
),
),
(
'resource',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to='toulouse_maelis.toulousemaelis'
),
),
],
options={
'ordering': ('resource', 'wcs_form_number'),
'unique_together': {('resource', 'wcs_form_number')},
},
),
]

View File

@ -21,10 +21,11 @@ import json
import re
from decimal import Decimal
from operator import itemgetter
from urllib.parse import urljoin
from urllib.parse import urljoin, urlparse
import zeep
from dateutil import rrule
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models, transaction
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.soap import SOAPFault, SOAPServiceUnreachable
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
@ -310,9 +312,24 @@ class ToulouseMaelis(BaseResource, HTTPResource):
):
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):
self.notify_invoices_paid()
self.cancel_basket_invoices()
self.trigger_subscriptions_cron()
def get_referential(self, referential_name, id=None, q=None, limit=None, distinct=True):
if id is not None:
@ -779,7 +796,7 @@ class ToulouseMaelis(BaseResource, HTTPResource):
return data
def get_baskets_raw(self, family_id):
return (
baskets = (
self.call(
'Activity',
'getFamilyBasket',
@ -790,6 +807,25 @@ class ToulouseMaelis(BaseResource, HTTPResource):
or []
)
# remove pending basket subscriptions if they are no more listed into the baskets
basket_id_ins = {}
for basket in baskets:
regie_id = str(basket['codeRegie'])
basket_id_ins[regie_id] = []
for line in basket['lignes']:
basket_id_ins[regie_id].append(line['idIns'])
for subscription in self.subscription_set.filter(invoice__isnull=True, family_id=family_id):
for line in subscription.maelis_data['basket']['lignes']:
if line['idIns'] in basket_id_ins.get(subscription.regie_id, []):
break
else:
subscription.basket_removal_date = now()
subscription.save()
subscription.set_trigger()
return baskets
def get_basket_raw(self, family_id, basket_id):
for basket in self.get_baskets_raw(family_id):
if basket['id'] == basket_id:
@ -3399,6 +3435,34 @@ class ToulouseMaelis(BaseResource, HTTPResource):
response = self.call('Activity', 'addPersonUnitBasket', **payload)
if not response['controlResult']['controlOK']:
raise APIError(response['controlResult']['message'])
# record subscription in order to trigger w.c.s. demand
basket = response.get('basket')
if post_data.get('form_api_url') and post_data.get('form_number') and basket:
regie_id = basket.get('codeRegie')
if regie_id:
related_lines = []
for line in basket.get('lignes') or []:
if (
str(line['personneInfo']['numPerson']) == post_data['person_id']
and line['inscription']['idAct'] == post_data['activity_id']
and line['inscription']['idUnit'] == post_data['unit_id']
and line['inscription']['idLieu'] == post_data['place_id']
):
if line.get('idIns'):
# subscription ids will be used to match an invoice
related_lines.append(line)
if related_lines:
# remove unrelated basket lines from response
response['basket']['lignes'] = related_lines
self.subscription_set.create(
wcs_form_api_url=post_data['form_api_url'],
wcs_form_number=post_data['form_number'],
regie_id=regie_id,
family_id=family_id,
maelis_data=response,
)
return {'data': response}
@endpoint(
@ -3769,6 +3833,7 @@ class ToulouseMaelis(BaseResource, HTTPResource):
maelis_data_update_date=now(),
)
self.logger.info("Ajout de %s sur la famille '%s'", repr(invoice), family_id)
invoice.match_subscriptions()
return {'data': response}
@endpoint(
@ -4096,6 +4161,11 @@ class ToulouseMaelis(BaseResource, HTTPResource):
invoice.lingo_data = post_data
invoice.lingo_notification_date = now()
invoice.save(update_fields=['updated', 'lingo_notification_date', 'lingo_data'])
if invoice.basket_generation_date is not None:
# match paid invoice with subscriptions and trigger w.c.s.
invoice.set_trigger_subscriptions()
self.add_job(
'notify_invoice_paid_job',
regie_id=regie_id,
@ -4111,6 +4181,13 @@ class ToulouseMaelis(BaseResource, HTTPResource):
return
invoice.notify()
def trigger_subscription_job(self, pk):
try:
subscription = self.subscription_set.get(pk=pk)
except Invoice.DoesNotExist:
return
subscription.trigger()
@endpoint(
display_category='Facture',
name='regie',
@ -4290,6 +4367,30 @@ class Invoice(models.Model):
obj.save()
return True
def match_subscriptions(self):
invoice_subscription_ids = []
for line in self.maelis_data['lineInvoiceList'] or []:
subscription_id = line.get('idIns')
if subscription_id:
invoice_subscription_ids.append(subscription_id)
if not invoice_subscription_ids:
return
for subscription in Subscription.objects.filter(
regie_id=self.regie_id, family_id=self.family_id, invoice__isnull=True
):
for line in subscription.maelis_data['basket']['lignes']:
subscription_id = line.get('idIns')
if subscription_id and subscription_id in invoice_subscription_ids:
subscription.invoice = self
subscription.save()
break
def set_trigger_subscriptions(self):
for subscription in self.subscription_set.filter(wcs_trigger_payload__isnull=True):
if subscription.trigger_status() == 'triggering':
subscription.set_trigger()
@transaction.atomic
def cancel(self):
obj = Invoice.objects.select_for_update().get(pk=self.pk)
@ -4306,8 +4407,138 @@ class Invoice(models.Model):
obj.maelis_cancel_notification_date = now()
obj.save()
obj.resource.logger.info("Annulation de %s sur la famille '%s'", repr(obj), obj.family_id)
# match cancelled invoice with subscriptions and trigger w.c.s.
obj.set_trigger_subscriptions()
return True
class Meta:
ordering = ('resource', 'regie_id', 'invoice_id')
unique_together = [['resource', 'regie_id', 'invoice_id']]
class Subscription(models.Model):
'''WCS demand performing basket subscription'''
resource = models.ForeignKey(ToulouseMaelis, on_delete=models.CASCADE)
wcs_form_number = models.CharField(max_length=16)
wcs_form_api_url = models.CharField(max_length=256)
regie_id = models.CharField(blank=False, max_length=128)
family_id = models.CharField(blank=False, max_length=128)
maelis_data = JSONField(encoder=DjangoJSONEncoder)
basket_removal_date = models.DateTimeField(null=True)
invoice = models.ForeignKey(Invoice, null=True, on_delete=models.CASCADE)
wcs_trigger_payload = JSONField(encoder=DjangoJSONEncoder, null=True)
wcs_trigger_response = JSONField(null=True)
wcs_trigger_date = models.DateTimeField(null=True)
created = models.DateTimeField('Created', auto_now_add=True)
updated = models.DateTimeField('Updated', auto_now=True)
def __repr__(self):
return '<Subscription "%s/%s">' % (self.wcs_form_number, self.pk)
def status(self):
if self.invoice is not None:
if self.invoice.status() in ['paid', 'notified']:
# related invoice is paid
return 'paid'
if self.invoice.status() == 'cancelled':
# related invoice is cancelled
return 'cancelled'
else:
# there is a related invoice
return 'pending_invoice'
else:
if self.basket_removal_date is not None:
# no basket validation (no related invoice generated)
return 'removed'
else:
# subscription is into the maelis basket
return 'pending_basket'
def trigger_status(self):
if self.wcs_trigger_date is not None:
# wcs demand was triggered
return 'triggered'
if self.status() in ['paid', 'cancelled', 'removed']:
# wcs demand can be triggered
return 'triggering'
else:
# waiting for a definive subscription status
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 ['cancelled', '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é"
if self.status() == 'cancelled':
self.wcs_trigger_payload['err_desc'] = "La facture a été annulée"
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:
ordering = ('resource', 'wcs_form_number')
unique_together = [['resource', 'wcs_form_number']]

View File

@ -1,42 +1,78 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:addPersonUnitBasketResponse xmlns:ns2="activity.ws.maelis.sigec.com" xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com">
<ns2:addPersonUnitBasketResponse xmlns:ns2="activity.ws.maelis.sigec.com" xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com" xmlns:ns4="bean.persistence.school.ws.maelis.sigec.com">
<addPersonUnitBasketResponseBean>
<controlResult>
<controlOK>true</controlOK>
</controlResult>
<basket>
<dateAdd>2023-01-22T18:55:09+01:00</dateAdd>
<dateMaj>2023-01-22T18:55:09+01:00</dateMaj>
<delai>0</delai>
<id>S10053190349</id>
<idFam>S10053180370</idFam>
<codeRegie>109</codeRegie>
<dateAdd>2023-04-20T20:41:58+02:00</dateAdd>
<dateMaj>2023-04-20T20:40:50+02:00</dateMaj>
<delai>30</delai>
<id>S10055641698</id>
<idFam>S10055638201</idFam>
<lignes>
<dateMaj>2023-01-22T18:55:09+01:00</dateMaj>
<id>S10053190350</id>
<idIns>S10053190347</idIns>
<idInsLieu>S10053190348</idInsLieu>
<idPanier>S10053190349</idPanier>
<inscription>
<dateDeb>2022-09-01T00:00:00+02:00</dateDeb>
<dateFin>2023-08-31T00:00:00+02:00</dateFin>
<idAct>A10051141965</idAct>
<idInsAct>S10053190347</idInsAct>
<idInsLieu>S10053190348</idInsLieu>
<idLieu>A10053179226</idLieu>
<idRegie>A10049327675</idRegie>
<idUnit>A10051141990</idUnit>
<libAct>Vitrail Fusing 1/2 Je Adultes 2022/2023 - Mardi 14h-17h</libAct>
<libLieu>Centre Culturel ALBAN MINVILLE</libLieu>
<libUnit>Inscription 1er semestre</libUnit>
</inscription>
<personneInfo>
<dateBirth>2014-04-01T00:00:00+02:00</dateBirth>
<firstname>BART</firstname>
<lastname>SIMPSON</lastname>
<numPerson>246423</numPerson>
<lastname>EO_NICOLAS</lastname>
<numPerson>266145</numPerson>
<sexe>M</sexe>
</personneInfo>
<idPanier>S10055641698</idPanier>
<id>S10055641705</id>
<idIns>S10055641658</idIns>
<idInsLieu>S10055641703</idInsLieu>
<inscription>
<dateDeb>2023-02-01T00:00:00+01:00</dateDeb>
<dateFin>2023-06-30T00:00:00+02:00</dateFin>
<idAct>A10053179798</idAct>
<idInsAct>S10055641702</idInsAct>
<idInsLieu>S10055641703</idInsLieu>
<idLieu>A10053179757</idLieu>
<idRegie>A10049327667</idRegie>
<idUnit>A10053179809</idUnit>
<libAct>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libAct>
<libLieu>ARGOULETS</libLieu>
<libUnit>MERCREDI - 15h30/17h - 8/15Ans</libUnit>
</inscription>
<qte>1.0</qte>
<prixUnit>150.0</prixUnit>
<montant>150.0</montant>
<datEchn>2023-12-31T00:00:00+01:00</datEchn>
<dateMaj>2023-04-20T20:41:58+02:00</dateMaj>
</lignes>
<lignes>
<personneInfo>
<dateBirth>2016-05-09T00:00:00+02:00</dateBirth>
<firstname>LISA</firstname>
<lastname>EO_NICOLAS</lastname>
<numPerson>266148</numPerson>
<sexe>F</sexe>
</personneInfo>
<idPanier>S10055641698</idPanier>
<id>S10055641699</id>
<idIns>S10055641665</idIns>
<idInsLieu>S10055641696</idInsLieu>
<inscription>
<dateDeb>2023-02-01T00:00:00+01:00</dateDeb>
<dateFin>2023-06-30T00:00:00+02:00</dateFin>
<idAct>A10053179798</idAct>
<idInsAct>S10055641695</idInsAct>
<idInsLieu>S10055641696</idInsLieu>
<idLieu>A10053179757</idLieu>
<idRegie>A10049327667</idRegie>
<idUnit>A10053179809</idUnit>
<libAct>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libAct>
<libLieu>ARGOULETS</libLieu>
<libUnit>MERCREDI - 15h30/17h - 8/15Ans</libUnit>
</inscription>
<qte>1.0</qte>
<prixUnit>150.0</prixUnit>
<montant>150.0</montant>
<datEchn>2023-12-31T00:00:00+01:00</datEchn>
<dateMaj>2023-04-20T20:40:50+02:00</dateMaj>
</lignes>
</basket>
</addPersonUnitBasketResponseBean>

View File

@ -0,0 +1,501 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:readFamilyResponse xmlns:ns2="family.ws.maelis.sigec.com">
<familyResult>
<number>1312</number>
<category>BI</category>
<situation>MARI</situation>
<flagCom>true</flagCom>
<nbChild>3</nbChild>
<nbTotalChild>4</nbTotalChild>
<nbAES>1</nbAES>
<RL1>
<num>266143</num>
<firstname>MARGE</firstname>
<lastname>EO_NICOLAS</lastname>
<maidenName>BOUVIER</maidenName>
<quality>MERE</quality>
<civility>MME</civility>
<birth>
<dateBirth>1950-10-01T00:00:00+01:00</dateBirth>
</birth>
<adresse>
<idStreet>2317</idStreet>
<num>4</num>
<street1>RUE ACHILLE VIADIEU</street1>
<town>Springfield</town>
<zipcode>62701</zipcode>
</adresse>
<contact>
<isContactMail>false</isContactMail>
<isContactSms>false</isContactSms>
<isInvoicePdf>false</isInvoicePdf>
</contact>
</RL1>
<RL2>
<num>266144</num>
<firstname>HOMER</firstname>
<lastname>EO_NICOLAS</lastname>
<quality>PERE</quality>
<civility>MR</civility>
<birth>
<dateBirth>1956-05-12T00:00:00+01:00</dateBirth>
</birth>
<adresse>
<idStreet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<num>742</num>
<street1>Evergreen Terrace</street1>
<town>Springfield</town>
<zipcode>90701</zipcode>
</adresse>
<contact>
<phone>0122222222</phone>
<mobile>0622222222</mobile>
<mail>homer.simpson@example.org.com</mail>
<isContactMail>true</isContactMail>
<isContactSms>true</isContactSms>
<isInvoicePdf>true</isInvoicePdf>
</contact>
<profession>
<codeCSP>46</codeCSP>
<profession>Inspecteur de sécurité</profession>
<employerName>Burns</employerName>
<phone>0133333333</phone>
<addressPro>
<zipcode>90701</zipcode>
<town>Springfield</town>
</addressPro>
</profession>
<CAFInfo>
<number>123</number>
<organ>GENE</organ>
</CAFInfo>
<indicatorList>
<code>AVL</code>
<libelle>Auxiliaire de Vie loisirs</libelle>
</indicatorList>
<indicatorList>
<code>ETABSPEC</code>
<libelle>Etablissement spécialisé</libelle>
<note>SNPP</note>
</indicatorList>
<subscribeActivityList>
<libelle>TEST Promenade lac de plaisance du T.</libelle>
<typeIns>1</typeIns>
<libelleTypeIns>DEFINITIVE</libelleTypeIns>
<subscribesUnit>
<idUnit>A10055638555</idUnit>
<libelle>TEST Promenade lac de plaisance du T.</libelle>
<bUniStd>true</bUniStd>
<dateStart>2023-10-10T00:00:00+02:00</dateStart>
<dateEnd>2023-10-10T00:00:00+02:00</dateEnd>
</subscribesUnit>
<place>TERRITOIRE OUEST</place>
<typeConsum>SENIOR</typeConsum>
<libelleTypeConsum>SENIOR</libelleTypeConsum>
<idActivity>A10055638553</idActivity>
<typeActivity>
<code>1-APE</code>
<libelle>Activité Pédestre Activité régulière</libelle>
<natureSpec>
<code>1</code>
<libelle>Activités Régulières</libelle>
</natureSpec>
</typeActivity>
</subscribeActivityList>
</RL2>
<childList>
<num>266145</num>
<lastname>EO_NICOLAS</lastname>
<firstname>BART</firstname>
<sexe>M</sexe>
<birth>
<dateBirth>2014-04-01T00:00:00+02:00</dateBirth>
</birth>
<dietcode>MENU_AV</dietcode>
<bPhoto>true</bPhoto>
<bLeaveAlone>true</bLeaveAlone>
<authorizedPersonList>
<personInfo>
<num>266159</num>
<lastname>SIMPSON</lastname>
<firstname>ABRAHAM JEBEDIAH</firstname>
<dateBirth>1927-05-24T00:00:00+01:00</dateBirth>
<civility>MR</civility>
<sexe>M</sexe>
<contact>
<phone>0312345678</phone>
<mail>abe.simpson@example.org</mail>
</contact>
</personInfo>
<personQuality>
<code>13</code>
<libelle>Famille</libelle>
</personQuality>
</authorizedPersonList>
<authorizedPersonList>
<personInfo>
<num>266160</num>
<lastname>SIMPSON</lastname>
<firstname>MONA PENELOPE</firstname>
<dateBirth>1929-03-15T00:00:00Z</dateBirth>
<civility>MME</civility>
<sexe>F</sexe>
<contact>
<phone>0412345678</phone>
<mobile>0612345678</mobile>
<mail>mona.simpson@example.org</mail>
</contact>
</personInfo>
<personQuality>
<code>13</code>
<libelle>Famille</libelle>
</personQuality>
</authorizedPersonList>
<indicatorList>
<code>LUNETTE</code>
<libelle>Port de lunettes</libelle>
</indicatorList>
<indicatorList>
<code>AUTRE</code>
<libelle>Autre</libelle>
<note>rebellious</note>
</indicatorList>
<medicalRecord>
<familyDoctor>
<name>MONROE</name>
<phone>0612341234</phone>
<address>
<street1>Alameda</street1>
<zipcode>90701</zipcode>
<town>Springfield</town>
</address>
</familyDoctor>
<allergy1>butterscotch, imitation butterscotch, glow-in-the-dark monster make-up</allergy1>
<allergy2>shrimp and cauliflower</allergy2>
<comment1>the shrimp allergy isn't fully identified</comment1>
<observ1>Ay Caramba!</observ1>
<observ2>Eat my shorts!</observ2>
<isAuthHospital>true</isAuthHospital>
<hospital>Springfield General Hospital</hospital>
<vaccinList>
<code>24</code>
<libelle>IMOVAX OREILLONS</libelle>
<vaccinationDate>2022-02-22T00:00:00+01:00</vaccinationDate>
</vaccinList>
<vaccinList>
<code>45</code>
<libelle>DT TETANOS COQ</libelle>
<vaccinationDate>2011-01-11T00:00:00+01:00</vaccinationDate>
</vaccinList>
</medicalRecord>
<paiInfoBean>
<code>PAI_01</code>
<dateDeb>2022-09-01T00:00:00+02:00</dateDeb>
<dateFin>2023-07-01T00:00:00+02:00</dateFin>
<description>mischievous, rebellious, misunderstood, disruptive</description>
</paiInfoBean>
<mother>
<num>266143</num>
<civility>MME</civility>
<firstname>MARGE</firstname>
<lastname>EO_NICOLAS</lastname>
</mother>
<father>
<num>266144</num>
<civility>MR</civility>
<firstname>HOMER</firstname>
<lastname>EO_NICOLAS</lastname>
</father>
<subscribeActivityList>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<typeIns>1</typeIns>
<libelleTypeIns>DEFINITIVE</libelleTypeIns>
<subscribesUnit>
<idUnit>A10053179800</idUnit>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<bUniStd>true</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<subscribesUnit>
<idUnit>A10053179809</idUnit>
<libelle>MERCREDI - 15h30/17h - 8/15Ans</libelle>
<bUniStd>false</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<place>ARGOULETS</place>
<planningHebdomadaire>1101111</planningHebdomadaire>
<typeConsum>INS</typeConsum>
<libelleTypeConsum>INSCRIT</libelleTypeConsum>
<idActivity>A10053179798</idActivity>
<typeActivity>
<code>1-AAQ</code>
<libelle>Activités Aquatiques Activité Réguliére</libelle>
<natureSpec>
<code>1</code>
<libelle>Activités Régulières</libelle>
</natureSpec>
</typeActivity>
</subscribeActivityList>
<subscribeActivityList>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<typeIns>1</typeIns>
<libelleTypeIns>DEFINITIVE</libelleTypeIns>
<subscribesUnit>
<idUnit>A10053179800</idUnit>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<bUniStd>true</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<subscribesUnit>
<idUnit>A10053179809</idUnit>
<libelle>MERCREDI - 15h30/17h - 8/15Ans</libelle>
<bUniStd>false</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<place>ARGOULETS</place>
<planningHebdomadaire>1101111</planningHebdomadaire>
<typeConsum>INS</typeConsum>
<libelleTypeConsum>INSCRIT</libelleTypeConsum>
<idActivity>A10053179798</idActivity>
<typeActivity>
<code>1-AAQ</code>
<libelle>Activités Aquatiques Activité Réguliére</libelle>
<natureSpec>
<code>1</code>
<libelle>Activités Régulières</libelle>
</natureSpec>
</typeActivity>
</subscribeActivityList>
<subscribeActivityList>
<libelle>ADL ELEMENTAIRE TEST 2023</libelle>
<typeIns>1</typeIns>
<libelleTypeIns>DEFINITIVE</libelleTypeIns>
<subscribesUnit>
<idUnit>A10055638275</idUnit>
<libelle>ADL ELEMENTAIRE TEST 2023</libelle>
<bUniStd>true</bUniStd>
<dateStart>2023-04-11T00:00:00+02:00</dateStart>
<dateEnd>2023-09-21T00:00:00+02:00</dateEnd>
</subscribesUnit>
<place>MAOURINE (la) ELEMENTAIRE</place>
<planningHebdomadaire>0000011</planningHebdomadaire>
<typeConsum>ENF</typeConsum>
<libelleTypeConsum>ENFANT</libelleTypeConsum>
<idActivity>A10055638273</idActivity>
<typeActivity>
<code>EXTVAC</code>
<libelle>Vacances</libelle>
<natureSpec>
<code>X</code>
<libelle>Extrascolaire</libelle>
</natureSpec>
</typeActivity>
</subscribeActivityList>
</childList>
<childList>
<num>266148</num>
<lastname>EO_NICOLAS</lastname>
<firstname>LISA</firstname>
<sexe>F</sexe>
<birth>
<dateBirth>2016-05-09T00:00:00+02:00</dateBirth>
</birth>
<dietcode>MENU_SV</dietcode>
<bPhoto>false</bPhoto>
<bLeaveAlone>false</bLeaveAlone>
<paiInfoBean>
<code>PAI_02</code>
</paiInfoBean>
<mother>
<num>266143</num>
<civility>MME</civility>
<firstname>MARGE</firstname>
<lastname>EO_NICOLAS</lastname>
</mother>
<father>
<num>266144</num>
<civility>MR</civility>
<firstname>HOMER</firstname>
<lastname>EO_NICOLAS</lastname>
</father>
<subscribeActivityList>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<typeIns>1</typeIns>
<libelleTypeIns>DEFINITIVE</libelleTypeIns>
<subscribesUnit>
<idUnit>A10053179800</idUnit>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<bUniStd>true</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<subscribesUnit>
<idUnit>A10053179809</idUnit>
<libelle>MERCREDI - 15h30/17h - 8/15Ans</libelle>
<bUniStd>false</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<place>ARGOULETS</place>
<planningHebdomadaire>1101111</planningHebdomadaire>
<typeConsum>INS</typeConsum>
<libelleTypeConsum>INSCRIT</libelleTypeConsum>
<idActivity>A10053179798</idActivity>
<typeActivity>
<code>1-AAQ</code>
<libelle>Activités Aquatiques Activité Réguliére</libelle>
<natureSpec>
<code>1</code>
<libelle>Activités Régulières</libelle>
</natureSpec>
</typeActivity>
</subscribeActivityList>
<subscribeActivityList>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<typeIns>1</typeIns>
<libelleTypeIns>DEFINITIVE</libelleTypeIns>
<subscribesUnit>
<idUnit>A10053179800</idUnit>
<libelle>TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES</libelle>
<bUniStd>true</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<subscribesUnit>
<idUnit>A10053179809</idUnit>
<libelle>MERCREDI - 15h30/17h - 8/15Ans</libelle>
<bUniStd>false</bUniStd>
<dateStart>2023-02-01T00:00:00+01:00</dateStart>
<dateEnd>2023-06-30T00:00:00+02:00</dateEnd>
</subscribesUnit>
<place>ARGOULETS</place>
<planningHebdomadaire>1101111</planningHebdomadaire>
<typeConsum>INS</typeConsum>
<libelleTypeConsum>INSCRIT</libelleTypeConsum>
<idActivity>A10053179798</idActivity>
<typeActivity>
<code>1-AAQ</code>
<libelle>Activités Aquatiques Activité Réguliére</libelle>
<natureSpec>
<code>1</code>
<libelle>Activités Régulières</libelle>
</natureSpec>
</typeActivity>
</subscribeActivityList>
<subscribeActivityList>
<libelle>ADL ELEMENTAIRE TEST 2023</libelle>
<typeIns>1</typeIns>
<libelleTypeIns>DEFINITIVE</libelleTypeIns>
<subscribesUnit>
<idUnit>A10055638275</idUnit>
<libelle>ADL ELEMENTAIRE TEST 2023</libelle>
<bUniStd>true</bUniStd>
<dateStart>2023-04-11T00:00:00+02:00</dateStart>
<dateEnd>2023-09-21T00:00:00+02:00</dateEnd>
</subscribesUnit>
<place>MAOURINE (la) ELEMENTAIRE</place>
<planningHebdomadaire>0000011</planningHebdomadaire>
<typeConsum>ENF</typeConsum>
<libelleTypeConsum>ENFANT</libelleTypeConsum>
<idActivity>A10055638273</idActivity>
<typeActivity>
<code>EXTVAC</code>
<libelle>Vacances</libelle>
<natureSpec>
<code>X</code>
<libelle>Extrascolaire</libelle>
</natureSpec>
</typeActivity>
</subscribeActivityList>
</childList>
<childList>
<num>266149</num>
<lastname>EO_NICOLAS</lastname>
<firstname>MAGGIE</firstname>
<sexe>F</sexe>
<birth>
<dateBirth>2018-12-17T00:00:00+01:00</dateBirth>
</birth>
<dietcode>MENU_PAI</dietcode>
<bPhoto>false</bPhoto>
<bLeaveAlone>false</bLeaveAlone>
<paiInfoBean>
<code>PAI_02</code>
</paiInfoBean>
<mother>
<num>266143</num>
<civility>MME</civility>
<firstname>MARGE</firstname>
<lastname>EO_NICOLAS</lastname>
</mother>
<father>
<num>266144</num>
<civility>MR</civility>
<firstname>HOMER</firstname>
<lastname>EO_NICOLAS</lastname>
</father>
</childList>
<childList>
<num>266150</num>
<lastname>EO_NICOLAS</lastname>
<firstname>HUGO</firstname>
<sexe>M</sexe>
<birth>
<dateBirth>2018-04-01T00:00:00+02:00</dateBirth>
</birth>
<dietcode>MENU_AV</dietcode>
<bPhoto>false</bPhoto>
<bLeaveAlone>false</bLeaveAlone>
<paiInfoBean>
<code>PAI_01</code>
</paiInfoBean>
<mother>
<num>266143</num>
<civility>MME</civility>
<firstname>MARGE</firstname>
<lastname>EO_NICOLAS</lastname>
</mother>
<father>
<num>266144</num>
<civility>MR</civility>
<firstname>HOMER</firstname>
<lastname>EO_NICOLAS</lastname>
</father>
</childList>
<emergencyPersonList>
<numPerson>266161</numPerson>
<civility>MME</civility>
<firstname>PATTY</firstname>
<lastname>BOUVIER</lastname>
<dateBirth>1948-08-30T00:00:00+01:00</dateBirth>
<sexe>F</sexe>
<quality>13</quality>
<contact>
<phone>0112345678</phone>
<mobile>0612345678</mobile>
<mail>patty.bouvier@example.org</mail>
</contact>
</emergencyPersonList>
<emergencyPersonList>
<numPerson>266162</numPerson>
<civility>MME</civility>
<firstname>SELMA</firstname>
<lastname>BOUVIER</lastname>
<dateBirth>1946-04-29T00:00:00+01:00</dateBirth>
<sexe>F</sexe>
<quality>13</quality>
<contact>
<phone>0112345678</phone>
<mobile>0612345678</mobile>
<mail>selma.bouvier@example.org</mail>
</contact>
</emergencyPersonList>
</familyResult>
</ns2:readFamilyResponse>
</soap:Body>
</soap:Envelope>

View File

@ -0,0 +1,7 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:updatePersonScheduleResponse xmlns:ns2="activity.ws.maelis.sigec.com" xmlns:ns3="bean.persistence.activity.ws.maelis.sigec.com" xmlns:ns4="bean.persistence.school.ws.maelis.sigec.com">
<resultBean/>
</ns2:updatePersonScheduleResponse>
</soap:Body>
</soap:Envelope>

View File

@ -15,6 +15,7 @@
import base64
import datetime
import json
import logging
import os
from unittest import mock
@ -138,6 +139,21 @@ def ape_service():
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')
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
@ -7515,48 +7531,81 @@ def test_add_person_basket_subscription(family_service, activity_service, con, a
assert resp.json['data'] == {
'controlResult': {'controlOK': True, 'message': None},
'basket': {
'codeRegie': None,
'dateAdd': '2023-01-22T18:55:09+01:00',
'dateMaj': '2023-01-22T18:55:09+01:00',
'delai': 0,
'id': 'S10053190349',
'idFam': 'S10053180370',
'codeRegie': 109,
'dateAdd': '2023-04-20T20:41:58+02:00',
'dateMaj': '2023-04-20T20:40:50+02:00',
'delai': 30,
'id': 'S10055641698',
'idFam': 'S10055638201',
'lignes': [
{
'datEchn': None,
'dateMaj': '2023-01-22T18:55:09+01:00',
'id': 'S10053190350',
'idIns': 'S10053190347',
'idInsLieu': 'S10053190348',
'idPanier': 'S10053190349',
'inscription': {
'dateDeb': '2022-09-01T00:00:00+02:00',
'dateFin': '2023-08-31T00:00:00+02:00',
'idAct': 'A10051141965',
'idInsAct': 'S10053190347',
'idInsLieu': 'S10053190348',
'idLieu': 'A10053179226',
'idRegie': 'A10049327675',
'idUnit': 'A10051141990',
'libAct': 'Vitrail Fusing 1/2 Je Adultes 2022/2023 - Mardi 14h-17h',
'libAct2': None,
'libLieu': 'Centre Culturel ALBAN MINVILLE',
'libUnit': 'Inscription 1er semestre',
},
'montant': None,
'personneInfo': {
'dateBirth': '2014-04-01T00:00:00+02:00',
'firstname': 'BART',
'lastname': 'SIMPSON',
'numPerson': 246423,
'lastname': 'EO_NICOLAS',
'numPerson': 266145,
'sexe': 'M',
},
'prixUnit': None,
'qte': None,
}
'idPanier': 'S10055641698',
'id': 'S10055641705',
'idIns': 'S10055641658',
'idInsLieu': 'S10055641703',
'inscription': {
'dateDeb': '2023-02-01T00:00:00+01:00',
'dateFin': '2023-06-30T00:00:00+02:00',
'idAct': 'A10053179798',
'idInsAct': 'S10055641702',
'idInsLieu': 'S10055641703',
'idLieu': 'A10053179757',
'idRegie': 'A10049327667',
'idUnit': 'A10053179809',
'libAct': 'TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES',
'libAct2': None,
'libLieu': 'ARGOULETS',
'libUnit': 'MERCREDI - 15h30/17h - 8/15Ans',
},
'qte': 1.0,
'prixUnit': 150.0,
'montant': 150.0,
'datEchn': '2023-12-31T00:00:00+01:00',
'dateMaj': '2023-04-20T20:41:58+02:00',
},
{
'personneInfo': {
'dateBirth': '2016-05-09T00:00:00+02:00',
'firstname': 'LISA',
'lastname': 'EO_NICOLAS',
'numPerson': 266148,
'sexe': 'F',
},
'idPanier': 'S10055641698',
'id': 'S10055641699',
'idIns': 'S10055641665',
'idInsLieu': 'S10055641696',
'inscription': {
'dateDeb': '2023-02-01T00:00:00+01:00',
'dateFin': '2023-06-30T00:00:00+02:00',
'idAct': 'A10053179798',
'idInsAct': 'S10055641695',
'idInsLieu': 'S10055641696',
'idLieu': 'A10053179757',
'idRegie': 'A10049327667',
'idUnit': 'A10053179809',
'libAct': 'TEST ECOLE DES SPORTS 22/23 SEMESTRE 2 - MULTIACTIVITES',
'libAct2': None,
'libLieu': 'ARGOULETS',
'libUnit': 'MERCREDI - 15h30/17h - 8/15Ans',
},
'qte': 1.0,
'prixUnit': 150.0,
'montant': 150.0,
'datEchn': '2023-12-31T00:00:00+01:00',
'dateMaj': '2023-04-20T20:40:50+02:00',
},
],
},
}
assert con.subscription_set.count() == 0
params['recurrent_week'] = None
resp = app.post_json(url + '?NameID=local', params=params)
@ -7599,6 +7648,43 @@ def test_add_person_basket_subscription_with_recurrent_week(family_service, acti
assert resp.json['err'] == 0
def test_basket_subscription_providing_wcs_demand(family_service, activity_service, con, app):
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'))
Link.objects.create(resource=con, family_id='1312', name_id='local')
url = get_endpoint('add-person-basket-subscription')
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',
}
resp = app.post_json(url + '?NameID=local', params=params)
assert resp.json['err'] == 0
assert resp.json['data']['controlResult']['controlOK'] is True
assert resp.json['data']['basket']['codeRegie'] == 109
# unrelated basket lines was removed from response
assert all([x['personneInfo']['numPerson'] == 266145 for x in resp.json['data']['basket']['lignes']])
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert (
subscription.wcs_form_api_url == 'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/'
)
assert subscription.created is not None
assert subscription.regie_id == '109'
assert subscription.family_id == '1312'
assert subscription.invoice is None
assert subscription.status() == 'pending_basket'
assert subscription.maelis_data == resp.json['data']
assert [x['idIns'] for x in subscription.maelis_data['basket']['lignes']] == ['S10055641658']
def test_add_person_basket_subscription_error(family_service, activity_service, con, app):
family_service.add_soap_response('readFamily', get_xml_file('R_read_family.xml'))
activity_service.add_soap_response(
@ -8643,6 +8729,53 @@ def test_get_baskets(activity_service, con, app):
}
def test_get_baskets_having_wcs_demand(family_service, activity_service, con, app):
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'))
Link.objects.create(resource=con, family_id='1312', name_id='local')
url = get_endpoint('get-baskets')
# subscribe providing a wcs demand
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 subscription.trigger_status() == 'pending'
assert [x['idIns'] for x in subscription.maelis_data['basket']['lignes']] == ['S10055641658']
# get basket having subscription still here
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket.xml'))
resp = app.get(url + '?NameID=local')
assert resp.json['err'] == 0
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.status() == 'pending_basket'
assert subscription.trigger_status() == 'pending'
# get basket having subscription removed
activity_service.add_soap_response('getFamilyBasket', get_xml_file('R_get_family_basket_empty.xml'))
resp = app.get(url + '?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'
def test_get_baskets_not_linked_error(con, app):
url = get_endpoint('get-baskets')
resp = app.get(url + '?NameID=local')
@ -10014,3 +10147,599 @@ def test_invoice_pdf_error(invoice_service, con, app):
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'django.http.response.Http404'
assert resp.json['err_desc'] == 'Fichier PDF non trouvé'
def test_trigger_wcs_on_paid_subscriptions_cron(
family_service, activity_service, invoice_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.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.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
assert resp.json['data']['controlResult']['controlOK'] is True
assert resp.json['data']['basket']['codeRegie'] == 109
assert len(resp.json['data']['basket']['lignes']) == 1
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.created.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 18:20:00'
assert subscription.invoice is None
assert subscription.status() == 'pending_basket'
assert subscription.maelis_data == resp.json['data']
assert [x['idIns'] for x in subscription.maelis_data['basket']['lignes']] == ['S10055641658']
# validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?NameID=local', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
assert len(resp.json['data']['factureLst']) == 1 # basket validation generate only one invoice
assert resp.json['data']['factureLst'][0]['regie']['code'] == 109
assert resp.json['data']['factureLst'][0]['numInvoice'] == '18'
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.created.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 18:30:00'
assert invoice.basket_generation_date.strftime('%Y-%m-%d %H:%M:%S') == '2023-03-03 18:30:00'
assert invoice.status() == 'created'
assert invoice.maelis_data == resp.json['data']['factureLst'][0]
assert 'S10055641658' in [x['idIns'] for x in invoice.maelis_data['lineInvoiceList']]
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.status() == 'pending_invoice'
assert subscription.trigger_status() == 'pending'
# get family invoices
freezer.move_to('2023-03-03 18:35:00')
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert '1312-18' in [x['id'] for x in resp.json['data']]
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
# pay invoice
freezer.move_to('2023-03-03 18:38:00')
resp = app.post_json(
get_endpoint('regie/109/invoice/1312-18/pay/') + '?NameID=ignored',
params={
'transaction_date': '2023-03-03T18:38:00',
'transaction_id': '1c5451752a064fc2bd7ea750998683e1',
},
)
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'paid'
job = Job.objects.get(method_name='notify_invoice_paid_job', natural_id='109/18')
assert job.status == 'registered'
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.invoice == invoice
assert subscription.status() == 'paid'
assert subscription.trigger_status() == 'triggering'
assert subscription.wcs_trigger_payload['data']['subscription_status'] == 'paid'
# 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'] == 0
assert trigger_body['data']['subscription_status'] == 'paid'
assert trigger_body['data']['invoice_status'] == 'paid'
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.wcs_trigger_date is not None
assert subscription.trigger_status() == 'triggered'
def test_trigger_wcs_on_paid_subscriptions_job(
family_service, activity_service, invoice_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.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
invoice_service.add_soap_response('readInvoices', get_xml_file('R_read_invoices_regie_109.xml'))
invoice_service.add_soap_response('payInvoices', get_xml_file('R_pay_invoices.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']
# validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?NameID=local', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
assert 'S10055641658' in [x['idIns'] for x in invoice.maelis_data['lineInvoiceList']]
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.status() == 'pending_invoice'
# get family invoices
freezer.move_to('2023-03-03 18:35:00')
resp = app.get(get_endpoint('regie/109/invoices') + '?family_id=1312')
assert '1312-18' in [x['id'] for x in resp.json['data']]
# pay invoice
freezer.move_to('2023-03-03 18:38:00')
resp = app.post_json(
get_endpoint('regie/109/invoice/1312-18/pay/') + '?NameID=ignored',
params={
'transaction_date': '2023-03-03T18:38:00',
'transaction_id': '1c5451752a064fc2bd7ea750998683e1',
},
)
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'paid'
job = Job.objects.get(method_name='notify_invoice_paid_job', natural_id='109/18')
assert job.status == 'registered'
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.trigger_status() == 'triggering'
job = Job.objects.get(method_name='trigger_subscription_job', natural_id='13-12/%s' % subscription.pk)
assert job.status == 'registered'
# send trigger to wcs
con.jobs()
assert any(
[
'https://wcs.example.com/api/forms/exemple-inscription-loisirs-1/12/hooks/update_subscription/'
in x.request.url
for x in wcs_service.calls
]
)
job = Job.objects.get(method_name='trigger_subscription_job', natural_id='13-12/%s' % subscription.pk)
assert job.status == 'completed'
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.trigger_status() == 'triggered'
def test_trigger_wcs_on_cancelled_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.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
activity_service.add_soap_response(
'cancelInvoiceAndDeleteSubscribeList',
get_xml_file('R_cancel_invoice_and_delete_subscribe_list.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']
# validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?NameID=local', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
assert 'S10055641658' in [x['idIns'] for x in invoice.maelis_data['lineInvoiceList']]
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.status() == 'pending_invoice'
# send invoice cancellation order to maelis and trigger wcs
freezer.move_to('2023-03-03 19:20:00')
con.hourly()
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
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'] == "La facture a été annulée"
assert trigger_body['data']['subscription_status'] == 'cancelled'
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_cancelled_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.xml'))
activity_service.add_soap_response('validateBasket', get_xml_file('R_validate_basket.xml'))
activity_service.add_soap_response(
'cancelInvoiceAndDeleteSubscribeList',
get_xml_file('R_cancel_invoice_and_delete_subscribe_list.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']
# validate basket
freezer.move_to('2023-03-03 18:30:00')
resp = app.post_json(
get_endpoint('validate-basket') + '?NameID=local', params={'basket_id': 'S10055641661'}
)
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'created'
assert 'S10055641658' in [x['idIns'] for x in invoice.maelis_data['lineInvoiceList']]
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.status() == 'pending_invoice'
# send invoice cancellation order to maelis (side effect of get-basket)
freezer.move_to('2023-03-03 19:20:00')
resp = app.get(get_endpoint('get-baskets') + '?NameID=local')
assert resp.json['err'] == 0
invoice = con.invoice_set.get(regie_id=109, invoice_id=18)
assert invoice.status() == 'cancelled'
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.trigger_status() == 'triggering'
job = Job.objects.get(method_name='trigger_subscription_job', natural_id='13-12/%s' % subscription.pk)
assert job.status == 'registered'
# send invoice cancellation order to maelis and trigger 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'] == "La facture a été annulée"
assert trigger_body['data']['subscription_status'] == 'cancelled'
assert trigger_body['data']['regie_text'] == 'DSBL'
assert any(['trigger wcs' in x.message for x in caplog.records])
job = Job.objects.get(method_name='trigger_subscription_job', natural_id='13-12/%s' % subscription.pk)
assert job.status == 'completed'
subscription = con.subscription_set.get(wcs_form_number='13-12')
assert subscription.trigger_status() == 'triggered'
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