HR Integration
Questi contenuti non sono ancora disponibili nella tua lingua.
4HSE provides tools for managing personnel records. However, it is increasingly common for structured companies to continue using their own HR software and integrate it with a safety management system like 4HSE. This need arises both during the project start-up phase and in subsequent daily operations, where it is essential to maintain solid alignment between the two systems.
We will see how to develop software that, by leveraging the APIs provided by 4HSE, allows any HR software to integrate with 4HSE, keeping personnel records always synchronized.
We will not focus on a specific HR software, but will take a generic approach to the problem, developing an integration script that can be easily adapted to various HR solutions on the market.
Prerequisites
Section titled “Prerequisites”What do you need to develop an HR integration script?
- Data source: we will use a simple CSV file as the data source, which we imagine is periodically exported from the HR software database to be aligned with 4HSE every 24 hours.
project_id
: you must have an active project on 4HSE and know its ID. The ID is visible in the “info” section of the project window.access_token
: you need an active user with admin role for the specified project, as this user will perform the synchronization. To authenticate this user via API, you need their access token, provided by 4HSE upon client request.
Data Source
Section titled “Data Source”To prepare the CSV file extracted from the data source, you must always follow these simple rules:
- The file follows a column structure predefined by 4HSE.
- The file always contains the entire, updated dataset of employees.
- The field values for each employee in the dataset overwrite the values of the same employee in 4HSE.
- Employees present in 4HSE but not in the source will be deactivated in 4HSE.
- Employees present in the source but not in 4HSE will be added to 4HSE.
The CSV file consists of the following columns:
field | required |
---|---|
code | no |
first_name | yes |
last_name | yes |
street | no |
locality | no |
postal_code | no |
region | no |
sex | no |
country | no |
birth_date | no |
birth_place | no |
tax_code | no |
note | no |
is_employee | yes |
Among these fields, you need to identify a key field, i.e., a field whose value uniquely identifies an employee. It can be any of these fields, usually code
or the tax code tax_code
.
4HSE REST API
Section titled “4HSE REST API”4HSE provides a standard OpenAPI REST API to perform and monitor synchronization. The create
endpoint receives a request containing an input CSV and creates a task on the 4HSE processing servers. The service responds immediately if the creation is successful, providing the unique task identifier. The task will be processed as soon as possible. The view
endpoint returns the processing status of the task identified by its ID.
The complete API documentation is available here: PeopleSync API.
Example
Section titled “Example”In this example, we want to perform periodic synchronization between the data extracted from our HR system and the “La mia azienda” project on 4HSE. Suppose the CSV file employees-1.csv
has been exported from our HR software and properly transformed to follow the column structure required by 4HSE. The file contains 7 employees.
code | first_name | last_name | street | locality | postal_code | region | sex | country | birth_date | birth_place | tax_code | note | is_employee |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | John | Doe | 123 Main St | Anytown | 12345 | CA | M | USA | 1980-05-15 | Anytown | ABC123456 | Note for John Doe | 1 |
2 | Jane | Smith | 456 Oak St | Someville | 67890 | NY | F | USA | 1985-08-21 | Someville | DEF789012 | Note for Jane Smith | 1 |
3 | Robert | Johnson | 789 Pine St | Another City | 54321 | TX | M | USA | 1972-11-03 | Another City | GHI345678 | Note for Robert Johnson | 1 |
4 | Emily | Davis | 101 Elm St | Yourtown | 98765 | FL | F | USA | 1992-04-18 | Yourtown | JKL901234 | Note for Emily Davis | 1 |
5 | Michael | Miller | 202 Cedar St | Cityville | 13579 | IL | M | USA | 1988-07-27 | Cityville | MNO567890 | Note for Michael Miller | 1 |
6 | Sophia | Brown | 303 Maple St | Smalltown | 24680 | GA | F | USA | 1995-02-09 | Smalltown | PQR123456 | Note for Sophia Brown | 1 |
7 | Daniel | Wilson | 404 Birch St | Newtown | 86420 | WA | M | USA | 1983-09-12 | Newtown | STU789012 | Note for Daniel Wilson | 1 |
Our project, identified by project_id
41ec7395-eeda-4c8e-bc73-5585d48e5161
, does not yet contain any people. It includes a project admin user john@4hse.com
identified by the access token test-key-1
. The first synchronization we perform is therefore a sort of “onboarding” or “zero point” on which all subsequent synchronizations will be based.
Now let’s write the import script in Java. We create a Main
class that will be instantiated when the script starts. Let’s start by defining the constants within the class with the input parameters required for the request.
public class Main { private final static String ACCESS_TOKEN = "test-key-1"; private final static String PROJECT_ID = "41ec7395-eeda-4c8e-bc73-5585d48e5161"; private final static String PRIMARY_KEY = "tax_code"; private final static int MAX_CHANGES = 150; private final static boolean EMULATION = true;}
ACCESS_TOKEN
: The token that identifies the 4HSE user who will perform the operation, a project admin.PROJECT_ID
: The ID of the project we just created.PRIMARY_KEY
: The column in the CSV that uniquely identifies an employee. We chose “tax_code.”EMULATION
: Allows you to run the request in emulation mode. This means you can run the synchronization and get the results without actually making any changes. This allows you to test the outcome of the operation before applying it. Initially, we set this value toTRUE
.MAX_CHANGES
: A safety parameter that controls the maximum number of changes that can be applied in the request. If the total number of changes exceeds this value, we will be notified and the request will be rejected. Initially, we set it to150
.
Now let’s define the createTask
method, which will be used to create the synchronization task at the people-sync
service of 4HSE. Creating the task is a simple HTTP POST
request to the server, which responds with a task_id
. The task ID is an identifier for the task that allows us to query the server to know its progress status (queued, executed, etc.).
The method only requires the input of the file name to be synchronized and returns the ID of the created task.
private static String createTask(String filePath) throws IOException { ...}
The first thing to do is to instantiate an HTTP client to send the request.
private static String createTask(String filePath) throws IOException { HttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost("https://service.4hse.com/people-sync/create");}
Since you need to send a file, the data must be transmitted in multipart format. We populate the multipart request body by providing the file and the remaining input parameters:
private static String createTask(String filePath) throws IOException { HttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost("https://service.4hse.com/people-sync/create");
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
// Add the file parameter builder.addBinaryBody("file", new File(filePath), ContentType.APPLICATION_OCTET_STREAM, "file.csv");
// Add other parameters builder.addTextBody("access-token", ACCESS_TOKEN); builder.addTextBody("project_id", PROJECT_ID); builder.addTextBody("pk", PRIMARY_KEY); builder.addTextBody("emulation", String.valueOf(EMULATION)); builder.addTextBody("max_changes", String.valueOf(MAX_CHANGES));
HttpEntity multipart = builder.build(); httpPost.setEntity(multipart);}
Finally, we execute the request. If successful, the server will respond with a JSON that must be parsed to obtain the task_id
, which we will return as output. In case of error, we print the error to the screen.
private static String createTask(String filePath) throws IOException { HttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost("https://service.4hse.com/people-sync/create");
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
// Add the file parameter builder.addBinaryBody("file", new File(filePath), ContentType.APPLICATION_OCTET_STREAM, "file.csv");
// Add other parameters builder.addTextBody("access-token", ACCESS_TOKEN); builder.addTextBody("project_id", PROJECT_ID); builder.addTextBody("pk", PRIMARY_KEY); builder.addTextBody("emulation", String.valueOf(EMULATION)); builder.addTextBody("max_changes", String.valueOf(MAX_CHANGES));
HttpEntity multipart = builder.build(); httpPost.setEntity(multipart);
// Execute the request HttpResponse response = httpClient.execute(httpPost);
// Parse the JSON response JSONParser parser = new JSONParser(); try (InputStream content = response.getEntity().getContent()) { JSONObject jsonResponse = (JSONObject) parser.parse(new InputStreamReader(content)); return (String) jsonResponse.get("task_id"); } catch (Exception e) { e.printStackTrace(); return null; }}
Now, let’s define a readTask
method that, using the task_id
, will get the current status of the task and print it to the screen.
private static void readTask(String taskId) throws IOException { ...}
Similarly to the previous method, we instantiate an HTTP client that, this time, makes a request using the GET
method, providing the task_id
and the access-token
for user identification.
private static void readTask(String taskId) throws IOException { HttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("https://service.4hse.com/people-sync/view?id=" + taskId + "&access-token=" + ACCESS_TOKEN);}
We execute the request, parse the JSON response, and print the task_id
to the screen.
private static void readTask(String taskId) throws IOException { HttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("https://service.4hse.com/people-sync/view?id=" + taskId + "&access-token=" + ACCESS_TOKEN);
// Execute the request HttpResponse response = httpClient.execute(httpGet);
// Parse and print the JSON response JSONParser parser = new JSONParser(); try (InputStream content = response.getEntity().getContent()) { JSONObject jsonResponse = (JSONObject) parser.parse(new InputStreamReader(content)); System.out.println("Task: " + jsonResponse.toJSONString()); } catch (Exception e) { e.printStackTrace(); }}
Finally, let’s define the main method, which will be the entry point of our script. The available commands for the script will be:
-
create
to create the taskjava -jar your-jar-file.jar create filename.csv
-
read
to read the taskjava -jar your-jar-file.jar read task-id
We then check that the input parameters are provided; otherwise, we display a message on the screen.
public static void main(String[] args) { String command = args[0]; //create or read
if (args.length < 2) { System.out.println("Usage:"); System.out.println("java -jar your-jar-file.jar create file.csv"); System.out.println("java -jar your-jar-file.jar read task-id"); return; }}
We then check if the first parameter passed to the command is create
or read
and call the respective methods, passing the CSV file name as an argument for creation or the task ID for reading.
public static void main(String[] args) { String command = args[0]; //create or read
if (args.length < 2) { System.out.println("Usage:"); System.out.println("java -jar your-jar-file.jar create file.csv"); System.out.println("java -jar your-jar-file.jar read task-id"); return; }
try { if ("create".equals(args[0])) { String taskId = createTask(args[1]); System.out.println("Created Task ID: " + taskId); } else if ("read".equals(args[0])) { readTask(args[1]); } else { System.out.println("Action must be 'create' or 'read'"); } } catch (IOException e) { e.printStackTrace(); }}
Now let’s run the script by starting the task creation with the command:
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar create employees2-1.csv
In response, we get the created task ID:
Created Task ID: be9eec92-621c-31b1-a525-7df391f599bf
After waiting a few seconds, we run the command to read the status of the created task:
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar read be9eec92-621c-31b1-a525-7df391f599bf
In response, we get the complete report of the server actions:
-
task_id
: Task ID -
entity_id
: Project ID -
user_id
: the user who created the task -
status
: indicates whether the task was successfully executed or not -
created_at
,updated_at
: provide timestamps for task creation and update -
response.result
: is a history of the operations performed for each record in our CSV file. In this case, since they were new employees, each employee, identified by theirtax_code
, was added to the project. -
response.changes
: is a summary of all operations performed. In this case, the task resulted in a total of 7 changes, all “add”. -
log
: the server log recorded during the execution of the task. It is useful for identifying problems in case of errors.{"task_id": "a386a5bf-5bc4-3be7-9392-c81c69918bfc","entity_id": "41ec7395-eeda-4c8e-bc73-5585d48e5161","user_id": "john@4hse.com","status": "DONE","created_at": "2024-01-11 08:14:53","updated_at": "2024-01-11 08:15:07","response": {"result": {"ABC123456": [{"action": "add","id": "ABC123456"}],"PQR123456": [{"action": "add","id": "PQR123456"}],"DEF789012": [{"action": "add","id": "DEF789012"}],"STU789012": [{"action": "add","id": "STU789012"}],"MNO567890": [{"action": "add","id": "MNO567890"}],"GHI345678": [{"action": "add","id": "GHI345678"}],"JKL901234": [{"action": "add","id": "JKL901234"}]},"log": [{"level": "INFO","text": "Task a386a5bf-5bc4-3be7-9392-c81c69918bfc executed","timestamp": 1704960907},{"level": "INFO","text": "Executing task a386a5bf-5bc4-3be7-9392-c81c69918bfc in emulation mode","timestamp": 1704960906}],"changes": {"add": 7,"total": 7,"activate": 0,"update": 0,"deactivate": 0}}}
At this point, change the emulation parameter to false
and rerun the creation command.
private final static boolean EMULATION = false;
You will receive the same response as before, but in this case, the changes have actually been made and you will have 7 employees in the “La mia azienda” project.
Now, suppose that in the company, 2 people have retired. As a result, our HR has created a new file employees2-2.csv
containing only the 5 remaining individuals. Also, for “John Doe”, the NOTE
field has been updated. In total, there are 3 differences compared to the employees2-1.csv
file.
code | first_name | last_name | street | locality | postal_code | region | sex | country | birth_date | birth_place | tax_code | note | is_employee |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | John | Doe | 123 Main St | Anytown | 12345 | CA | M | USA | 1980-05-15 | Anytown | ABC123456 | Updated note for John Doe | 1 |
2 | Jane | Smith | 456 Oak St | Someville | 67890 | NY | F | USA | 1985-08-21 | Someville | DEF789012 | Note for Jane Smith | 1 |
3 | Robert | Johnson | 789 Pine St | Another City | 54321 | TX | M | USA | 1972-11-03 | Another City | GHI345678 | Note for Robert Johnson | 1 |
4 | Emily | Davis | 101 Elm St | Yourtown | 98765 | FL | F | USA | 1992-04-18 | Yourtown | JKL901234 | Note for Emily Davis | 1 |
5 | Michael | Miller | 202 Cedar St | Cityville | 13579 | IL | M | USA | 1988-07-27 | Cityville | MNO567890 | Note for Michael Miller | 1 |
The file now contains only active employees, and for some of them, the NOTE
field has been updated. There have been no changes to the tax codes.
Now proceed with the synchronization by uploading the new file. Since the number of changes exceeds the MAX_CHANGES
value set to 150, the request will be rejected and you will receive an error message.
Therefore, reduce the number of changes to 50 to allow the request to be applied.
private final static int MAX_CHANGES = 50;
Repeat the task creation command.
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar create employees2-2.csv
After obtaining the task ID, read its status.
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar read task-id
The result will show that 2 employees have been removed (deactivated) and 3 have been updated.
Now, prepare a new file employees2-3.csv
in which all employees have been deactivated. The file will therefore contain only the TAX_CODE
column with the respective tax codes.
tax_code |
---|
ABC123456 |
DEF789012 |
GHI345678 |
JKL901234 |
MNO567890 |
PQR123456 |
STU789012 |
Set the MAX_CHANGES
parameter to 150 and upload the new file.
private final static int MAX_CHANGES = 150;
Run the creation command and then read the task status.
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar create employees2-3.csv
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar read task-id
The result will show that 7 employees have been deactivated.
Finally, prepare a last file employees2-4.csv
in which all employees have been reactivated. The file will therefore contain only the TAX_CODE
column with the respective tax codes.
tax_code |
---|
ABC123456 |
DEF789012 |
GHI345678 |
JKL901234 |
MNO567890 |
PQR123456 |
STU789012 |
Set the MAX_CHANGES
parameter to 150 and upload the new file.
private final static int MAX_CHANGES = 150;
Run the creation command and then read the task status.
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar create employees2-4.csv
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar read task-id
The result will show that 7 employees have been reactivated.
Conclusions
Section titled “Conclusions”We have seen how to develop an integration script between a generic HR software and 4HSE. Using the standard 4HSE APIs, we created a Java software capable of uploading a CSV file with employee data and synchronizing it with the 4HSE safety management system.
The script was designed to be easily adaptable to different HR software solutions by simply modifying the format of the input CSV file.
In this example, we used a CSV file as the data source. However, it is possible to adapt the script to extract data directly from a database or other data sources, depending on specific integration needs.
In addition, we implemented an emulation mode that allows you to test the outcome of the synchronization without making actual changes to the data. This is especially useful during the development and testing phase of the integration script.
Finally, it is important to emphasize that, although the script provides an automated solution for synchronizing HR data with 4HSE, it is always advisable to perform periodic manual checks and verifications to ensure the accuracy and consistency of data between the two systems.