toulouse-foederis: add candidature endpoint (#77524) #246
|
@ -30,6 +30,229 @@ from passerelle.utils.api import endpoint
|
|||
from passerelle.utils.json import datasource_array_schema, datasource_schema, response_schema
|
||||
|
||||
|
||||
def boolean_field(description):
|
||||
return {
|
||||
'description': description,
|
||||
'oneOf': [
|
||||
{'type': 'boolean'},
|
||||
{
|
||||
'type': 'string',
|
||||
'pattern': '[Oo]|[Nn]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|1|0',
|
||||
'pattern_description': _(
|
||||
'Values "0", "1", "O", "N", "true" or "false" are allowed (case insensitive).'
|
||||
csechet marked this conversation as resolved
Outdated
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def get_bool(obj):
|
||||
if obj is True or str(obj).lower() in ['true', 'o', '1']:
|
||||
return True
|
||||
if obj is False or str(obj).lower() in ['false', 'n', '0']:
|
||||
return False
|
||||
return obj
|
||||
|
||||
|
||||
APPLICATION_SCHEMA = {
|
||||
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||
'title': 'Toulouse Foederis application',
|
||||
smihai
commented
Une petite typo ici: association Une petite typo ici: association
csechet
commented
Bien vu et surtout c'est pas du tout AstreGS, c'est resté d'un copier / coller : j'ai corrigé. Bien vu et surtout c'est pas du tout AstreGS, c'est resté d'un copier / coller : j'ai corrigé.
|
||||
'description': '',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {
|
||||
'description': _('Application Type (External or Internal).'),
|
||||
'type': 'string',
|
||||
},
|
||||
'announce_id': {
|
||||
'description': _('ID of the concerned job offer.'),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]*$',
|
||||
csechet marked this conversation as resolved
Outdated
nroche
commented
C'est nouveau dans passerelle, on parle bien de ça ?
C'est nouveau dans passerelle, on parle bien de ça ?
https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
J'ai l'impression (je me trompe peut-être) que la pratique courante est de formater les date en anglais depuis w.c.s : {{ form_var_date|date:'Y-m-d' }}
Auquel cas peut-être ne pas prendre de risque et faire comme dans les autres connecteurs :
```
'type': 'string',
'pattern': '^[0-9]{4}-[0-9]{2}-[0-9]{2}$',
```
|
||||
},
|
||||
'civility': {
|
||||
'description': _("ID of an element of the data source 'civilite'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]+$',
|
||||
},
|
||||
'first_name': {
|
||||
'description': _('Applicant first name.'),
|
||||
'type': 'string',
|
||||
csechet marked this conversation as resolved
Outdated
nroche
commented
Tu n'as pas précisé de type ici. Tu n'as pas précisé de type ici.
|
||||
},
|
||||
'last_name': {
|
||||
'description': _('Applicant last name.'),
|
||||
'type': 'string',
|
||||
csechet marked this conversation as resolved
Outdated
nroche
commented
'type': 'boolean' 'type': 'boolean'
Je pense qu'il ne faut pas présumer que w.c.s. enverra toujours False/True.
Voir ce qui est fait du côté d'Axel : accepter aussi les chaînes Vrai/Faux, O/1, ...
|
||||
},
|
||||
'gender': {
|
||||
'description': _('Applicant gender.'),
|
||||
'type': 'string',
|
||||
'enum': ['H', 'F', ''],
|
||||
},
|
||||
'birth_date': {
|
||||
'description': _('Applicant birth date.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'nationality': {
|
||||
'description': _("ID of an element of the data source 'nationalite'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]+$',
|
||||
},
|
||||
'work_authorization_end_date': {
|
||||
'description': _("Applicant end of working authorization, if nationality is 'other'."),
|
||||
'example_value': '2023-04-05',
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'rqth': boolean_field(_('RQTH.')),
|
||||
'rqth_end_date': {
|
||||
'description': _('End of RQTH, or none if not applicable.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'driving_license': {
|
||||
'description': _('Driving license.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'fimo': boolean_field(_('FIMO licence.')),
|
||||
'fimo_delivrance_date': {
|
||||
'description': _('FIMO licence delivrance date.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'fimo_end_validity_date': {
|
||||
'description': _('FIMO licence end validity date.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'current_situation': {
|
||||
'description': _("ID of an element of the data source 'situation-actuelle'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]+$',
|
||||
},
|
||||
'agent_collectivity': {
|
||||
'description': _("Agent's collectivity"),
|
||||
'type': 'string',
|
||||
},
|
||||
'availability_start_date': {
|
||||
'description': _('Applicant availability start date.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'availability_end_date': {
|
||||
'description': _('Applicant availability end date.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'salary_expectations': {
|
||||
'description': _('Applicant salary expectations.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'address': {
|
||||
'description': _('Applicant address.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'address_complement': {
|
||||
'description': _('Applicant address complement.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'zip': {
|
||||
'description': _('Applicant zip code.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'city': {
|
||||
'description': _('Applicant city.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'phone': {
|
||||
'description': _('Applicant phone number.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'email': {
|
||||
'description': _('Applicant email.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'contract_start_date': {
|
||||
'description': _('Applicant contract start date.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'contract_end_date': {
|
||||
'description': _('Applicant contract end date.'),
|
||||
'type': 'string',
|
||||
'pattern': '^([0-9]{4}-[0-9]{2}-[0-9]{2})?$',
|
||||
},
|
||||
'additional_informations': {
|
||||
'description': _('Application information complement.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'origin': {
|
||||
'description': _("ID of an element of the data source 'origine-candidature'."),
|
||||
'pattern': '^[0-9]*$',
|
||||
'type': 'string',
|
||||
},
|
||||
'origin_precisions': {
|
||||
'description': _("Precisions if 'origine' is 'other'."),
|
||||
'type': 'string',
|
||||
},
|
||||
'rgpd_agreement': boolean_field(_('RGPD agreement.')),
|
||||
'job_type': {
|
||||
'description': _("ID of an element of the data source 'type-emploi'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]*$',
|
||||
},
|
||||
'job_realm': {
|
||||
'description': _("ID of an element of the data source 'domaine-emploi'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]*$',
|
||||
},
|
||||
'job_family': {
|
||||
'description': _("ID of an element of the data source 'sous-domaine-emploi'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]*$',
|
||||
},
|
||||
'job': {
|
||||
'description': _("ID of an element of the data source 'emploi'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]*$',
|
||||
},
|
||||
'desired_work_time': {
|
||||
'description': _('TC / TNC.'),
|
||||
'type': 'string',
|
||||
'enum': ['TC', 'TNC'],
|
||||
},
|
||||
'internship_duration': {
|
||||
'description': _('Duration of the desired internship.'),
|
||||
'type': 'string',
|
||||
},
|
||||
'school_name': {
|
||||
'description': _("Candidate trainee's school name."),
|
||||
'type': 'string',
|
||||
},
|
||||
'diploma_name': {
|
||||
'description': _("Candidate trainee's diploma name."),
|
||||
'type': 'string',
|
||||
},
|
||||
'diploma_speciality': {
|
||||
'description': _("Candidate trainee's diploma speciality."),
|
||||
'type': 'string',
|
||||
},
|
||||
'aimed_diploma_level': {
|
||||
'description': _("ID of an element of the data source 'niveau-diplome'."),
|
||||
'type': 'string',
|
||||
'pattern': '^[0-9]*$',
|
||||
},
|
||||
'last_obtained_diploma': {
|
||||
'description': _("Candidate trainee's last obtained diploma."),
|
||||
'type': 'string',
|
||||
},
|
||||
'last_course_taken': {
|
||||
'description': _("Candidate trainee's last taken course."),
|
||||
'type': 'string',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class UpdateError(Exception):
|
||||
pass
|
||||
|
||||
|
@ -295,6 +518,78 @@ class Resource(BaseResource, HTTPResource):
|
|||
with document.pdf.open() as fd:
|
||||
return HttpResponse(fd, content_type='application/pdf')
|
||||
|
||||
@endpoint(
|
||||
perm="can_access",
|
||||
name="create-application",
|
||||
post={
|
||||
"description": _("Creates an application"),
|
||||
"request_body": {"schema": {"application/json": APPLICATION_SCHEMA}},
|
||||
csechet marked this conversation as resolved
Outdated
nroche
commented
Ne fonctionne pas, faire comme c'est fait ailleurs : Ne fonctionne pas, faire comme c'est fait ailleurs :
`'rqth': 'O' if post_data['rqth'] else 'N'`
|
||||
},
|
||||
)
|
||||
def create_application(self, request, post_data):
|
||||
def _get_id(field_name):
|
||||
id = post_data.get(field_name, None)
|
||||
if id is None or id == '':
|
||||
return None
|
||||
return int(id)
|
||||
|
||||
request_data = {
|
||||
'type_de_candidature': post_data.get('type', 'E'),
|
||||
'annonce': _get_id('announce_id'),
|
||||
self.REFERENTIELS_FKEYS['civilite']: _get_id('civility'),
|
||||
'firstName': post_data.get('first_name', None),
|
||||
'lastName': post_data.get('last_name', None),
|
||||
'sexe': post_data.get('gender', None),
|
||||
'date_de_naissance': post_data.get('birth_date', None),
|
||||
self.REFERENTIELS_FKEYS['nationalite']: _get_id('nationality'),
|
||||
'date_fin_autorisation_de_travail': post_data.get('work_authorization_end_date', None),
|
||||
'rqth': 'O' if get_bool(post_data.get('rqth', False)) else 'N',
|
||||
'date_fin_rqth': post_data.get('rqth_end_date', None),
|
||||
'permis_de_conduire': post_data.get('driving_license', None),
|
||||
'fimo': 'O' if get_bool(post_data.get('fimo', False)) else 'N',
|
||||
'Date_delivrance_fimo': post_data.get('fimo_delivrance_date', None),
|
||||
'date_fin_validite_fimo': post_data.get('fimo_end_validity_date', None),
|
||||
self.REFERENTIELS_FKEYS['situation_actuelle']: _get_id('current_situation'),
|
||||
'collectivite_agent': post_data.get('agent_collectivity', None),
|
||||
'date_debut_disponibilite': post_data.get('availability_start_date', None),
|
||||
'date_fin_disponibilite': post_data.get('availability_end_date', None),
|
||||
'pretentions_salariales': post_data.get('salary_expectations', None),
|
||||
'adresse': post_data.get('address', None),
|
||||
'adresse_ligne_2': post_data.get('address_complement', None),
|
||||
'code_postal': post_data.get('zip', None),
|
||||
'ville': post_data.get('city', None),
|
||||
'telephone': post_data.get('phone', None),
|
||||
'email': post_data.get('email', None),
|
||||
nroche
commented
Quels sont les champs qui nécessite de ne pas être passés lorsqu'il sont vides ? Quels sont les champs qui nécessite de ne pas être passés lorsqu'il sont vides ?
C'est spécifié quelque part dans le excel ?
Si c'est vraiment utile, alors peut-être ajouter un test qui donne le cas d'usage.
csechet
commented
Le Excel dit n'importe quoi, tous les champs sont optionnels dans les faits. Ici on supprime juste les valeurs absente du post. Il n'y a pas de cas d'usage, ils seront tous transmis, c'est juste pour éviter de sauvegarder n'importe quoi dans Foederis s'il y a un oubli à un moment donné (rappel que ce connecteur n'a rien de générique : on a un paramétrage ad-hoc en face). J'ai supprimé des champs dans les tests pour vérifier qu'ils sont bien exclus lors de l'appel Foederis. Edit: En fait il y a des cas où des champs sont laissés vides (des champs spécifiques à une candidature de stage par ex.). Le Excel dit n'importe quoi, tous les champs sont optionnels dans les faits. Ici on supprime juste les valeurs absente du post. Il n'y a pas de cas d'usage, ils seront tous transmis, c'est juste pour éviter de sauvegarder n'importe quoi dans Foederis s'il y a un oubli à un moment donné (rappel que ce connecteur n'a rien de générique : on a un paramétrage ad-hoc en face). J'ai supprimé des champs dans les tests pour vérifier qu'ils sont bien exclus lors de l'appel Foederis.
Edit: En fait il y a des cas où des champs sont laissés vides (des champs spécifiques à une candidature de stage par ex.).
nroche
commented
Désolé, je n'arrive pas à me projeter. Désolé, je n'arrive pas à me projeter.
Est-ce que tu peux poser ton WF de test sur la recette, stp ?
(j'ai pas trouvé : https://demarches-montoulouse.test.entrouvert.org/backoffice/workflows/247/status/1/items/1/)
|
||||
'date_debut_contrat': post_data.get('contract_start_date', None),
|
||||
'date_fin_contrat': post_data.get('contract_end_date', None),
|
||||
'complement_information_candidature': post_data.get('additional_informations', None),
|
||||
self.REFERENTIELS_FKEYS['origine_candidature']: _get_id('origin'),
|
||||
'precision_origine_candidature': post_data.get('origin_precisions', None),
|
||||
'accord_RGPD': get_bool(post_data.get('rgpd_agreement', False)),
|
||||
self.REFERENTIELS_FKEYS['type_emploi']: _get_id('job_type'),
|
||||
self.REFERENTIELS_FKEYS['domaine_emploi']: _get_id('job_realm'),
|
||||
self.REFERENTIELS_FKEYS['sous_domaine_emploi']: _get_id('job_family'),
|
||||
self.REFERENTIELS_FKEYS['emploi']: _get_id('job'),
|
||||
'temps_de_travail_souhaite': post_data.get('desired_work_time', None),
|
||||
'duree_du_contrat_de_stage_apprentissage': post_data.get('internship_duration', None),
|
||||
'ecole_centre_de_formation_mission_loc': post_data.get('school_name', None),
|
||||
'intitule_diplome_vise': post_data.get('diploma_name', None),
|
||||
'specialite_diplome': post_data.get('diploma_speciality', None),
|
||||
self.REFERENTIELS_FKEYS['niveau_diplome']: _get_id('aimed_diploma_level'),
|
||||
'dernier_diplome_obtenu': post_data.get('last_obtained_diploma', None),
|
||||
'derniere_classe_suivie': post_data.get('last_course_taken', None),
|
||||
}
|
||||
|
||||
request_data = {k: v for k, v in request_data.items() if v is not None and v != ''}
|
||||
|
||||
results = self.http_request(
|
||||
'POST', 'data/candidature?viewIntegrationName=api_publik', json=request_data
|
||||
)
|
||||
return {
|
||||
'err': 0,
|
||||
'data': {'application_id': results[0]['id']},
|
||||
}
|
||||
|
||||
@endpoint(
|
||||
description=_('List announces'),
|
||||
long_description=_(
|
||||
|
|
|
@ -405,6 +405,114 @@ class TestEndpoints:
|
|||
app.get('/toulouse-foederis/foederis/announce/111/pdf/', status=404)
|
||||
assert response.headers['content-type'] == 'application/pdf'
|
||||
|
||||
def test_create_application(self, resource, app):
|
||||
nroche
commented
Cette variable n'est pas utilisée (étrange que pylint ne l'ai pas vu). Cette variable n'est pas utilisée (étrange que pylint ne l'ai pas vu).
csechet
commented
Bien vu. C'est une variable globale, pylint ne peut pas deviner qu'elle n'est pas utilisée dans un autre module. Bien vu. C'est une variable globale, pylint ne peut pas deviner qu'elle n'est pas utilisée dans un autre module.
|
||||
@httmock.urlmatch(path=r'^.*/data/candidature$')
|
||||
def handler(url, request):
|
||||
assert request.headers['content-type'] == 'application/json'
|
||||
assert request.headers['api-key'] == APIKEY
|
||||
payload = json.loads(request.body)
|
||||
assert payload == {
|
||||
"type_de_candidature": "E",
|
||||
"annonce": 524522,
|
||||
"R60284409": 170013,
|
||||
"firstName": "John",
|
||||
"lastName": "Doe",
|
||||
"sexe": "H",
|
||||
"date_de_naissance": "1985-03-06",
|
||||
"R1249730": 93421,
|
||||
"date_fin_autorisation_de_travail": "2023-05-09",
|
||||
"rqth": "N",
|
||||
"date_fin_rqth": "2023-05-08",
|
||||
"permis_de_conduire": "A,B",
|
||||
"fimo": "O",
|
||||
"Date_delivrance_fimo": "2023-05-07",
|
||||
"date_fin_validite_fimo": "2023-05-08",
|
||||
"R1258320": 1258319,
|
||||
"collectivite_agent": "Mairie de Toulouse",
|
||||
"date_debut_disponibilite": "2023-05-02",
|
||||
"date_fin_disponibilite": "2023-05-01",
|
||||
"pretentions_salariales": "1000",
|
||||
"adresse": "12 Sesame Street",
|
||||
"code_postal": "77710",
|
||||
"ville": "Nemours",
|
||||
"telephone": "+33 636656565",
|
||||
"email": "csechet@entrouvert.com",
|
||||
"date_debut_contrat": "2023-05-06",
|
||||
"date_fin_contrat": "2023-05-04",
|
||||
"complement_information_candidature": "I need money.",
|
||||
"R1261279": 1561049,
|
||||
"accord_RGPD": True,
|
||||
"R1249707": 157193,
|
||||
"R60845221": 5776395,
|
||||
"R60845244": 5776394,
|
||||
"temps_de_travail_souhaite": "TC",
|
||||
"duree_du_contrat_de_stage_apprentissage": "2h",
|
||||
"ecole_centre_de_formation_mission_loc": "Ecole de la vie",
|
||||
"intitule_diplome_vise": "BE",
|
||||
"specialite_diplome": "Curling",
|
||||
"R1249737": 1124022,
|
||||
"dernier_diplome_obtenu": "BAC",
|
||||
"derniere_classe_suivie": "Terminale",
|
||||
}
|
||||
|
||||
return httmock.response(200, json.dumps({"code": 200, "results": [{"id": 42}]}))
|
||||
|
||||
@httmock.urlmatch()
|
||||
def error_handler(url, request):
|
||||
assert False, 'should not be reached'
|
||||
|
||||
with httmock.HTTMock(handler, error_handler):
|
||||
response = app.post_json(
|
||||
'/toulouse-foederis/foederis/create-application',
|
||||
params={
|
||||
"additional_informations": "I need money.",
|
||||
"address": "12 Sesame Street",
|
||||
"address_complement": "",
|
||||
"agent_collectivity": "Mairie de Toulouse",
|
||||
"aimed_diploma_level": "1124022",
|
||||
"announce_id": "0524522",
|
||||
"availability_end_date": "2023-05-01",
|
||||
"availability_start_date": "2023-05-02",
|
||||
"birth_date": "1985-03-06",
|
||||
"city": "Nemours",
|
||||
"civility": "170013",
|
||||
"contract_end_date": "2023-05-04",
|
||||
"contract_start_date": "2023-05-06",
|
||||
"current_situation": "1258319",
|
||||
"desired_work_time": "TC",
|
||||
"diploma_name": "BE",
|
||||
"diploma_speciality": "Curling",
|
||||
"driving_license": "A,B",
|
||||
"email": "csechet@entrouvert.com",
|
||||
"fimo": "o",
|
||||
"fimo_delivrance_date": "2023-05-07",
|
||||
"fimo_end_validity_date": "2023-05-08",
|
||||
"first_name": "John",
|
||||
"gender": "H",
|
||||
"internship_duration": "2h",
|
||||
"job_family": "5776394",
|
||||
"job_realm": "5776395",
|
||||
"job_type": "157193",
|
||||
"last_course_taken": "Terminale",
|
||||
"last_name": "Doe",
|
||||
"last_obtained_diploma": "BAC",
|
||||
"nationality": "93421",
|
||||
"origin": "1561049",
|
||||
"origin_precisions": "",
|
||||
"phone": "+33 636656565",
|
||||
"rgpd_agreement": "tRuE",
|
||||
"rqth": False,
|
||||
"rqth_end_date": "2023-05-08",
|
||||
"salary_expectations": "1000",
|
||||
"school_name": "Ecole de la vie",
|
||||
"type": "E",
|
||||
"work_authorization_end_date": "2023-05-09",
|
||||
"zip": "77710",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.json["data"]["application_id"] == 42
|
||||
|
||||
|
||||
def test_migration_0003_no_null_no_charfield(migration):
|
||||
with connection.cursor() as cur:
|
||||
|
|
Peut-être laisser la porte ouverte ici, pour être plus souple si les valeurs possibles venaient à changer.