I am attempting to write a suitescript search that allows me to pass an array of identifiers to a particular field in Netsuite and return the results. I have tried 'ANYOF', 'ALLOF' and "WITHIN' but I keep getting errors
Here is my code so far:
if(params.type=='sku'){
var filter_name = 'itemid';
}else{
var filter_name = 'upccode';
}
var filters = [
search.createFilter({
name: filter_name,
operator: search.Operator.ANYOF,
values: ['HERHR5201','HERHR5202','HERHR5203']
}),
];
var s = search.create({
'type': record.Type.INVENTORY_ITEM,
'filters':filters,
}).run();
s = s.getRange(0,100);
return JSON.stringify(s);
Does anyone know the right sequence to create a multiple search of itemid's? Also, for a bonus, is there a way to have the resultset return the columns I need verses just the ideas? Do I need to createColumn?
You cannot use ANYOF, ALLOF, etc. when filtering a search on a text field. You'll need to create a filter expression with ORs to search on multiple values.
I would do this:
if(params.type=='sku'){
var filter_name = 'itemid';
}else{
var filter_name = 'upccode';
}
var filters = [
[filter_name, 'is', 'HERHR5201'], 'OR',
[filter_name, 'is', 'HERHR5202'], 'OR',
[filter_name, 'is', 'HERHR5203']
];
var s = search.create({
'type': record.Type.INVENTORY_ITEM,
'filters':filters
}).run();
As far as returning specific columns from your search, you'll need to use search.createColumn(), as you point out. So it'd be something like:
//Previous code...
var s = search.create({
'type': record.Type.INVENTORY_ITEM,
'filters':filters,
'columns': [search.createColumn({name: 'internalid'}),
search.createColumn({name: 'upccode'}),
search.createColumn({name: 'itemid'})
/*Other columns as needed*/]
}).run();
The provided answer is correct, however based on your example code provided I am assuming that the search needs to be created somewhat dynamically. Meaning the 'array of identifiers' you mention will not always be the same, nor will they always be the same length. In order to create a search that is completely dynamic based on incoming 'array of identifiers' you would need to get pretty creative. In the below solution I am assuming the function parameter 'params' is an object with a 'type' property, and an arrIn (array of strings) property. The search below uses the formula function 'DECODE', a description of which can be found here.
function execute(params) {
var filter_name;
var itemSearchObj;
var stringArr = '';
var arrIn = params.arrIn;
var i;
var count;
// create search filter type
filter_name = params.type === 'sku' ? 'itemid' : 'upccode';
// create stringArr using incoming arrIn
for (i = 0; arrIn && arrIn.length > i; i += 1) {
stringArr += i > 0 ? ", '" + arrIn[i] + "', 'true'" : "'" + arrIn[i] + "', 'true'";
}
if (arrIn.length > 0) {
itemSearchObj = nsSearch.create({
type: 'item',
filters: [
["formulatext: DECODE({" + filter_name + "}," + stringArr + ")", "is", 'true']
],
columns: [
'itemid', // dont need to get fancy here, just put the internal id of whichever fields you want in the columns
'description'
]
});
count = itemSearchObj.runPaged().count;
itemSearchObj.run().each(function (result) {
// Do things for each result
});
}
}
I found this quite a bit of a curveball as I was overthinking it. The search requires an array:
results = search.create({
type: search.Type.CUSTOMER,
filters: [],
columns: [ search.createColumn({ name: "internalid", sort: search.Sort.ASC }) ]
})
This filter array consists of a search term array and logical operators (AND, OR).
['fieldid', 'is', yourValue], // search term array
'AND' // logical operator
So you can create your own filter array, by pushing in the search terms and logical operators as required.
For example if you want to return the internal id for customer emails in an array:
let email = ['abc#test.com', 'def#test.com'];
let myFilter = createFilter(email);
function createFilter(email){
let filter = [];
email.forEach((result, index) => {
if(index > 0)
filter.push('OR'); // Logical Operator
filter.push(['email', 'is', result]);
});
return filter;
}
Variable myFilter will now equal:
[ ['email', 'is', 'abc#test.com'], 'OR', ['email', 'is', 'def#test.com'] ]
And can be directly referenced as the filter in the search.
let results = search.create({
type: search.Type.CUSTOMER,
filters: myFilter,
columns: [ search.createColumn({ name: "internalid", sort: search.Sort.ASC }) ]
})
Related
I am trying to add a search filter for a custom line item field in a suitelet and it is returning no results.
function getExpenseSearch(soNum){
log.debug('getExpenseSearch entered')
log.debug('soNum: ' + soNum)
var billSearch = search.create({
type: 'transaction',
filters: [
[ 'type', search.Operator.ANYOF , ['VendBill']], 'and',
['mainline', search.Operator.IS,['F']], 'and',
['custcol_connected_so', search.Operator.ANYOF, [soNum]] ///<=== this is the problem why is it not registering?
],
columns: ['trandate',
'tranid',
'amount'
]
}).run().getRange({start: 0, end: 100})
log.debug('return billSearch[0].tranid: ' + billSearch[0].tranid) //<== always undefined
return billSearch
}
I have isolated the problem to the sublist field
custcol_connected_so is a List field (of sales orders)
soNum is the netsuite internal id of the record
I have already tried the following:
changed .IS to to .ANYOF
used TEXT value instead of recID
hardcoded the correct recID
used 'expense.custcol_connected_so (and other variations, shot in the dark)
input different syntax for create filter
In the records browser there is no join table for the vendorBill so I would think just the custcol_connected_so filter should work fine.
Taking another look there are a couple of issues.
Your syntax for getting the tranid is incorrect (or at least not supported) and you would not be able to get the mainline amount with a single query because you are only going to be able to return the line level amount. In the example below you could use ref.id to load the sales order or to do a lookup Fields call:
This works in my account:
require(['N/search'], search=>{
search.create({
type:'creditmemo',
filters:[
['mainline', 'is', 'F'], 'AND',
['custcol_linked_transaction', 'anyof', [2403605]]
],
columns:['tranid', 'custcol_linked_transaction']
}).run().each(ref=>{
console.log(ref.id, ref.getValue({name:'tranid'}), ref.getValue({name:'custcol_linked_transaction'}));
return false;
});
});
If soNum is the visible sales order number you'd need to get the SO's internal id in order to run that search. Consider:
function getExpenseSearch(soNum) {
log.debug('getExpenseSearch entered')
log.debug('soNum: ' + soNum)
var soInternalId = null;
search.create({
type: 'salesorder',
filters: ['tranid', 'is', soNum]
}).run().each(function(ref) {
soInternalId = ref.id;
return false;
});
var billSearch = search.create({
type: 'vendorbill',
filters: [
['mainline', 'is', 'F'], 'and',
['custcol_connected_so', 'is', soInternalId] ///<=== this is the problem why is it not registering?
],
columns: ['trandate',
'tranid',
'amount'
]
}).run().getRange({
start: 0,
end: 100
})
log.debug('return billSearch[0].tranid: ' + billSearch[0].tranid) //<== always undefined
return billSearch
}
I want to add filters with OR condition dynamically to the search object in script 2.0.
You can use filter expressions to add and/or operators.
As per NetSuite
Use filter expressions as a shortcut to create filters (search.Filter).
eg. search filter like
search.createFilter({
name: 'transactionnumber',
operator: 'is',
values: 'ABC'
});
can be replaced with
[['transactionnumber', 'is', 'ABC'], 'or', ['transactionnumber', 'is', 'XYZ']]
In essence, Search Filters are array or array joined together with operators.
Had the same issue. This is what worked for me:
var soLines = salesOrder.lineItems;
var filteredIDs = [];
for (var i = 0; i < soLines.length; i++) {
filteredIDs.push(['customid', 'is', soLines[i].customID])
filteredIDs.push('OR');
}
filteredIDs.pop();
var mySearch = search.create({
type: search.Type.SALES_ORDER
join: 'item',
columns: [
"internalid",
"item.itemid",
"customid"
],
filters: [filteredIDs]
});
The trick is to create the filter before the search is created. Then you can assign the filtered list as your filters
I am trying to create a custom sublist with sublist field with source to be States record that is managed in Setup > Company > States/Provinces/Countries section.
Here is the example code that I am using and it doesn't work.
_sublist.addField({
id: 'custpage_license_state,
type: serverWidgetModule.FieldType.SELECT,
label: 'LICENSE STATE',
source: 'state' //not recognizing record id
});
I have tried using 'state', 'states', '-195', -195 (was able to locate that this is the internal id for states record in our instance "-195"), but nothing works.
Does anybody has an idea on how to make that work.
Thanks.
The State/Province record isn't exposed. You'll need to add the options to the field manually. You could either perform a search against the customer records which will only return states currently assigned;
/**
* Gets customers geographical states.
*
* #returns {Array} of state information.
*/
function getStates() {
var records = [];
var customerSearchObj = search.create({
type: "customer",
filters: [
["formulatext: {country}", "isnotempty", ""],
"AND",
["formulatext: {state}", "isnotempty", ""]
],
columns: [
search.createColumn({
name: "statedisplayname",
summary: "GROUP",
sort: search.Sort.ASC
}),
search.createColumn({ // abbreviation
name: "state",
summary: "GROUP"
})
]
});
customerSearchObj.run().each(function (result) {
var rec = {
state: result.getValue({name: 'state', summary: 'GROUP'}),
stateDisplay: result.getValue({name: 'statedisplayname', summary: 'GROUP'})
};
records.push(rec);
return true;
});
return records;
}
Or, create a customer in memory and then get the states; (Sorry, SS1 code, taken from SA 63293.)
function getAllStatesForCountry() {
var customer_record = nlapiCreateRecord('customer', {recordmode: 'dynamic'});
customer_record.selectLineItem('addressbook', 1);
var addrSubrecord = customer_record.createCurrentLineItemSubrecord('addressbook', 'addressbookaddress');
addrSubrecord.setFieldValue('country', 'GB');
var stateField = addrSubrecord.getField('dropdownstate');
return stateField.getSelectOptions();
}
And then loop through the result and add them to your field using mySelect.addSelectOption().
Is it possible to do multiple orderBy() columns?
knex
.select()
.table('products')
.orderBy('id', 'asc')
The orderBy() chainable only takes a single column key and a sort value, but how can I order by multiple columns?
You can call .orderBy multiple times to order by multiple columns:
knex
.select()
.table('products')
.orderBy('name', 'desc')
.orderBy('id', 'asc')
The original answer is technically correct, and useful, but my intention was to find a way to programatically apply the orderBy() function multiple times, here is the actual solution I went with for reference:
var sortArray = [
{'field': 'title', 'direction': 'asc'},
{'field': 'id', 'direction': 'desc'}
];
knex
.select()
.table('products')
.modify(function(queryBuilder) {
_.each(sortArray, function(sort) {
queryBuilder.orderBy(sort.field, sort.direction);
});
})
Knex offers a modify function which allows the queryBuilder to be operated on directly. An array iterator then calls orderBy() multiple times.
The Knex orderBy function also receives an array:
knex('users').orderBy(['email', 'age', 'name'])
or
knex('users').orderBy(['email', { column: 'age', order: 'desc' }])
or
knex('users').orderBy([{ column: 'email' }, { column: 'age', order: 'desc' }])
You can use the following solution to solve your problem:
const builder = knex.table('products');
sortArray.forEach(
({ field, direction }) => builder.orderBy(field, direction)
);
orderBy accepts an array of type:
[
{column: 'id', order: 'asc'},
{column: 'name', order: 'desc'},
{column: 'created_at', order: 'desc'},
]
i have a function that takes a param from the request:
sort=id,name,-created_at
and builds an array that is passed to the queryBuilder
columns is an array with the accepted values of table columns
sort(model, sorts, columns) {
let confirmed = true;
sorts = sorts.split(',')
sorts.forEach((sort: string) => {
sort = sort.replace('-', '')
sort = sort.replace(' ', '')
confirmed = columns.includes(sort)
if (!confirmed) {
let index = sorts.indexOf(sort)
sorts.splice(index, 1)
}
})
let sortsArr = [];
sorts.forEach((sort) => {
if (sort.startsWith('-')) {
sort = sort.replace('-', '')
sortsArr.push({column: model.tableName + '.' + sort, order: 'desc'})
} else {
sortsArr.push({column: model.tableName + '.' + sort, order: 'asc'})
}
})
return sortsArr;
}
and then use it like this in the query
const sortsArr = sort(model, sorts, model.columns);
knex('users').orderBy(sortsArr)
Through the UI, I have created several Message records attached to a Support Ticket record, two of which have file attachments. I have been able to retrieve the ticket, and its related messages in Suitescript - which are correctly reporting hasAttachment as 'T' - but I cannot seem to access the attachments themselves. The documentation states that the attachments are a sublist called 'mediaitem' (or 'mediaitemlist', depending on where you look), but none of the sublist APIs have any success on those names.
var record = nlapiLoadRecord('message', 1092823, {recordmode: 'dynamic'});
var itemCount = record.getLineItemCount('mediaitem');
// returns -1
The documentation and other online info is pretty sparse, so any help would be greatly appreciated.
Yeah, indeed there is a poor documentation. And mediaitem sublist did not help me either to give any meaningful result.
However, there is an alternate solution to it.
Create a saved search from UI on message record type.
Make sure you add a search column Attachments : Internal ID (i.e.
using attachment fields...)
Once, this is done, run your search in suitescript as
var res = nlapiSearchRecord('message', 'YOUR_UI_SEARCH_ID', ARRAY_OF_ADDITIONAL_FITLTERS);
res[i].getValue('internalid', 'attachments')
This is how you can do it in Suitescript 2.0. First search the message ids then search for the attachments related to those message ids. You can create the searches on the fly so no need for saved searches.
You can pass an array of internal ids of cases or messages if you want to save governance points depending on your scenario.
Note: The following code samples assumes that you loaded the search module as SEARCHMODULE.
Step 1 - This is how to get the message ids with attachments from a support case record (just change the type to support ticket):
function getMessageIdsFromCase(supportCaseId){
var supportcaseSearchObj = SEARCHMODULE.create({
type: "supportcase", //Change if you need to
filters: [
["internalid","anyof",supportCaseId],
"AND",
["messages.hasattachment","is","T"]
],
columns: [
SEARCHMODULE.createColumn({
name: "internalid",
join: "messages"
})
]
});
var resultsSet = supportcaseSearchObj.run();
var results = resultsSet.getRange(0, 999);
var messages = [];
for (var i in results) {
var result = results[i];
var message = result.getValue(result.columns[0]);
messages.push(message);
}
return messages;
}
Then you just call the function like this:
getMessageIdsFromCase(caseInternalId); //Returns an array of message ids
Step 2 - Then you search the attachments using the message internal id with this function:
function getAttachmentIdsFromMessage(messageInternalId){
var messageSearchObj = SEARCHMODULE.create({
type: "message",
filters: [
["internalid","anyof",messageInternalId]
],
columns: [
SEARCHMODULE.createColumn({
name: "internalid",
join: "attachments"
})
]
});
var resultsSet = messageSearchObj.run();
var results = resultsSet.getRange(0, 999);
var attachments = [];
for (var i in results) {
var result = results[i];
var attachment = result.getValue(result.columns[0]);
attachments.push(attachment);
}
return attachments;
}
Then you just call the function like this:
getAttachmentIdsFromMessage(messageInternalId); //Returns an array of attachment ids
UPDATE:
heard from NS after submitting a case. Appears this is not supported yet:
Hi Shane,
I hope you are doing well today.
Upon checking, the ability to access files attached to records are not yet supported in SuiteScript. You can check out SuiteScript Records Browser at SuiteAnswers ID 10511 for the full list of all available records in SuiteScripts and each's accessible sublist. Let me know if you have further questions.
Caleb Francisco | Customer Support
NetSuite: Where Business is Going
Using search.createColumn with joins is the key it looks like. I ended up using quick code below this to get any $files (html) attached to $transaction (returnauthorization), which were in my case, supposed to be mediaitems on a return auth that I couldn't get via the record module in ss2.0
var getHtmlFilesOnReturnAuth = function (return_auth_id, file_type) {
var filters = [
search.createFilter({name: "internalid", operator: "is", values: [return_auth_id]}),
search.createFilter({name: "filetype", join: "file", operator: "is", values: [file_type]}),
];
var images = [];
search.create({
type: "returnauthorization",
filters: filters,
columns: [
search.createColumn({name: "internalid", summary: "group"}),
search.createColumn({name: "internalid", join: "file", summary: "group"}),
search.createColumn({name: "filetype", join: "file", summary: "group"}),
search.createColumn({name: "name", join: "file", summary: "group"}),
]
}).run().each(function (result) {
if (result) {
images.push({
id: result.getValue({name: "internalid", join: "file", summary: "group"}),
file_name: result.getValue({name: "name", join: "file", summary: "group"}),
file_type: result.getValue({name: "filetype", join: "file", summary: "group"}),
});
}
return true;
});
return images;
};
var images = getHtmlFilesOnReturnAuth("2134404", "HTMLDOC");