870 lines
34 KiB
Python
870 lines
34 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import json
|
|
import datetime
|
|
import requests
|
|
from requests.exceptions import RequestException
|
|
|
|
from django.utils.timezone import now
|
|
from django.conf import settings
|
|
from django.db import DatabaseError
|
|
|
|
from zoo.models import Entity, Job
|
|
|
|
from . import utils
|
|
|
|
|
|
def adresse_to_fragment(adresse, principale=False):
|
|
'''Converti une adresse pour la construction d'un fragment'''
|
|
adresse_content = adresse.content
|
|
return {
|
|
'chez': adresse_content['at'],
|
|
'complement1': adresse_content['ext1'],
|
|
'complement2': adresse_content['ext2'],
|
|
'numero': adresse_content['streetnumber'],
|
|
'extension': adresse_content['streetnumberext'],
|
|
'voie': adresse_content['streetname'],
|
|
'code-voie': adresse_content['streetmatriculation'],
|
|
'code-postal': adresse_content['zipcode'],
|
|
'ville': adresse_content['city'],
|
|
'insee': adresse_content['inseecode'],
|
|
'pays': adresse_content['country'],
|
|
'estAdressePrincipale': principale,
|
|
}
|
|
|
|
|
|
def individu_to_fragment(individu, inclure_adresse=True, application=None, identite=True,
|
|
contact=True):
|
|
'''Converti un individu pour la construction d'un fragment'''
|
|
content = individu.content
|
|
SEXE = {
|
|
'femme': 'F',
|
|
'homme': 'H',
|
|
'autre': 'O',
|
|
}
|
|
sexe = SEXE[content['genre']]
|
|
fragment = {
|
|
}
|
|
if identite:
|
|
fragment.update({
|
|
'sexe': sexe,
|
|
'prenom': content['prenoms'],
|
|
'nom': content['nom_d_usage'] or content['nom_de_naissance'],
|
|
'nom-naissance': content['nom_de_naissance'],
|
|
'date-naissance': content['date_de_naissance'],
|
|
})
|
|
if contact:
|
|
fragment['email'] = content.get('email', u'')
|
|
telephones = []
|
|
for telephone in content.get('telephones', []):
|
|
telephone = telephone.copy()
|
|
t = telephone.get('type')
|
|
if t == 'maison':
|
|
telephone['type'] = 'domicile'
|
|
elif t == 'pro':
|
|
telephone['type'] = 'professionnel'
|
|
elif t == 'mobile':
|
|
pass
|
|
else:
|
|
continue
|
|
telephone['poste'] = None
|
|
telephones.append(telephone)
|
|
fragment['telephones'] = telephones
|
|
|
|
if inclure_adresse:
|
|
if content['statut_legal'] == 'mineur':
|
|
adresses = utils.adresses(individu)
|
|
fragment['adresses'] = [adresse_to_fragment(adresse,
|
|
principale=rel.content['principale'])
|
|
for adresse, rel in adresses]
|
|
else:
|
|
adresse = utils.adresse(individu)
|
|
assert adresse, 'individu %s avec plus d\'une adresse ou sans adresse' % individu.id
|
|
if adresse:
|
|
fragment['adresse'] = adresse_to_fragment(adresse)
|
|
if application:
|
|
id_metier = content.get('cles_de_federation', {}).get(application)
|
|
assert id_metier
|
|
fragment['id-metier'] = id_metier
|
|
return fragment
|
|
|
|
|
|
class FakeResponse(object):
|
|
status_code = 200
|
|
|
|
def __init__(self, content):
|
|
self.content = content
|
|
|
|
@property
|
|
def text(self):
|
|
return json.dumps(self.content, ensure_ascii=False)
|
|
|
|
def json(self):
|
|
return self.content
|
|
|
|
|
|
class FragmentBuilder(object):
|
|
ID_FRAGMENT = 'id-fragment'
|
|
|
|
human_result = None
|
|
state_on_network_error = Job.STATE_ERROR
|
|
lock_individus = False
|
|
|
|
def __init__(self):
|
|
self.fragments = []
|
|
self.individu_ids = {}
|
|
self.beneficiaires = []
|
|
self.origine = 'agent'
|
|
self.error = {}
|
|
self.response = {}
|
|
self.fragment_counter = 1
|
|
self.fragment_to_id = {}
|
|
self.id_to_fragment = {}
|
|
self.date_soumission = None
|
|
self.meta = None
|
|
|
|
def add_fragment(self, fragment):
|
|
old_fragment_counter = self.fragment_counter
|
|
fragment[self.ID_FRAGMENT] = self.fragment_counter
|
|
self.fragments.append(fragment)
|
|
self.fragment_counter += 1
|
|
return old_fragment_counter
|
|
|
|
@classmethod
|
|
def create(cls, application, individus, meta=None):
|
|
self = cls()
|
|
self.application_id = application
|
|
self.application = utils.get_application(application)
|
|
assert 'rsu_ws_url' in self.application, \
|
|
'application %s does not have a rsu_ws_url' % application
|
|
assert individus, 'pas d\'individus, pas de flux'
|
|
self.individus = individus
|
|
self.meta = meta
|
|
if self.lock_individus:
|
|
# on verrouille les entités si nécessaire
|
|
Entity.objects.filter(
|
|
id__in=[individu.id for individu in self.individus]
|
|
).select_for_update()
|
|
return self
|
|
|
|
def message(self):
|
|
content = {
|
|
'metadonnees': {
|
|
'service': self.service,
|
|
'origine': self.origine,
|
|
'date-soumission': self.date_soumission,
|
|
|
|
},
|
|
'fragments': self.fragments,
|
|
}
|
|
if self.beneficiaires:
|
|
content['metadonnees']['beneficiaires'] = self.beneficiaires
|
|
return content
|
|
|
|
def debug_response(self):
|
|
return []
|
|
|
|
def do(self, job=None, **kwargs):
|
|
# verrouille les objets
|
|
try:
|
|
if self.lock_individus:
|
|
Entity.objects.filter(
|
|
id__in=[individu.id for individu in self.individus]).select_for_update(
|
|
nowait=True)
|
|
except DatabaseError:
|
|
self.human_result = (
|
|
'Erreur due à une action concurrente sur les mêmes individus dans le RSU')
|
|
self.error = {
|
|
'code': 'lock-error',
|
|
}
|
|
for individu in self.individus:
|
|
utils.journalize(
|
|
individu,
|
|
meta=self.meta,
|
|
job_url=job and job.admin_url,
|
|
transaction=job and job.transaction,
|
|
text=self.human_result,
|
|
**kwargs)
|
|
return self.state_on_network_error
|
|
error_detail = None
|
|
try:
|
|
if settings.ZOO_NANTERRE_RSU_WS_DEBUG:
|
|
response = FakeResponse(self.debug_response())
|
|
else:
|
|
self.date_soumission = datetime.datetime.now().isoformat(' ').split('.')[0]
|
|
response = requests.post(
|
|
self.application['rsu_ws_url'],
|
|
data=json.dumps(self.message(), ensure_ascii=False).encode('utf-8'),
|
|
timeout=settings.ZOO_NANTERRE_RSU_TIMEOUT,
|
|
headers={
|
|
'Content-Type': 'application/json'
|
|
})
|
|
except RequestException as e:
|
|
error_detail = u'erreur réseau/SSL ou expiration'
|
|
self.error = {
|
|
'code': 'transport-error',
|
|
'detail': str(e),
|
|
}
|
|
state = self.state_on_network_error
|
|
else:
|
|
state = self.handle_response(response, job=job)
|
|
app_name = self.application['name']
|
|
kwargs = {}
|
|
message = self.journal_message
|
|
if state == job.STATE_SUCCESS:
|
|
# reset errors
|
|
self.error = {}
|
|
message += u': ok'
|
|
elif state == Job.STATE_ERROR:
|
|
message += u': erreur temporaire'
|
|
kwargs['error'] = self.error
|
|
elif state == Job.STATE_UNRECOVERABLE_ERROR:
|
|
message += u': erreur irrécupérable'
|
|
kwargs['error'] = self.error
|
|
else:
|
|
raise NotImplementedError
|
|
if (self.error
|
|
and self.error.get('code') == 'rsu-error'
|
|
and isinstance(self.error.get('content'), dict)
|
|
and self.error['content'].get('metier')):
|
|
message += ': ' + self.error['content']['metier']
|
|
if error_detail:
|
|
message += u': ' + error_detail
|
|
self.human_result = message
|
|
# on journalise:
|
|
# - si c'est un succès ou une erreur irrécupérable
|
|
# - dans tous les cas si l'état actuel du job n'est pas une erreur (TODO), i.e. on
|
|
# journalise toujours au premier essai
|
|
if (state == Job.STATE_SUCCESS or state == Job.STATE_UNRECOVERABLE_ERROR
|
|
or (job.state != Job.STATE_ERROR
|
|
and job.state != Job.STATE_UNRECOVERABLE_ERROR)):
|
|
for individu in self.individus:
|
|
utils.journalize(
|
|
individu,
|
|
meta=self.meta,
|
|
job_url=job and job.admin_url,
|
|
transaction=job and job.transaction,
|
|
text=message,
|
|
**kwargs)
|
|
return state
|
|
|
|
@property
|
|
def journal_message(self):
|
|
app_name = self.application['name']
|
|
return u'Synchronisation avec %s' % app_name
|
|
|
|
def to_json(self):
|
|
s = {
|
|
'application_id': self.application_id,
|
|
'application': self.application,
|
|
'individus': [],
|
|
'fragment_to_id': {k: v.id for k, v in self.fragment_to_id.items()},
|
|
'fragments': self.fragments,
|
|
'beneficiaires': self.beneficiaires,
|
|
'meta': self.meta,
|
|
'response': self.response,
|
|
'date_soumission': self.date_soumission,
|
|
'state_on_network_error': self.state_on_network_error,
|
|
}
|
|
for individu in sorted(self.individus, key=lambda i: i.id):
|
|
s['individus'].append(individu.id)
|
|
if self.error:
|
|
s['error'] = self.error
|
|
return s
|
|
|
|
@classmethod
|
|
def from_json(cls, serialization):
|
|
self = cls()
|
|
|
|
self.application_id = serialization['application_id']
|
|
self.application = serialization['application']
|
|
ids = [individu_id for individu_id in serialization.get('individus', [])]
|
|
self.individus = Entity.objects.filter(id__in=ids)
|
|
id_map = {individu.id: individu for individu in self.individus}
|
|
self.fragments = serialization['fragments']
|
|
self.fragment_to_id = serialization['fragment_to_id']
|
|
# JSON normalize keys to strings
|
|
self.fragment_to_id = {int(k): v for k, v in self.fragment_to_id.items()}
|
|
for other_id in set(self.fragment_to_id.values()) - set(id_map.keys()):
|
|
id_map[other_id] = Entity.objects.get(id=other_id)
|
|
# rebuild reverse of fragment_to_id
|
|
self.fragment_to_id = {k: id_map[v] for k, v in self.fragment_to_id.items()}
|
|
self.id_to_fragment = {v: k for k, v in self.fragment_to_id.items()}
|
|
self.error = serialization.get('error', {})
|
|
self.beneficiaires = serialization['beneficiaires']
|
|
self.meta = serialization['meta']
|
|
self.response = serialization['response']
|
|
self.date_soumission = serialization.get('date_soumission')
|
|
self.state_on_network_error = serialization.get('state_on_network_error',
|
|
cls.state_on_network_error)
|
|
return self
|
|
|
|
def reference(self, individu):
|
|
ref = individu.content['cles_de_federation'].get(self.application_id)
|
|
if ref:
|
|
return ref
|
|
if individu in self.id_to_fragment:
|
|
return '@%s' % self.id_to_fragment[individu]
|
|
return None
|
|
|
|
def relation_situation_familiale(self, individu1, individu2, relation):
|
|
if relation.content['statut'] == 'pacs/mariage':
|
|
type_de_relation = 'Marie'
|
|
elif relation.content['statut'] == 'unionlibre':
|
|
type_de_relation = 'UnionLibre'
|
|
else:
|
|
raise NotImplementedError
|
|
beneficiaire1 = self.reference(individu1)
|
|
beneficiaire2 = self.reference(individu2)
|
|
assert beneficiaire1
|
|
assert beneficiaire2
|
|
fragment = {
|
|
'type': 'relation-situation-familiale',
|
|
'fragment': {
|
|
'beneficiaire1': beneficiaire1,
|
|
'beneficiaire2': beneficiaire2,
|
|
'type-de-relation': type_de_relation,
|
|
}
|
|
}
|
|
self.add_fragment(fragment)
|
|
|
|
def suppression_relation(self, relation):
|
|
if relation.content['statut'] == 'pacs/mariage':
|
|
type_de_relation = 'Marie'
|
|
elif relation.content['statut'] == 'unionlibre':
|
|
type_de_relation = 'UnionLibre'
|
|
else:
|
|
type_de_relation = None
|
|
beneficiaire1 = self.reference(relation.left)
|
|
beneficiaire2 = self.reference(relation.right)
|
|
assert beneficiaire1
|
|
assert beneficiaire2
|
|
fragment = {
|
|
'type': 'suppression-relation',
|
|
'fragment': {
|
|
'beneficiaire1': beneficiaire1,
|
|
'beneficiaire2': beneficiaire2,
|
|
}
|
|
}
|
|
if type_de_relation:
|
|
fragment['fragment']['type-de-relation'] = type_de_relation
|
|
self.add_fragment(fragment)
|
|
|
|
def relation_responsabilite_legale(self, parent, enfant, relation):
|
|
if relation.content['statut'] == 'parent':
|
|
type_de_relation = 'Parent'
|
|
elif relation.content['statut'] == 'tiers_de_confiance':
|
|
type_de_relation = 'TiersDeConfiance'
|
|
elif relation.content['statut'] == 'representant_personne_morale_qualifiee':
|
|
type_de_relation = 'RepresentantPersonneMoraleQualifiee'
|
|
else:
|
|
raise NotImplementedError
|
|
parent_ref = self.reference(parent)
|
|
assert parent_ref
|
|
enfant_ref = self.reference(enfant)
|
|
assert enfant_ref
|
|
fragment = {
|
|
'type': 'relation-responsabilite-legale',
|
|
'fragment': {
|
|
'responsable': parent_ref,
|
|
'beneficiaire': enfant_ref,
|
|
'type-de-relation': type_de_relation,
|
|
},
|
|
}
|
|
self.add_fragment(fragment)
|
|
|
|
def mise_a_jour_contact(self, individu):
|
|
fragment = {
|
|
'type': 'maj-contact-usager',
|
|
'fragment': individu_to_fragment(individu, application=self.application_id,
|
|
inclure_adresse=False, identite=False)
|
|
}
|
|
return self.add_fragment(fragment)
|
|
|
|
def mise_a_jour_identite_usager(self, individu):
|
|
fragment = {
|
|
'type': 'maj-identite-usager',
|
|
'fragment': individu_to_fragment(individu, application=self.application_id,
|
|
inclure_adresse=False, contact=False)
|
|
}
|
|
return self.add_fragment(fragment)
|
|
|
|
def mise_a_jour_adresse(self, individu):
|
|
beneficiaire = self.reference(individu)
|
|
assert beneficiaire
|
|
|
|
adresses = [adresse_to_fragment(adresse, principale=rel.content['principale'])
|
|
for adresse, rel in utils.adresses(individu)]
|
|
fragment = {
|
|
'type': 'maj-adresse',
|
|
'fragment': {
|
|
'id-metier': beneficiaire,
|
|
'adresses': adresses,
|
|
}
|
|
}
|
|
return self.add_fragment(fragment)
|
|
|
|
def nouvel_individu(self, individu):
|
|
if self.application_id in individu.content['cles_de_federation']:
|
|
return # individu déjà fédéré, pas nouveau, on ne fait rien
|
|
|
|
# tout message contenant une création de fédération n'est pas rejoué
|
|
self.state_on_network_error = Job.STATE_UNRECOVERABLE_ERROR
|
|
|
|
content = individu_to_fragment(individu)
|
|
if utils.is_majeur(individu):
|
|
fragment = {
|
|
'type': 'nouvel-adulte',
|
|
'fragment': content,
|
|
}
|
|
else:
|
|
fragment = {
|
|
'type': 'creation-enfant',
|
|
'fragment': content,
|
|
}
|
|
fragment_id = self.add_fragment(fragment)
|
|
self.fragment_to_id[fragment_id] = individu
|
|
self.id_to_fragment[individu] = fragment_id
|
|
if individu in self.individus:
|
|
self.beneficiaires.append(fragment_id)
|
|
|
|
def mise_a_jour_individu(self, individu):
|
|
# ne gère que les individus déjà fédérés, sinon ne fait rien
|
|
if self.application_id in individu.content['cles_de_federation']:
|
|
self.mise_a_jour_identite_usager(individu)
|
|
self.mise_a_jour_adresse(individu)
|
|
self.mise_a_jour_contact(individu)
|
|
|
|
def archivage(self, individu):
|
|
beneficiaire = self.reference(individu)
|
|
assert beneficiaire
|
|
assert not beneficiaire.startswith('@')
|
|
fragment = {
|
|
'type': 'archivage',
|
|
'fragment': {
|
|
'id-metier': beneficiaire,
|
|
}
|
|
}
|
|
return self.add_fragment(fragment)
|
|
|
|
def handle_response(self, response, job):
|
|
try:
|
|
result = response.json()
|
|
except ValueError:
|
|
self.error = {
|
|
'code': 'response-is-not-json',
|
|
'status-code': response.status_code,
|
|
'content': response.text[:1024],
|
|
}
|
|
return self.state_on_network_error
|
|
self.response = result
|
|
if isinstance(result, dict) and 'http_code' in result:
|
|
# code spécifique pour infor :(
|
|
status_code = result['http_code']
|
|
content = result.get('response')
|
|
else:
|
|
status_code = response.status_code
|
|
content = result
|
|
|
|
if status_code == 200:
|
|
return self.handle_response_200(response, content, job)
|
|
else:
|
|
self.error = {
|
|
'code': 'rsu-error',
|
|
'status-code': status_code,
|
|
'content': content,
|
|
}
|
|
return Job.STATE_UNRECOVERABLE_ERROR
|
|
|
|
def handle_response_200(self, response, content, job):
|
|
return Job.STATE_SUCCESS
|
|
|
|
@property
|
|
def description(self):
|
|
from django.utils.html import mark_safe
|
|
trame = json.dumps(self.message(), indent=4, sort_keys=True,
|
|
ensure_ascii=False)
|
|
args = ()
|
|
content = mark_safe('<div style="display: inline-block">')
|
|
if self.error:
|
|
content += mark_safe('<div>Erreur: <pre>%s</pre></div>')
|
|
args += (json.dumps(self.error, indent=4, sort_keys=True,
|
|
ensure_ascii=False),)
|
|
content += mark_safe('<div>Trame: <pre>%s</pre></div>')
|
|
content += mark_safe('</div>')
|
|
args += (trame,)
|
|
return content % args
|
|
|
|
|
|
class Synchronization(FragmentBuilder):
|
|
# pour chaque adulte, on l'ajoute en nouvel-individu ou en mise à jour profil+adresse
|
|
# pour chaque enfant on ajoute les parents en nouvel-individu s'il n'existe pas
|
|
# pour chaque enfant on ajoute l'enfant ou mise à jour
|
|
# pour chaque adulte on ajoute son union si son conjoint est déjà lié ou en cours de l'être
|
|
# pour chaque enfant on ajoute ses liens de parentalité
|
|
# pour chaque individu pour lequel la synchronisation est demandée et qui est créé, son
|
|
# identifiant de fragment est ajouté à la métadonnée bénéficiaires
|
|
service = 'synchronisation'
|
|
lock_individus = True
|
|
|
|
@classmethod
|
|
def create(cls, application, individus, meta=None):
|
|
self = super(Synchronization, cls).create(application, individus, meta=meta)
|
|
individus = self.individus
|
|
|
|
adultes = set([individu for individu in individus if utils.is_majeur(individu)])
|
|
enfants = set([individu for individu in individus if utils.is_mineur(individu)])
|
|
for enfant in enfants:
|
|
# on n'ajoute les parents en synchronisation que si l'enfant
|
|
# n'a pas encore de fédération avec l'application concernée
|
|
# on appelle ça une synchronisation manuelle
|
|
if self.application_id not in enfant.content['cles_de_federation']:
|
|
for parent, rel in utils.parents(enfant):
|
|
if parent not in adultes:
|
|
adultes.add(parent)
|
|
|
|
# on ajoute les nouveaux adultes puis enfants
|
|
for adulte in adultes:
|
|
self.nouvel_individu(adulte)
|
|
for enfant in enfants:
|
|
self.nouvel_individu(enfant)
|
|
# on met à jour les autres
|
|
for adulte in (adultes & set(individus)): # uniquement les adultes bénéficiaires
|
|
self.mise_a_jour_individu(adulte)
|
|
for enfant in enfants:
|
|
self.mise_a_jour_individu(enfant)
|
|
|
|
# on ajoute les relations parentales
|
|
relation_parentale_seen = set()
|
|
for enfant in enfants:
|
|
for parent, rel in utils.parents(enfant):
|
|
parent_ref = self.reference(parent)
|
|
if not parent_ref:
|
|
continue
|
|
relation_parentale_seen.add(rel)
|
|
self.relation_responsabilite_legale(parent, enfant, rel)
|
|
# on envoie les relations parentales des enfants uniquement pour les adultes bénéficiaires
|
|
for adulte in (adultes & set(individus)):
|
|
for enfant, rel in utils.enfants(adulte):
|
|
if rel in relation_parentale_seen:
|
|
continue
|
|
enfant_ref = self.reference(enfant)
|
|
if not enfant_ref:
|
|
continue
|
|
self.relation_responsabilite_legale(adulte, enfant, rel)
|
|
|
|
# on ajoute les unions
|
|
adultes_beneficiaires = adultes & set(self.individus)
|
|
for adulte in list(adultes_beneficiaires):
|
|
if adulte not in adultes_beneficiaires:
|
|
continue
|
|
conjoint, rel = utils.conjoint(adulte)
|
|
if not conjoint:
|
|
continue
|
|
adultes_beneficiaires.discard(conjoint)
|
|
conjoint_ref = self.reference(conjoint)
|
|
if conjoint_ref:
|
|
self.relation_situation_familiale(adulte, conjoint, rel)
|
|
|
|
return self
|
|
|
|
def handle_response_200(self, response, result, job):
|
|
state = self.handle_response_200_real(response, result, job)
|
|
if state != Job.STATE_SUCCESS:
|
|
for individu in self.fragment_to_id.values():
|
|
old_key = individu.content['cles_de_federation'].pop(self.application_id, None)
|
|
if old_key:
|
|
individu.content.setdefault('anciennes_cles_de_federation', []).insert(
|
|
0, [self.application_id, now().isoformat(), old_key])
|
|
individu.save()
|
|
return state
|
|
|
|
def handle_response_200_real(self, response, result, job):
|
|
id_metiers_map = []
|
|
# result can be "null" we must accept it
|
|
if result is None:
|
|
result = []
|
|
# verify that response is coherent
|
|
if not isinstance(result, list):
|
|
self.error = {
|
|
'code': 'response-is-not-a-list',
|
|
}
|
|
return Job.STATE_UNRECOVERABLE_ERROR
|
|
if len(result) != len(self.id_to_fragment):
|
|
self.error = {
|
|
'code': 'less-responses-than-creation-fragments',
|
|
}
|
|
return Job.STATE_UNRECOVERABLE_ERROR
|
|
for fragment in result:
|
|
id_fragment = fragment.get(self.ID_FRAGMENT)
|
|
if not id_fragment:
|
|
self.error = {
|
|
'code': 'missing-id-fragment-in-response',
|
|
}
|
|
return Job.STATE_UNRECOVERABLE_ERROR
|
|
if fragment.get('ignore'):
|
|
if id_fragment not in self.beneficiaires:
|
|
continue
|
|
else:
|
|
self.error = {
|
|
'code': 'beneficiaire-fragment-was-ignored',
|
|
}
|
|
return Job.STATE_UNRECOVERABLE_ERROR
|
|
id_metier = fragment.get('id-metier')
|
|
if not id_metier:
|
|
self.error = {
|
|
'code': 'missing-id-metier-in-response',
|
|
}
|
|
return Job.STATE_UNRECOVERABLE_ERROR
|
|
if id_fragment not in self.fragment_to_id:
|
|
self.error = {
|
|
'code': 'unknown-fragment-id-in-response',
|
|
}
|
|
return Job.STATE_UNRECOVERABLE_ERROR
|
|
individu = self.fragment_to_id[id_fragment]
|
|
id_metiers_map.append((individu, id_metier))
|
|
# apply new id_metier
|
|
for individu, id_metier in id_metiers_map:
|
|
old_key = individu.content['cles_de_federation'].get(self.application_id)
|
|
individu.content['cles_de_federation'][self.application_id] = id_metier
|
|
# on sauvegarde les anciennes clés en cas d'écrasement
|
|
if old_key:
|
|
individu.content.setdefault('anciennes_cles_de_federation', []).insert(
|
|
0, [self.application_id, now().isoformat(), old_key])
|
|
individu.save()
|
|
return Job.STATE_SUCCESS
|
|
|
|
def debug_response(self):
|
|
import uuid
|
|
|
|
response = []
|
|
for fragment in self.fragments:
|
|
if fragment['type'] in ('creation-enfant', 'nouvel-adulte'):
|
|
id_fragment = fragment[self.ID_FRAGMENT]
|
|
if id_fragment in self.beneficiaires:
|
|
response.append({
|
|
self.ID_FRAGMENT: fragment[self.ID_FRAGMENT],
|
|
'id-metier': uuid.uuid4().hex,
|
|
})
|
|
else:
|
|
response.append({
|
|
self.ID_FRAGMENT: fragment[self.ID_FRAGMENT],
|
|
'ignore': True,
|
|
})
|
|
return response
|
|
|
|
|
|
def synchronize(applications, individus, meta=None, transaction=None):
|
|
'''Send synchronisation messages to applications for individus'''
|
|
|
|
for application in applications:
|
|
app_dfn = utils.get_application(application)
|
|
assert 'rsu_ws_url' in app_dfn, 'application %s has no rsu_ws_url' % application
|
|
action = Synchronization.create(application, individus, meta=meta)
|
|
job = Job.create(action, transaction=transaction)
|
|
if job.state != Job.STATE_SUCCESS:
|
|
if action.human_result:
|
|
yield action.human_result
|
|
else:
|
|
yield 'Erreur interne'
|
|
|
|
|
|
class SimpleSynchro(FragmentBuilder):
|
|
@classmethod
|
|
def create(cls, application, individus, meta=None):
|
|
self = super(SimpleSynchro, cls).create(application, individus, meta=meta)
|
|
|
|
# on ajoute les fragments de mise à jour, les enfants en dernier
|
|
individus = sorted(self.individus, key=utils.is_mineur)
|
|
for individu in individus:
|
|
assert application in individu.content['cles_de_federation']
|
|
self.pour_chaque_individu(individu)
|
|
return self
|
|
|
|
@classmethod
|
|
def par_application(cls, application, individus, meta=None, transaction=None):
|
|
action = cls.create(application, individus, meta=meta)
|
|
Job.create(action, transaction=transaction)
|
|
yield action.human_result
|
|
|
|
@classmethod
|
|
def pour_chaque_application(cls, individus, meta=None, transaction=None):
|
|
def trier_par_application(individus):
|
|
for application in utils.get_applications(rsu_ws_url=True):
|
|
federated = []
|
|
for individu in individus:
|
|
if application in individu.content['cles_de_federation']:
|
|
federated.append(individu)
|
|
if federated:
|
|
yield application, federated
|
|
|
|
def helper():
|
|
for application, federated in trier_par_application(individus):
|
|
for message in cls.par_application(application, federated, meta=meta,
|
|
transaction=transaction):
|
|
yield message
|
|
return list(helper())
|
|
|
|
|
|
class SignalementChangementAdresse(SimpleSynchro):
|
|
service = 'signalement-changement-adresse'
|
|
|
|
def pour_chaque_individu(self, individu):
|
|
self.mise_a_jour_adresse(individu)
|
|
|
|
|
|
class MiseAJourIdentite(SimpleSynchro):
|
|
service = 'mise-a-jour-identite'
|
|
|
|
def pour_chaque_individu(self, individu):
|
|
self.mise_a_jour_identite_usager(individu)
|
|
|
|
|
|
class MiseAJourInformationsContact(SimpleSynchro):
|
|
service = 'mise-a-jour-informations-contact'
|
|
|
|
def pour_chaque_individu(self, individu):
|
|
self.mise_a_jour_contact(individu)
|
|
|
|
|
|
class RelationSynchro(FragmentBuilder):
|
|
@classmethod
|
|
def create(cls, application, relation, meta=None):
|
|
self = super(RelationSynchro, cls).create(application, [relation.left, relation.right],
|
|
meta=meta)
|
|
assert cls.condition(application, relation)
|
|
self.pour_relation(relation)
|
|
return self
|
|
|
|
@classmethod
|
|
def condition(self, application, relation):
|
|
left_cond = application in relation.left.content['cles_de_federation']
|
|
right_cond = application in relation.right.content['cles_de_federation']
|
|
return left_cond and right_cond
|
|
|
|
@classmethod
|
|
def par_application(cls, application, relation, meta=None, transaction=None):
|
|
if not cls.condition(application, relation):
|
|
return
|
|
action = cls.create(application, relation, meta=meta)
|
|
Job.create(action, transaction=transaction)
|
|
yield action.human_result
|
|
|
|
@classmethod
|
|
def pour_chaque_application(cls, relation, meta=None, transaction=None):
|
|
def helper():
|
|
for application in utils.get_applications(rsu_ws_url=True):
|
|
for message in cls.par_application(application, relation, meta=meta,
|
|
transaction=transaction):
|
|
yield message
|
|
return list(helper())
|
|
|
|
|
|
class DeclarationResponsabiliteLegaleEnfant(RelationSynchro):
|
|
service = 'declaration-responsabilite-legale-enfant'
|
|
|
|
def pour_relation(self, relation):
|
|
assert relation.schema.slug == utils.RESPONSABILITE_LEGALE_REL
|
|
self.relation_responsabilite_legale(relation.left, relation.right, relation)
|
|
self.mise_a_jour_adresse(relation.right)
|
|
|
|
|
|
class DeclarationUnion(RelationSynchro):
|
|
service = 'declaration-union'
|
|
|
|
@classmethod
|
|
def par_application(cls, application, relation, meta=None, transaction=None):
|
|
for message in super(DeclarationUnion, cls).par_application(application, relation,
|
|
meta=meta,
|
|
transaction=transaction):
|
|
yield message
|
|
# Si la relation ne peut pas être envoyée on génère un simple signalement de changement
|
|
# d'adresse
|
|
if not cls.condition(application, relation):
|
|
individus = set([relation.left, relation.right]) | set(utils.enfants_couple(relation))
|
|
individus = [individu for individu in individus if application in
|
|
individu.content['cles_de_federation']]
|
|
if not individus:
|
|
return
|
|
for message in SignalementChangementAdresse.par_application(application, individus,
|
|
meta=meta,
|
|
transaction=transaction):
|
|
yield message
|
|
|
|
def pour_relation(self, relation):
|
|
# On crée la relation si les deux individus sont fédérés, sinon on envoie simplement un
|
|
# changement d'adresse
|
|
assert relation.schema.slug == utils.UNION_REL
|
|
self.relation_situation_familiale(relation.left, relation.right, relation)
|
|
self.mise_a_jour_adresse(relation.left)
|
|
self.mise_a_jour_adresse(relation.right)
|
|
|
|
# mettre à jour les adresses des enfants fédérés
|
|
for enfant in utils.enfants_couple(relation):
|
|
if self.application_id in enfant.content['cles_de_federation']:
|
|
self.mise_a_jour_adresse(enfant)
|
|
|
|
|
|
class DeclarationSeparation(RelationSynchro):
|
|
service = 'declaration-separation'
|
|
|
|
def pour_relation(self, relation):
|
|
assert relation.schema.slug == utils.UNION_REL
|
|
self.suppression_relation(relation)
|
|
|
|
|
|
class SuppressionResponsabiliteEnfant(RelationSynchro):
|
|
service = 'suppression-responsabilite-enfant'
|
|
|
|
def pour_relation(self, relation):
|
|
assert relation.schema.slug == utils.RESPONSABILITE_LEGALE_REL
|
|
self.suppression_relation(relation)
|
|
self.mise_a_jour_adresse(relation.right)
|
|
|
|
|
|
class PassageALaMajorite(RelationSynchro):
|
|
# PassageALaMajorite does not send fragments immediatelly, it's special
|
|
service = 'passage-majorite'
|
|
lock_individus = True
|
|
|
|
@classmethod
|
|
def create(cls, application, individu, relations, meta=None):
|
|
individus = set([individu])
|
|
for relation in relations:
|
|
individus.add(relation.left)
|
|
individus.add(relation.right)
|
|
self = super(RelationSynchro, cls).create(application, individus, meta=meta)
|
|
self.mise_a_jour_adresse(individu)
|
|
for relation in relations:
|
|
assert cls.condition(application, relation)
|
|
self.suppression_relation(relation)
|
|
return self
|
|
|
|
@classmethod
|
|
def condition(self, application, relation):
|
|
left_cond = application in relation.left.content['cles_de_federation']
|
|
right_cond = application in relation.right.content['cles_de_federation']
|
|
return left_cond and right_cond
|
|
|
|
@classmethod
|
|
def par_application(cls, application, individu, relations, meta=None, transaction=None):
|
|
relations = [relation for relation in relations if cls.condition(application, relation)]
|
|
action = cls.create(application, individu, relations, meta=meta)
|
|
# PassageALaMajorite does not send fragments immediatelly, it's special
|
|
Job.create(action, do_later=True, transaction=transaction)
|
|
|
|
@classmethod
|
|
def pour_chaque_application(cls, individu, relations, meta=None, transaction=None):
|
|
def helper():
|
|
for application in utils.get_applications(rsu_ws_url=True):
|
|
if application not in individu.content['cles_de_federation']:
|
|
continue
|
|
cls.par_application(application, individu, relations,
|
|
meta=meta, transaction=transaction)
|
|
helper()
|
|
|
|
@property
|
|
def journal_message(self):
|
|
app_name = self.application['name']
|
|
return u'Passage à la majorité sur %s' % app_name
|