From 7395fa5560dd87dfedf9c2dee4322bed6d547a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20S=C3=A9chet?= Date: Thu, 11 May 2023 22:38:08 +0200 Subject: [PATCH] toulouse-foederis: add candidature endpoint (#77524) --- .../contrib/toulouse_foederis/models.py | 295 ++++++++++++++++++ tests/test_toulouse_foederis.py | 108 +++++++ 2 files changed, 403 insertions(+) diff --git a/passerelle/contrib/toulouse_foederis/models.py b/passerelle/contrib/toulouse_foederis/models.py index 31946eb4..11a87c97 100644 --- a/passerelle/contrib/toulouse_foederis/models.py +++ b/passerelle/contrib/toulouse_foederis/models.py @@ -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).' + ), + }, + ], + } + + +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', + '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]*$', + }, + 'civility': { + 'description': _("ID of an element of the data source 'civilite'."), + 'type': 'string', + 'pattern': '^[0-9]+$', + }, + 'first_name': { + 'description': _('Applicant first name.'), + 'type': 'string', + }, + 'last_name': { + 'description': _('Applicant last name.'), + 'type': 'string', + }, + '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}}, + }, + ) + 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), + '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=_( diff --git a/tests/test_toulouse_foederis.py b/tests/test_toulouse_foederis.py index e86a0bcb..f71d1531 100644 --- a/tests/test_toulouse_foederis.py +++ b/tests/test_toulouse_foederis.py @@ -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): + @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: