saga: limite à 6 mois et récupération des factures du conjoint (fixes #17731)

Le noms des redevables est disponible dans le dictionnaire d'une facture dans
['extra']['redevable'].
This commit is contained in:
Benjamin Dauvergne 2017-07-20 16:00:29 +02:00
parent 8a93b28207
commit 0d51ab873a
4 changed files with 91 additions and 75 deletions

View File

@ -105,6 +105,7 @@ setup(
'six',
'djangorestframework<3.4',
'pytz',
'python-dateutil',
],
zip_safe=False,
cmdclass={

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
import httmock
import xml.etree.ElementTree as ET
@ -28,10 +30,15 @@ def test_tiers_saga(app, settings, nanterre_classic_family):
f['jean'].content['cles_de_federation']['technocarte'] = '1234'
f['jean'].save()
f['marie'].content['cles_de_federation']['technocarte'] = '4567'
f['marie'].save()
assert 'saga_tiers' not in f['jean'].content['cles_de_federation']
# Définition du début de la séquence des codes tiers
utils.set_saga_sequence(42)
# Calcul du code tiers pour Jean
response = app.get(reverse('rsu-api-saga-tiers', kwargs={
'application': 'technocarte',
'identifier': '1234'
@ -46,6 +53,16 @@ def test_tiers_saga(app, settings, nanterre_classic_family):
}))
assert response.json['code'] == 'RG0000000000042'
# Calcul du code tiers pour Marie
response = app.get(reverse('rsu-api-saga-tiers', kwargs={
'application': 'technocarte',
'identifier': '4567'
}))
assert response.json['code'] == 'RG0000000000043'
f['marie'].refresh_from_db()
assert 'saga_tiers' in f['marie'].content['cles_de_federation']
# Mock du web-service SAGA, répond vite celui là
@httmock.urlmatch()
def saga_ok(url, request):
assert url.netloc == 'saga.example.com'
@ -91,7 +108,8 @@ def test_tiers_saga(app, settings, nanterre_classic_family):
'identifier': f['jean'].id,
}))
assert response.json['err'] == 0
assert len(response.json['data']) == 2
assert len(response.json['data']) == 4
assert response.json['data'][0]['extra']['redevable']
num = response.json['data'][0]['num']
num2 = response.json['data'][1]['num']

View File

