How to mark a SalesOrder Picked then Packed using NetSuite SuiteTalk WebServices - netsuite

My objective is to respond to a picked event in an external system and mark the SalesOrder "Picked" in NetSuite and later respond to a pack event in an external system and mark the SalesOrder "Packed" in NetSuite.
I am using the code from the SuiteTalk sample application. I first get a copy of the existing ItemFulfillment record and then populate a new ItemFulfillment record.
The code works great when I respond to the pick event. Unfortunately, when I respond to the pack event, when I try to get a copy of the existing ItemFulfillment Record for the SalesOrder I get this error.
"You must have at least one valid line item for this transaction."
I assumed that NetSuite is complaining that there are no more line items to fulfill, so I tried not adding any ItemFulfillmentItem(s) when I set the status to picked, but NetSuite didn't like that either.
The only documentation that I could find referenced a task Id, /app/accounting/transactions/itemshipmanager.nl?type=pack. This approach seemed credible because when I brought up Fiddler, this is the call that it made when I click the "Mark Packed" button in the UI. However, I would prefer not to introduce a different paradigm for talking to the NetSuite server.
I have found that NetSuite will let me go straight to the Pack state if I set shipStatus and shipStatusSpecified in the ItemFulfillment.
Can I move a SalesOrder through both the picked and packed states using only NetSuite SuiteTalk?

I was going about the problem incorrectly. Instead of adding items to a new packed item fulfillment, the correct approach is to find the existing ItemFulfillment and change its status to packed.
This is the search that finds an existing ItemFulfillment for a SalesOrder:
TransactionSearch xactionSearch = new TransactionSearch
{
basic = new TransactionSearchBasic
{
type = new SearchEnumMultiSelectField
{
#operator = SearchEnumMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new System.String[]
{
"_itemFulfillment"
}
},
createdFrom = new SearchMultiSelectField
{
#operator = SearchMultiSelectFieldOperator.anyOf,
operatorSpecified = true,
searchValue = new RecordRef[1]
{
new RecordRef
{
internalId = salesOrderInternalId,
type = RecordType.salesOrder,
typeSpecified = true
}
}
}
}
};
This is the code that updates the ItemFulfillment:
ItemFulfillment update = new ItemFulfillment { internalId = existing.internalId, shipStatus = status, shipStatusSpecified = true };
WriteResponse res = _service.update(update);

Related

How to Create a Purchase Receipt with Document Details via code

