NetSuite SuiteTalk:已保存 "Deleted Record" 类型的搜索

NetSuite SuiteTalk: SavedSearch for "Deleted Record" Type

如何在 NetSuite 中获得 "Saved Search" 类型 "Deleted Record" 的结果?其他搜索类型是显而易见的(CustomerSearchAdvanced、ItemSearchAdvanced 等...)但是这个似乎没有在线参考,只有关于 deleting records 的文档,而不是 运行ning 保存的关于它们的搜索。

更新 1

我应该更清楚地说明我想做什么。在 NetSuite 中,您可以 运行(并保存)记录类型 "Deleted Record" 上的已保存搜索,我相信您可以通过此过程从 Web 界面访问至少 5 列(不包括用户定义的列):

您还可以将搜索条件设置为 "Saved Search" 的一部分。我想访问我的系统中已经存在的一系列 "Saved Search's",利用它们已经设置的搜索条件并从它们显示的所有 5 个列中检索数据。

从 2016_2 版本开始,SuiteTalk 不支持 Deleted Record 记录,这意味着您无法 运行 保存的搜索并下拉结果。

与 NetSuite 集成时,这种情况并不少见。 :(

我在这些情况下一直做的是创建一个 RESTlet(NetSuite 的崇拜者 RESTful API 框架)SuiteScript 将 运行 搜索(或做任何可能的事情SuiteScript,而 SuiteTalk 则不可能)和 return 结果。

来自文档:

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.

在您的情况下,这将是一个近乎微不足道的脚本来创建,它将收集结果并 return JSON 编码(最简单)或您需要的任何格式。

与编写脚本相比,您花在使基于令牌的身份验证 (TBA) 正常工作上的时间可能更多。

[更新] 添加一些与我在下面评论中提到的相关的代码示例:

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

感谢@SteveK 修改后的最终答案。我认为从长远来看我将不得不实施建议的内容,短期内我尝试实施他的第一个解决方案("getDeleted")并且我想添加更多细节以防有人需要使用它以后的方法:

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

我必须在 API 端应用所有过滤器,而这只有 returns 三列:

  • 删除日期
  • 记录类型(与 Web 的格式不同 UI
  • 姓名