we recently have need to get the location data in ...
# suitescript
s
we recently have need to get the location data in multiple scripts, we built a little lib to be used , however what is still a discussion does the cache even work when the script is imported and used inside another script for example I might have an item receipt and I am iterate thru all the line items and calling getLocationNameById(id) 100 times
Copy code
/**
 * @NApiVersion 2.1
 * @NScriptType ClientScript
 * @NModuleScope SameAccount
 */
define(["N/query"], function (query) {
  let cachedLocations = null; // Global cache for active locations
  function pageInit(scriptContext) {}

  function getActiveLocations() {
    if (!cachedLocations) {
      let subSql = `SELECT
                id,
                name,
                isinactive
                    FROM
                location
                WHERE
                isinactive = 'F'`;
      var mySuiteQLQuery = query.runSuiteQL(subSql);
      cachedLocations = mySuiteQLQuery.asMappedResults();
      ///  an array of objects
    }
    return cachedLocations;
  }

  function getLocationNameById(idData) {
    let id = parseInt(idData);
    const locations = getActiveLocations();
    const location = locations.find((loc) => loc.id === id); // Find by ID
    return location ? location.name : null; // Return name or null
  }
  function clearCache() {
    cachedLocations = null;
  }

  return {
    pageInit: pageInit,
    getActiveLocations: getActiveLocations,
    getLocationNameById: getLocationNameById,
    clearCache: clearCache,
  };
});
e
cachedLocations
is just a local variable. It will not persist across Scripts. You might look into
N/cache
for that
s
that is what i said
or we could make the first call at the top of the script and pass that array info
Copy code
let locationArray =getActiveLocations()
getLocationNameById(idData, locationArray)
e
You won't be able to run
getActiveLocations()
outside an entry point
s
the current need is inside an after submit
e
I just mean you can't declare
locationArray
"at the top of the script" because it would try to run a query as soon as the module is loaded into memory, and you can't use NetSuite modules yet at that point of execution. I'm assuming "at the top of the script" here means "the first thing in the module", but perhaps you're saying it's "the first thing in the
afterSubmit
function"
The latter would work fine. The former would not.
s
yes it would be at the begining of the after submit function
1
i dont like the approach thought
what is more performant rerun the query or cache?
e
Reading from
cachedLocations
should be much faster than re-running a query, especially re-running it 100 times
I would expect the same to be true if reading from
N/cache
a
yeah this needs you to use N/cache, this isn't really caching at all its just ... a library function to do your query
e
It's like a local memoization cache, not a persistent one or a shared one
a
i guess it depends on the shape of your requests... if you have lots of requests per record, but less records, this is probably fine, if you have lots of records, then the cache is gonna kill the local variable
s
I have 150 line items with potentially different reciving locations
a
oh okay then yeah this will be 150 times faster than doing 150 lookups 😄
if you have 100s of sales orders processed per minute tho then the
n/cache
will really shine
or item recipts or whatever the base record is
s
im a little confused how to implement the n/cache is this
Copy code
function getActiveLocations() {
    // Get cache instance
    const locationCache = cache.getCache({
      name: 'LocationCache',
      scope: cache.Scope.PROTECTED, // Adjust scope if needed (PRIVATE, PROTECTED, PUBLIC)
    });

    // Attempt to retrieve cached locations
    let cachedLocations = locationCache.get({ key: CACHE_KEY });
    if (cachedLocations) {
      // Parse cached JSON back into an object
      return JSON.parse(cachedLocations);
    }

    // Query SuiteQL for active locations
    const subSql = `SELECT
                      id,
                      name,
                      isinactive
                    FROM
                      location
                    WHERE
                      isinactive = 'F'`;
    const mySuiteQLQuery = query.runSuiteQL(subSql);
    const locations = mySuiteQLQuery.asMappedResults(); // Array of objects

    // Cache the results as a JSON string
    locationCache.put({
      key: CACHE_KEY,
      value: JSON.stringify(locations),
      ttl: CACHE_DURATION,
    });

    return locations;
  }
a
umm you said you had a library function but you're just including it in the script? normally you'd do that as a custom module which you import like the NetSuite
n/modulename
modules then you call the lib function to populate your variable once at the top of the script before you do your line loop
umm i'm not on my work computer so I can't compare your code with sample cache stuff I use, but what you have doesn't look obviously wrong
s
then inside my for loop i call
Copy code
let locationtxt = locationData.getLocationNameById(item.location);
a
you can make your cache results into a JS object so you can just reference it by
locationData[item.location]
just an object key to get the value
s
im confused now
a
i think you need like a retrigger of you cache failsafe as well
😄
n/cache
is confusing the first few times you use it
let me switch comps and grab a working example for you
e
Your
locationCache.get()
call should also specify a
loader
function, which is where you'll put the query code
That tells the cache how to initialize that particular value when it does not exist yet
s
my goal is that any script that my "lib/custom module " returns a function that you can call pass in id and get the text of that location i want to leave all the logic inside the customer module
a
k this is accounting periods cache function set i use...
Copy code
//accounting periods query function
const getAccountingPeriods = () => {
            let myQuery = query.create({
                type: query.Type.ACCOUNTING_PERIOD
            });

            myQuery.condition = myQuery.and(
                myQuery.createCondition({fieldId: 'isyear', operator: 'IS', values: [false]}),
                myQuery.createCondition({fieldId: 'isquarter', operator: 'IS', values: [false]})
            );
            myQuery.columns = [
                myQuery.createColumn({fieldId: 'id', context: query.FieldContext.RAW}),
                myQuery.createColumn({fieldId: 'periodname', context: query.FieldContext.RAW}),
                myQuery.createColumn({fieldId: 'startdate', context: query.FieldContext.RAW}),
                myQuery.createColumn({fieldId: 'enddate', context: query.FieldContext.RAW}),
                myQuery.createColumn({fieldId: 'closed', alias:'isClosed', context: query.FieldContext.RAW})
            ];

            myQuery.sort = [
                myQuery.createSort({
                    column: myQuery.columns[2],
                    ascending: true
                }),
            ];

            let accountingPeriods = [];
            let resultSet = myQuery.runPaged({pageSize:1000});   // gov cost 10
            for (let i = 0; i < resultSet.pageRanges.length; i++) {
                let thisPage = resultSet.fetch({ index: i });
                accountingPeriods = accountingPeriods.concat(thisPage.data.asMappedResults());
            }

            return accountingPeriods;
        }
