HR integration
Introduction
4HSE provides tools for managing personnel records. However it’s becoming increasingly common for well-established companies to want to continue using their HR software and integrate it with a security management system like 4HSE. This challenge arises both during the project onboarding phase and in subsequent daily operational phases where maintaining a solid alignment between the two systems is crucial.
We will explore how to develop software that leveraging the APIs provided by 4hse allows any HR software to integrate with 4HSE keeping personnel records data consistently synchronized.
We won’t focus on a specific HR software but aim for a generic approach to the problem by developing an integration script that can be easily adapted to various HR software solutions in the market.
Prerequisites
What do we need to develop an HR integration script?
- Data source: We will use a simple CSV file as a data source, which we imagine to be exported periodically from an HR software database to align with 4hse every 24 hours.
-
project_id
: We need to have an active project in 4hse and know its ID. You can find the ID in the “info” section of your project window. -
access_token
: We need an active user with admin role for the mentioned project, as this user will be the one executing the synchronization. To authenticate this user via API, we need its access token. The token is provided by 4hse upon client request.
Data Source
To prepare the CSV file extracted from the data source, we must always consider these simple rules:
- The file follows a column layout predefined by 4hse.
- The file always contains the entire dataset of employees, updated.
- The field values for each individual employee in the dataset will overwrite the values for the same employee in 4hse.
- Employees present in 4hse but not in the source will cause the historical tracking of those present in 4hse.
- Employees present in the source but not in 4hse will be added to 4hse.
The CSV file consists of the following columns:
Among these fields, it is necessary to identify a key field, i.e., a field whose value uniquely identifies an employee. It can be any of these fields, usually the code
or the tax code tax_code
.
4HSE REST API
4hse provides a standard OpenAPI REST API for executing and monitoring synchronization. The create
endpoint receives a request containing an input CSV and creates a task at the 4hse processing servers. The service responds immediately upon successful task creation, providing the unique task identifier. The task processing will be executed as soon as possible. The view
endpoint provides the processing status of the task identified by its ID.
Complete documentation of the APIs can be found here: PeopleSync API.
Example
In this example, we want to establish periodic synchronization between the data extracted from our HR and the “My Company” project in 4hse. Suppose the CSV file employees-1.csv
has been extracted from our HR software and appropriately transformed to follow the CSV column structure provided by 4hse. The file contains 7 employees.
-
ACCESS_TOKEN
: It is the token that identifies the 4hse user who will perform the operation, an admin of the project. -
PROJECT_ID
: It is the ID of the project we have just created. -
PRIMARY_KEY
: It is the column in the CSV that uniquely identifies an employee. We have chosen “tax_code.” -
EMULATION
: It allows us to execute the request in emulation mode. This means we can perform the sync and obtain the results without actually making the changes. This allows us to test the outcome of the operation before applying it. We will initially set this value toTRUE
. -
MAX_CHANGES
: It is a security parameter that controls the maximum number of changes applicable in the request. If the total number of changes made exceeds this value, we will be alerted, and the request will be rejected. We initially 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. The creation of 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 (queued, executed, etc.).
The method only requires the input of the file name to synchronize and provides the output of the created task ID.
|
The first thing to do is to instantiate an HTTP client for sending the request.
|
Since we need to send a file, the data must be transmitted in multipart format. Let’s populate the body of the multipart request by providing the file and the remaining input parameters:
|
Finally, we execute the request. In case of success, the server will respond with a JSON that needs to be parsed to obtain the task_id
, which we will return as output. In case of failure, we print the error on the screen.
private static String createTask(String filePath) throws IOException {
HttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost("http://localhost:8081/people-sync/create");
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
// Add 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 response JSON
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 method readTask
that, using the task_id
, will obtain the current status of the task and print it on 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.
|
We execute the request, parse the JSON response, and print the task_id
on the screen.
private static void readTask(String taskId) throws IOException {
HttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://localhost:8081/people-sync/view?id=" + taskId +
"&access-token=" + ACCESS_TOKEN);
// Execute the request
HttpResponse response = httpClient.execute(httpGet);
// Parse and print the response JSON
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, we 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.
|
Now let’s run the script by initiating the task creation with the command:
|
In response, we obtain the ID of the created task:
|
After waiting for a few seconds, we execute the command to read the status of the created task:
|
In response, we get the complete report of the server’s actions:
-
task_id
: task ID -
entity_id
: project ID -
user_id
: the user who created the task -
status
: indicates whether the task was executed successfully or not -
created_at
,updated_at
: provide temporal references to the creation and update of the task -
response.result
: is a history of operations performed for each record in our CSV file. In this case, as 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 made a total of 7 changes, all of which were “additions.” -
log
: the server log recorded during the task execution. 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, let’s change the emulation parameter to false
and rerun the create command.
|
We will receive the same response as before, but in this case, the changes have been actually executed, and therefore, we will have 7 employees in the “La mia azienda” project.
Now, let’s assume that in the company, 2 people have retired. Consequently, our HR has created a new file employees2-2.csv
containing only the remaining 5 individuals. Additionally, for “John Doe,” the NOTE
field has been modified. In total, there are 3 differences compared to the employees2-1.csv
file.
Let’s modify our script, specifically the MAX_CHANGES
parameter, reducing it to 2.
|
Let’s run the create command again, this time sending the employees2-2.csv
file:
|
Read the status of the newly created task, and we encounter an error as the actual number of changes (3) exceeds the maximum allowed by this request (2).
{
"task_id": "b9819d5e-a0a7-34e0-844d-cde54ed91858",
"entity_id": "41ec7395-eeda-4c8e-bc73-5585d48e5161",
"user_id": "john@4hse.com",
"status": "FAILED",
"created_at": "2024-01-11 09:19:03",
"updated_at": "2024-01-11 09:19:21",
"response": {
"log": [
{
"level": "ERROR",
"text": "Out of max changes",
"timestamp": 1704964761
}
]
}
}
Let’s modify the value of MAX_CHANGES
again, setting it to 150
.
|
Let’s rerun the task creation command and check its status:
{
"task_id": "b7ede071-e5c2-37c9-8075-9cd1b5580f26",
"entity_id": "41ec7395-eeda-4c8e-bc73-5585d48e5161",
"user_id": "john@4hse.com",
"created_at": "2024-01-11 08:22:36",
"updated_at": "2024-01-11 08:23:16",
"status": "DONE"
"response": {
"result": {
"ABC123456": [
{
"changes": {
"note": "Another note for John Doe"
},
"action": "update",
"id": "ABC123456"
}
],
"PQR123456": [
{
"action": "deactivate",
"periods": [
{
"end_date": "2024-01-11 00:00:00",
"start_date": "1970-01-01 00:00:00"
}
],
"id": "PQR123456"
}
],
"STU789012": [
{
"action": "deactivate",
"periods": [
{
"end_date": "2024-01-11 00:00:00",
"start_date": "1970-01-01 00:00:00"
}
],
"id": "STU789012"
}
]
},
"log": [
{
"level": "INFO",
"text": "Task b7ede071-e5c2-37c9-8075-9cd1b5580f26 executed",
"timestamp": 1704961396
},
{
"level": "INFO",
"text": "Executing task b7ede071-e5c2-37c9-8075-9cd1b5580f26 in emulation mode",
"timestamp": 1704961396
}
],
"changes": {
"add": 0,
"total": 3,
"activate": 0,
"update": 1,
"deactivate": 2
}
}
}
The response shows the three expected changes. The two individuals who retired have been historically recorded, and the notes for the relevant employee have been modified.
Conclusions
We have seen how easy it is to synchronize your HR database with 4hse, keeping both systems perfectly aligned. In the next tutorial, we will further extend this example script to leverage more in-depth synchronization mechanisms.