Complex Inspection Flow
Subscription Tier Required
This feature requires the Premier subscription tier or higher.
It is common to have workflow requirements for Inspections which fall outside what is possible using the standard Sub-Inspection settings. This includes things like:
- Requiring a set of checks to be completed whenever a Real-Time Failure is encountered.
- Repeating a Sub-Inspection until a Shift ends.
- Selecting different Sub-Inspections based on the Part being measured.
In this guide, you will create an Inspection which will change behavior when a Defect is encountered. When data is submitted which includes a Defect, the operator will be directed to a Sub-Inspection which asks some questions to determine whether the Part should be scrapped or reworked, requiring Sign-Off from a Supervisor. Once those checks are complete, the Operator will be asked whether they would like to measure another Part or exit the Inspection.
Prerequisites
This guide assumes that you have:
- At least one Process.
- At least one Part.
- At least two Defects.
For the purpose of this guide, the Process will be Final Inspection, the Part will be Steel Chassis, and the Defects will be Scratch and Chipped Paint.
Create the Inspection
To begin, create an Inspection:
- Navigate to the Inspection list.
- Press the Add button.

- Fill in the Name field with Inspection Flow.
- Press the Save button.

Inspection Data
Next, open the Inspection Data panel and add a Part Test and a Process Test. Adding these in the Inspection Data area will require the operator to fill them out once; the values will then be carried through all Sub-Inspections.
Open the Inspection Settings and set Autoload First Sub-Inspection to Yes.
First Sub-Inspection
In the existing Sub-Inspection, add two Pass/Fail Tests.
For the Pass/Fail Tests, select the Defects.
Finally, edit the Sub-Inspection settings to give it a Script ID of defect-checks and the name Defect Checks. This will be used to reference this Sub-Inspection in the Script.
Second Sub-Inspection
Next, add another Sub-Inspection and click the tab to navigate to it.
Add two Pass/Fail tests with the following Labels:
- Scratches only affect the paint
- Paint chipping covers a small area
Modify the Script IDs to be:
- scratch
- chip
These Pass Fail tests will only be used to determine whether the Part should be sent for rework, and the answers will not be stored. To accomplish this, change the following settings of both Pass/Fail tests:
- Uncheck Visible.
- Set Store Data to Bypass Validity And Do Not Store.
- Set Note Button to Hide Note Button.
- Set "Pass" Label to Yes.
- Set "Fail" Label to No.
Add a Task Test. Check the Require Sign-Off option and check an appropriate role for the Sign-Off Roles. Hide this test by unchecking Visible , as it will only be shown after the operator has filled out the other tests.
The label and text of this Task test will be customized with scripting.
Edit the Sub-Inspection settings to give it a Script ID of sign-off, set Show Cancel Button to No, and set the name to Sign-Off.
Script
Begin by adding a new Inspection Script and giving it a memorable name. If this interface is unfamiliar, review the Inspection Scripting Principles and Code Editor articles.
There are three separate tasks which need to be accomplished by scripting.
- Redirecting to the second Sub-Inspection on Failure.
- Setting up the second Sub-Inspection when it is loaded.
- Prompting whether to measure another part or end the Inspection.
Redirecting On Failure
Begin by creating a reference to the first Sub-Inspection and creating a variable to keep track of any Defects which were submitted:
const defectCheckSubInspection = gsApi.inspection.subInspection('defect-checks');
let selectedDefects;
Next, bind to the onBeforeEnd event of the first Sub-Inspection. This will allow checking if any of the Pass/Fail Tests were submitted with a value of fail. We also want to reset the selectedDefects variable to an empty object, and ensure that this check only occurs if the Sub-Inspection was actually submitted (not cancelled):
defectCheckSubInspection.onBeforeEnd(async (e) => {
selectedDefects = {};
if (e.data.actionType === 'cancel') {
return;
}
});
After this, check if there are currently any Pass/Fail tests set to fail. This can be done by getting the list of tests on the Sub-Inspection and filtering it to only include passFail tests. Then, for each Pass/Fail test, if it is set to fail, set it to true in the selectedDefects object. The end result is that after this Sub-Inspection is submitted, the selectedDefects object will contain a mapping of all possible Defect Names in the Sub-Inspection to whether or not that Defect was submitted.
if (e.data.actionType === 'cancel') {
return;
}
const subInspectionProperties = await defectCheckSubInspection.getProperties();
const passFailTestIds = subInspectionProperties.tests.filter(x => x.type === 'passFail');
for (let i = 0; i < passFailTestIds.length; i++) {
const properties = await defectCheckSubInspection.passFail(passFailTestIds[i].scriptId).getProperties();
const defectName = properties.defect?.name;
if (defectName) {
selectedDefects[defectName] = properties.value === 'fail';
}
}
Finally, bind to the onAfterEnd event of the first Sub-Inspection and redirect to the sign-off Sub-Inspection if there were any Defects. This is determined by whether any of the Defect names in selectedDefects are true, which represents a recorded Defect. As before, only do this if the data was submitted. If the Sub-Inspection was cancelled, redirect them to the list of Inspections:
defectCheckSubInspection.onAfterEnd(async (e) => {
if (e.data.actionType === 'cancel') {
await gsApi.inspection.goToInspectionList();
return;
}
const hasAnyDefect = Object.values(selectedDefects).some(x => x === true);
if (hasAnyDefect) {
await gsApi.inspection.goToSubInspection('sign-off');
}
});
Setting Up Questions
The next step is to configure the Sign-Off Sub-Inspection. When it is opened, the appropriate Pass/Fail Tests will need to be shown. When all of the visible Pass/Fail tests have been completed, the Task Test will be shown with the appropriate message. Remember that the Pass/Fail Tests on this Sub-Inspection are being used as a questionnaire for the operator, and are not being stored.
To begin, get a reference to the Sign-Off Sub-Inspection API:
const defectCheckSubInspection = gsApi.inspection.subInspection('defect-checks');
const signOffSubInspection = gsApi.inspection.subInspection('sign-off');
let selectedDefects;
When this Sub-Inspection starts, we will determine which questions the Operator should be asked in order to determine whether the Part will be scrapped or undergo rework. In order to do this, set up a mapping between Defect names and the Test ID corresponding to that defect. Then, loop through all of the Defects set in the selectedDefect mapping, hiding any which were not defective:
const defectNamesToSignOffIds = {
'Scratch': 'scratch',
'Chipped Paint': 'chip'
};
signOffSubInspection.onAfterStart(async () => {
for (const defectName in selectedDefects) {
const signOffTestId = defectNamesToSignOffIds[defectName];
const hasDefect = selectedDefects[defectName];
await signOffSubInspection.passFail(signOffTestId).updateProperties({
userDefinedVisible: hasDefect,
required: hasDefect
});
}
});
Updating Sign-Off
When a value is selected for a Pass/Fail Test on the Sign-Off Sub-Inspection, we will need to:
- Check whether the operator has selected an answer for all of the Pass/Fail tests, and show the Task Test if so.
- Update the label of the Task Test based on whether the Part should be scrapped or sent for rework.
To begin, bind to the onOptionSelected event of each Pass/Fail test. Since there is already a mapping of Defect name to Test ID, that can be used:
for (const defectName in defectNamesToSignOffIds) {
const signOffId = defectNamesToSignOffIds[defectName];
signOffSubInspection.passFail(signOffId).onOptionSelected(async () => {
});
}
Next, check whether all of the tests have been filled out. Only Pass/Fail tests which were marked as visible in the previous section need to be checked:
signOffSubInspection.passFail(signOffId).onOptionSelected(async () => {
let allTestsAreFilledOut = true;
let scrapPart = false;
for (const defectName in selectedDefects) {
if (selectedDefects[defectName]) {
const signOffTestId = defectNamesToSignOffIds[defectName];
const properties = await signOffSubInspection.passFail(signOffTestId).getProperties();
if (properties.value === undefined) {
allTestsAreFilledOut = false;
}
if (properties.value === 'fail') {
scrapPart = true;
}
}
}
});
At this point, we have determined whether all of the tests have been filled out and what is going to happen to the Part. Add this code at the end of the onOptionSelected event to update the Task Test:
const taskLabel = scrapPart ? 'The Part has been scrapped.' : 'The Part has been sent for rework.';
await signOffSubInspection.task('sign-off').updateProperties({
label: taskLabel,
userDefinedVisible: allTestsAreFilledOut
});
Asking To Measure Another Part
As a convenience measure for the operator, we will ask them if they want inspect another Part. To do this, bind to the event so that when any Sub-Inspection ends, they are shown a prompt:
gsApi.inspection.allSubInspectionEvents().onAfterEnd(async (e) => {
const response = await gsApi.display.prompt('Measure Another?', 'Would you like to measure another Part?', [{
return: true,
text: 'Yes',
type: 'blue'
}, {
return: false,
text: 'No',
type: 'white'
}]);
if (response) {
await gsApi.inspection.goToSubInspection('defect-checks');
} else {
await gsApi.inspection.goToInspectionList();
}
});
However, you may notice that this will overwrite the previous code which sends the operator to the Sign-Off Inspection. In order to prevent this, use the stopPropagation function on the onAfterEnd event:
defectCheckSubInspection.onAfterEnd(async (e) => {
if (e.data.actionType === 'cancel') {
await gsApi.inspection.goToInspectionList();
return;
}
const hasAnyDefect = Object.values(selectedDefects).some(x => x === true);
if (hasAnyDefect) {
e.stopPropagation();
await gsApi.inspection.goToSubInspection('sign-off');
}
});
stopPropagation prevents the event from running any additional user-defined code.
Test the Inspection
Save and Run the Inspection. Try out some combinations of Pass/Fails and navigate through the Inspection.
Final Code
const defectCheckSubInspection = gsApi.inspection.subInspection('defect-checks');
const signOffSubInspection = gsApi.inspection.subInspection('sign-off');
let selectedDefects;
const defectNamesToSignOffIds = {
'Scratch': 'scratch',
'Chipped Paint': 'chip'
};
defectCheckSubInspection.onBeforeEnd(async (e) => {
selectedDefects = {};
if (e.data.actionType === 'cancel') {
return;
}
const subInspectionProperties = await defectCheckSubInspection.getProperties();
const passFailTestIds = subInspectionProperties.tests.filter(x => x.type === 'passFail');
for (let i = 0; i < passFailTestIds.length; i++) {
const properties = await defectCheckSubInspection.passFail(passFailTestIds[i].scriptId).getProperties();
const defectName = properties.defect?.name;
if (defectName) {
selectedDefects[defectName] = properties.value === 'fail';
}
}
});
defectCheckSubInspection.onAfterEnd(async (e) => {
if (e.data.actionType === 'cancel') {
await gsApi.inspection.goToInspectionList();
return;
}
const hasAnyDefect = Object.values(selectedDefects).some(x => x === true);
if (hasAnyDefect) {
e.stopPropagation();
await gsApi.inspection.goToSubInspection('sign-off');
}
});
signOffSubInspection.onAfterStart(async () => {
for (const defectName in selectedDefects) {
const signOffTestId = defectNamesToSignOffIds[defectName];
const hasDefect = selectedDefects[defectName];
await signOffSubInspection.passFail(signOffTestId).updateProperties({
userDefinedVisible: hasDefect,
required: hasDefect
});
}
});
for (const defectName in defectNamesToSignOffIds) {
const signOffId = defectNamesToSignOffIds[defectName];
signOffSubInspection.passFail(signOffId).onOptionSelected(async () => {
let allTestsAreFilledOut = true;
let scrapPart = false;
for (const defectName in selectedDefects) {
if (selectedDefects[defectName]) {
const signOffTestId = defectNamesToSignOffIds[defectName];
const properties = await signOffSubInspection.passFail(signOffTestId).getProperties();
if (properties.value === undefined) {
allTestsAreFilledOut = false;
}
if (properties.value === 'fail') {
scrapPart = true;
}
}
}
const taskLabel = scrapPart ? 'The Part has been scrapped.' : 'The Part has been sent for rework.';
await signOffSubInspection.task('sign-off').updateProperties({
label: taskLabel,
userDefinedVisible: allTestsAreFilledOut
});
});
}
gsApi.inspection.allSubInspectionEvents().onAfterEnd(async (e) => {
const response = await gsApi.display.prompt('Measure Another?', 'Would you like to measure another Part?', [{
return: true,
text: 'Yes',
type: 'blue'
}, {
return: false,
text: 'No',
type: 'white'
}]);
if (response) {
await gsApi.inspection.goToSubInspection('defect-checks');
} else {
await gsApi.inspection.goToInspectionList();
}
});








