0
votes

How does one get the results of a "Saved Search" of Type "Deleted Record" in NetSuite? Other search types are obvious(CustomerSearchAdvanced, ItemSearchAdvanced, etc...) but this one seems to have no reference online, just documentation around deleting records, not running saved searches on them.

Update 1

I should clarify a little bit more what I'm trying to do. In NetSuite you can run(and Save) Saved Search's on the record type "Deleted Record", I believe you are able to access at least 5 columns(excluding user defined ones) through this process from the web interface:

  • Date Deleted
  • Deleted By
  • Context
  • Record Type
  • Name

You are also able to setup search criteria as part of the "Saved Search". I would like to access a series of these "Saved Search's" already present in my system utilizing their already setup search criteria and retrieving data from all 5 of their displayed columns.

2

2 Answers

2
votes

The Deleted Record record isn't supported in SuiteTalk as of version 2016_2 which means you can't run a Saved Search and pull down the results.

This is not uncommon when integrating with NetSuite. :(

What I've always done in these situations is create a RESTlet (NetSuite's wannabe RESTful API framework) SuiteScript that will run the search (or do whatever is possible with SuiteScript and not possible with SuiteTalk) and return the results.

From the documentation:

You can deploy server-side scripts that interact with NetSuite data following RESTful principles. RESTlets extend the SuiteScript API to allow custom integrations with NetSuite. Some benefits of using RESTlets include the ability to:

Find opportunities to enhance usability and performance, by implementing a RESTful integration that is more lightweight and flexible than SOAP-based web services. Support stateless communication between client and server. Control client and server implementation. Use built-in authentication based on token or user credentials in the HTTP header. Develop mobile clients on platforms such as iPhone and Android. Integrate external Web-based applications such as Gmail or Google Apps. Create backends for Suitelet-based user interfaces. RESTlets offer ease of adoption for developers familiar with SuiteScript and support more behaviors than NetSuite's SOAP-based web services, which are limited to those defined as SuiteTalk operations. RESTlets are also more secure than Suitelets, which are made available to users without login. For a more detailed comparison, see RESTlets vs. Other NetSuite Integration Options.

In your case this would be a near trivial script to create, it would gather the results and return JSON encoded (easiest) or whatever format you need.

You will likely spend more time getting the Token Based Authentication (TBA) working than you will writing the script.

[Update] Adding some code samples related to what I mentioned in the comments below:

Note that the SuiteTalk proxy object model is frustrating in that it lacks inheritance that it could make such good use of. So you end with code like your SafeTypeCastName(). Reflection is one of the best tools in my toolbox when it comes to working with SuiteTalk proxies. For example, all *RecordRef types have common fields/props so reflection saves you type checking all over the place to work with the object you suspect you have.

public static TType GetProperty<TType>(object record, string propertyID)
{
    PropertyInfo pi = record.GetType().GetProperty(propertyID);
    return (TType)pi.GetValue(record, null);
}

public static string GetInternalID(Record record)
{
    return GetProperty<string>(record, "internalId");
}

public static string GetInternalID(BaseRef recordRef)
{
    PropertyInfo pi = recordRef.GetType().GetProperty("internalId");
    return (string)pi.GetValue(recordRef, null);
}

public static CustomFieldRef[] GetCustomFieldList(Record record)
{
    return GetProperty<CustomFieldRef[]>(record, CustomFieldPropertyName);
}
0
votes

Credit to @SteveK for both his revised and final answer. I think long term I'm going to have to implement what is suggested, short term I tried implementing his first solution("getDeleted") and I'd like to add some more detail on this in case anyone needs to use this method in the future:

//private NetSuiteService nsService = new DataCenterAwareNetSuiteService("login");
//private TokenPassport createTokenPassport() { ... }

private IEnumerable<DeletedRecord> DeletedRecordSearch()
{
    List<DeletedRecord> results = new List<DeletedRecord>();
    int totalPages = Int32.MaxValue;
    int currentPage = 1;

    while (currentPage <= totalPages)
    {
        //You may need to reauthenticate here
        nsService.tokenPassport = createTokenPassport();

        var queryResults = nsService.getDeleted(new GetDeletedFilter
        {
            //Add any filters here...
            //Example
            /*
            deletedDate = new SearchDateField()
                    {
                        @operator = SearchDateFieldOperator.after,
                        operatorSpecified = true,
                        searchValue = DateTime.Now.AddDays(-49),
                        searchValueSpecified = true,
                        predefinedSearchValueSpecified = false,
                        searchValue2Specified = false
                    }
            */
        }, currentPage);

        currentPage++;
        totalPages = queryResults.totalPages;

        results.AddRange(queryResults.deletedRecordList);
    }

    return results;
}

private Tuple<string, string> SafeTypeCastName(
    Dictionary<string, string> customList, 
    BaseRef input)
{
    if (input.GetType() == typeof(RecordRef)) {
        return new Tuple<string, string>(((RecordRef)input).name, 
            ((RecordRef)input).type.ToString());
    }
    //Not sure why "Last Sales Activity Record" doesn't return a type...
    else if (input.GetType() == typeof(CustomRecordRef)) {
        return new Tuple<string, string>(((CustomRecordRef)input).name, 
            customList.ContainsKey(((CustomRecordRef)input).internalId) ? 
                customList[((CustomRecordRef)input).internalId] : 
                "Last Sales Activity Record"));
    }
    else {
        return new Tuple<string, string>("", "");
    }
}

public Dictionary<string, string> GetListCustomTypeName()
{
    //You may need to reauthenticate here
    nsService.tokenPassport = createTokenPassport();

    return
        nsService.search(new CustomListSearch())
            .recordList.Select(a => (CustomList)a)
            .ToDictionary(a => a.internalId, a => a.name);
}

//Main code starts here
var results = DeletedRecordSearch();
var customList = GetListCustomTypeName();

var demoResults = results.Select(a => new
{
    DeletedDate = a.deletedDate,
    Type = SafeTypeCastName(customList, a.record).Item2,
    Name = SafeTypeCastName(customList, a.record).Item1
}).ToList();

I have to apply all the filters API side, and this only returns three columns:

  • Date Deleted
  • Record Type(Not formatted in the same way as the Web UI)
  • Name