How to create NetSuite SuiteTalk search with multiple terms? - netsuite

How does one create a SuiteTalk (NetSuite web api) search query specifying multiple search terms indicating a logical OR operator?
For example, I want to retrieve TimeBill records whose create OR last modified dates are within a particular range. Here's my code that works great for a single search term. Simply adding another search term appears to create a logical AND operation.
/// <summary>
/// Return the list of time bills whose last modified date is within
/// the indicated date range.
/// </summary>
/// <param name="from">Required from date</param>
/// <param name="to">Optional to date</param>
/// <returns>List of time bills</returns>
public IEnumerable<TimeBill> GetTimeBills(DateTime from, DateTime to)
{
_log.Debug(String.Format("Enter TimeBill(DateTime from='{0}', DateTime to='{1}')", from, to));
// Build search criteria.
TimeBillSearch search = new TimeBillSearch();
TimeBillSearchBasic searchBasic = new TimeBillSearchBasic();
SearchDateField searchDateField = new SearchDateField();
searchDateField.#operator = SearchDateFieldOperator.within;
searchDateField.operatorSpecified = true;
searchDateField.searchValue = from;
searchDateField.searchValueSpecified = true;
searchDateField.searchValue2 = to;
searchDateField.searchValue2Specified = true;
searchBasic.dateCreated = searchDateField;
search.basic = searchBasic;
return this.Get<TimeBill>(search);
}
/// <summary>
/// Perform a paged search and convert the returned record to the indicated type.
/// </summary>
private IEnumerable<T> Get<T>(SearchRecord searchRecord)
{
_log.Debug("Enter Get<T>(SearchRecord searchRecord)");
// This is returned.
List<T> list = new List<T>();
// The suitetalk service return this.
SearchResult result = null;
using (ISuiteTalkService service = SuiteTalkFactory.Get<SuiteTalkService>())
{
do
{
// .search returns the first page of data.
if (result == null)
{
result = service.search(searchRecord);
}
else // .searchMore returns the next page(s) of data.
{
result = service.searchMoreWithId(result.searchId, result.pageIndex + 1);
}
if (result.status.isSuccess)
{
foreach (Record record in result.recordList)
{
if (record is T)
{
list.Add((T)Convert.ChangeType(record, typeof(T)));
}
}
}
}
while (result.pageIndex < result.totalPages);
}
return list;
}

