Tracking the progress of business processes with
Process Management
is one of Kintone's strongest features. To add on to that, it would help even more to be able to see how long certain stages of a business process are taking so that bottlenecks can be fixed and to optimize overall process time from start to finish. This article introduces how to track the amount of time taken between process management statuses in a Kintone App..
Sample Image
The below image shows a table with Status Name, Status Datetime, and Status Days. The current status is "Collect provider responses and summarize".
Clicking the "Provider Responses Summarized" action shows the confirmation for proceeding the status to "Review provider options with client".
Clicking Confirm proceeds the status and adds a new row to the table. The data includes the new current status, the current datetime, and how many days it took for the status change.
Prepare the App
Create the Form
In terms of App set up, this customization only requires a table to input the process management information into. Therefore, it is very easy to implement into any App that would benefit from a visualization of the process management. The field settings are as follows.
Field Type
Field Name
Field Code
Notes
Table
Log Table
Status_Log
-
Text
Status Name
Status_Name
Part of Log Table table
Datetime
Status Datetime
Status_Datetime
Part of Log Table table
Number
Status Days
Status_Days
Part of Log Table table
The completed table should look like the following.
Prepare the following JavaScript code in a text editor and navigate to the Kintone App's settings.
Upload the file into the Upload JavaScript for PC option of the
JavaScript and CSS Customization settings
.
(function() {
'use strict';
var defaultRow = {
'Status_Name': {
'value': '',
'type': 'SINGLE_LINE_TEXT' },
'Status_Datetime': {
'type': 'DATETIME',
'value': '' },
'Status_Days': {
'type': 'NUMBER',
'value': 0 }
};
function handleAddProcessLog(event) {
var record = event.record;
var processLogTable = record.Status_Log.value;
var processLogTableLeng = processLogTable.length;
var rowIdx = processLogTableLeng <= 0 ? 0 : processLogTableLeng - 1;
var currentDatetime = luxon.DateTime.local();
var previousDatetime = processLogTable[rowIdx].value.Status_Datetime.value;
var duration = Math.round(currentDatetime.diff(luxon.DateTime.fromISO(previousDatetime)).as('days'));
var nextStatus = event.nextStatus ? event.nextStatus.value : record.Status.value;
var newRow = defaultRow;
newRow.Status_Name.value = nextStatus;
newRow.Status_Datetime.value = currentDatetime.toISO();
newRow.Status_Days.value = Number.isNaN(duration) ? 0 : duration;
processLogTable.push({'value': newRow});
return event;
}
function resetTableRow(event) {
var record = event.record;
var type = event.type;
if (type === 'app.record.create.show') {
if (event.reuse) {
record.Status_Log.value = [{
'value': defaultRow
}];
}
}
return event;
}
function handleInitialLog(event) {
var record = event.record;
var currentTableRowValue = record.Status_Log.value[0];
currentTableRowValue.value.Status_Name.value = 'Not started';
currentTableRowValue.value.Status_Datetime.value = luxon.DateTime.local().toISO();
currentTableRowValue.value.Status_Days.value = 0;
record.Status_Log.value = [currentTableRowValue];
return event;
}
// Events
kintone.events.on('app.record.detail.process.proceed', handleAddProcessLog);
kintone.events.on(['app.record.create.show', 'app.record.edit.show', 'app.record.index.edit.show'], resetTableRow);
// For process log, only initialize when creating a record
kintone.events.on('app.record.create.submit', handleInitialLog);
})();
After saving the settings, update the App. Add a new record into the App and proceed the Status. Every time the Status proceeds, a new row should be added into the Table field. The rows should be filled with the Status name, the date time and the number of days between the statuses.
The defaultRow variable specifies a blank row in the process management table. This variable is used in the functions declared afterward.
The handleAddProcessLog function retrieves the next status and calculates the time in days that it took to get from the previous status to the current status. The information is then added as a new row to the table.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function handleAddProcessLog(event) {
var record = event.record;
var processLogTable = record.Status_Log.value;
var processLogTableLeng = processLogTable.length;
var rowIdx = processLogTableLeng <= 0 ? 0 : processLogTableLeng - 1;
var currentDatetime = luxon.DateTime.local();
var previousDatetime = processLogTable[rowIdx].value.Status_Datetime.value;
var duration = Math.round(currentDatetime.diff(luxon.DateTime.fromISO(previousDatetime)).as('days'));
var nextStatus = event.nextStatus ? event.nextStatus.value : record.Status.value;
var newRow = defaultRow;
newRow.Status_Name.value = nextStatus;
newRow.Status_Datetime.value = currentDatetime.toISO();
newRow.Status_Days.value = Number.isNaN(duration) ? 0 : duration;
processLogTable.push({'value': newRow});
return event;
}
The resetTableRow function is used in the case that a record is reused that already has process management information in the table. It will reset the table information using the defaultRow variable information.
1
2
3
4
5
6
7
8
9
10
11
12
function resetTableRow(event) {
var record = event.record;
var type = event.type;
if (type === 'app.record.create.show') {
if (event.reuse) {
record.Status_Log.value = [{
'value': defaultRow
}];
}
}
return event;
}
The handleInitialLog function is used to input the initial row in the process management table containing the Not started status when the record is initially created.
1
2
3
4
5
6
7
8
9
function handleInitialLog(event) {
var record = event.record;
var currentTableRowValue = record.Status_Log.value[0];
currentTableRowValue.value.Status_Name.value = 'Not started';
currentTableRowValue.value.Status_Datetime.value = luxon.DateTime.local().toISO();
currentTableRowValue.value.Status_Days.value = 0;
record.Status_Log.value = [currentTableRowValue];
return event;
}
Finally, the applicable Kintone events are declared and each function is assigned to the Kintone events they should be executed at. In this case, handleAddProcessLog is executed when the process management is proceeded. resetTableRow is executed at the create record and edit record events. Finally, handleInitialLog is executed when a new record is created.
1
2
3
4
5
6
7
// Events
kintone.events.on('app.record.detail.process.proceed', handleAddProcessLog);
kintone.events.on(['app.record.create.show', 'app.record.edit.show', 'app.record.index.edit.show'], resetTableRow);
// For process log, only initialize when creating a record
kintone.events.on('app.record.create.submit', handleInitialLog);
Customization Variation
This customization can be further improved by making the process management table uneditable through the GUI. An example of the uneditable table is shown below.
(function() {
'use strict';
var defaultRow = {
'Status_Name': {
'value': '',
'type': 'SINGLE_LINE_TEXT' },
'Status_Datetime': {
'type': 'DATETIME',
'value': '' },
'Status_Days': {
'type': 'NUMBER',
'value': 0 }
};
function handleAddProcessLog(event) {
var record = event.record;
var processLogTable = record.Status_Log.value;
var processLogTableLeng = processLogTable.length;
var rowIdx = processLogTableLeng <= 0 ? 0 : processLogTableLeng - 1;
var currentDatetime = luxon.DateTime.local();
var previousDatetime = processLogTable[rowIdx].value.Status_Datetime.value;
var duration = Math.round(currentDatetime.diff(luxon.DateTime.fromISO(previousDatetime)).as('days'));
var nextStatus = event.nextStatus ? event.nextStatus.value : record.Status.value;
var newRow = defaultRow;
newRow.Status_Name.value = nextStatus;
newRow.Status_Datetime.value = currentDatetime.toISO();
newRow.Status_Days.value = Number.isNaN(duration) ? 0 : duration;
processLogTable.push({'value': newRow});
return event;
}
function resetTableRow(event) {
var record = event.record;
var type = event.type;
if (type === 'app.record.create.show') {
if (event.reuse) {
record.Status_Log.value = [{
'value': defaultRow
}];
}
}
}
function handleInitialLog(event) {
var record = event.record;
var currentTableRowValue = record.Status_Log.value[0];
currentTableRowValue.value.Status_Name.value = 'Not started';
currentTableRowValue.value.Status_Datetime.value = luxon.DateTime.local().toISO();
currentTableRowValue.value.Status_Days.value = 0;
record.Status_Log.value = [currentTableRowValue];
return event;
}
function disableTableEdits(event) {
var tableRows = event.record.Status_Log.value;
tableRows.forEach(function(row) {
row.value.Status_Datetime.disabled = true;
row.value.Status_Days.disabled = true;
row.value.Status_Name.disabled = true;
});
}
// Events
kintone.events.on('app.record.detail.process.proceed', handleAddProcessLog);
kintone.events.on(['app.record.create.show', 'app.record.edit.show', 'app.record.index.edit.show'], function(event) {
resetTableRow(event);
disableTableEdits(event);
return event;
});
// For process log, only initialize when creating a record
kintone.events.on('app.record.create.submit', handleInitialLog);
})();
The main change to the code is the addition of the disableTableEdits function. This function goes through each row in the table and sets the disabled property of each field to true.
1
2
3
4
5
6
7
8
function disableTableEdits(event) {
var tableRows = event.record.Status_Log.value;
tableRows.forEach(function(row) {
row.value.Status_Datetime.disabled = true;
row.value.Status_Days.disabled = true;
row.value.Status_Name.disabled = true;
});
}
The other change is in the events declarations. disableTableEdits is added to the three events that resetTableRow is executed at, and the event is returned within the event declaration instead of within each individual function.