How to Create a Purchase Receipt with Document Details via code - acumatica

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?

Related

How to set a custom field based on a custom field on a different entity?

I have a checkbox on a DAC extension to the BAccount that I want to use to set a checkbox on Case when a customer is selected. Here is what the checkbox looks like on the Business Account screen.
Here is the field on the Case screen that I want to set when the customer is selected.
I found a stack overflow question where a couple of custom fields on a customer screen were to be copied to custom fields on a sales order. I have tried to substitute my fields into the code but I haven't been able to make it work.
Here is what I have tried. I'm not sure what I am missing.
protected void CRCase_CustomerID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
var crcase = e.Row as CRCase;
if (crcase.CustomerID != null)
{
var customer = PXSelectorAttribute.Select<CRCase.customerID>(sender, crcase) as BAccountR;
if (customer != null)
{
var customerExt = customer.GetExtension<BAccountExt>();
var crcaseExt = crcase.GetExtension<CRCaseExt>();
crcaseExt.UsrContractCustomer = customerExt.UsrSage100;
}
}
}
I do not understand why BAccountR is used instead of just BAccount. Neither works right now.
I solved the issue by combining are response I received on an earlier post. I modified the last two line as follows:
var customerExt = customer.GetExtension<BAccountExt>();
//var crcaseExt = crcase.GetExtension<CRCaseExt>();
//crcaseExt.UsrContractCustomer = customerExt.UsrSage100;
sender.SetValueExt<CRCaseExt.usrContractCustomer>(crcase, customerExt.UsrSage100 != null);
So here is the final issue that I need to resolve. The checkbox I used on the Business Account screen opens a new tab that displays custom fields. These custom fields are an extension of the BAccount DAC called DSDSage100. It is similar to the extension of the BAccount called Customer. In the DSDSage100 extension there is a field called UsrContractCustomer. That is the field that I want to read and set the Case field to the same value. Here is what the Sage 100 Tab looks like. I have a using directive for my project but I can't find the right reference to the DSDSage100 extension.
var customerExt = customer.GetExtension<DSDSage100>();
sender.SetValueExt<CRCaseExt.usrContractCustomer>(crcase, customerExt.UsrContractCustomer != null);
Acumatica provided the answer. While I "extended the BAccount DAC" with my custom table (DSDSage100, similar to the way Customer is an extension of the BAccount DAC), it is it's own DAC. The solution was:
DSDSage100 mydac = PXSelect<DSDSage100, Where<DSDSage100.bAccountID, Equal<Required<DSDSage100.bAccountID>>>>.Select(Base, crcase.CustomerID);
sender.SetValueExt<CRCaseExt.usrContractCustomer>(crcase, mydac.UsrContractCustomer != null);
Thanks for the help and support.

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.

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

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

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.

Update child entity / Attach entity to context

I am creating a pre event plugin for CRM 2011 that sets the account owner and updates all the child contacts with the same owner. The plugin is installed correctly and updates the main account record correctly but the child contact owner does not change . I have pushed the owner name into another field of the contact to check I have the correct details and that field is updating.
I'm sure its something to do with attaching the child contacts to the correct context but so far I have drawn a blank.
//Set new account owner - Works fine
account.OwnerId = new EntityReference(SystemUser.EntityLogicalName, ownerId);
//Pass the same owner into the contacts - Does not get updated
UpdateContacts(account.Id, ownerId, service, tracingService);
The system is successfully updating the account owner and the description label of the child record.
public static void UpdateContacts(Guid parentCustomerId, Guid ownerId, IOrganizationService service, ITracingService tracingService)
{
// Create the FilterExpression.
FilterExpression filter = new FilterExpression();
// Set the properties of the filter.
filter.FilterOperator = LogicalOperator.And;
filter.AddCondition(new ConditionExpression("parentcustomerid", ConditionOperator.Equal, parentCustomerId));
// Create the QueryExpression object.
QueryExpression query = new QueryExpression();
// Set the properties of the QueryExpression object.
query.EntityName = Contact.EntityLogicalName;
query.ColumnSet = new ColumnSet(true);
query.Criteria = filter;
// Retrieve the contacts.
EntityCollection results = service.RetrieveMultiple(query);
tracingService.Trace("Results : " + results.Entities.Count);
SystemUser systemUser = (SystemUser)service.Retrieve(SystemUser.EntityLogicalName, ownerId, new ColumnSet(true));
tracingService.Trace("System User : " + systemUser.FullName);
XrmServiceContext xrmServiceContext = new XrmServiceContext(service);
for (int i = 0; i < results.Entities.Count; i++)
{
Contact contact = (Contact)results.Entities[i];
contact.OwnerId = new EntityReference(SystemUser.EntityLogicalName, systemUser.Id);
contact.Description = systemUser.FullName;
xrmServiceContext.Attach(contact);
xrmServiceContext.UpdateObject(contact);
xrmServiceContext.SaveChanges();
tracingService.Trace("Updating : " + contact.FullName);
}
}
The tracing service prints out everything I would expect. Do I need to also attach the system user and somehow attach the entity reference to the context?
Any help appreciated.
You have to make a separate web service call using AssignRequest to change the ownership of a record. Unfortunately you cannot just change the Owner attribute.
I think I was getting myself into all sorts of mess with this plugin as by default changing the account owner automatically changes the associated contacts owner. I was therefore trying to overwrite something that it was already doing.
By using the AssignRequest to set the account owner rather than the child records it worked fine. Credit given to Chris as he pointed me in the right direction.
All that was needed was to change the first line of my code to use AssignRequest and the entire UpdateContacts method became obselete.

Resources