Copy code
//cache function
const getPeriods2 = ({ start, end }) => {
            let returnVal = [];
            let crCache = cache.getCache({
                name: CR_CACHE,
                scope: cache.Scope.PUBLIC
            });
            let accountingPeriods = crCache.get({  // gov cost 1 or 2
                key: CacheKeys.ACCOUNTING_PERIODS,
                loader: getAccountingPeriods
            });

            if (typeof accountingPeriods === 'string') accountingPeriods = JSON.parse(accountingPeriods);
            let first = 0
            let last = 0;

            for (let i = 0; i<accountingPeriods.length; i++){
                const cacheStart = new Date(accountingPeriods[i].startdate);
                const cacheEnd = new Date(accountingPeriods[i].enddate);
                if ( cacheStart.valueOf() === start.valueOf() ){
                    first = i;
                }
                if ( cacheEnd.valueOf() === end.valueOf()){
                    last = i;
                    returnVal = accountingPeriods.slice(first,last+1);
                }
            }
            return returnVal;
        }
at the top of the cache library I have this
Copy code
const CR_CACHE = 'Contract Renewals Cache';
const CacheKeys = {
    ACCOUNTING_PERIODS: 'Accounting Periods',
};
I call the cache like this
Copy code
const periods = getPeriods2({start, end});
oh actually this is a more complicated example, but should still be useful... I'm returning an array with multiple results to loop thru, which you wouldn't need
Copy code
for (let i=0; i < periods.length; i++){
                //Positive Revenue
                const snapData = {
                    period: periods[i].id, ... <snip>
I'm bringing back a range of accounting periods between two dates, so there's more complexity here, but hopefully still useful for you, and you can see the loader function in action
s
so this is may chache function
Copy code
function getchacheLocation() {
    const CACHE_KEY = 'active_locations';
    const CACHE_DURATION = 3600; // Cache duration in seconds (e.g., 1 hour)
    let locationCache = cache.getCache({
      name: CACHE_DURATION,
      scope: cache.Scope.PROTECTED
    });
    let cachedLocations = locationCache.get({ key: CACHE_KEY, loader: getActiveLocations });
    return JSON.parse(cachedLocations);
  }
or this is the full module
Copy code
/**
 * @NApiVersion 2.1
 * @NScriptType ClientScript
 * @NModuleScope SameAccount
 */
define(["N/query", 'N/cache'], function (query, cache) {
  let cachedLocations = null; // Global cache for active locations
  function pageInit(scriptContext) { }

  function getActiveLocations() {

    let subSql = `SELECT
                id,
                name,
                isinactive
                    FROM
                location
                WHERE
                isinactive = 'F'`;
    var mySuiteQLQuery = query.runSuiteQL(subSql);
    cachedLocations = mySuiteQLQuery.asMappedResults();
    ///  an array of objects
    return cachedLocations;
  }


  function getchacheLocation() {
    const CACHE_KEY = 'active_locations';
    const CACHE_DURATION = 3600; // Cache duration in seconds (e.g., 1 hour)
    let locationCache = cache.getCache({
      name: CACHE_DURATION,
      scope: cache.Scope.PROTECTED
    });
    let cachedLocations = locationCache.get({ key: CACHE_KEY, loader: getActiveLocations });
    return JSON.parse(cachedLocations);
  }




  function getLocationNameById(idData) {
    let id = parseInt(idData);
    const locations = getchacheLocation();
    const location = locations.find((loc) => loc.id === id); // Find by ID
    return location ? location.name : null; // Return name or null
  }


  return {
    pageInit: pageInit,
    getActiveLocations: getActiveLocations,
    getLocationNameById: getLocationNameById,
    clearCache: clearCache,
  };
});
a
you shouldn't need to do a
.find
on your results
your mapped results should come back as an object
s
it comes back as an array of obejcts
a
Copy code
{
    174: 'warehouseA',
    175: 'warehouseB'
}
s
const mockLocations = [ { id: 1, name: "Location A", isinactive: "F" }, { id: 2, name: "Location B", isinactive: "F" }, ];
a
oh well then shape it your cache function
and do it once, and send it back as an object
s
on the question of the cahce function it will check the cache if not it calls the loader function ?
a
yes
the cache duration is just a suggestion 🙂 depending what else is going on in your account it might not last that long, so you need to failsafe check if empty and reload if it is
i don't know if that's ever a practical problem... but this is just the way to do it to not have to worry about it
s
that is what I was asking does the code do that or I have to check it manually if (cachedLocations) {}
a
right the loader function does that, no need to check if null