Per the NetSuite User Community (forum), I do not believe this is currently possible (at least it does not appear in the WSDL for SuiteTalk/Web Services): https://usergroup.netsuite.com/users/showthread.php?t=29818
However, you can dynamically create a complex Saved Search (using SuiteScript in a Suitelet) that includes AND/OR and parentheses by using the nlobjSearchFilter methods setLeftParens(), setRightParens(), and setOr() (as you surmised, a logical "AND" is the default behavior when multiple filters are present).
A dynamically created Saved Search (confusing terminology here, I know) can also be saved and loaded for later reuse. You could therefore potentially take advantage of dynamically created Saved Searches on the NetSuite server by having your Web Services code invoke a Saved Search and retrieve the results, yet still have everything be dynamic (no hardcoded search filters/values).
As mentioned on the Forum, you could also potentially execute multiple searches and stitch the results together yourself (in your SuiteTalk C#/Java etc. code).

Related

access indexfields, batchfields and batch variables in custom module

In my setup form I configure some settings for my custom module. The settings are stored in the custom storage of the batch class. Given the variable IBatchClass batchClass I can access the data by executing
string data = batchClass.get_CustomStorageString("myKey");
and set the data by executing
batchClass.set_CustomStorageString("myKey", "myValue");
When the custom module gets executed I want to access this data from the storage. The value I get returned is the key for the batchfield collection or indexfield collection or batch variables collection. When creating Kofax Export Connector scripts I would have access to the ReleaseSetupData object holding these collections.
Is it possible to access these fields during runtime?
private string GetFieldValue(string fieldName)
{
string fieldValue = string.Empty;
try
{
IIndexFields indexFields = null; // access them
fieldValue = indexFields[fieldName].ToString();
}
catch (Exception e)
{
}
try
{
IBatchFields batchFields = null; // access them
fieldValue = batchFields[fieldName].ToString();
}
catch (Exception e)
{
}
try
{
dynamic batchVariables = null; // access them
fieldValue = batchVariables[fieldName].ToString();
}
catch (Exception e)
{
}
return fieldValue;
}
The format contains a string like
"{#Charge}; {Current Date} {Current Time}; Scan Operator: {Scan
Operator's User ID}; Page: x/y"
and each field wrapped by {...} represents a field from one of these 3 collections.
Kofax exposes a batch as an XML, and DBLite is basically a wrapper for said XML. The structure is explained in AcBatch.htm and AcDocs.htm (to be found under the CaptureSV directory). Here's the basic idea (just documents are shown):
AscentCaptureRuntime
Batch
Documents
Document
For a standard server installation, the file would be located here: \\servername\CaptureSV\AcBatch.htm. A single document has child elements itself such as index fields, and multiple properties such as Confidence, FormTypeName, and PDFGenerationFileName.
Here's how to extract the elements from the active batch (your IBatch instance) as well as accessing all batch fields:
var runtime = activeBatch.ExtractRuntimeACDataElement(0);
var batch = runtime.FindChildElementByName("Batch");
foreach (IACDataElement item in batch.FindChildElementByName("BatchFields").FindChildElementsByName("BatchField"))
{
}
The same is true for index fields. However, as those reside on document level, you would need to drill down to the Documents element first, and then retrieve all Document children. The following example accesses all index fields as well, storing them in a dictionary named IndexFields:
var documents = batch.FindChildElementByName("Documents").FindChildElementsByName("Document");
var indexFields = DocumendocumentstData.FindChildElementByName("IndexFields").FindChildElementsByName("IndexField");
foreach (IACDataElement indexField in indexFields)
{
IndexFields.Add(indexField["Name"], indexField["Value"]);
}
With regard to Batch Variables such as {Scan Operator's User ID}, I am not sure. Worst case scenario is to assign them as default values to index or batch fields.

Get the Scan Operator when releasing documents

When releasing documents the scan operator should get logged to a file. I know this is a kofax system variable but how do I get it from the ReleaseData object?
Maybe this value is hold by the Values collection? What is the key then? I would try to access it by using
string scanOperator = documentData.Values["?scanOperator?"].Value;
Kofax's weird naming convention strikes again - during setup, said items are referred to as BatchVariableNames. However, during release they are KFX_REL_VARIABLEs (an enum named KfxLinkSourceType).
Here's how you can add all available items during setup:
foreach (var item in setupData.BatchVariableNames)
{
setupData.Links.Add(item, KfxLinkSourceType.KFX_REL_VARIABLE, item);
}
The following sample iterates over the DocumentData.Values collection, storing each BatchVariable in a Dictionary<string, string> named BatchVariables.
foreach (Value v in DocumentData.Values)
{
switch (v.SourceType)
{
case KfxLinkSourceType.KFX_REL_VARIABLE:
BatchVariables.Add(v.SourceName, v.Value);
break;
}
}
You can then access any of those variables by key - for example Scan Operator's User ID yields the scan user's domain and name.

null ContentItem in Orchard

First - disclaimer: I know I should not do it like this, but use LazyField instead and that model should not contain logic and I will modify my code accordingly, but I wanted to explore the 1-n relationship between content items and orchard in general.
I'm creating a system where user can respond to selected job offer, so I have two content types - Job, which lists all available jobs, and JobAnswer which contains my custom part and provides link to appropriate Job content item:
public class JobPart : ContentPart<JobPartRecord>
{
public ContentItem Job
{
get
{
if (Record.ContentItemRecord != null)
{
var contentItem = ContentItem.ContentManager.Get(Record.Job.Id);
return contentItem;
}
var nullItem = ContentItem.ContentManager.Query("Job").List().First();
return nullItem;
}
set { Record.Job = value.Record; }
}
}
This works, but I'm not sure how should I handle returning a null contentItem, when creating new content item, now it just returns first Job content item, which is far from ideal.

Google Apps Application APIs: Is there a better way to find correspondance between a Documents List API Document and a Spreadsheets API Spreadsheet?

There is a task, using the .NET library for the Google Data API, to traverse across Google Drive folders, find required spreadsheets and change data of the selected spreadsheets.
Folders traversing is performed using the Google.GData.Documents.FolderQuery and other classes of the Google.GData.Documents namespace. After a correct document is found is necessary to manage it using the Google.GData.Spreadsheets.Spreadsheet class. Now I find a correspondence between the Google.GData.Documents.DocumentEntry class and the Google.GData.Spreadsheets.Spreadsheet class instances by extracting the document key from the document URL, iterating all spreadsheets, extracting a spreadsheet URL and comparing the two keys. The code looks like
private string GetKey(string url) {
string res = null;
Match match = Regex.Match(url, #"\?key=([A-Za-z0-9]+)");
if (match.Success) {
res = match.Groups[1].Value;
}
return res;
}
private SpreadsheetEntry GetSpreadSheetForDocument(SpreadsheetsService serviceSS, DocumentEntry entrySS) {
SpreadsheetEntry res = null;
string strSSKey = GetKey(entrySS.AlternateUri.Content);
Google.GData.Spreadsheets.SpreadsheetQuery query = new Google.GData.Spreadsheets.SpreadsheetQuery();
SpreadsheetFeed feed = serviceSS.Query(query);
foreach (SpreadsheetEntry entry in feed.Entries) {
if (GetKey(entry.AlternateUri.Content) == strSSKey) {
res = entry;
break;
}
}
return res;
}
Is there another, more elegant and correct, way to do this?
As best I can tell, not only is there no better way to do this, but even this technique will fail. As of recent(?) changes to Google Drive API, the keys for the SAME DOCUMENT retrieved by Document List versus Spreadsheets APIs are incompatible. Though using a spreadsheet URL constructed from a key returned by Document List API WILL get you a SpreadsheetEntry, spreadsheet operations on that entry are likely to produce "Invalid Token" Authentication Exceptions.
Your mileage may vary, depending on the authentication style you use. I am using the least recommended User Credentials method.

Sitecore Advanced Database Crawler null dates

I have a property in my index (using the Advanced Database Crawler) for an archive date.
I want to find all items where the date is null or in the future....Date range search will accomplish the second part, but what about the first?
You can't do a null search against Lucene. What I've done in the past is to test for emtpy fields and insert the word "EMPTY" in the index. Then when querying the index you need to add a test that checks for the presence (or absence) of that term. It feels kind of dirty doing it that way but that is the only solution I've been able to find or come up with in the 3 years I've been working with Sitecore and Lucene.
In the DateFieldCrawler class, we modified the following code:
public override string GetValue()
{
if (String.IsNullOrEmpty(_field.Value))
{
return DateTools.DateToString(DateTime.MinValue, DateTools.Resolution.DAY);
}
if (FieldTypeManager.GetField(_field) is DateField)
{
var dateField = new DateField(_field);
if(dateField.DateTime > DateTime.MinValue)
{
return DateTools.DateToString(dateField.DateTime, DateTools.Resolution.DAY);
}
}
return String.Empty;
}
By storing this value, we were able to perform the following queries to include null value dates:
DateRangeSearchParam.DateRange toFirstDate =
new DateRangeSearchParam.DateRange(EVENT_FIRST_DATE,
DateTime.MinValue, toDate.Value);
toFirstDate.InclusiveEnd = false;
eventDates.Add(toFirstDate);

Resources