@ -16,10 +16,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import isodate
import copy
import re
import traceback
from dateutil.relativedelta import relativedelta
import datetime
from django.conf import settings
from django.shortcuts import get_object_or_404
@ -46,19 +49,21 @@ def flatten_errors(serializer_errors):
return errors
def individu_to_text(individu):
def individu_to_text(individu, short=False):
d = individu.content
text = d['nom_de_naissance'] + ' '
if d.get('nom_d_usage'):
text += '(' + d['nom_d_usage'] + ') '
text += d['prenoms']
date = isodate.parse_date(d['date_de_naissance'])
text += ' - %02d/%02d/%04d - ' % (date.day, date.month, date.year)
text += d['genre']
if d.get('statut_legal'):
text += '/' + d['statut_legal']
if not short:
date = isodate.parse_date(d['date_de_naissance'])
text += ' - %02d/%02d/%04d - ' % (date.day, date.month, date.year)
text += d['genre']
if d.get('statut_legal'):
text += '/' + d['statut_legal']
return text
def adresse_to_text(adresse):
d = adresse.content
text = '%(streetnumber)s%(streetnumberext)s %(streetname)s, ' % d
@ -70,6 +75,7 @@ def adresse_to_text(adresse):
text += ' (%(country)s)' % d
return text
def individu_to_response(individu, add_text=False, add_conjoint=True, add_enfant=True,
add_parents=True):
'''Serialize a person'''
@ -1355,15 +1361,25 @@ saga_tiers = SagaTiers.as_view()
class SagaMixin(object):
individu = None
def error_response(self, error, status=200):
logger = logging.getLogger('zoo_nanterre.saga')
if self.individu:
logger.warning(u'SAGA: %s pour %s', error, self.individu)
else:
logger.warning(u'SAGA: %s', error)
return None, Response({
'err': 1,
'errors': [
error
]
}, status=status)
def get_ws(self):
app_dfn = utils.get_application('saga')
if not app_dfn or 'url' not in app_dfn:
return None, Response({
'err': 1,
'errors': [
'URL de l\'application SAGA non configurée',
]
})
return self.error_response('URL de l\'application SAGA non configurée')
ws = self.ws = saga.Saga(app_dfn['url'],
timeout=app_dfn.get('timeout'),
@ -1372,15 +1388,24 @@ class SagaMixin(object):
return ws, None
def get_factures(self, identifier):
self.individu = individu = self.get_individu(identifier)
self.individu = self.get_individu(identifier)
conjoint = utils.conjoint(self.individu)[0]
debut = datetime.date.today() - relativedelta(months=6)
factures, error_response = self.get_facture_for_individu(self.individu, debut=debut)
if error_response:
return None, error_response
if conjoint:
factures_conjoint, error_response = self.get_facture_for_individu(conjoint, debut=debut)
if not error_response:
factures.extend(factures_conjoint)
# retrier les facture par date d'emission
factures.sort(key=lambda f: f.date_facture)
return factures, None
def get_facture_for_individu(self, individu, debut=None):
if 'saga_tiers' not in individu.content['cles_de_federation']:
return None, Response({
'err': 1,
'errors': [
'l\'individu n\'a pas de code tiers SAGA',
]
})
return self.error_response('l\'individu n\'a pas de code tiers SAGA')
code_tiers = individu.content['cles_de_federation']['saga_tiers']
ws, error_response = self.get_ws()
@ -1395,23 +1420,17 @@ class SagaMixin(object):
if not federation:
federation, error = ws.resolve_code_tiers(code_tiers)
if error:
return None, Response({
'err': 1,
'errors': [
error,
]
})
return self.error_response(error)
individu.content['cles_de_federation']['saga'] = federation
individu.save()
factures, error = ws.factures(federation)
factures, error = ws.factures(federation, debut=debut)
if error:
return None, Response({
'err': 1,
'errors': [
error,
]
})
return self.error_response(error)
# définir le redevable sur les factures
redevable = individu_to_text(individu, short=True)
for facture in factures:
facture.extra['redevable'] = redevable
return factures, None
@ -1453,10 +1472,7 @@ class SagaTransaction(SagaFactures):
def post(self, request, identifier, format=None):
serializer = TransactionSagaSerializer(data=request.data)
if not serializer.is_valid():
return Response({
'err': 1,
'errors': flatten_errors(serializer.errors),
}, status=400)
return self.error_response(flatten_errors(serializer.errors), status=400)
data = serializer.validated_data
@ -1471,12 +1487,7 @@ class SagaTransaction(SagaFactures):
if facture.num in data['num_factures']:
factures_a_payer.append(facture)
if not factures_a_payer:
return Response({
'err': 1,
'errors': [
u'numéro(s) de facture inconnu',
]
})
return self.error_response(u'numéro(s) de facture inconnu')
data = serializer.validated_data
email = data.get('email') or self.individu.content['email'] or ''
@ -1486,13 +1497,7 @@ class SagaTransaction(SagaFactures):
urlretour_synchrone=data['urlretour_synchrone'],
email=email)
if error:
return Response({
'err': 1,
'errors': [
error,
]
})
return self.error_response(error)
return Response({
'err': 0,
'data': {
@ -1511,10 +1516,7 @@ class SagaRetourAsynchrone(SagaMixin, APIView):
def post(self, request, format=None):
serializer = RetourSagaSerializer(data=request.data)
if not serializer.is_valid():
return Response({
'err': 1,
'errors': flatten_errors(serializer.errors),
}, status=400)
return self.error_response(flatten_errors(serializer.errors))
ws, error_response = self.get_ws()
if error_response:
@ -1522,12 +1524,7 @@ class SagaRetourAsynchrone(SagaMixin, APIView):
result, error = ws.page_retour_asynchrone(serializer.validated_data['idop'])
if error:
return Response({
'err': 1,
'errors': [
error,
]
})
return self.error_response(error)
return Response({
'err': 0,
@ -1541,10 +1538,7 @@ class SagaRetourSynchrone(SagaMixin, APIView):
def post(self, request, format=None):
serializer = RetourSagaSerializer(data=request.data)
if not serializer.is_valid():
return Response({
'err': 1,
'errors': flatten_errors(serializer.errors),
}, status=400)
return self.error_response(flatten_errors(serializer.errors))
ws, error_response = self.get_ws()
if error_response:
@ -1552,12 +1546,7 @@ class SagaRetourSynchrone(SagaMixin, APIView):
result, error = ws.page_retour_synchrone(serializer.validated_data['idop'])
if error:
return Response({
'err': 1,
'errors': [
error,
]
})
return self.error_response(error)
return Response({
'err': 0,

View File

@ -13,7 +13,7 @@ from collections import namedtuple
Facture = namedtuple('Facture', ['date_facture', 'date_limite_recouvrement', 'etat',
'incident_paiement', 'montant_initial', 'num', 'reste_a_payer',
'creances', 'commentaire'])
'creances', 'commentaire', 'extra'])
Creance = namedtuple('Creance', [
'imputation',
@ -97,20 +97,27 @@ class Saga(object):
if t:
return t.text
def factures(self, federation):
def factures(self, federation, debut=None, fin=None):
'''
federation - string
debut - datetime
fin - datetime
'''
body = '''
<etatFactureParTiersFedere>
<num_tiers>{federation}</num_tiers>
<num_service>{num_service}</num_service>
<type_facture>toute</type_facture>
<periode_debut></periode_debut>
<periode_fin></periode_fin>
<periode_debut>{periode_debut}</periode_debut>
<periode_fin>{periode_fin}</periode_fin>
<detail>oui</detail>
</etatFactureParTiersFedere>'''
tree, error = self.soap_call(
self.creance_url, body, 'etatFactureParTiersFedereReturn',
num_service=self.num_service,
periode_debut=debut.strftime('%d/%m/%Y') if debut else '',
periode_fin=fin.strftime('%d/%m/%Y') if fin else '',
federation=federation, ns=self.ns)
if tree is None:
return None, error
@ -147,7 +154,8 @@ class Saga(object):
num=a['num'],
reste_a_payer=decimal.Decimal(a['reste_a_payer']),
commentaire=self.get_child_content(t, 'commentaire'),
creances=list(helper2()))
creances=list(helper2()),
extra={})
yield facture
return list(helper()), None