The Goal is creating a Purchase Order and then its corresponding Purchase Order receipt via code with 2 separate actions/buttons.
The PO (Type Normal) is created without any issues. And is then Approved via code making it visible in the "Add Purchase Order" smartpanel from the Purchase Receipt page.
The UI workflow would be the selection of the PO order and then pressing on "ADD PO".
I'm looking to replicate that via code.
Looking at the page's ASPX definition I can see that the smartpanel button is associated to action AddPOOrder2
I'm creating the Purchase receipt like this:
if (orderRecord.Approved == true)
{
poReceiptEntryGraph = PXGraph.CreateInstance<POReceiptEntry>();
receiptRow = new POReceipt();
//Summary
receiptRow.ReceiptType = "RT";
receiptRow = poReceiptEntryGraph.Document.Insert(receiptRow);
receiptRow.Hold = false;
receiptRow.ReceiptDate = DateTime.Now;
receiptRow.VendorID = orderRecord.VendorID;
receiptRow.InvoiceNbr = "123";
poReceiptEntryGraph.Document.Update(receiptRow);
poReceiptEntryGraph.Actions.PressSave();
Then I create a PXView:
int startActualRow = PXView.StartRow;
int totalActualRows = 1;
List<Object> createdView = new PXView(poReceiptEntryGraph, false, PXSelect<POOrder, Where<POOrder.orderNbr, Equal<Required<POOrder.orderNbr>>>>
.GetCommand()).Select(PXView.Currents, /*Filter value from the BQL Required*/ new object[] { "PO000683"/*orderRecord.OrderNbr*/ },
PXView.Searches, PXView.SortColumns, PXView.Descendings, PXView.Filters,
ref startActualRow, PXView.MaximumRows, ref totalActualRows);
PXView dummyActualView = new DummyView(poReceiptEntryGraph, poReceiptEntryGraph.Document.View.BqlSelect, createdView);
Finally, the PXView is used to press on the AddPOOrder2 action:
poReceiptEntryGraph.addPOOrder2.Press(new PXAdapter(dummyActualView));
poReceiptEntryGraph.Actions.PressSave();
No error messages are received and the summary section of the Receipt gets created correctly but without any content in the grid.
I also attempted to use addPOOrder which is another Acumatica action that executes addPOOrder2 but the result was the same.
Any ideas if I'm missing something?
Thanks.
I found the solution for this.
Acumatica's AddPOOrder2 action iterates over the selected records and invokes method AddPurchaseOrder
Invoking the method directly in my action worked correctly:
if (orderRecord.Approved == true)
{
poReceiptEntryGraph = PXGraph.CreateInstance<POReceiptEntry>();
receiptRow = new POReceipt();
receiptRow.ReceiptType = "RT";
receiptRow = poReceiptEntryGraph.Document.Insert(receiptRow);
receiptRow.Hold = false;
receiptRow.ReceiptDate = DateTime.Now;
receiptRow.VendorID = orderRecord.VendorID;
poReceiptEntryGraph.Document.Update(receiptRow);
poReceiptEntryGraph.Actions.PressSave();
poReceiptEntryGraph.AddPurchaseOrder(orderRecord);
poReceiptEntryGraph.Actions.PressSave();
}
However, I wonder, had the method not existed, was I in the correct path with the use of the PXView and the PXAdapter? Is it possible to execute other similar actions that may not have a method?

Field index for queries not updating when value set programmatically

My module creates a custom content item through the controller:
private ContentItem createContentItem()
{
// Add the field
_contentDefinitionManager.AlterPartDefinition(
"TestType",
cfg => cfg
.WithField(
"NewField",
f => f
.OfType(typeof(BooleanField).Name)
.WithDisplayName("New Field"))
);
// Not sure if this is needed
_contentDefinitionManager.AlterTypeDefinition(
"TestType",
cfg => cfg
.WithPart("TestType")
);
// Create new TestType item
var newItem = _contentManager.New("TestType");
_contentManager.Create(TestItem, VersionOptions.Published);
// Set the added boolean field to true
BooleanField newField = ((dynamic)newItem).TestType.NewField as BooleanField;
newField.Value = true;
// Set title (as date created, for convenience)
var time = DateTime.Now.ToString("MM-dd-yyyy h:mm:ss tt", CultureInfo.InvariantCulture).Replace(':', '.');
newItem.As<TitlePart>().Title = time;
return newItem;
}
The end result of this is a new TestType item with a field that's set to true. Viewing the content item in the dashboard as well as examining ContentItemVersionRecord in the database confirms that the value was set correctly.
However, queries don't seem to work properly on fields that are set in this manner. I found the record IntegerFieldIndexRecord, which is what I assume projections use to fill query result pages. On this, the value of TestField remains at 0 (false), instead of 1 (true).
Going to the content item edit page and simply clicking 'save' updates IntegerFieldIndexRecord correctly, meaning that the value is now picked up by the query. How can the record be updated for field values set programmatically?
Relevant section of migration:
SchemaBuilder.CreateTable(typeof(TestTypePartRecord).Name, table => table
.ContentPartRecord()
);
ContentDefinitionManager.AlterTypeDefinition(
"TestType",
cfg => cfg
.DisplayedAs("Test Type")
.WithPart(typeof(TitlePart).Name)
.WithPart(typeof(ContainablePart).Name)
.WithPart(typeof(CommonPart).Name)
.WithPart(typeof(IdentityPart).Name)
);
Edit: The fix for this is to manually change the projection index record whenever changing a field value, using this call:
_fieldIndexService.Set(testResultItem.As<FieldIndexPart>(),
"TestType", // Resolves as TestTypePart, which holds the field
"newField",
"", // Not sure why value name should be empty, but whatever
true, // The value to be set goes here
typeof(bool));
In some cases a simple contentManager.Publish() won't do.
I've had a similar problem some time ago and actually implemented a simple helper service to tackle this problem; here's an excerpt:
public T GetStringFieldValues<T>(ContentPart contentPart, string fieldName)
{
var fieldIndexPart = contentPart.ContentItem.As<FieldIndexPart>();
var partName = contentPart.PartDefinition.Name;
return this.fieldIndexService.Get<T>(fieldIndexPart, partName, fieldName, string.Empty);
}
private void SetStringFieldValue(ContentPart contentPart, string fieldName, IEnumerable<int> ids)
{
var fieldIndexPart = contentPart.ContentItem.As<FieldIndexPart>();
var partName = contentPart.PartDefinition.Name;
var encodedValues = "{" + string.Join("},{", ids) + "}";
this.fieldIndexService.Set(fieldIndexPart, partName, fieldName, string.Empty, encodedValues, typeof(string));
}
I've actually built this for use with MediaLibrary- and ContentPicker fields (they encode their value as string internally), so it might not be suitable for the boolean field in your example.
But it can't be that hard to implement, just look at the existing drivers and handlers for those fields.
There are 2 ways to fix this:
1) Ensure the newly created item is getting published by calling ContentManager.Publish() as Orchard.Projections.Handlers.FieldIndexPartHandler listens to the publish event to update the FieldIndexPartRecord
2) use IFieldIndexService to update FieldIndexPartRecord manually, see implementation of Orchard.Projections.Handlers.FieldIndexPartHandler to get in idea how to do this
Hope this helps.
:edit
Due to calling Create(...Published) the ContentManager.Published() won't do anything as the item is already considered published.
You can do the following to force the publish logic to run:
bool itemPublished = newItem.VersionRecord.Published;
// unpublish item first when it is already published as ContentManager.Publish() internally first checks for published flag and when set it aborts silently
// -> this behaviour prevents calling publish listeners
if (itemPublished)
_contentManager.Unpublish(newItem);
// the following call will result in calls to IContentHandler.Publishing() / IContentHandler.Published()
_contentManager.Publish(newItem);
or just create the item as a draft and publish it when everything is setup correctly.

