passerelle/passerelle/apps/astregs/models.py

767 lines
25 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (C) 2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# 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 base64
import zeep
from zeep.helpers import serialize_object
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.six.moves.urllib import parse as urlparse
from django.http import Http404
from passerelle.base.models import BaseResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
from passerelle.utils.validation import is_number
ASSOCIATION_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS assocation",
"description": "",
"type": "object",
"required": [
"Financier",
"CodeFamille",
"CatTiers",
"NomEnregistrement",
"StatutTiers",
"Type",
"AdresseTitre",
"AdresseIsAdresseDeCommande",
"AdresseIsAdresseDeFacturation",
],
"properties": {
"Financier": {"description": "financial association", "type": "string", "enum": ["true", "false"]},
"CodeFamille": {
"description": "association family code",
"type": "string",
},
"CatTiers": {
"description": "association category",
"type": "string",
},
"NomEnregistrement": {
"description": "association name",
"type": "string",
},
"StatutTiers": {
"description": "association status",
"type": "string",
"enum": ["PROPOSE", "VALIDE", "REFUSE", "BLOQUE", "A COMPLETER"],
},
"Type": {"description": "association type", "type": "string", "enum": ["D", "F", "*"]},
"NumeroSiret": {
"description": "SIREN number",
"type": "string",
},
"NumeroSiretFin": {
"description": "NIC number",
"type": "string",
},
"AdresseTitre": {
"type": "string",
},
"AdresseIsAdresseDeCommande": {"type": "string", "enum": ["true", "false"]},
"AdresseIsAdresseDeFacturation": {"type": "string", "enum": ["true", "false"]},
},
}
CONTACT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS contact",
"description": "",
"type": "object",
"required": [
"CodeContact",
"CodeTitreCivilite",
"Nom",
"AdresseDestinataire",
"CodePostal",
"Ville",
"EncodeKeyStatut",
],
"properties": {
"CodeContact": {
"type": "string",
},
"CodeTitreCivilite": {
"type": "string",
},
"Nom": {
"type": "string",
},
"AdresseDestinataire": {
"type": "string",
},
"CodePostal": {
"type": "string",
},
"Ville": {
"type": "string",
},
"EncodeKeyStatut": {
"type": "string",
},
},
}
DOCUMENT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS assocation",
"description": "",
"type": "object",
"required": [
"Sujet",
"Entite",
"CodType",
"Type",
"hdnCodeTrt",
"EncodeKeyEntite",
"CodeDomaine",
"CodDom",
"document",
],
"properties": {
"Sujet": {
"type": "string",
},
"Entite": {
"type": "string",
},
"CodType": {
"type": "string",
},
"Type": {
"type": "string",
},
"hdnCodeTrt": {
"type": "string",
},
"EncodeKeyEntite": {
"type": "string",
},
"CodeDomaine": {
"type": "string",
},
"CodDom": {
"type": "string",
},
"document": {
"type": "object",
"required": ['filename', 'content_type', 'content'],
'properties': {
'filename': {
'type': 'string',
},
'content_type': {
'type': 'string',
},
'content': {
'type': 'string',
},
},
},
},
}
GRANT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS grant",
"description": "",
"type": "object",
"required": [
"Libelle",
"LibelleCourt",
"ModGestion",
"TypeAide",
"Sens",
"CodeTiersDem",
"CodeServiceGestionnaire",
"CodeServiceUtilisateur",
],
"properties": {
"Libelle": {
"type": "string",
},
"LibelleCourt": {
"type": "string",
},
"ModGestion": {"type": "string", "enum": ["1", "2", "3", "4"]},
"TypeAide": {
"type": "string",
},
"Sens": {
"type": "string",
},
"CodeTiersDem": {
"type": "string",
},
"CodeServiceGestionnaire": {
"type": "string",
},
"CodeServiceUtilisateur": {
"type": "string",
},
},
}
INDANA_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS INDANA indicator",
"description": "",
"type": "object",
"required": ["CodeDossier", "CodeInd_1", "AnneeInd_1", "ValInd_1"],
"properties": {
"CodeDossier": {
"type": "string",
},
"CodeInd_1": {
"type": "string",
},
"AnneeInd_1": {
"type": "string",
},
"ValInd_1": {
"type": "string",
},
"IndAide": {
"type": "string",
},
},
}
INDANA_KEY_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS INDANA indicator key",
"description": "",
"type": "object",
"required": ["CodeDossier", "CodeInd_1", "AnneeInd_1"],
"properties": {
"CodeDossier": {
"type": "string",
},
"CodeInd_1": {
"type": "string",
},
"AnneeInd_1": {
"type": "string",
},
},
}
TIERS_RIB_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS TiersRib",
"description": "TiersRib",
"type": "object",
"required": [
"CodeTiers",
"CodePaiement",
"LibelleCourt",
"NumeroIban",
"CleIban",
'CodeBic',
"CodeDomiciliation",
"CodeStatut",
"CodeDevise",
"CodeIso2Pays",
"LibelleCompteEtranger",
],
"properties": {
"CodeDevise": {"type": "string"},
"CodeDomiciliation": {"type": "string"},
"CodeIso2Pays": {"type": "string"},
"CodePaiement": {"type": "string"},
"CodeStatut": {
"type": "string",
"enum": ["PROPOSE", "VALIDE", "REFUSE", "A COMPLETER", "BLOQUE", "EN MODIFICATION"],
},
"CodeTiers": {"type": "string"},
"IndicateurRibDefaut": {"type": "string"},
"LibelleCompteEtranger": {"type": "string"},
"LibelleCourt": {"type": "string"},
"NumeroIban": {"type": "string"},
"CleIban": {"type": "string"},
"CodeBic": {"type": "string"},
"IdRib": {"type": "string"},
},
}
TIERS_RIB_UPDATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AstreGS TiersRib",
"description": "TiersRib Update",
"type": "object",
"required": [
"CodePaiement",
"LibelleCourt",
"NumeroIban",
"CleIban",
"CodeBic",
"CodeDomiciliation",
"CodeStatut",
"CodeDevise",
"CodeIso2Pays",
"LibelleCompteEtranger",
],
"properties": {
"CodeDevise": {"type": "string"},
"CodeDomiciliation": {"type": "string"},
"CodeIso2Pays": {"type": "string"},
"CodePaiement": {"type": "string"},
"CodeStatut": {
"type": "string",
"enum": ["PROPOSE", "VALIDE", "REFUSE", "A COMPLETER", "BLOQUE", "EN MODIFICATION"],
},
"IndicateurRibDefaut": {"type": "string"},
"LibelleCompteEtranger": {"type": "string"},
"LibelleCourt": {"type": "string"},
"NumeroIban": {"type": "string"},
"CleIban": {"type": "string"},
"CodeBic": {"type": "string"},
},
}
class AstreGS(BaseResource):
wsdl_base_url = models.URLField(_('Webservices base URL'))
username = models.CharField(_('Username'), max_length=32)
password = models.CharField(_('Password'), max_length=32)
organism = models.CharField('Organisme', max_length=32)
budget = models.CharField('Budget', max_length=32)
exercice = models.CharField('Exercice', max_length=32)
category = _('Business Process Connectors')
log_requests_errors = False
class Meta:
verbose_name = u'AstresGS'
def check_status(self):
response = self.requests.get(self.wsdl_base_url)
response.raise_for_status()
@property
def authentication(self):
return {'USERNOM': self.username, 'USERPWD': self.password}
@property
def context(self):
return {'Organisme': self.organism, 'Budget': self.budget, 'Exercice': self.exercice}
def get_client(self, wsdl_name):
url = urlparse.urljoin(self.wsdl_base_url, '%s?wsdl' % wsdl_name)
client = self.soap_client(wsdl_url=url, transport_kwargs={'remove_first_bytes_for_xml': True})
parsed_wsdl_address = urlparse.urlparse(client.service._binding_options['address'])
parsed_real_address = urlparse.urlparse(self.wsdl_base_url)
client.service._binding_options['address'] = urlparse.urlunparse(
parsed_real_address[:2] + parsed_wsdl_address[2:]
)
return client
def call(self, wsdl_name, service, *args, **kwargs):
try:
client = self.get_client(wsdl_name)
operation = getattr(client.service, service)
kwargs['Authentification'] = self.authentication
kwargs['Contexte'] = self.context
if service == 'liste':
return operation(**kwargs)
return operation(kwargs)
except zeep.exceptions.Error as e:
raise APIError(e)
def search_tiers(self, criterias):
return self.call('RechercheTiers', 'liste', Criteres=criterias)
@endpoint(
description=_('Find associations by SIREN number'),
perm='can_access',
parameters={'siren': {'description': _('SIREN Number'), 'example_value': '77567227216096'}},
)
def associations(self, request, siren):
r = self.call('RechercheTiersDetails', 'liste', Criteres={'siren': '%s*' % siren})
data = []
if r.liste:
for item in r.liste.EnregRechercheTiersDetailsReturn:
association_data = serialize_object(item)
association_data['id'] = association_data['Numero_SIRET']
association_data['text'] = '%(Numero_SIRET)s - %(Nom_enregistrement)s' % association_data
association_data['code'] = association_data['Code_tiers']
association_data['name'] = association_data['Nom_enregistrement']
data.append(association_data)
return {'data': data}
@endpoint(
description=_('Check if association exists by its SIRET number'),
name='check-association-by-siret',
perm='can_access',
parameters={'siret': {'description': _('SIRET Number'), 'example_value': '7756722721609600014'}},
)
def check_association_by_siret(self, request, siret):
r = self.search_tiers({'siren': siret})
if r.liste:
return {'exists': True}
return {'exists': False}
@endpoint(
name='get-association-link-means',
description=_('Get association linking means'),
perm='can_access',
parameters={
'association_id': {'description': _('Association ID'), 'example_value': '42435'},
'NameID': {'description': _('Publik ID'), 'example_value': 'xyz24d934'},
},
)
def get_association_link_means(self, request, association_id, NameID=None):
r = self.call('Tiers', 'Chargement', TiersCle={'CodeTiers': association_id})
response = {'already_paired': False}
if (
NameID
and Link.objects.filter(resource=self, name_id=NameID, association_id=association_id).exists()
):
response['already_paired'] = True
data = []
# assocation contact is defined in EncodeKeyContact attribute
if not r.EncodeKeyContact:
return {'data': data}
r = self.call('Contact', 'Chargement', ContactCle={'idContact': r.EncodeKeyContact})
if r.AdresseMail:
data.append(
{
'id': 'email',
'text': 'par courriel vers %s***@***%s' % (r.AdresseMail[:2], r.AdresseMail[-3:]),
'value': r.AdresseMail,
'type': 'email',
}
)
if r.TelephoneMobile:
mobile = ''.join((n for n in r.TelephoneMobile if is_number(n)))
if mobile and len(mobile) == 10 and mobile[:2] in ('06', '07'):
data.append(
{
'id': 'mobile',
'text': 'par SMS vers %s*****%s' % (mobile[:2], mobile[-3:]),
'value': mobile,
'type': 'mobile',
}
)
response['data'] = data
response['raw_data'] = serialize_object(r)
return response
@endpoint(
description=_('Create link between user and association'),
perm='can_access',
parameters={
'NameID': {
'description': _('Publik NameID'),
'example_value': 'xyz24d934',
},
'association_id': {
'description': _('Association ID'),
'example_value': '12345',
},
},
)
def link(self, request, NameID, association_id):
link, created = Link.objects.get_or_create(
resource=self, name_id=NameID, association_id=association_id
)
link.get_label()
return {'link': link.id, 'created': created, 'association_id': link.association_id}
@endpoint(
description=_('Remove link between user and association'),
perm='can_access',
parameters={
'NameID': {'description': _('Publik NameID'), 'example_value': 'xyz24d934'},
'association_id': {'description': _('Association ID'), 'example_value': '12345'},
},
)
def unlink(self, request, NameID, association_id):
try:
link = Link.objects.get(resource=self, name_id=NameID, association_id=association_id)
link.delete()
return {'deleted': True}
except Link.DoesNotExist:
raise Http404('link not found')
@endpoint(
description=_('List user links'),
perm='can_access',
parameters={
'NameID': {
'description': _('Publik NameID'),
'example_value': 'xyz24d934',
}
},
)
def links(self, request, NameID):
if not Link.objects.filter(resource=self, name_id=NameID).exists():
return {'data': []}
data = []
for link in Link.objects.filter(resource=self, name_id=NameID):
data.append({'id': link.association_id, 'text': link.get_label()})
return {'data': data}
@endpoint(
perm='can_access',
name='create-association',
post={
'description': _('Creates an association'),
'request_body': {'schema': {'application/json': ASSOCIATION_SCHEMA}},
},
)
def create_association(self, request, post_data):
r = self.call('Tiers', 'Creation', Tiers=post_data)
return {'data': serialize_object(r)}
@endpoint(
description=_('Get association informations'),
name='get-association-by-id',
perm='can_access',
parameters={
'association_id': {'description': _('Association ID'), 'example_value': '71904'},
'NameID': {'description': _('Publik NameID'), 'example_value': 'xyz24d934'},
},
)
def get_association_by_id(self, request, association_id, NameID=None):
if (
NameID
and not Link.objects.filter(resource=self, name_id=NameID, association_id=association_id).exists()
):
raise Http404('no association found')
r = self.call('Tiers', 'Chargement', TiersCle={'CodeTiers': association_id})
return {'data': serialize_object(r)}
@endpoint(
name='get-contact',
perm='can_access',
description=_('Get contact details'),
parameters={
'contact_id': {
'description': _('Contact identifier'),
'example_value': '1111',
}
},
)
def get_contact(self, request, contact_id):
r = self.call('Contact', 'Chargement', ContactCle={'idContact': contact_id})
return {'data': serialize_object(r)}
@endpoint(
name='create-contact',
perm='can_access',
post={
'description': _('Create contact'),
'request_body': {'schema': {'application/json': CONTACT_SCHEMA}},
},
)
def create_contact(self, request, post_data):
contact = self.call('Contact', 'Creation', Contact=post_data)
# address should be set separatedly
post_data['EncodeKeyContact'] = contact.idContact
address = self.call('ContactAdresses', 'Creation', ContactAdresses=post_data)
return {'data': serialize_object(contact)}
@endpoint(
description=_('Delete contact'),
name='delete-contact',
perm='can_access',
parameters={
'contact_id': {'description': _('Contact ID'), 'example_value': '4242'},
},
)
def delete_contact(self, request, contact_id):
r = self.call('Contact', 'Suppression', ContactCle={'idContact': contact_id})
return {'data': serialize_object(r)}
@endpoint(
name='create-document',
perm='can_access',
post={
'description': _('Create document'),
'request_body': {'schema': {'application/json': DOCUMENT_SCHEMA}},
},
)
def create_document(self, request, post_data):
document = post_data.pop('document')
post_data['NomFichier'] = document['filename']
post_data['MimeType'] = document['content_type']
post_data['Contenu'] = base64.b64decode(document['content'])
r = self.call('DocumentAnnexe', 'Creation', DocumentAnnexe=post_data)
return {'data': serialize_object(r)}
@endpoint(
name='create-grant-demand',
perm='can_access',
post={
'description': _('Create grant demand'),
'request_body': {'schema': {'application/json': GRANT_SCHEMA}},
},
)
def create_grant_demand(self, request, post_data):
r = self.call('Dossier', 'Creation', Dossier=post_data)
return {'data': serialize_object(r)}
@endpoint(
name='create-indana-indicator',
perm='can_access',
post={
'description': _('Create indana indicator'),
'request_body': {'schema': {'application/json': INDANA_SCHEMA}},
},
)
def create_indana_indicator(self, request, post_data):
r = self.call('DossierIndicateur', 'Creation', DossierIndicateur=post_data)
return {'data': serialize_object(r)}
@endpoint(
name='update-indana-indicator',
perm='can_access',
post={
'description': _('Update indana indicator'),
'request_body': {'schema': {'application/json': INDANA_SCHEMA}},
},
)
def update_indana_indicator(self, request, post_data):
r = self.call('DossierIndicateur', 'Modification', DossierIndicateur=post_data)
return {'data': serialize_object(r)}
@endpoint(
name='delete-indana-indicator',
perm='can_access',
post={
'description': _('Delete indana indicator'),
'request_body': {'schema': {'application/json': INDANA_KEY_SCHEMA}},
},
)
def delete_indana_indicator(self, request, post_data):
r = self.call('DossierIndicateur', 'Suppression', DossierIndicateurCle=post_data)
return {'data': serialize_object(r)}
@endpoint(
name='create-tiers-rib',
perm='can_access',
post={
'description': _('Create RIB'),
'request_body': {'schema': {'application/json': TIERS_RIB_SCHEMA}},
},
)
def create_tiers_rib(self, request, post_data):
r = self.call('TiersRib', 'Creation', TiersRib=post_data)
return {'data': serialize_object(r)}
@endpoint(
name='get-tiers-rib',
perm='can_access',
description=_('Get RIB'),
parameters={
'CodeTiers': {'example_value': '42435'},
'IdRib': {'example_value': '4242'},
},
)
def get_tiers_rib(self, request, CodeTiers, IdRib):
payload = {'CodeTiers': CodeTiers, 'IdRib': IdRib}
r = self.call('TiersRib', 'Chargement', TiersRibCle=payload)
return {'data': serialize_object(r)}
@endpoint(
name='update-tiers-rib',
perm='can_access',
post={
'description': _('Update RIB'),
'request_body': {'schema': {'application/json': TIERS_RIB_UPDATE_SCHEMA}},
},
parameters={
'CodeTiers': {'example_value': '42435'},
'IdRib': {'example_value': '4242'},
},
)
def update_tiers_rib(self, request, CodeTiers, IdRib, post_data):
post_data['CodeTiers'] = CodeTiers
post_data['IdRib'] = IdRib
r = self.call('TiersRib', 'Modification', TiersRib=post_data)
return {'data': serialize_object(r)}
@endpoint(
name='delete-tiers-rib',
perm='can_access',
description=_('Delete RIB'),
parameters={
'CodeTiers': {'example_value': '42435'},
'IdRib': {'example_value': '4242'},
},
)
def delete_tiers_rib(self, request, CodeTiers, IdRib):
payload = {'CodeTiers': CodeTiers, 'IdRib': IdRib}
r = self.call('TiersRib', 'Suppression', TiersRibCle=payload)
return {'data': serialize_object(r)}
@endpoint(
name='find-tiers-by-rib',
perm='can_access',
description=_('Find person by RIB'),
parameters={
'banque': {'example_value': '30001'},
'guichet': {'example_value': '00794'},
'numero_compte': {'example_value': '12345678901'},
'cle': {'example_value': '85'},
},
)
def find_tiers_by_rib(self, request, banque, guichet, numero_compte, cle, **kwargs):
criteres = {'banque': banque, 'guichet': guichet, 'numeroCompte': numero_compte, 'cleRIB': cle}
# add other params to search criterias
criteres.update(kwargs)
r = self.search_tiers(criteres)
data = []
if r.liste:
for item in r.liste.EnregRechercheTiersReturn:
tiers_data = serialize_object(item)
tiers_data['id'] = tiers_data['N']
tiers_data['text'] = '%{Nom_Enregistrement}s (%{N}s)'.format(**tiers_data)
data.append(tiers_data)
return {'data': data}
class Link(models.Model):
resource = models.ForeignKey(AstreGS, on_delete=models.CASCADE)
name_id = models.CharField(max_length=32)
association_id = models.CharField(max_length=32)
association_label = models.CharField(max_length=128, null=True)
created = models.DateTimeField(auto_now_add=True)
def get_label(self):
r = self.resource.call(
'RechercheTiersDetails', 'liste', Criteres={'numeroTiers': self.association_id}
)
if r.liste:
# ugly API, it always returns a list of only one element
item = r.liste.EnregRechercheTiersDetailsReturn[0]
self.association_label = '%s - %s' % (item.Numero_SIRET, item.Nom_enregistrement)
self.save()
else:
self.resource.logger.error('No data found for association %s', self.association_id)
return self.association_label
class Meta:
unique_together = ('resource', 'name_id', 'association_id')