Expand your SuiteScript skills in the Sustainable SuiteScript community for NetSuite developers.

Searching for Missing Records with SuiteScript

Created: February 1, 2024

This is a sample chapter adapted from the "Advanced Searching with SuiteScript 2.1" Cookbook, part of the SuiteScript by Example series.

This code example uses the require function for importing modules. This allows you to copy and paste the snippets directly into the debugger or your browser's developer console and run them. For more on how to test SuiteScript in your browser's console, watch my tutorial video .

The majority of the time that we're building NetSuite searches, we're searching for the existence of Records that meet our criteria. Once in a while, however, we actually need to search for the absence of something, and this can often be trickier.

For example, how do we find out which Employees have no Time Entries for this week?

If the Records don't exist, we can't find them directly. Because we are looking for the absence of a Record, we'll actually need to combine the Results from two different searches.

/**
 * Finds Employees that have no Time Entries for the last week.
 *
 * Combines the Results from two different searches in order to search
 * for the absence of Records.
 *
 * @author Eric T Grubaugh <eric@stoic.software> (https://stoic.software/)
 */
require(['N/search'], (s) => {
  const employeeSearch = s.create({
    type: s.Type.EMPLOYEE,
    filters: [
      ['isinactive', s.Operator.IS, 'F']
    ],
    columns: [
      { name: 'formulatext', formula: "{firstname} || ' ' || {lastname}" }
    ]
  })

  const timeSearch = s.create({
    type: s.Type.TIME_BILL,
    filters: [
      ['date', s.Operator.WITHIN, 'thisBusinessWeek'], 'and',
      ['type', s.Operator.ANYOF, 'A'] // 'A' => 'Actual Time'
    ],
    columns: [
      { name: 'employee', summary: s.Summary.GROUP }
    ]
  })

  // Display result counts from each search
  console.log(`# Employees = ${employeeSearch.runPaged().count}`)
  console.log(`# Employees with Entries = ${timeSearch.runPaged().count}`)

  // Assume there are less than 1,000 Employees
  const employees = employeeSearch.run().getRange({ start: 0, end: 1000 })
  const timeEntries = timeSearch.run().getRange({ start: 0, end: 1000 })

  // Does the given employee have at least one Time Entry in entries Array?
  const employeeHasEntry = (entries, employee) =>
    entries.some((entry) => {
      let entryEmployee = entry.getValue({
        name: 'employee',
        summary: s.Summary.GROUP
      })
      return (entryEmployee == employee.id)
    })

  // Returns a copy of employees Array but removes any Employees
  // that *do* have Time Entries in entries Array
  const findEmployeesWithNoEntries = (employees, entries) =>
    employees.filter((employee) => !employeeHasEntry(entries, employee))

  const printEmployeeName = (result) =>
    console.log(result.getValue({ name: 'formulatext' }))

  const employeesWithNoEntries = findEmployeesWithNoEntries(
    employees,
    timeEntries
  )

  console.log(`# Employees With No Entries = ${employeesWithNoEntries.length}`)
  employeesWithNoEntries.forEach(printEmployeeName)
})

To determine which Employees have zero time entries this week, we can find the logical difference between the list of all Employees and the list of Employees who do have time entries this week:

const employeeSearch = s.create({
  type: s.Type.EMPLOYEE,
  filters: [
    ['isinactive', s.Operator.IS, 'F']
  ],
  columns: [
    { name: 'formulatext', formula: "{firstname} || ' ' || {lastname}" }
  ]
})

const timeSearch = s.create({
  type: s.Type.TIME_BILL,
  filters: [
    ['date', s.Operator.WITHIN, 'thisBusinessWeek'], 'and',
    ['type', s.Operator.ANYOF, 'A'] // 'A' => 'Actual Time'
  ],
  columns: [
    { name: 'employee', summary: s.Summary.GROUP }
  ]
})

console.log(`# Employees = ${employeeSearch.runPaged().count}`)
console.log(`# Employees with Entries = ${timeSearch.runPaged().count}`)

const employees = employeeSearch.run().getRange({ start: 0, end: 1000 })
const timeEntries = timeSearch.run().getRange({ start: 0, end: 1000 })

We run two separate Searches to get our requisite data.

Now in order to determine which Employees don't have any Time Entries, we can "subtract" the Employees who do have Time Entries from the list of all Employees. This will leave us with Employees who do not have Time Entries.

To accomplish this, we leverage the JavaScript Array's filter and some methods.

First, we write the employeeHasEntry function that accepts an Array of Time Entry Search Results and a single Employee Search Result. Its job is to determine whether a specific Employee has a Time Entry:

const employeeHasEntry = (entries, employee) =>
  entries.some((entry) => {
    let entryEmployee = entry.getValue({
      name: 'employee',
      summary: s.Summary.GROUP
    })
    return (entryEmployee == employee.id)
  })

We use some to determine if the given employee exists within the entries Array.

Now that we can detect whether a single Employee has any Time Entries, we need to extend that over the full list of Employees. This falls to findEmployeesWithNoEntries:

const findEmployeesWithNoEntries = (employees, entries) =>
  employees.filter((employee) => !employeeHasEntry(entries, employee))

We use employeeHasEntry as the predicate of a filter to remove the Employees that do have a Time Entry from employees.

We can now pass in our two separate Result Arrays and list the Employees that have no Time Entries this week:

const employeesWithNoEntries = findEmployeesWithNoEntries(
  employees,
  timeEntries
)

console.log(`# Employees With No Entries = ${employeesWithNoEntries.length}`)
employeesWithNoEntries.forEach(printEmployeeName)

This will print out the number of Employees with no Time Entries, followed by the list of their names.