MS Dynamics CRM 2011, Get subgrid elements from other form

I am new to jscript and have problems to get all elements in a subgrid.
I tried the code from this sites,
Retrieve rows in crm2011 subgrid with JScript
https://lakshmanindian.wordpress.com/2012/05/25/retrieve-subgrid-rows-in-crm-2011-using-jscript/
but get every time the error message:
(Translated)
Error in the user defined event of the field
Field:window
Event: onload
Error: The preference "control" of a undefined or null reference can not be called.
The last code I tried:
var grid = document.getElementById("accountContactsGrid").control;
for (var rowNo = 0; rowNo<grid.getRecordsFromInnerGrid().length; rowNo++)
for (var cellNo = 0; cellNo<grid.getRecordsFromInnerGrid()[rowNo][3].cells.length; cellNo++)
alert(grid.getRecordsFromInnerGrid()[rowNo][3].cells[cellNo].outerText);
I tried it in the entity Account(Company) with the subgrid "accountContactsGrid".
My main goal would be to catch all the assigned elements in the account form and list it under the contacts form. But only if the checkbox "Eko" is activated.
This is my working code so far:
var chkEko = Xrm.Page.getAttribute("testcrm_ekonomi").getValue();
if (chkEko === true)
{
alert("Eko active: " + chkEko);
}
else
{
alert("Eko not active: " + chkEko);
}
After a time and the help of some threads I was able to get information of this grid. But now I have the problem to catch the elements.
I looked up the variable "grid" and found out that variable is an Object.
Since I don't really know the properties of the objects I tried to get it all.
But it seems, that my code doesn't work and I can not understand why.
Here is the code so far:
function subgridItemCount() {
// Get the Subgrid Control
var grid = Xrm.Page.ui.controls.get('accountContactsGrid')._control;
var keys = Object.keys(grid);
var getKeys = function(obj){
var keys = [];
for(var key in obj){
keys.push(key);
}
return keys;
}
for(var i = 0; i<keys.length; i++) {
document.write(keys[i]);
}
}
First I wanted to get the property of the object and then the propertyValue.
Or is there an other way to get all values of an object?
It seems like I am on the wrong way. This is what I try to do:
In the account/company form is an existing grid which is called Contacts. In this field are some Contacts assigned (with the button "add existing contact").
Now when I open some Contact there should be a box/grid/iframe with a list of all companies this contact is assigned too.
This list should be linked to the Companies (When i click on them CRM should open the form).
Maybe someone can give me a tip?
My plan was first to look for all companies and then to compare the assigned contacts to the opened one with some Jscript. Then the script should list all matching contacts in the contact form.
This way is not really performant since the script needs to read all companies first. But I don't know an other way...

