Skip to content

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:

  1. Navigate to the Inspection list.
  2. Press the Add button. An image showing the location of the Add button on the Inspection list
  3. Fill in the Name field with Inspection Flow.
  4. Press the Save button. An image showing the location of the save button on the Inspection create

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.

An image showing the open Inspection Data tab with a Part and Process test

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.

An image showing the location of the Pass/Fail Test Defect seletion

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.

An image showing the location of the Sub-Inspection settings button

Second Sub-Inspection

Next, add another Sub-Inspection and click the tab to navigate to it.

An image showing the location of button to add a Sub-Inspection

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.

An image showing the 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.

An image showing the location of the Inspection Script action

An image showing the location of the add button in the Inspection Script overlay

There are three separate tasks which need to be accomplished by scripting.

  1. Redirecting to the second Sub-Inspection on Failure.
  2. Setting up the second Sub-Inspection when it is loaded.
  3. 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:

  1. Check whether the operator has selected an answer for all of the Pass/Fail tests, and show the Task Test if so.
  2. 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.

A page showing the defect checks

A page showing the sign-off checks

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();
    }
});