Create A Record as Closed with C#

Can a record be created as closed?
If I create the records and then change the state, that could work, but is it possible to do it in a single step?
I am using ExecuteMultipleRequest to create Cases.
No, You have to make two request to create and resolve the case. See the examples below:
// Create an incident.
var incident = new Incident
{
CustomerId = new EntityReference(Account.EntityLogicalName, _accountId),
Title = "Sample Incident"
};
_incidentId = _serviceProxy.Create(incident);
// Create the incident's resolution.
var incidentResolution = new IncidentResolution
{
Subject = "Resolved Sample Incident",
IncidentId = new EntityReference(Incident.EntityLogicalName, _incidentId)
};
// Close the incident with the resolution.
var closeIncidentRequest = new CloseIncidentRequest
{
IncidentResolution = incidentResolution,
Status = new OptionSetValue((int)incident_statuscode.ProblemSolved)
};
_serviceProxy.Execute(closeIncidentRequest);
Ref: sdk\SampleCode\CS\BusinessDataModel\Service\CloseAnIncident.cs
You will always need two request, one to create the record and one to change the state.
You could have a plugin close the record on the create, so that way it happens in the same database transaction, but I'm guessing that it wouldn't be worth the over head.

How can I update a content item (draft) from a background task in Orchard?

I have a simple IBackgroundTask implementation that performs a query and then either performs an insert or one or more updates depending on whether a specific item exists or not. However, the updates are not persisted, and I don't understand why. New items are created just as expected.
The content item I'm updating has a CommonPart and I've tried authenticating as a valid user. I've also tried flushing the content manager at the end of the Sweep method. What am I missing?
This is my Sweep, slightly edited for brevity:
public void Sweep()
{
// Authenticate as the site's super user
var superUser = _membershipService.GetUser(_orchardServices.WorkContext.CurrentSite.SuperUser);
_authenticationService.SetAuthenticatedUserForRequest(superUser);
// Create a dummy "Person" content item
var item = _contentManager.New("Person");
var person = item.As<PersonPart>();
if (person == null)
{
return;
}
person.ExternalId = Random.Next(1, 10).ToString();
person.FirstName = GenerateFirstName();
person.LastName = GenerateLastName();
// Check if the person already exists
var matchingPersons = _contentManager
.Query<PersonPart, PersonRecord>(VersionOptions.AllVersions)
.Where(record => record.ExternalId == person.ExternalId)
.List().ToArray();
if (!matchingPersons.Any())
{
// Insert new person and quit
_contentManager.Create(item, VersionOptions.Draft);
return;
}
// There are at least one matching person, update it
foreach (var updatedPerson in matchingPersons)
{
updatedPerson.FirstName = person.FirstName;
updatedPerson.LastName = person.LastName;
}
_contentManager.Flush();
}
Try to add _contentManager.Publish(updatedPerson). If you do not want to publish, but just to save, you don't need to do anything more, as changes in Orchard as saved automatically unless the ambient transaction is aborted. The call to Flush is not necessary at all. This is the case both during a regular request and on a background task.

Resources